From c59d6ba725e93acb504864e533564b110e5f278f Mon Sep 17 00:00:00 2001 From: kukabi Date: Mon, 12 Feb 2024 16:30:41 +0300 Subject: [PATCH] Validator details progress: panels and icons completed. --- subvt/build.gradle.kts | 2 +- .../java/io/helikon/subvt/app/SubVTApp.kt | 2 + .../subvt/data/extension/RewardDestination.kt | 26 +++ .../io/helikon/subvt/ui/screen/main/Tab.kt | 2 +- .../details/ValidatorDetailsScreen.kt | 156 ++++++++++++++++-- .../validator/details/view/AccountAgeView.kt | 102 ++++++++++++ .../details/view/HorizontalDataView.kt | 66 ++++++++ .../validator/details/view/IconsView.kt | 81 +++++++++ .../validator/details/view/IdenticonView.kt | 3 +- .../validator/details/view/IdentityView.kt | 2 +- .../details/view/NominatorListView.kt | 155 +++++++++++++++++ .../details/view/OneKVDetailsView.kt | 63 +++++++ .../details/view/VerticalDataView.kt | 54 ++++++ .../validator/list/ValidatorListScreen.kt | 12 +- .../src/main/res/drawable-hdpi/arrow_down.png | Bin 0 -> 351 bytes subvt/src/main/res/drawable-hdpi/arrow_up.png | Bin 0 -> 363 bytes .../res/drawable-hdpi/blue_exclamation.png | Bin 0 -> 692 bytes .../src/main/res/drawable-mdpi/arrow_down.png | Bin 0 -> 289 bytes subvt/src/main/res/drawable-mdpi/arrow_up.png | Bin 0 -> 261 bytes .../res/drawable-mdpi/blue_exclamation.png | Bin 0 -> 497 bytes .../res/drawable-night-hdpi/arrow_down.png | Bin 0 -> 334 bytes .../main/res/drawable-night-hdpi/arrow_up.png | Bin 0 -> 334 bytes .../res/drawable-night-mdpi/arrow_down.png | Bin 0 -> 253 bytes .../main/res/drawable-night-mdpi/arrow_up.png | Bin 0 -> 241 bytes .../res/drawable-night-xhdpi/arrow_down.png | Bin 0 -> 423 bytes .../res/drawable-night-xhdpi/arrow_up.png | Bin 0 -> 404 bytes .../res/drawable-night-xxhdpi/arrow_down.png | Bin 0 -> 565 bytes .../res/drawable-night-xxhdpi/arrow_up.png | Bin 0 -> 546 bytes .../res/drawable-night-xxxhdpi/arrow_down.png | Bin 0 -> 702 bytes .../res/drawable-night-xxxhdpi/arrow_up.png | Bin 0 -> 685 bytes .../main/res/drawable-xhdpi/arrow_down.png | Bin 0 -> 496 bytes .../src/main/res/drawable-xhdpi/arrow_up.png | Bin 0 -> 476 bytes .../res/drawable-xhdpi/blue_exclamation.png | Bin 0 -> 880 bytes .../main/res/drawable-xxhdpi/arrow_down.png | Bin 0 -> 625 bytes .../src/main/res/drawable-xxhdpi/arrow_up.png | Bin 0 -> 627 bytes .../res/drawable-xxhdpi/blue_exclamation.png | Bin 0 -> 1222 bytes .../main/res/drawable-xxxhdpi/arrow_down.png | Bin 0 -> 787 bytes .../main/res/drawable-xxxhdpi/arrow_up.png | Bin 0 -> 799 bytes .../res/drawable-xxxhdpi/blue_exclamation.png | Bin 0 -> 1560 bytes subvt/src/main/res/values/dimens.xml | 6 +- subvt/src/main/res/values/strings.xml | 34 ++++ 41 files changed, 740 insertions(+), 26 deletions(-) create mode 100644 subvt/src/main/java/io/helikon/subvt/data/extension/RewardDestination.kt create mode 100644 subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/AccountAgeView.kt create mode 100644 subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/HorizontalDataView.kt create mode 100644 subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IconsView.kt create mode 100644 subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/NominatorListView.kt create mode 100644 subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/OneKVDetailsView.kt create mode 100644 subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/VerticalDataView.kt create mode 100644 subvt/src/main/res/drawable-hdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-hdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-hdpi/blue_exclamation.png create mode 100644 subvt/src/main/res/drawable-mdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-mdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-mdpi/blue_exclamation.png create mode 100644 subvt/src/main/res/drawable-night-hdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-night-hdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-night-mdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-night-mdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-night-xhdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-night-xhdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-night-xxhdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-night-xxhdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-night-xxxhdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-night-xxxhdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-xhdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-xhdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-xhdpi/blue_exclamation.png create mode 100644 subvt/src/main/res/drawable-xxhdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-xxhdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-xxhdpi/blue_exclamation.png create mode 100644 subvt/src/main/res/drawable-xxxhdpi/arrow_down.png create mode 100644 subvt/src/main/res/drawable-xxxhdpi/arrow_up.png create mode 100644 subvt/src/main/res/drawable-xxxhdpi/blue_exclamation.png diff --git a/subvt/build.gradle.kts b/subvt/build.gradle.kts index d02ad40..9a498c1 100644 --- a/subvt/build.gradle.kts +++ b/subvt/build.gradle.kts @@ -78,7 +78,7 @@ dependencies { implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.material3:material3:1.2.0-rc01") + implementation("androidx.compose.material3:material3:1.2.0") implementation("androidx.navigation:navigation-compose:2.7.6") implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0") diff --git a/subvt/src/main/java/io/helikon/subvt/app/SubVTApp.kt b/subvt/src/main/java/io/helikon/subvt/app/SubVTApp.kt index e2a6c57..7650bec 100644 --- a/subvt/src/main/java/io/helikon/subvt/app/SubVTApp.kt +++ b/subvt/src/main/java/io/helikon/subvt/app/SubVTApp.kt @@ -1,6 +1,7 @@ package io.helikon.subvt.app import android.app.Application +import com.google.android.filament.utils.Utils import dagger.hilt.android.HiltAndroidApp import io.helikon.subvt.BuildConfig import timber.log.Timber @@ -9,6 +10,7 @@ import timber.log.Timber class SubVTApp : Application() { override fun onCreate() { super.onCreate() + Utils.init() if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } diff --git a/subvt/src/main/java/io/helikon/subvt/data/extension/RewardDestination.kt b/subvt/src/main/java/io/helikon/subvt/data/extension/RewardDestination.kt new file mode 100644 index 0000000..170a1be --- /dev/null +++ b/subvt/src/main/java/io/helikon/subvt/data/extension/RewardDestination.kt @@ -0,0 +1,26 @@ +package io.helikon.subvt.data.extension + +import android.content.Context +import io.helikon.subvt.R +import io.helikon.subvt.data.model.substrate.RewardDestination +import io.helikon.subvt.data.model.substrate.RewardDestinationType +import io.helikon.subvt.util.truncateAddress + +fun RewardDestination.display( + context: Context, + prefix: Short, +): String { + return when (this.destinationType) { + RewardDestinationType.ACCOUNT -> + if (this.destination != null) { + truncateAddress(this.destination!!.getAddress(prefix)) + } else { + "-" + } + + RewardDestinationType.CONTROLLER -> context.resources.getString(R.string.reward_destination_controller) + RewardDestinationType.NONE -> context.resources.getString(R.string.reward_destination_none) + RewardDestinationType.STAKED -> context.resources.getString(R.string.reward_destination_staked) + RewardDestinationType.STASH -> context.resources.getString(R.string.reward_destination_stash) + } +} diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/main/Tab.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/main/Tab.kt index 55878c8..d5fe68d 100644 --- a/subvt/src/main/java/io/helikon/subvt/ui/screen/main/Tab.kt +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/main/Tab.kt @@ -107,7 +107,7 @@ fun TabLayout( modifier = Modifier .fillMaxWidth() - .height(104.dp) + .height(dimensionResource(id = R.dimen.common_bottom_gradient_height)) .zIndex(12f) .align(Alignment.BottomCenter) .background( diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/ValidatorDetailsScreen.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/ValidatorDetailsScreen.kt index bb0fe62..11bdae4 100644 --- a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/ValidatorDetailsScreen.kt +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/ValidatorDetailsScreen.kt @@ -1,8 +1,6 @@ package io.helikon.subvt.ui.screen.validator.details -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image -import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement @@ -24,14 +22,13 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -45,7 +42,9 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.helikon.subvt.R +import io.helikon.subvt.data.extension.display import io.helikon.subvt.data.extension.inactiveNominationTotal +import io.helikon.subvt.data.extension.inactiveNominations import io.helikon.subvt.data.extension.nominationTotal import io.helikon.subvt.data.model.Network import io.helikon.subvt.data.model.app.ValidatorDetails @@ -55,12 +54,19 @@ import io.helikon.subvt.ui.component.AnimatedBackground import io.helikon.subvt.ui.modifier.noRippleClickable import io.helikon.subvt.ui.modifier.scrollHeader import io.helikon.subvt.ui.screen.network.status.view.NetworkSelectorButton +import io.helikon.subvt.ui.screen.validator.details.view.AccountAgeView import io.helikon.subvt.ui.screen.validator.details.view.BalanceView +import io.helikon.subvt.ui.screen.validator.details.view.HorizontalDataView +import io.helikon.subvt.ui.screen.validator.details.view.IconsView import io.helikon.subvt.ui.screen.validator.details.view.IdenticonView import io.helikon.subvt.ui.screen.validator.details.view.IdentityView +import io.helikon.subvt.ui.screen.validator.details.view.NominatorListView +import io.helikon.subvt.ui.screen.validator.details.view.OneKVDetailsView +import io.helikon.subvt.ui.screen.validator.details.view.VerticalDataView import io.helikon.subvt.ui.style.Color import io.helikon.subvt.ui.style.Font import io.helikon.subvt.ui.util.ThemePreviews +import io.helikon.subvt.util.formatDecimal data class ValidatorDetailsScreenState( val serviceStatus: RPCSubscriptionServiceStatus, @@ -107,7 +113,6 @@ fun ValidatorDetailsScreen( ) } -@OptIn(ExperimentalFoundationApi::class) @Composable fun ValidatorDetailsScreenContent( modifier: Modifier = Modifier, @@ -313,6 +318,7 @@ fun ValidatorDetailsScreenContent( .height(dimensionResource(id = R.dimen.validator_details_identicon_height)), ) IdentityView(validator = state.validator) + // Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.common_panel_padding) / 2)) BalanceView( titleResourceId = R.string.validator_details_nomination_total, network = state.network, @@ -323,28 +329,142 @@ fun ValidatorDetailsScreenContent( network = state.network, balance = state.validator?.selfStake?.activeAmount, ) - BalanceView( - titleResourceId = R.string.validator_details_active_stake, - network = state.network, - balance = state.validator?.validatorStake?.totalStake, - ) - BalanceView( + state.validator?.let { validator -> + validator.validatorStake?.let { validatorStake -> + NominatorListView( + titleResourceId = R.string.validator_details_active_stake, + network = state.network, + count = validatorStake.nominators.size, + total = validatorStake.totalStake, + nominations = + validatorStake.nominators.map { + Triple(it.account.address, false, it.stake) + }.sortedByDescending { + it.third + }, + ) + } + } + NominatorListView( titleResourceId = R.string.validator_details_inactive_nominations, network = state.network, - balance = state.validator?.inactiveNominationTotal(), + count = state.validator?.inactiveNominations()?.size, + total = state.validator?.inactiveNominationTotal(), + nominations = + state.validator?.inactiveNominations()?.map { + Triple(it.stashAccount.address, false, it.stake.activeAmount) + }?.sortedByDescending { + it.third + }, + ) + state.validator?.account?.discoveredAt?.let { + AccountAgeView(discoveredAt = it) + } + HorizontalDataView( + titleResourceId = R.string.validator_details_offline_faults, + text = state.validator?.offlineOffenceCount?.toString() ?: "-", + displayExclamation = (state.validator?.offlineOffenceCount ?: 0) > 0, + ) + VerticalDataView( + modifier = Modifier.fillMaxWidth(), + titleResourceId = R.string.validator_details_reward_destination, + text = + state.validator?.rewardDestination?.display( + context = LocalContext.current, + prefix = state.network?.ss58Prefix?.toShort() ?: 0, + ) ?: "-", + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.common_panel_padding)), + ) { + VerticalDataView( + modifier = Modifier.weight(1.0f), + titleResourceId = R.string.validator_details_commission, + text = + String.format( + stringResource(id = R.string.percentage), + formatDecimal( + number = + ( + state.validator?.preferences?.commissionPerBillion + ?: 0 + ).toBigInteger(), + tokenDecimalCount = 7, + formatDecimalCount = 2, + ), + ), + ) + VerticalDataView( + modifier = Modifier.weight(1.0f), + titleResourceId = R.string.validator_details_apr, + text = + String.format( + stringResource(id = R.string.percentage), + formatDecimal( + number = (state.validator?.returnRatePerBillion ?: 0).toBigInteger(), + tokenDecimalCount = 7, + formatDecimalCount = 2, + ), + ), + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.common_panel_padding)), + ) { + VerticalDataView( + modifier = Modifier.weight(1.0f), + titleResourceId = R.string.validator_details_era_blocks, + text = state.validator?.blocksAuthored?.toString() ?: "-", + ) + VerticalDataView( + modifier = Modifier.weight(1.0f), + titleResourceId = R.string.validator_details_era_points, + text = state.validator?.rewardPoints?.toString() ?: "-", + ) + } + state.validator?.let { validator -> + if (validator.onekvCandidateRecordId != null) { + OneKVDetailsView( + modifier = Modifier.fillMaxWidth(), + validator = validator, + ) + } + } + Spacer( + modifier = + Modifier + .navigationBarsPadding() + .padding( + 0.dp, + 0.dp, + 0.dp, + dimensionResource(id = R.dimen.common_scrollable_content_margin_bottom), + ), ) - Spacer(modifier = Modifier.navigationBarsPadding()) } - - CompositionLocalProvider( - LocalOverscrollConfiguration provides null, - ) { + state.validator?.let { + IconsView( + Modifier + .padding( + 0.dp, + 0.dp, + 0.dp, + dimensionResource(id = R.dimen.common_padding), + ) + .navigationBarsPadding() + .fillMaxWidth() + .zIndex(8.0f) + .align(Alignment.BottomCenter), + validator = it, + ) } Box( modifier = Modifier .fillMaxWidth() - .height(104.dp) + .height(dimensionResource(id = R.dimen.common_bottom_gradient_height)) .zIndex(7.0f) .align(Alignment.BottomCenter) .background( diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/AccountAgeView.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/AccountAgeView.kt new file mode 100644 index 0000000..10d1bd0 --- /dev/null +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/AccountAgeView.kt @@ -0,0 +1,102 @@ +package io.helikon.subvt.ui.screen.validator.details.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.sp +import io.helikon.subvt.R +import io.helikon.subvt.ui.style.Color +import io.helikon.subvt.ui.style.Font +import java.time.Instant +import java.time.LocalDate +import java.time.Period +import java.time.ZoneId + +@Composable +fun AccountAgeView( + modifier: Modifier = Modifier, + isDark: Boolean = isSystemInDarkTheme(), + discoveredAt: Long, +) { + val period = + Period.between( + Instant.ofEpochMilli(discoveredAt).atZone(ZoneId.systemDefault()).toLocalDate(), + LocalDate.now(), + ) + val periodText = + if (period.years > 0) { + String.format( + stringResource(R.string.validator_details_period_years), + period.years, + ) + } else { + "" + } + + if (period.months > 0) { + " " + + String.format( + stringResource(R.string.validator_details_period_months), + period.months, + ) + } else { + "" + } + + if (period.days > 0) { + val days = period.days % 7 + val weeks = period.days / 7 + var weeksText = "" + if (weeks > 0) { + weeksText = + " " + + String.format( + stringResource(R.string.validator_details_period_weeks), + weeks, + ) + } + if (days > 0) { + "$weeksText " + + String.format( + stringResource(R.string.validator_details_period_days), + days, + ) + } else { + weeksText + } + } else { + "" + } + Column( + modifier = + modifier + .background( + color = Color.panelBg(isDark), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.common_panel_border_radius)), + ) + .padding(dimensionResource(id = R.dimen.common_padding)) + .fillMaxWidth(), + ) { + Text( + text = stringResource(id = R.string.validator_details_account_age), + style = Font.light(12.sp), + color = Color.text(isDark), + ) + Spacer( + modifier = Modifier.height(dimensionResource(id = R.dimen.common_data_panel_content_margin_top)), + ) + Text( + text = periodText.trim(), + style = Font.semiBold(20.sp), + color = Color.text(isDark), + ) + } +} diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/HorizontalDataView.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/HorizontalDataView.kt new file mode 100644 index 0000000..e571ad2 --- /dev/null +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/HorizontalDataView.kt @@ -0,0 +1,66 @@ +package io.helikon.subvt.ui.screen.validator.details.view + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.sp +import io.helikon.subvt.R +import io.helikon.subvt.ui.style.Color +import io.helikon.subvt.ui.style.Font + +@Composable +fun HorizontalDataView( + modifier: Modifier = Modifier, + isDark: Boolean = isSystemInDarkTheme(), + titleResourceId: Int, + text: String, + color: androidx.compose.ui.graphics.Color = Color.text(isDark = isSystemInDarkTheme()), + displayExclamation: Boolean = false, +) { + Row( + modifier = + modifier + .background( + color = Color.panelBg(isDark), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.common_panel_border_radius)), + ) + .padding(dimensionResource(id = R.dimen.common_padding)) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = titleResourceId), + style = Font.light(12.sp), + color = Color.text(isDark), + ) + Spacer(modifier = Modifier.weight(1.0f)) + Text( + text = text, + style = Font.semiBold(20.sp), + color = color, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (displayExclamation) { + Spacer(modifier = Modifier.width(dimensionResource(id = R.dimen.common_panel_padding))) + Image( + painter = painterResource(id = R.drawable.blue_exclamation), + contentDescription = "", + ) + } + } +} diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IconsView.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IconsView.kt new file mode 100644 index 0000000..0682f29 --- /dev/null +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IconsView.kt @@ -0,0 +1,81 @@ +package io.helikon.subvt.ui.screen.validator.details.view + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import io.helikon.subvt.R +import io.helikon.subvt.data.model.app.ValidatorDetails + +@Composable +fun IconsView( + modifier: Modifier = Modifier, + validator: ValidatorDetails, +) { + Column(modifier = modifier) { + Row( + modifier = + Modifier + .wrapContentSize() + .align(Alignment.CenterHorizontally), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.common_panel_padding)), + ) { + if (validator.onekvCandidateRecordId != null) { + Image( + modifier = Modifier.size(dimensionResource(id = R.dimen.validator_details_icon_size)), + painter = painterResource(id = R.drawable.onekv_icon), + contentDescription = "", + ) + } + if (validator.isParaValidator) { + Image( + modifier = Modifier.size(dimensionResource(id = R.dimen.validator_details_icon_size)), + painter = painterResource(id = R.drawable.para_validator_icon), + contentDescription = "", + ) + } + if (validator.isActiveNextSession) { + Image( + modifier = Modifier.size(dimensionResource(id = R.dimen.validator_details_icon_size)), + painter = painterResource(id = R.drawable.active_next_session_icon), + contentDescription = "", + ) + } + if (validator.heartbeatReceived == true) { + Image( + modifier = Modifier.size(dimensionResource(id = R.dimen.validator_details_icon_size)), + painter = painterResource(id = R.drawable.heartbeat_received_icon), + contentDescription = "", + ) + } + if (validator.oversubscribed) { + Image( + modifier = Modifier.size(dimensionResource(id = R.dimen.validator_details_icon_size)), + painter = painterResource(id = R.drawable.oversubscribed_icon), + contentDescription = "", + ) + } + if (validator.preferences.blocksNominations) { + Image( + modifier = Modifier.size(dimensionResource(id = R.dimen.validator_details_icon_size)), + painter = painterResource(id = R.drawable.blocks_nominations_icon), + contentDescription = "", + ) + } + if (validator.slashCount > 0) { + Image( + modifier = Modifier.size(dimensionResource(id = R.dimen.validator_details_icon_size)), + painter = painterResource(id = R.drawable.slashed_icon), + contentDescription = "", + ) + } + } + } +} diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IdenticonView.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IdenticonView.kt index 6ae151e..fa2ed52 100644 --- a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IdenticonView.kt +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IdenticonView.kt @@ -22,7 +22,7 @@ fun IdenticonView(modifier: Modifier = Modifier) { val modelLoader = rememberModelLoader(engine) val cameraNode = rememberCameraNode(engine) { - position = Position(z = 2.75f) + position = Position(z = 3.0f) } val centerNode = rememberNode(engine).addChildNode(cameraNode) val environmentLoader = rememberEnvironmentLoader(engine) @@ -65,7 +65,6 @@ fun IdenticonView(modifier: Modifier = Modifier) { environmentLoader = environmentLoader, onViewCreated = { this.setZOrderOnTop(true) - this.setBackgroundColor(android.graphics.Color.TRANSPARENT) this.holder.setFormat(PixelFormat.TRANSPARENT) this.uiHelper.isOpaque = false this.view.blendMode = com.google.android.filament.View.BlendMode.TRANSLUCENT diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IdentityView.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IdentityView.kt index 7943304..8d9614a 100644 --- a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IdentityView.kt +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/IdentityView.kt @@ -58,7 +58,7 @@ fun IdentityView( Text( textAlign = TextAlign.Center, text = validator?.identityDisplay() ?: "-", - style = Font.normal(26.sp), + style = Font.normal(24.sp), color = Color.text(isDark), maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/NominatorListView.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/NominatorListView.kt new file mode 100644 index 0000000..766d080 --- /dev/null +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/NominatorListView.kt @@ -0,0 +1,155 @@ +package io.helikon.subvt.ui.screen.validator.details.view + +import android.graphics.Typeface +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.helikon.subvt.R +import io.helikon.subvt.data.model.Network +import io.helikon.subvt.ui.modifier.noRippleClickable +import io.helikon.subvt.ui.style.Color +import io.helikon.subvt.ui.style.Font +import io.helikon.subvt.util.formatDecimal +import io.helikon.subvt.util.truncateAddress +import java.math.BigInteger + +@Composable +fun NominatorListView( + modifier: Modifier = Modifier, + isDark: Boolean = isSystemInDarkTheme(), + titleResourceId: Int, + network: Network?, + count: Int?, + total: BigInteger?, + nominations: List>?, +) { + var isOpen by rememberSaveable { + mutableStateOf(false) + } + val arrowDrawable = + if (isOpen) { + R.drawable.arrow_up + } else { + R.drawable.arrow_down + } + Column( + modifier = + modifier + .noRippleClickable { + isOpen = !isOpen + } + .background( + color = Color.panelBg(isDark), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.common_panel_border_radius)), + ) + .padding(dimensionResource(id = R.dimen.common_padding)) + .fillMaxWidth(), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(id = titleResourceId) + count?.let { " ($it)" }, + style = Font.light(12.sp), + color = Color.text(isDark), + ) + Image(painter = painterResource(id = arrowDrawable), contentDescription = "") + } + Spacer( + modifier = Modifier.height(dimensionResource(id = R.dimen.common_data_panel_content_margin_top)), + ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.common_panel_padding)), + ) { + Text( + text = + if (total != null && network != null) { + formatDecimal( + total, + network.tokenDecimalCount, + ) + } else { + "-" + }, + style = Font.semiBold(28.sp), + color = Color.text(isDark), + ) + if (network != null) { + Text( + text = network.tokenTicker, + style = Font.normal(28.sp), + color = Color.text(isDark).copy(alpha = 0.6f), + ) + } + } + if (isOpen) { + val monospaceFont = Font.normal(12.sp).copy(fontFamily = FontFamily(Typeface.MONOSPACE)) + Spacer( + modifier = Modifier.height(dimensionResource(id = R.dimen.common_panel_padding)), + ) + LazyColumn( + modifier = + Modifier + .fillMaxWidth() + .heightIn( + 0.dp, + dimensionResource(id = R.dimen.validator_details_nomination_list_max_height), + ), + ) { + items(nominations ?: listOf(), key = { it.first }) { + Row(modifier = Modifier.fillMaxWidth()) { + Text( + text = + truncateAddress(it.first) + + if (it.second) { + " (1KV)" + } else { + "" + }, + style = monospaceFont, + color = Color.text(isDark = isDark), + ) + Spacer(modifier = Modifier.weight(1.0f)) + Text( + text = + formatDecimal( + it.third, + network?.tokenDecimalCount ?: 0, + ), + style = monospaceFont, + color = Color.text(isDark = isDark), + ) + } + } + } + } + } +} diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/OneKVDetailsView.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/OneKVDetailsView.kt new file mode 100644 index 0000000..070a7d5 --- /dev/null +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/OneKVDetailsView.kt @@ -0,0 +1,63 @@ +package io.helikon.subvt.ui.screen.validator.details.view + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.sp +import io.helikon.subvt.R +import io.helikon.subvt.data.model.app.ValidatorDetails +import io.helikon.subvt.ui.style.Color +import io.helikon.subvt.ui.style.Font + +@Composable +fun OneKVDetailsView( + modifier: Modifier = Modifier, + isDark: Boolean = isSystemInDarkTheme(), + validator: ValidatorDetails, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.common_panel_padding)), + ) { + Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.common_panel_padding))) + Text( + text = stringResource(id = R.string.validator_details_onekv), + style = Font.semiBold(18.sp), + color = Color.text(isDark), + ) + Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.common_panel_padding))) + HorizontalDataView( + titleResourceId = R.string.validator_details_onekv_rank, + text = validator.onekvRank?.toString() ?: "-", + ) + HorizontalDataView( + titleResourceId = R.string.validator_details_onekv_location, + text = validator.onekvLocation ?: "-", + ) + HorizontalDataView( + titleResourceId = R.string.validator_details_onekv_validity, + text = + stringResource( + id = + if (validator.onekvIsValid == true) { + R.string.validator_details_onekv_valid + } else { + R.string.validator_details_onekv_invalid + }, + ), + color = + if (validator.onekvIsValid == true) { + Color.text(isDark) + } else { + Color.statusError() + }, + ) + } +} diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/VerticalDataView.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/VerticalDataView.kt new file mode 100644 index 0000000..0f900ab --- /dev/null +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/view/VerticalDataView.kt @@ -0,0 +1,54 @@ +package io.helikon.subvt.ui.screen.validator.details.view + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.sp +import io.helikon.subvt.R +import io.helikon.subvt.ui.style.Color +import io.helikon.subvt.ui.style.Font + +@Composable +fun VerticalDataView( + modifier: Modifier = Modifier, + isDark: Boolean = isSystemInDarkTheme(), + titleResourceId: Int, + text: String, + color: androidx.compose.ui.graphics.Color = Color.text(isDark = isSystemInDarkTheme()), +) { + Column( + modifier = + modifier + .background( + color = Color.panelBg(isDark), + shape = RoundedCornerShape(dimensionResource(id = R.dimen.common_panel_border_radius)), + ) + .padding(dimensionResource(id = R.dimen.common_padding)), + ) { + Text( + text = stringResource(id = titleResourceId), + style = Font.light(12.sp), + color = Color.text(isDark), + ) + Spacer( + modifier = Modifier.height(dimensionResource(id = R.dimen.common_data_panel_content_margin_top)), + ) + Text( + text = text, + style = Font.semiBold(20.sp), + color = color, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } +} diff --git a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/list/ValidatorListScreen.kt b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/list/ValidatorListScreen.kt index d98d9a1..266b9d3 100644 --- a/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/list/ValidatorListScreen.kt +++ b/subvt/src/main/java/io/helikon/subvt/ui/screen/validator/list/ValidatorListScreen.kt @@ -419,14 +419,22 @@ fun ValidatorListScreenContent( ) } item { - Spacer(modifier = Modifier.navigationBarsPadding()) + Spacer( + modifier = + Modifier.navigationBarsPadding().padding( + 0.dp, + 0.dp, + 0.dp, + dimensionResource(id = R.dimen.common_scrollable_content_margin_bottom), + ), + ) } } Box( modifier = Modifier .fillMaxWidth() - .height(104.dp) + .height(dimensionResource(id = R.dimen.common_bottom_gradient_height)) .zIndex(7.0f) .align(Alignment.BottomCenter) .background( diff --git a/subvt/src/main/res/drawable-hdpi/arrow_down.png b/subvt/src/main/res/drawable-hdpi/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..8b8c794e14082db1f88ec1f4843a2592cb7584cc GIT binary patch literal 351 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9Drb3m9;?W~mvP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{ufUd$B+ufw-asi4mk+8?caD^(3NZYiGUp8 zWX;}whdcqlrlO_^(gn=V+3cJq2`}lLk|wR?${;}1w##?>YyK}3pQ>I8V?(BM09;0>dE$__J_ft>a{K~{HuOPBXcIArc z&aXe1s7*2cnR;y6@!Kl@R<3rna9%z4#i_^oY}uSf|C+xW8P}&3tG`_#W|poHYu|Dp z|LK$en=Q^!``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBIR;M`$B+ufw-X$@4mk+8o_?poVdR{^mZmnd zF>dRjoG)xu%(jhH%=ru4KX6nr-R;!=$g-`uhJe z1|BwNtu^XRmjqzie>D8OwJO!-*#|NfRH@92=3AZH>^i~fz_(Oc#$62$CS{yUHj0|p zyXn5jnRN&6Bu;f#d=nRZS6Z;@)EuW@mg;L_&WNtMXT4kKZ@x#dVC%=8>P=j`BI@d# z6f^!miSCT@FY}A?Kj$D`%5?j`{;O9%=dW3IQJQD{F~R)T;i_fn*|4@JNU7!ClL>_-`oxBxjpoa6wR;R0|262gGUDue^X z8-!rxWJO@)N>$H8PMn8(>`6Y!@=V+A-(B5Z^_!4`X94>iV2T(o68Q_pq%`}z%P|O> z2H2GI+ImPI^7Kik3I0n^mz#o=&}l>rG%p&oRzr&5D*R}U>oSVB4uaViV9bA6rY7}``#5??U5Evr4Yp*9I-&OlW0J4tuZ+7V0qw_SvHCpPfo)oc5E z;!_Pb*K2UVj@nGuxN=6L?PIf`VIwK90N&rLa3(>;FyS5wFt&FEu88JSBVnF`BbRn= zpb#>(x~hlG#YON9&IuKcQGg-C9(&ZSpHB0v3rhE|G9J4NlNbk2&TDWK)-B|iG!DY} zZY0>btV)_a|0lk^d0>xU_us)4GFUa9dL_qs{&V*|I^( z*SB8c^6xt~``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{x(k+$B+ufw-XHcnj8dN(;HcJJUnuE0&-YY z%;XL#SFq$OxJURem>a&~D@R$`7qhZ5Hz8-IuuaM$YSNn&MW*aY`*i<|h%tlk<44BN zcI%|p{#|E$C7^C@u5L;m+s9?r8LlsPiTLHQ8iwy($@a7IE>FIlGQ;v^+kW1vl2Wyv zP~q0loz5L6>iYG*;)2FD&$bSBrxSvXObREZ_O72_&gZ=FVUcU|lf>4Kx3A4H;V7SI fboa5E+cWkaX1!fcjF)c*`hdaH)z4*}Q$iB}YB*!m literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/drawable-mdpi/arrow_up.png b/subvt/src/main/res/drawable-mdpi/arrow_up.png new file mode 100644 index 0000000000000000000000000000000000000000..31eb924d980bbf7e7595b456686efac12098c438 GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{wz-y$B+ufw|$0OhYTcI<9&myEJ_M4ggl&I z$E+uJQ2C3-J%|2SJ`pkrdN-8`R$nVKguIB*4Z&FPdlCKz2o~# zad+0b6(-k<^33AH#bZu2<>wZDa9weFSzFRy2lsm$dKK2ay!tW~=t>4pS3j3^P6h^!IdwWWM*gP?A)1qu0RM`hmzfIJa3^9c*1gc8~Du$j=P4J+EC-q(Dlvz0 zq8Dalq{Q}hSv0gDhx*jN_~^7^5-F)pXA-;HTSxOK4Ffteq$F%&LXbe#8Yk4L&K#`v zr)imbkVQTqC$0gj9cR%v#=w@CFczQFHHAI@xPf-!1xDDK7RRmpX^&br`J n@mIS#%{VjEV_ud;E4c3mG?``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{$o!U$B+ufx04R?HaQ5migT9lm{8Enr{QQd z!K*{GL-xkOTa2nJL~k&eGqoIO6`t_5f8vn|u8J3L?@QTq*x---Zk=SVRwu96>vi*< z?_T=XHSoueV^jCuKG9Wszu5Qkq)j^fZ@5E4T_fJbUjKNMO~5`WvqiwLY*K<%@hxXl zpL6Yr78i}H@`H5EoTsI(TlZRgiJp=vkMaf`?b^=WGb|imB}+)9``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{$o!U$B+ufw}Gd5n;b-9e0H2>6v~^@AnU*$ z(YTR$t%LUlmbDDl2lzIyVihce5GBK4Umn||NhbF;QbQ$s_?DRP?J z#J@}icA~BaJC~&XHVJf$z5aSzuCvGc)q$7wmSxJh{+)H;PGcp@%LY!L?{hwjmsrfR zEseb|)i&`=<(g-Dt5)4AjTQHItG)l*``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{v=Nq$B+ufxBYv04=aeYG;&_LSk=-qLD0y_ zti!y+*wLuT{1eCeC;lCb4u{+mPr6Nedg$FB&+t9F*=2fSWxP2avYAF#zU}-uA*kx8 z1bgzC&2c5?Y$phB>dOrjTeDS?-EQ*pz4hY!k^U~-Vl{7t)|P6w{3ty4KlPmD_6y-& uH#hEmaMao*L+IMRE!;~(g2VS&Gz$8B2)a_je>xoKItEWyKbLh*2~7ZNt6FIQ literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/drawable-night-mdpi/arrow_up.png b/subvt/src/main/res/drawable-night-mdpi/arrow_up.png new file mode 100644 index 0000000000000000000000000000000000000000..7d65158fa40bf3fc24d86b6820c156bfd2339697 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBew(L@V@L(#TfdXMEd~PK+nBr-wCE;!FPQR{ z!OAhDVMT+GhT{1-mqaGGJoqCZ^l-wD#FPD!)i!Bwj&4?1#H3R^@BF+4Th}>p9ICdL zO5Qh}Y0XyC=X?4M%Ouzfg1r9ToO$ZUwWiO%7;7Jcnr8X;sIQJ-P0)?P*q$3e7cqFc`njxgN@xNA#N$_O literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/drawable-night-xhdpi/arrow_down.png b/subvt/src/main/res/drawable-night-xhdpi/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..9e8534c23c8f31b8f1ab7377b1ac7c2505393fb6 GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=xACbJCmTMH`;x-d?oh{PG(H zJO{75TFSOgE$8+t6RhwvadCt*mD$c&hPl zs>jQV20h(|53A!}{Qh^f^zPqDySjN53Y4xDR4O}lCni*1zY_u!W<1jGoqaEbq4V*L z>!r3rJ^}~Nl&yQ3{LJs5xy||H#b-O`Bs@L1_E9}seMsi4ePG|-F`n;rykm9F^yXAx PxH5RU`njxgN@xNADf77i literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/drawable-night-xhdpi/arrow_up.png b/subvt/src/main/res/drawable-night-xhdpi/arrow_up.png new file mode 100644 index 0000000000000000000000000000000000000000..876758241d50e0c267d5246651cac8a5b93d9b79 GIT binary patch literal 404 zcmV;F0c-w=P)QFt98X|8f!<)ef!;uN{Dt6#6%c%t*a?~urWswB z)R%O+J5+d8^(svy2!bH|XR4~YF3a+qPC(HLnx=`muDi!^%lB~{pIq0y@O}TC3^7gs z;v0xR;uwAVlsb-+Ac>R=uuOvMy1wlDem@a^W#P{=cV!PtHe|rj3Bd2$VHkF3x5szv z4{h5fs5-{=PA7r&lIQsq{T67shpM=;QLjS+MN!;LV|YN1`M+8a|Khh&iihkh2!iKB z@tl}tSuCu(v^bnz)0Mm&6&4 y;Y4XNoU|6M23~Gy1>8YcvOD#m5(GgI{+th18?7gr$c2gk0000RQbC}CBo#m^fDT9pP=a@SC9{DAKj&Tqk~4D17$%EE z??@wmX6E|6H}_d7K@bE%5ClOGgg;8mbfM>Y#ocb##cc}XEt}2ultrk&-ENz)Sj@Gp zePTYJcQO3SSS%LL$`Z@v@&S6@MSD7(woLarrVCM-RPfpN{q}4&>!Wfhbq6fO@p$Y( zFQ`*B-EW)#KG4C%4ZcHCb=Yde<5v-3;t0OaLg;gj;|vUC%zJ=LCX-@39v|Yis7<*T z4S08KqI2%2Fdc={c*^JVugVbP6Ci~`!A&F*6;xiYP!VHf;$2j|y5H|dDx?U|twjl* zL`s09)9F=4oG>}9u4@+J8oj(L+fyhn#9oL%s0{y;TuPe5KC)Q3NNLX_- zv0N_KR+jmd1jxC$Pf5V@9UFcZ1VIo4K@bE%_*Z-aot*7$dX<-R00000NkvXXu0mjf DYTxju literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/drawable-night-xxhdpi/arrow_up.png b/subvt/src/main/res/drawable-night-xxhdpi/arrow_up.png new file mode 100644 index 0000000000000000000000000000000000000000..685616ee346eee9742bf1bc85ba93c4666b6b419 GIT binary patch literal 546 zcmV+-0^R+IP)2SD6R2cZL~Aff}PfTRLU1*8L#3ZR1|6@(IecXTU=Yy3LgvQC&|p^mb}tJP{l1mRIhO4q>gc$~7|RqVrg_B#YDA@E6ELohGr^Lg0qc0F#$b|1H~RvvT6KyeqcP~!I$hF2Mc_<5(( zanttgb~_h=%&tU~DDK<=B;WTXDZXaEqZ>iUTh@NgxNX}Db-rkE=L&%Ro@N=~vDeuy zq(S0+zClMlCvS0U*8pBRP7}}GhTr?T;u-!|HdWl(0YK=zI>&y8Wk$VTFZVY+tGJ^m za&^zq4geW@{~jlPpZry;M1& literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/drawable-night-xxxhdpi/arrow_down.png b/subvt/src/main/res/drawable-night-xxxhdpi/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..1bf59e293f857a72c4d67f790bcbf01b811d1a22 GIT binary patch literal 702 zcmV;v0zv(WP)@~0drDELIAGL9O(c600d`2O+f$vv5yPqA=pb}}cDx5~SnrBd-FBg^IT3&wBfaycElkFlS{f>i);=bhbdH+i@D8Nuk)>-8r|k!Bsn9^m8M z+2&ELR_ls|yja-p_fE*>GVaDe?~J4blW$}4zvS)Y)!kyTXp04_02GVGG2TK`4q4zK z2wx>(tJUfQ0zbq@Cu~20Ult42Jpd8^h_^?>A!F30n?zsGtkS?54jH2aI%2^p0~7}Q zfj1r0k2plrb*TM9ENQ(X&_mPYap#}5+pSgozkFY=q|0ETP>4)T@Ntp7YPC9&3j~eU zbY3mDKANs4SFp~2SIuTqm!Xc{iHJiSTSOeN6tFWF*r7C_{7fCFOb#lWGqL^ZMk7zVP#G^GF|VvYz~ZH@~0drDELIAGL9O(c600d`2O+f$vv5yPzd z8bK6=FCcgh62DJbIxroW4xoa>71*i3O9i0=k_t=*&;dyW>>K}r7akCQIak3_WM@aq zcy^KH^GPcPW_Ol*=AL^OL~!^YPI?#S0pW006}}R*{q+* zWJbH)ZiR98T0*2GVG!sIf_}o#dodMcv)M*IpARHK63u|+a(Tbs@B8vPc*TP-ePqLp zBuHWc%;)p(hr^+BI-N|Z?%^0tCX)wAh}bQl-ae=N?IqWxQYpV!EVdu7K_CfgWq+B*Ekv&)^P+O=QJVUP`if2L@XHqzdvI4e8VXL>OrGg ztp@r=p9jzqL&cK60<}t|(l2q0(+p6*YkxNdk6rC;Hk^*gG(?t0U3+5moU)oL}Xol2rOS_^<;H^fWmV`~(@ zN8v2Olj&Zt-`7H+@KY?X65yPk{r@fqzhmwjcIAgS1cG<)avw6ih8=1)HNmP4BEYa4 z1(<8N4qm~U51LgW(`rYnA5m5UoIN*q@v-{79>>GVGioWMdke!?8vp?UvEWTbaJ$|5 z@aJc=xZf`P9+iKKqgoDKrd_F2I`Yv_Ax5LoRsT{Wgb+dqA%qY@2qAXS} T4zBmu00000NkvXXu0mjf#0M%S literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/drawable-xhdpi/arrow_down.png b/subvt/src/main/res/drawable-xhdpi/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..1f798ebdfbff9e4841b739aac81bd40c2c8e440e GIT binary patch literal 496 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 literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/drawable-xxxhdpi/arrow_down.png b/subvt/src/main/res/drawable-xxxhdpi/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..cd508784f9a51321086358b5c39ddddc62685a65 GIT binary patch literal 787 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 KMNUMnLSTYh{MFR} literal 0 HcmV?d00001 diff --git a/subvt/src/main/res/values/dimens.xml b/subvt/src/main/res/values/dimens.xml index e5086f3..8f38a22 100644 --- a/subvt/src/main/res/values/dimens.xml +++ b/subvt/src/main/res/values/dimens.xml @@ -6,6 +6,8 @@ 36dp 24dp 12dp + 104dp + 54dp 266dp 64dp @@ -50,6 +52,8 @@ 158dp - 274dp + 230dp 128dp + 120dp + 34dp \ No newline at end of file diff --git a/subvt/src/main/res/values/strings.xml b/subvt/src/main/res/values/strings.xml index 684cfdd..6cc33e7 100644 --- a/subvt/src/main/res/values/strings.xml +++ b/subvt/src/main/res/values/strings.xml @@ -7,6 +7,13 @@ Go back + Controller + Staked + Stash + None + + %1$s\%% + Exclamation Icon Welcome to SubVT @@ -74,4 +81,31 @@ Self Stake Active Stake Inactive Nominations + Account Age + %1$dy + %1$dmo + %1$dw + %1$dd + Offline Faults + Reward Destination + Commission + APR + Era Blocks + Era Points + 1KV + Rank + Location + Validity + + Valid + Invalid + Enrolled in 1KV + Parachain Validator + Active Next Session + Heartbeat Received + Oversubscribed + Blocks Nominations + Slashed + Added + Removed \ No newline at end of file