visual prototyping

This commit is contained in:
2026-06-29 11:04:58 +02:00
parent 43890d430b
commit 8004d3531f
4 changed files with 557 additions and 58 deletions

View File

@ -39,6 +39,7 @@ import com.tomhempel.rbarometer.questionnaire.QuestionnaireValidation
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.json.JSONObject
import com.tomhempel.rbarometer.utils.ScreenLoading
import com.tomhempel.rbarometer.utils.ViewUtils
@ -63,11 +64,15 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private lateinit var sessionMetricClientsValue: TextView
private lateinit var sessionMetricMeasuresValue: TextView
private lateinit var sessionMetricSyncValue: TextView
private lateinit var todaySessionsTitle: TextView
private lateinit var todaySessionsSummary: TextView
private lateinit var todaySessionsContainer: LinearLayout
private lateinit var nextStepBannerCard: View
private lateinit var nextStepEyebrow: TextView
private lateinit var nextStepTitle: TextView
private lateinit var nextStepBody: TextView
private lateinit var nextStepActionButton: MaterialButton
private lateinit var questionnaireListHost: View
private lateinit var questionnaireScroll: View
private lateinit var buttonContainer: LinearLayout
private lateinit var bottomUploadDock: View
@ -108,6 +113,65 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
UPLOAD,
}
private data class TodaySession(
val clientCode: String,
val status: String,
val cycle: Int,
val session: Int,
val totalSessions: Int,
val plannedDate: String,
val lastSession: String,
val nextSuggestedDate: String,
val measures: List<String>,
)
private val dummyTodaySessions = listOf(
TodaySession(
clientCode = "A102",
status = "Today",
cycle = 3,
session = 2,
totalSessions = 6,
plannedDate = "Today 14:00",
lastSession = "Jun 17",
nextSuggestedDate = "Jul 01",
measures = listOf("WHO-5", "Stress thermometer", "Relationship check"),
),
TodaySession(
clientCode = "B447",
status = "Overdue",
cycle = 1,
session = 5,
totalSessions = 6,
plannedDate = "Jun 18",
lastSession = "Jun 10",
nextSuggestedDate = "Jun 26",
measures = listOf("Mood check", "Goal tracking", "Counselor score review"),
),
TodaySession(
clientCode = "C083",
status = "Upcoming",
cycle = 2,
session = 1,
totalSessions = 6,
plannedDate = "Tomorrow",
lastSession = "Jun 21",
nextSuggestedDate = "Jul 08",
measures = listOf("Cycle baseline", "WHO-5", "Stress thermometer"),
),
TodaySession(
clientCode = "D219",
status = "Inactive",
cycle = 4,
session = 6,
totalSessions = 6,
plannedDate = "Not planned",
lastSession = "Jun 03",
nextSuggestedDate = "On hold",
measures = listOf("End-of-cycle review", "Participation status"),
),
)
private fun color(@ColorRes res: Int) = ContextCompat.getColor(activity, res)
private fun t(id: String) = LanguageManager.getText(languageID, id)
@ -186,6 +250,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
bindViews()
loadQuestionnaireOrder()
renderTodaySessions()
createQuestionnaireButtons()
setupCoachDisplay()
setupLanguageSpinner()
@ -219,11 +284,15 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
sessionMetricClientsValue = activity.findViewById(R.id.sessionMetricClientsValue)
sessionMetricMeasuresValue = activity.findViewById(R.id.sessionMetricMeasuresValue)
sessionMetricSyncValue = activity.findViewById(R.id.sessionMetricSyncValue)
todaySessionsTitle = activity.findViewById(R.id.todaySessionsTitle)
todaySessionsSummary = activity.findViewById(R.id.todaySessionsSummary)
todaySessionsContainer = activity.findViewById(R.id.todaySessionsContainer)
nextStepBannerCard = activity.findViewById(R.id.nextStepBannerCard)
nextStepEyebrow = activity.findViewById(R.id.nextStepEyebrow)
nextStepTitle = activity.findViewById(R.id.nextStepTitle)
nextStepBody = activity.findViewById(R.id.nextStepBody)
nextStepActionButton = activity.findViewById(R.id.nextStepActionButton)
questionnaireListHost = activity.findViewById(R.id.questionnaireListHost)
questionnaireScroll = activity.findViewById(R.id.questionnaireScroll)
bottomUploadDock = activity.findViewById(R.id.bottomUploadDock)
questionnaireListLoading = activity.findViewById(R.id.questionnaireListLoading)
@ -242,6 +311,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
textView.text = hubFallback("Sitzungsinstrumente", "Session measures")
applyHubLabels()
updateSessionMetrics()
renderTodaySessions()
applyCoachDisplay()
}
@ -276,6 +346,355 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
}
}
private fun renderTodaySessions() {
if (!::todaySessionsContainer.isInitialized) return
todaySessionsTitle.text = hubFallback("Sitzungen", "Sessions")
val dueCount = dummyTodaySessions.count { it.status == "Today" || it.status == "Overdue" }
todaySessionsSummary.text = "$dueCount today"
todaySessionsContainer.removeAllViews()
addSessionGroup(
title = hubFallback("Heute", "Today"),
sessions = dummyTodaySessions.filter { it.status == "Today" || it.status == "Overdue" },
)
addSessionGroup(
title = hubFallback("Demnächst", "Upcoming"),
sessions = dummyTodaySessions.filter { it.status == "Upcoming" },
)
addSessionGroup(
title = hubFallback("Inaktiv", "Inactive"),
sessions = dummyTodaySessions.filter { it.status == "Inactive" },
)
}
private fun addSessionGroup(title: String, sessions: List<TodaySession>) {
if (sessions.isEmpty()) return
todaySessionsContainer.addView(TextView(activity).apply {
text = title
setPadding(dp(2), if (todaySessionsContainer.childCount == 0) 0 else dp(14), 0, dp(4))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTextColor(color(R.color.brand_text_dark))
})
sessions.forEach { session ->
todaySessionsContainer.addView(createTodaySessionCard(session))
}
}
private fun createTodaySessionCard(session: TodaySession): View {
val card = MaterialCardView(activity).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
).also {
it.setMargins(0, 0, 0, dp(10))
}
radius = dp(14).toFloat()
strokeWidth = dp(if (session.status == "Today") 2 else 1)
setStrokeColor(color(if (session.status == "Overdue") R.color.brand_chip_start_fill else R.color.brand_purple))
setCardBackgroundColor(color(R.color.brand_header_surface))
cardElevation = dp(2).toFloat()
}
val content = LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(16), dp(14), dp(16), dp(14))
}
val header = LinearLayout(activity).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
}
val titleBlock = LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
titleBlock.addView(TextView(activity).apply {
text = session.clientCode
setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f)
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTextColor(color(R.color.brand_text_dark))
})
titleBlock.addView(TextView(activity).apply {
text = "Cycle ${session.cycle} - Session ${session.session} of ${session.totalSessions}"
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
setTextColor(color(R.color.brand_text_muted))
})
header.addView(titleBlock)
header.addView(sessionStatusChip(session.status))
content.addView(header)
content.addView(TextView(activity).apply {
text = "Planned: ${session.plannedDate} Last: ${session.lastSession}"
setPadding(0, dp(10), 0, dp(8))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
setTextColor(color(R.color.brand_text_dark))
})
content.addView(TextView(activity).apply {
text = hubFallback("Startet automatisch", "Starts automatically")
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTextColor(color(R.color.brand_text_muted))
})
session.measures.forEachIndexed { index, measure ->
content.addView(measureRow(measure, checked = index == 0))
}
content.addView(scheduleNextDateBlock(session.nextSuggestedDate))
val actions = LinearLayout(activity).apply {
orientation = LinearLayout.HORIZONTAL
setPadding(0, dp(12), 0, 0)
}
actions.addView(todayAction("Manage", filled = false, weight = 1f) {
showManageSessionSheet(session)
})
actions.addView(todayAction("Start session", filled = true, weight = 1.25f))
content.addView(actions)
card.addView(content)
return card
}
private fun sessionStatusChip(status: String): TextView =
TextView(activity).apply {
text = status
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTextColor(color(if (status == "Upcoming" || status == "Inactive") R.color.brand_chip_locked_text else R.color.brand_text_on_dark))
setBackgroundResource(
when (status) {
"Overdue" -> R.drawable.bg_chip_amber
"Upcoming", "Inactive" -> R.drawable.bg_chip_locked
else -> R.drawable.bg_chip_green
},
)
setPadding(dp(10), dp(6), dp(10), dp(6))
}
private fun measureRow(label: String, checked: Boolean): View =
LinearLayout(activity).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
setPadding(0, dp(7), 0, 0)
addView(TextView(activity).apply {
text = if (checked) "[x]" else "[ ]"
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTextColor(color(if (checked) R.color.brand_purple else R.color.brand_text_muted))
})
addView(TextView(activity).apply {
text = label
setPadding(dp(8), 0, 0, 0)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
setTextColor(color(R.color.brand_text_dark))
})
}
private fun scheduleNextDateBlock(suggestedDate: String): View =
LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
background = ContextCompat.getDrawable(activity, R.drawable.bg_session_metric)
setPadding(dp(12), dp(10), dp(12), dp(10))
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
)
lp.setMargins(0, dp(12), 0, 0)
layoutParams = lp
addView(TextView(activity).apply {
text = hubFallback("Nächsten Termin planen", "Schedule next date")
setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)
setTextColor(color(R.color.brand_text_muted))
})
val row = LinearLayout(activity).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
setPadding(0, dp(4), 0, 0)
}
row.addView(TextView(activity).apply {
text = suggestedDate
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTextColor(color(R.color.brand_text_dark))
})
row.addView(todayAction("Edit date", filled = false, weight = 0f, fixedWidth = dp(96)))
addView(row)
}
private fun todayAction(
label: String,
filled: Boolean,
weight: Float,
fixedWidth: Int? = null,
onClick: (() -> Unit)? = null,
): MaterialButton =
MaterialButton(activity).apply {
layoutParams = LinearLayout.LayoutParams(
fixedWidth ?: 0,
dp(40),
if (fixedWidth == null) weight else 0f,
).also { it.setMargins(0, 0, dp(8), 0) }
text = label
isAllCaps = false
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
cornerRadius = dp(18)
strokeWidth = dp(1)
insetTop = 0
insetBottom = 0
setTextColor(color(if (filled) R.color.brand_text_on_dark else R.color.brand_purple))
backgroundTintList = ColorStateList.valueOf(color(if (filled) R.color.brand_purple else R.color.brand_header_surface))
strokeColor = ColorStateList.valueOf(color(R.color.brand_purple))
setOnClickListener {
if (onClick != null) onClick()
else Toast.makeText(activity, "$label is prototype-only", Toast.LENGTH_SHORT).show()
}
}
private fun showManageSessionSheet(session: TodaySession) {
val container = LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
setBackgroundColor(color(R.color.brand_surface))
setPadding(dp(20), dp(18), dp(20), dp(18))
}
val header = LinearLayout(activity).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
}
val titleBlock = LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
}
titleBlock.addView(TextView(activity).apply {
text = "Manage ${session.clientCode}"
setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f)
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTextColor(color(R.color.brand_text_dark))
})
titleBlock.addView(TextView(activity).apply {
text = "Cycle ${session.cycle} - Session ${session.session} of ${session.totalSessions}"
setPadding(0, dp(3), 0, 0)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
setTextColor(color(R.color.brand_text_muted))
})
val closeButton = MaterialButton(activity).apply {
layoutParams = LinearLayout.LayoutParams(dp(78), dp(40))
text = "Close"
isAllCaps = false
cornerRadius = dp(18)
strokeWidth = dp(1)
insetTop = 0
insetBottom = 0
setTextColor(color(R.color.brand_purple))
backgroundTintList = ColorStateList.valueOf(color(R.color.brand_header_surface))
strokeColor = ColorStateList.valueOf(color(R.color.brand_header_stroke))
}
header.addView(titleBlock)
header.addView(closeButton)
val body = LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
setPadding(0, dp(14), 0, 0)
}
body.addView(manageSectionTitle("Client status"))
body.addView(LinearLayout(activity).apply {
orientation = LinearLayout.HORIZONTAL
addView(manageStatusButton("Active", selected = session.status != "Inactive"))
addView(manageStatusButton("Inactive", selected = session.status == "Inactive"))
addView(manageStatusButton("Archived", selected = false))
})
body.addView(manageSectionTitle("Scheduling"))
body.addView(manageAction("Edit next planned date", "Current suggestion: ${session.nextSuggestedDate}"))
body.addView(manageAction("Reschedule current session", "Move this session to another date"))
body.addView(manageAction("Skip this occurrence", "Keep the cycle but omit this planned visit"))
body.addView(manageSectionTitle("Session"))
body.addView(manageAction("View session protocol", "${session.measures.size} measures assigned"))
body.addView(manageAction("Add counselor note", "Attach a local note to this client"))
body.addView(manageAction("Sync client data", "Review pending local data for this client"))
body.addView(manageSectionTitle("Lifecycle"))
body.addView(manageAction("Mark no longer participating", "Move client to inactive"))
body.addView(manageAction("Archive client", "Hide from active scheduling"))
body.addView(manageAction("Delete client", "Prototype-only destructive option", destructive = true))
val scroll = ScrollView(activity).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
dp(430),
)
isFillViewport = false
addView(body)
}
container.addView(header)
container.addView(scroll)
val dialog = MaterialAlertDialogBuilder(activity)
.setView(container)
.create()
closeButton.setOnClickListener { dialog.dismiss() }
dialog.show()
}
private fun manageSectionTitle(title: String): View =
TextView(activity).apply {
text = title
setPadding(dp(2), dp(14), 0, dp(7))
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTextColor(color(R.color.brand_text_muted))
}
private fun manageStatusButton(label: String, selected: Boolean): MaterialButton =
MaterialButton(activity).apply {
layoutParams = LinearLayout.LayoutParams(0, dp(40), 1f).also {
it.setMargins(0, 0, dp(8), 0)
}
text = label
isAllCaps = false
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
cornerRadius = dp(18)
strokeWidth = dp(1)
insetTop = 0
insetBottom = 0
setTextColor(color(if (selected) R.color.brand_text_on_dark else R.color.brand_text_dark))
backgroundTintList = ColorStateList.valueOf(
color(if (selected) R.color.brand_purple else R.color.brand_header_surface),
)
strokeColor = ColorStateList.valueOf(color(R.color.brand_purple))
setOnClickListener {
Toast.makeText(activity, "Set status to $label is prototype-only", Toast.LENGTH_SHORT).show()
}
}
private fun manageAction(
title: String,
subtitle: String,
destructive: Boolean = false,
): View =
MaterialButton(activity).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
).also { it.setMargins(0, 0, 0, dp(8)) }
text = "$title\n$subtitle"
isAllCaps = false
gravity = Gravity.START or Gravity.CENTER_VERTICAL
textAlignment = View.TEXT_ALIGNMENT_VIEW_START
setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f)
cornerRadius = dp(16)
strokeWidth = dp(1)
insetTop = 0
insetBottom = 0
setPadding(dp(14), dp(11), dp(14), dp(11))
setTextColor(color(if (destructive) R.color.brand_destructive_text else R.color.brand_text_dark))
backgroundTintList = ColorStateList.valueOf(
color(if (destructive) R.color.brand_destructive_fill else R.color.brand_header_surface),
)
strokeColor = ColorStateList.valueOf(
color(if (destructive) R.color.brand_destructive_fill else R.color.brand_header_stroke),
)
setOnClickListener {
Toast.makeText(activity, "$title is prototype-only", Toast.LENGTH_SHORT).show()
}
}
private fun loadQuestionnaireOrder() {
val serverItems = QuestionnaireCache.getQuestionnaireList(activity)
Log.d(TAG, "loadQuestionnaireOrder: serverItems from cache = ${serverItems.map { it.id }}")
@ -765,11 +1184,11 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
val hasClients = assignedClientsForUi().isNotEmpty()
if (hasClients) {
noClientsAssignedText.visibility = View.GONE
textView.visibility = View.VISIBLE
nextStepBannerCard.visibility = View.VISIBLE
questionnaireScroll.visibility = View.VISIBLE
textView.visibility = View.GONE
nextStepBannerCard.visibility = View.GONE
questionnaireListHost.visibility = View.GONE
questionnaireScroll.visibility = View.GONE
bottomUploadDock.visibility = View.VISIBLE
updateNextStepBanner()
} else {
clearLoadedClientState()
noClientsAssignedText.text = t("no_clients_assigned")
@ -777,6 +1196,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
textView.visibility = View.GONE
updateSessionMetrics()
nextStepBannerCard.visibility = View.GONE
questionnaireListHost.visibility = View.GONE
questionnaireScroll.visibility = View.GONE
bottomUploadDock.visibility = View.GONE
}
@ -784,53 +1204,9 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private fun updateNextStepBanner() {
if (!::nextStepBannerCard.isInitialized) return
val total = dynamicButtons.size
if (total == 0) {
nextStepBannerCard.visibility = View.GONE
return
}
if (assignedClientsForUi().isEmpty()) return
nextStepBannerCard.visibility = View.VISIBLE
val completed = dynamicButtons.count { isCompleted(it) }
nextStepEyebrow.text = hubFallback("Nächster Sitzungsschritt", "Next session step")
nextStepTitle.text = hubFallback("Sitzungsstatus wird geprüft", "Checking session status")
nextStepBody.text = hubFallback("Lokale Daten, Reviews und Synchronisation werden ausgewertet.", "Reviewing local measures, scoring work, and sync status.")
nextStepActionButton.visibility = View.GONE
nextStepActionButton.setOnClickListener(null)
nextStepJob?.cancel()
val clientCode = selectedClientCode
val token = TokenStore.getToken(activity)
nextStepJob = CoroutineScope(Dispatchers.Main).launch {
val summary = withContext(Dispatchers.IO) { UnuploadedWorkRepository.summarize() }
val needsReview = withContext(Dispatchers.IO) {
if (clientCode.isBlank() || token.isNullOrBlank() || !TokenStore.isSessionValid(activity)) {
false
} else {
ReviewScoresHandler.hasPendingReviewsForClient(activity, token, clientCode)
}
}
val action = when {
summary.pendingQuestionnaires > 0 || summary.pendingScoringReviews > 0 -> NextStepAction.UPLOAD
needsReview -> NextStepAction.REVIEW
completed < total && nextAvailableQuestionnaireButton() != null -> NextStepAction.QUESTIONNAIRE
else -> NextStepAction.NONE
}
val titleKey = when {
action == NextStepAction.UPLOAD -> "next_step_upload_title"
action == NextStepAction.REVIEW -> "next_step_review_title"
completed < total -> "next_step_questionnaire_title"
else -> "next_step_completed_title"
}
val bodyKey = when {
action == NextStepAction.UPLOAD -> "next_step_upload_body"
action == NextStepAction.REVIEW -> "next_step_review_body"
completed < total -> "next_step_questionnaire_body"
else -> "next_step_completed_body"
}
applyNextStepBanner(titleKey, bodyKey, action)
}
}
private fun applyNextStepBanner(titleKey: String, bodyKey: String, action: NextStepAction) {
@ -1120,6 +1496,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
}
}
reviewScoresButton.text = t("review_scores")
reviewScoresButton.visibility = View.GONE
uploadButton.text = t("upload")
applyHubLabels()
applyCoachDisplay()
@ -1230,6 +1607,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private fun setupReviewScoresButton() {
reviewScoresButton.text = t("review_scores")
reviewScoresButton.visibility = View.GONE
reviewScoresButton.setOnClickListener { openReviewScores() }
}
@ -1258,7 +1636,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
}
private fun setupUploadButton() {
reviewScoresButton.text = t("review_scores")
reviewScoresButton.visibility = View.GONE
uploadButton.text = t("upload")
uploadButton.setOnClickListener {
TokenStore.ensureLegacyLoginTimestamp(activity)

View File

@ -176,6 +176,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:orientation="vertical">
<TextView
@ -348,13 +349,73 @@
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/nextStepBannerCard" />
app:layout_constraintTop_toBottomOf="@id/todaySessionsSection" />
<LinearLayout
android:id="@+id/todaySessionsSection"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="18dp"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@id/bottomUploadDock"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sessionMetricsRow">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/todaySessionsTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Today's sessions"
android:textColor="@color/brand_text_dark"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:id="@+id/todaySessionsSummary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_session_metric"
android:paddingStart="10dp"
android:paddingTop="6dp"
android:paddingEnd="10dp"
android:paddingBottom="6dp"
android:textColor="@color/brand_text_muted"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:layout_weight="1"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingBottom="8dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/todaySessionsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/nextStepBannerCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:visibility="gone"
app:cardBackgroundColor="@color/brand_emphasis_bg"
app:cardCornerRadius="18dp"
app:cardElevation="3dp"
@ -362,7 +423,7 @@
app:strokeWidth="1dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sessionMetricsRow">
app:layout_constraintTop_toBottomOf="@id/todaySessionsSection">
<LinearLayout
android:layout_width="match_parent"

View File

@ -46,12 +46,11 @@
android:background="@drawable/bg_auth_logo">
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:contentDescription="@null"
android:src="@drawable/ic_person_24"
app:tint="@color/brand_purple" />
android:src="@drawable/ic_launcher_foreground" />
</FrameLayout>
<View

View File

@ -181,6 +181,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
android:visibility="gone"
android:orientation="vertical">
<TextView
@ -353,13 +354,73 @@
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/nextStepBannerCard" />
app:layout_constraintTop_toBottomOf="@id/todaySessionsSection" />
<LinearLayout
android:id="@+id/todaySessionsSection"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="18dp"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@id/bottomUploadDock"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sessionMetricsRow">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/todaySessionsTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Today's sessions"
android:textColor="@color/brand_text_dark"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:id="@+id/todaySessionsSummary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_session_metric"
android:paddingStart="10dp"
android:paddingTop="6dp"
android:paddingEnd="10dp"
android:paddingBottom="6dp"
android:textColor="@color/brand_text_muted"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:layout_weight="1"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingBottom="8dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/todaySessionsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/nextStepBannerCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:visibility="gone"
app:cardBackgroundColor="@color/brand_emphasis_bg"
app:cardCornerRadius="18dp"
app:cardElevation="3dp"
@ -367,7 +428,7 @@
app:strokeWidth="1dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sessionMetricsRow">
app:layout_constraintTop_toBottomOf="@id/todaySessionsSection">
<LinearLayout
android:layout_width="match_parent"