refactor: extract VerdictRenderer and finish MainActivity render decomposition

This commit is contained in:
xtclovver 2026-06-11 18:56:49 +03:00
parent d910e6a045
commit c8e3951d67
2 changed files with 312 additions and 246 deletions

View file

@ -6,7 +6,6 @@ import android.content.ClipboardManager
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -17,8 +16,6 @@ import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.AttrRes
import androidx.annotation.ColorRes
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
@ -27,7 +24,6 @@ import androidx.core.content.edit
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isNotEmpty
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
@ -59,11 +55,6 @@ import com.notcvnt.rknhardering.model.ObservedIp
import com.notcvnt.rknhardering.model.StunProbeGroupResult
import com.notcvnt.rknhardering.export.CompletedExportSnapshot
import com.notcvnt.rknhardering.export.createCompletedExportSnapshot
import com.notcvnt.rknhardering.model.ExposureStatus
import com.notcvnt.rknhardering.model.NarrativeRow
import com.notcvnt.rknhardering.model.Verdict
import com.notcvnt.rknhardering.model.VerdictNarrative
import com.notcvnt.rknhardering.model.VerdictNarrativeBuilder
import com.notcvnt.rknhardering.network.DnsResolverConfig
import com.notcvnt.rknhardering.ui.main.CategoryTiles
import com.notcvnt.rknhardering.ui.main.MainExportController
@ -77,6 +68,8 @@ import com.notcvnt.rknhardering.ui.main.render.FindingViewFactory
import com.notcvnt.rknhardering.ui.main.render.IpChannelsRenderer
import com.notcvnt.rknhardering.ui.main.render.IpComparisonRenderer
import com.notcvnt.rknhardering.ui.main.render.MainRenderEnvironment
import com.notcvnt.rknhardering.ui.main.render.VerdictRenderer
import com.notcvnt.rknhardering.ui.main.render.VerdictViews
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@ -208,7 +201,6 @@ class MainActivity : AppCompatActivity() {
private var activeCheckSettings: CheckSettings? = null
private var retainedDiagnosticsSnapshot: RetainedDiagnosticsSnapshot? = null
private var completedExportSnapshot: CompletedExportSnapshot? = null
private var isVerdictDetailsExpanded = false
// Redesign
private lateinit var mainContentRoot: LinearLayout
@ -273,6 +265,7 @@ class MainActivity : AppCompatActivity() {
private val bypassRenderer by lazy { BypassRenderer(renderEnv, findingViews) }
private val ipChannelsRenderer by lazy { IpChannelsRenderer(renderEnv) }
private val domainReachabilityRenderer by lazy { DomainReachabilityRenderer(renderEnv) }
private val verdictRenderer by lazy { VerdictRenderer(renderEnv, findingViews) }
override fun onCreate(savedInstanceState: Bundle?) {
AppUiSettings.applySavedTheme(this)
@ -711,31 +704,6 @@ class MainActivity : AppCompatActivity() {
private fun checkStatusStopped(): String = getString(R.string.main_check_stopped)
private fun themedContext(): android.content.Context = resultsScrollView.context
private fun themeColor(@AttrRes attrRes: Int, @ColorRes fallbackColorRes: Int): Int {
return MaterialColors.getColor(
resultsScrollView,
attrRes,
ContextCompat.getColor(themedContext(), fallbackColorRes),
)
}
private fun surfaceColor(): Int =
themeColor(com.google.android.material.R.attr.colorSurface, R.color.md_surface)
private fun onSurfaceColor(): Int =
themeColor(com.google.android.material.R.attr.colorOnSurface, R.color.md_on_surface)
private fun onSurfaceVariantColor(): Int =
themeColor(
com.google.android.material.R.attr.colorOnSurfaceVariant,
R.color.md_on_surface_variant,
)
private fun outlineVariantColor(): Int =
themeColor(com.google.android.material.R.attr.colorOutlineVariant, R.color.md_outline_variant)
private fun updateCheckStatus(message: String?) {
textCheckStatus.text = message.orEmpty()
textCheckStatus.visibility = if (message.isNullOrBlank()) View.GONE else View.VISIBLE
@ -1676,9 +1644,6 @@ class MainActivity : AppCompatActivity() {
private fun createFindingView(finding: Finding, privacyMode: Boolean = false): View =
findingViews.createFindingView(finding, privacyMode)
private fun createInfoView(label: String, value: String): View =
findingViews.createInfoView(label, value)
// Reflected by MainActivityUiRenderingTest (name + arity) — keep this
// delegator even without production call sites.
private fun createIpCheckerResponseView(response: IpCheckerResponse, privacyMode: Boolean = false): View =
@ -1768,182 +1733,35 @@ class MainActivity : AppCompatActivity() {
)
}
private fun verdictViews(): VerdictViews = VerdictViews(
cardVerdict = cardVerdict,
iconVerdict = iconVerdict,
textVerdict = textVerdict,
textVerdictExplanation = textVerdictExplanation,
textVerdictHomeRoutedRoamingNote = textVerdictHomeRoutedRoamingNote,
btnVerdictDetails = btnVerdictDetails,
verdictDetailsDivider = verdictDetailsDivider,
verdictDetailsContent = verdictDetailsContent,
verdictHero = verdictHero,
verdictAvatar = verdictAvatar,
verdictAvatarIcon = verdictAvatarIcon,
verdictLabel = verdictLabel,
verdictTitle = verdictTitle,
verdictSubtitle = verdictSubtitle,
verdictHomeRoutedRoamingNote = verdictHomeRoutedRoamingNote,
whitelistWarningBanner = whitelistWarningBanner,
)
private fun displayVerdict(result: CheckResult, privacyMode: Boolean) {
cardVerdict.visibility = View.VISIBLE
isVerdictDetailsExpanded = false
when (result.verdict) {
Verdict.NOT_DETECTED -> {
val visual = statusVisual(StatusSemantic.CLEAN)
iconVerdict.setImageResource(visual.iconRes)
iconVerdict.imageTintList = ColorStateList.valueOf(visual.accentColor)
textVerdict.text = getString(R.string.main_verdict_not_detected)
textVerdict.setTextColor(visual.accentColor)
cardVerdict.setCardBackgroundColor(visual.containerColor)
}
Verdict.NEEDS_REVIEW -> {
val visual = statusVisual(StatusSemantic.REVIEW)
iconVerdict.setImageResource(visual.iconRes)
iconVerdict.imageTintList = ColorStateList.valueOf(visual.accentColor)
textVerdict.text = getString(R.string.main_verdict_needs_review)
textVerdict.setTextColor(visual.accentColor)
cardVerdict.setCardBackgroundColor(visual.containerColor)
}
Verdict.DETECTED -> {
val visual = statusVisual(StatusSemantic.DETECTED)
iconVerdict.setImageResource(visual.iconRes)
iconVerdict.imageTintList = ColorStateList.valueOf(visual.accentColor)
textVerdict.text = getString(R.string.main_verdict_detected)
textVerdict.setTextColor(visual.accentColor)
cardVerdict.setCardBackgroundColor(visual.containerColor)
}
}
bindVerdictNarrative(VerdictNarrativeBuilder.build(this, result, privacyMode))
bindWhitelistWarningBanner(result.operatorWhitelistProbe?.whitelistDetected == true)
}
private fun bindWhitelistWarningBanner(show: Boolean) {
whitelistWarningBanner.visibility = if (show) View.VISIBLE else View.GONE
}
private fun bindVerdictNarrative(narrative: VerdictNarrative) {
textVerdictExplanation.text = narrative.explanation
textVerdictExplanation.visibility = View.VISIBLE
bindHomeRoutedRoamingNote(narrative.homeRoutedRoamingNote)
verdictDetailsContent.removeAllViews()
addVerdictSection(
title = getString(R.string.main_verdict_section_meaning),
content = narrative.meaningRows.map(::createVerdictBulletView),
)
addVerdictSection(
title = getString(R.string.main_verdict_section_discovered),
content = narrative.discoveredRows.map(::createVerdictRowView),
)
addVerdictSection(
title = getString(R.string.main_verdict_section_reasons),
content = narrative.reasonRows.map(::createVerdictBulletView),
)
val hasDetails = verdictDetailsContent.isNotEmpty()
verdictDetailsDivider.visibility = if (hasDetails) View.VISIBLE else View.GONE
btnVerdictDetails.visibility = if (hasDetails) View.VISIBLE else View.GONE
verdictDetailsContent.visibility = if (hasDetails && isVerdictDetailsExpanded) View.VISIBLE else View.GONE
updateVerdictDetailsButton()
}
private fun bindHomeRoutedRoamingNote(note: String?) {
if (note != null) {
textVerdictHomeRoutedRoamingNote.text = note
textVerdictHomeRoutedRoamingNote.visibility = View.VISIBLE
textVerdictHomeRoutedRoamingNote.setTextColor(onSurfaceColor())
textVerdictHomeRoutedRoamingNote.setBackgroundResource(
R.drawable.bg_verdict_home_routed_roaming_note,
)
verdictHomeRoutedRoamingNote.text = note
verdictHomeRoutedRoamingNote.visibility = View.VISIBLE
} else {
textVerdictHomeRoutedRoamingNote.text = ""
textVerdictHomeRoutedRoamingNote.visibility = View.GONE
verdictHomeRoutedRoamingNote.text = ""
verdictHomeRoutedRoamingNote.visibility = View.GONE
}
}
private fun addVerdictSection(title: String, content: List<View>) {
if (content.isEmpty()) return
if (verdictDetailsContent.isNotEmpty()) {
verdictDetailsContent.addView(
View(themedContext()).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
1.dp,
).apply {
topMargin = 12.dp
bottomMargin = 12.dp
}
setBackgroundColor(outlineVariantColor())
alpha = 0.7f
},
)
}
verdictDetailsContent.addView(createVerdictSectionTitleView(title))
content.forEach { verdictDetailsContent.addView(it) }
}
private fun createVerdictSectionTitleView(title: String): View {
return TextView(themedContext()).apply {
text = title
textSize = 11f
typeface = Typeface.DEFAULT_BOLD
isAllCaps = true
letterSpacing = 0.05f
setPadding(0, 0, 0, 6.dp)
setTextColor(onSurfaceVariantColor())
}
}
private fun createVerdictBulletView(text: String): View {
val row = LinearLayout(themedContext()).apply {
orientation = LinearLayout.HORIZONTAL
setPadding(0, 4.dp, 0, 4.dp)
}
val bullet = TextView(themedContext()).apply {
this.text = ""
textSize = 14f
typeface = Typeface.DEFAULT_BOLD
setPadding(0, 0, 8.dp, 0)
setTextColor(onSurfaceVariantColor())
}
val body = TextView(themedContext()).apply {
this.text = text
textSize = 13f
setLineSpacing(2.dp.toFloat(), 1f)
setTextColor(onSurfaceColor())
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
row.addView(bullet)
row.addView(body)
return row
}
private fun createVerdictRowView(row: NarrativeRow): View {
return createInfoView(row.label, row.value)
verdictRenderer.displayVerdict(result, verdictViews(), privacyMode)
}
private fun clearVerdictCard() {
isVerdictDetailsExpanded = false
textVerdict.text = ""
textVerdictExplanation.text = ""
textVerdictExplanation.visibility = View.GONE
bindHomeRoutedRoamingNote(null)
bindWhitelistWarningBanner(false)
verdictDetailsDivider.visibility = View.GONE
btnVerdictDetails.visibility = View.GONE
btnVerdictDetails.text = getString(R.string.main_verdict_details)
verdictDetailsContent.removeAllViews()
verdictDetailsContent.visibility = View.GONE
verdictRenderer.clearVerdictCard(verdictViews())
}
private fun toggleVerdictDetails() {
if (btnVerdictDetails.visibility != View.VISIBLE) return
isVerdictDetailsExpanded = !isVerdictDetailsExpanded
verdictDetailsContent.visibility = if (isVerdictDetailsExpanded) View.VISIBLE else View.GONE
updateVerdictDetailsButton()
if (isVerdictDetailsExpanded) {
animateContentReveal(verdictDetailsContent)
}
}
private fun updateVerdictDetailsButton() {
btnVerdictDetails.text = if (isVerdictDetailsExpanded) getString(R.string.main_verdict_hide_details) else getString(R.string.main_verdict_details)
verdictRenderer.toggleVerdictDetails(verdictViews()) { view -> animateContentReveal(view) }
}
private val Int.dp: Int
@ -2291,50 +2109,14 @@ class MainActivity : AppCompatActivity() {
}
private fun bindVerdictHeroIdle() {
val visual = statusVisual(StatusSemantic.NEUTRAL)
applyVerdictHeroColors(visual)
verdictAvatarIcon.setImageResource(visual.iconRes)
verdictLabel.text = getString(R.string.verdict_label)
verdictTitle.text = getString(R.string.verdict_title_idle)
verdictSubtitle.text = getString(R.string.verdict_subtitle_idle)
bindHomeRoutedRoamingNote(null)
bindWhitelistWarningBanner(false)
verdictRenderer.bindVerdictHeroIdle(verdictViews())
}
private fun bindVerdictHeroRunning() {
val visual = statusVisual(StatusSemantic.NEUTRAL)
applyVerdictHeroColors(visual)
verdictAvatarIcon.setImageResource(visual.iconRes)
verdictLabel.text = getString(R.string.verdict_label)
verdictTitle.text = getString(R.string.verdict_title_idle)
verdictSubtitle.text = getString(R.string.verdict_subtitle_running)
bindHomeRoutedRoamingNote(null)
bindWhitelistWarningBanner(false)
verdictRenderer.bindVerdictHeroRunning(verdictViews())
}
private fun bindVerdictHero(result: CheckResult) {
val (semantic, titleRes) = when (result.verdict) {
Verdict.NOT_DETECTED -> StatusSemantic.CLEAN to R.string.verdict_title_clean
Verdict.NEEDS_REVIEW -> StatusSemantic.REVIEW to R.string.verdict_title_review
Verdict.DETECTED -> StatusSemantic.DETECTED to R.string.verdict_title_detected
}
val visual = statusVisual(semantic)
applyVerdictHeroColors(visual)
verdictAvatarIcon.setImageResource(visual.iconRes)
verdictLabel.text = getString(R.string.verdict_label)
verdictTitle.text = getString(titleRes)
verdictSubtitle.text = getString(R.string.verdict_subtitle_done, tiles.size)
bindWhitelistWarningBanner(result.operatorWhitelistProbe?.whitelistDetected == true)
}
private fun applyVerdictHeroColors(visual: StatusVisual) {
verdictHero.setCardBackgroundColor(visual.containerColor)
verdictTitle.setTextColor(visual.accentColor)
verdictLabel.setTextColor(visual.accentColor)
val avatarBg = android.graphics.drawable.GradientDrawable().apply {
shape = android.graphics.drawable.GradientDrawable.OVAL
setColor(visual.accentColor)
}
verdictAvatar.background = avatarBg
verdictRenderer.bindVerdictHero(verdictViews(), result, tiles.size)
}
}

View file

@ -0,0 +1,284 @@
package com.notcvnt.rknhardering.ui.main.render
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isNotEmpty
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.notcvnt.rknhardering.R
import com.notcvnt.rknhardering.StatusSemantic
import com.notcvnt.rknhardering.StatusVisual
import com.notcvnt.rknhardering.model.CheckResult
import com.notcvnt.rknhardering.model.NarrativeRow
import com.notcvnt.rknhardering.model.Verdict
import com.notcvnt.rknhardering.model.VerdictNarrative
import com.notcvnt.rknhardering.model.VerdictNarrativeBuilder
/**
* Bundle of the verdict card and hero views. Built by MainActivity per call;
* the lateinit view fields stay activity-side.
*/
internal class VerdictViews(
val cardVerdict: MaterialCardView,
val iconVerdict: ImageView,
val textVerdict: TextView,
val textVerdictExplanation: TextView,
val textVerdictHomeRoutedRoamingNote: TextView,
val btnVerdictDetails: MaterialButton,
val verdictDetailsDivider: View,
val verdictDetailsContent: LinearLayout,
val verdictHero: MaterialCardView,
val verdictAvatar: View,
val verdictAvatarIcon: ImageView,
val verdictLabel: TextView,
val verdictTitle: TextView,
val verdictSubtitle: TextView,
val verdictHomeRoutedRoamingNote: TextView,
val whitelistWarningBanner: View,
)
/**
* Renders the final verdict card (status, narrative, expandable details) and
* the verdict hero states. Moved verbatim from MainActivity; owns the
* details-expanded toggle state that previously lived in the activity.
*/
internal class VerdictRenderer(
env: MainRenderEnvironment,
private val findingViews: FindingViewFactory,
) : SectionRenderer(env) {
private var isVerdictDetailsExpanded = false
fun displayVerdict(result: CheckResult, views: VerdictViews, privacyMode: Boolean) = with(views) {
cardVerdict.visibility = View.VISIBLE
isVerdictDetailsExpanded = false
when (result.verdict) {
Verdict.NOT_DETECTED -> {
val visual = statusVisual(StatusSemantic.CLEAN)
iconVerdict.setImageResource(visual.iconRes)
iconVerdict.imageTintList = ColorStateList.valueOf(visual.accentColor)
textVerdict.text = getString(R.string.main_verdict_not_detected)
textVerdict.setTextColor(visual.accentColor)
cardVerdict.setCardBackgroundColor(visual.containerColor)
}
Verdict.NEEDS_REVIEW -> {
val visual = statusVisual(StatusSemantic.REVIEW)
iconVerdict.setImageResource(visual.iconRes)
iconVerdict.imageTintList = ColorStateList.valueOf(visual.accentColor)
textVerdict.text = getString(R.string.main_verdict_needs_review)
textVerdict.setTextColor(visual.accentColor)
cardVerdict.setCardBackgroundColor(visual.containerColor)
}
Verdict.DETECTED -> {
val visual = statusVisual(StatusSemantic.DETECTED)
iconVerdict.setImageResource(visual.iconRes)
iconVerdict.imageTintList = ColorStateList.valueOf(visual.accentColor)
textVerdict.text = getString(R.string.main_verdict_detected)
textVerdict.setTextColor(visual.accentColor)
cardVerdict.setCardBackgroundColor(visual.containerColor)
}
}
bindVerdictNarrative(views, VerdictNarrativeBuilder.build(env.context, result, privacyMode))
bindWhitelistWarningBanner(views, result.operatorWhitelistProbe?.whitelistDetected == true)
}
fun bindVerdictHeroIdle(views: VerdictViews) = with(views) {
val visual = statusVisual(StatusSemantic.NEUTRAL)
applyVerdictHeroColors(views, visual)
verdictAvatarIcon.setImageResource(visual.iconRes)
verdictLabel.text = getString(R.string.verdict_label)
verdictTitle.text = getString(R.string.verdict_title_idle)
verdictSubtitle.text = getString(R.string.verdict_subtitle_idle)
bindHomeRoutedRoamingNote(views, null)
bindWhitelistWarningBanner(views, false)
}
fun bindVerdictHeroRunning(views: VerdictViews) = with(views) {
val visual = statusVisual(StatusSemantic.NEUTRAL)
applyVerdictHeroColors(views, visual)
verdictAvatarIcon.setImageResource(visual.iconRes)
verdictLabel.text = getString(R.string.verdict_label)
verdictTitle.text = getString(R.string.verdict_title_idle)
verdictSubtitle.text = getString(R.string.verdict_subtitle_running)
bindHomeRoutedRoamingNote(views, null)
bindWhitelistWarningBanner(views, false)
}
fun bindVerdictHero(views: VerdictViews, result: CheckResult, tileCount: Int) = with(views) {
val (semantic, titleRes) = when (result.verdict) {
Verdict.NOT_DETECTED -> StatusSemantic.CLEAN to R.string.verdict_title_clean
Verdict.NEEDS_REVIEW -> StatusSemantic.REVIEW to R.string.verdict_title_review
Verdict.DETECTED -> StatusSemantic.DETECTED to R.string.verdict_title_detected
}
val visual = statusVisual(semantic)
applyVerdictHeroColors(views, visual)
verdictAvatarIcon.setImageResource(visual.iconRes)
verdictLabel.text = getString(R.string.verdict_label)
verdictTitle.text = getString(titleRes)
verdictSubtitle.text = getString(R.string.verdict_subtitle_done, tileCount)
bindWhitelistWarningBanner(views, result.operatorWhitelistProbe?.whitelistDetected == true)
}
fun clearVerdictCard(views: VerdictViews) = with(views) {
isVerdictDetailsExpanded = false
textVerdict.text = ""
textVerdictExplanation.text = ""
textVerdictExplanation.visibility = View.GONE
bindHomeRoutedRoamingNote(views, null)
bindWhitelistWarningBanner(views, false)
verdictDetailsDivider.visibility = View.GONE
btnVerdictDetails.visibility = View.GONE
btnVerdictDetails.text = getString(R.string.main_verdict_details)
verdictDetailsContent.removeAllViews()
verdictDetailsContent.visibility = View.GONE
}
fun toggleVerdictDetails(views: VerdictViews, animateContentReveal: (View) -> Unit) = with(views) {
if (btnVerdictDetails.visibility != View.VISIBLE) return@with
isVerdictDetailsExpanded = !isVerdictDetailsExpanded
verdictDetailsContent.visibility = if (isVerdictDetailsExpanded) View.VISIBLE else View.GONE
updateVerdictDetailsButton(views)
if (isVerdictDetailsExpanded) {
animateContentReveal(verdictDetailsContent)
}
}
private fun bindWhitelistWarningBanner(views: VerdictViews, show: Boolean) {
views.whitelistWarningBanner.visibility = if (show) View.VISIBLE else View.GONE
}
private fun bindVerdictNarrative(views: VerdictViews, narrative: VerdictNarrative) = with(views) {
textVerdictExplanation.text = narrative.explanation
textVerdictExplanation.visibility = View.VISIBLE
bindHomeRoutedRoamingNote(views, narrative.homeRoutedRoamingNote)
verdictDetailsContent.removeAllViews()
addVerdictSection(
views,
title = getString(R.string.main_verdict_section_meaning),
content = narrative.meaningRows.map(::createVerdictBulletView),
)
addVerdictSection(
views,
title = getString(R.string.main_verdict_section_discovered),
content = narrative.discoveredRows.map(::createVerdictRowView),
)
addVerdictSection(
views,
title = getString(R.string.main_verdict_section_reasons),
content = narrative.reasonRows.map(::createVerdictBulletView),
)
val hasDetails = verdictDetailsContent.isNotEmpty()
verdictDetailsDivider.visibility = if (hasDetails) View.VISIBLE else View.GONE
btnVerdictDetails.visibility = if (hasDetails) View.VISIBLE else View.GONE
verdictDetailsContent.visibility = if (hasDetails && isVerdictDetailsExpanded) View.VISIBLE else View.GONE
updateVerdictDetailsButton(views)
}
private fun bindHomeRoutedRoamingNote(views: VerdictViews, note: String?) = with(views) {
if (note != null) {
textVerdictHomeRoutedRoamingNote.text = note
textVerdictHomeRoutedRoamingNote.visibility = View.VISIBLE
textVerdictHomeRoutedRoamingNote.setTextColor(onSurfaceColor())
textVerdictHomeRoutedRoamingNote.setBackgroundResource(
R.drawable.bg_verdict_home_routed_roaming_note,
)
verdictHomeRoutedRoamingNote.text = note
verdictHomeRoutedRoamingNote.visibility = View.VISIBLE
} else {
textVerdictHomeRoutedRoamingNote.text = ""
textVerdictHomeRoutedRoamingNote.visibility = View.GONE
verdictHomeRoutedRoamingNote.text = ""
verdictHomeRoutedRoamingNote.visibility = View.GONE
}
}
private fun addVerdictSection(views: VerdictViews, title: String, content: List<View>) = with(views) {
if (content.isEmpty()) return@with
if (verdictDetailsContent.isNotEmpty()) {
verdictDetailsContent.addView(
View(themedContext()).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
1.dp,
).apply {
topMargin = 12.dp
bottomMargin = 12.dp
}
setBackgroundColor(outlineVariantColor())
alpha = 0.7f
},
)
}
verdictDetailsContent.addView(createVerdictSectionTitleView(title))
content.forEach { verdictDetailsContent.addView(it) }
}
private fun createVerdictSectionTitleView(title: String): View {
return TextView(themedContext()).apply {
text = title
textSize = 11f
typeface = Typeface.DEFAULT_BOLD
isAllCaps = true
letterSpacing = 0.05f
setPadding(0, 0, 0, 6.dp)
setTextColor(onSurfaceVariantColor())
}
}
private fun createVerdictBulletView(text: String): View {
val row = LinearLayout(themedContext()).apply {
orientation = LinearLayout.HORIZONTAL
setPadding(0, 4.dp, 0, 4.dp)
}
val bullet = TextView(themedContext()).apply {
this.text = ""
textSize = 14f
typeface = Typeface.DEFAULT_BOLD
setPadding(0, 0, 8.dp, 0)
setTextColor(onSurfaceVariantColor())
}
val body = TextView(themedContext()).apply {
this.text = text
textSize = 13f
setLineSpacing(2.dp.toFloat(), 1f)
setTextColor(onSurfaceColor())
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
row.addView(bullet)
row.addView(body)
return row
}
private fun createVerdictRowView(row: NarrativeRow): View {
return findingViews.createInfoView(row.label, row.value)
}
private fun updateVerdictDetailsButton(views: VerdictViews) = with(views) {
btnVerdictDetails.text = if (isVerdictDetailsExpanded) getString(R.string.main_verdict_hide_details) else getString(R.string.main_verdict_details)
}
private fun applyVerdictHeroColors(views: VerdictViews, visual: StatusVisual) = with(views) {
verdictHero.setCardBackgroundColor(visual.containerColor)
verdictTitle.setTextColor(visual.accentColor)
verdictLabel.setTextColor(visual.accentColor)
val avatarBg = android.graphics.drawable.GradientDrawable().apply {
shape = android.graphics.drawable.GradientDrawable.OVAL
setColor(visual.accentColor)
}
verdictAvatar.background = avatarBg
}
}