Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,14 @@ fun App() {
// The session outlives the link, so leaving the
// screen just uncovers the app underneath it.
onExitClick = { backStack.removeLastOrNull() },
// A confirmed guest upgrade started on Profile and
// is only visible there — the banner and the
// upgrade row are gone. Popping would land on
// Home, which the deep link seeded underneath.
onContinueClick = {
backStack.clear()
backStack.add(Profile)
},
)
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package de.tabmates.core.designsystem.result

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import de.tabmates.core.designsystem.buttons.TabMatesButton
import de.tabmates.core.designsystem.preview.PreviewThemes
import de.tabmates.core.designsystem.spacer.VerticalSpacer
import de.tabmates.core.designsystem.theme.TabMatesTheme
import de.tabmates.core.designsystem.theme.extended

/**
* Full-screen outcome of something the user just finished, or that just failed — a confirmed email,
* a redeemed invite, an empty list.
*
* Scrolls instead of centring rigidly: a badge, a headline, two paragraphs and a button stop
* fitting in landscape and on short windows, and clipped copy on a screen the user cannot leave any
* other way is worse than a scrollbar.
*/
@Composable
fun ResultLayout(
title: String,
modifier: Modifier = Modifier,
description: String? = null,
supportingText: String? = null,
badge: (@Composable () -> Unit)? = null,
actions: @Composable ColumnScope.() -> Unit = {},
) {
Column(
modifier =
modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp, vertical = 32.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (badge != null) {
badge()
VerticalSpacer(24.dp)
}
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
)
if (description != null) {
VerticalSpacer(12.dp)
Text(
modifier = Modifier.widthIn(max = 400.dp),
text = description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
)
}
if (supportingText != null) {
VerticalSpacer(12.dp)
Text(
modifier = Modifier.widthIn(max = 400.dp),
text = supportingText,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
)
}
VerticalSpacer(32.dp)
actions()
}
}

enum class ResultTone {
Positive,
Error,
}

/**
* The circular icon that heads a [ResultLayout].
*
* The icon is a parameter rather than something the tone picks: artwork belongs to the features, so
* the design system owns only the shape and the colour pairing.
*/
@Composable
fun ResultBadge(
icon: ImageVector,
tone: ResultTone,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
val containerColor =
when (tone) {
ResultTone.Positive -> MaterialTheme.colorScheme.extended.positiveContainer
ResultTone.Error -> MaterialTheme.colorScheme.errorContainer
}
val contentColor =
when (tone) {
ResultTone.Positive -> MaterialTheme.colorScheme.extended.onPositiveContainer
ResultTone.Error -> MaterialTheme.colorScheme.onErrorContainer
}

// Keyed on the icon so the badge pops once when the outcome lands, and again only if the
// outcome itself changes.
val scale = remember { Animatable(INITIAL_BADGE_SCALE) }
LaunchedEffect(icon) {
scale.snapTo(INITIAL_BADGE_SCALE)
scale.animateTo(
targetValue = 1f,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
)
}

Box(
modifier =
modifier
.graphicsLayer {
scaleX = scale.value
scaleY = scale.value
}.size(88.dp)
.background(containerColor, CircleShape),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier.size(40.dp),
imageVector = icon,
contentDescription = contentDescription,
tint = contentColor,
)
}
}

private const val INITIAL_BADGE_SCALE = 0.8f

@PreviewThemes
@Composable
private fun ResultLayoutPreview() {
TabMatesTheme {
Surface {
ResultLayout(
title = "Email confirmed",
description = "Your email address is confirmed. Sign in to start using TabMates.",
badge = {
ResultBadge(icon = Icons.Default.Check, tone = ResultTone.Positive)
},
actions = {
TabMatesButton(
modifier = Modifier.widthIn(max = 300.dp).fillMaxWidth(),
text = "Login",
onClick = {},
)
},
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="#FFFFFF"
android:pathData="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z" />
</group>
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="#FFFFFF"
android:pathData="m770-302-60-62q40-11 65-42.5t25-73.5q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 57-29.5 105T770-302ZM634-440l-80-80h86v80h-6ZM792-56 56-792l56-56 736 736-56 56ZM440-280H280q-83 0-141.5-58.5T80-480q0-69 42-123t108-71l74 74h-24q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h65l79 80H320Z" />
</group>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@
<string name="close">Schließen</string>

<!-- Email Verification -->
<string name="verifying_account">Konto wird verifiziert …</string>
<string name="email_verified_successfully">E-Mail erfolgreich verifiziert!</string>
<string name="email_verified_failed">E-Mail-Verifizierung fehlgeschlagen</string>
<string name="email_verified_successfully_desc">Deine E-Mail wurde verifiziert. Du kannst dich jetzt mit deinen Zugangsdaten anmelden.</string>
<string name="email_verified_upgraded_desc">Deine E-Mail wurde verifiziert. Du bleibst hier angemeldet und kannst dich ab jetzt auf jedem Gerät mit deinen Zugangsdaten anmelden.</string>
<string name="email_verified_failed_desc">Deine E-Mail konnte nicht verifiziert werden. Möglicherweise ist der Link ungültig oder abgelaufen.</string>
<string name="verifying_account">E-Mail wird bestätigt …</string>
<string name="email_verified_title">E-Mail bestätigt</string>
<string name="email_verified_successfully_desc">Deine E-Mail-Adresse ist bestätigt. Melde dich an, um mit TabMates loszulegen.</string>
<string name="email_verified_upgraded_title">Alles erledigt</string>
<string name="email_verified_upgraded_desc">Deine E-Mail-Adresse ist bestätigt und deine Gruppen gehören jetzt zu deinem Konto. Du bleibst hier angemeldet und kannst dich auf jedem Gerät mit deinen Zugangsdaten anmelden.</string>
<string name="email_verified_changed_title">E-Mail-Adresse geändert</string>
<string name="email_verified_changed_desc">Deine neue Adresse ist bestätigt. Du wurdest absichtlich abgemeldet — melde dich mit der neuen Adresse wieder an.</string>
<string name="email_verified_failed">Dieser Link hat nicht funktioniert</string>
<string name="email_verified_failed_desc">Deine E-Mail konnte nicht bestätigt werden. Der Link ist ungültig oder abgelaufen.</string>
<string name="email_verified_failed_hint_no_session">Melde dich an, um dir eine neue Bestätigungs-E-Mail zu schicken.</string>
<string name="email_verified_failed_hint_guest">Öffne Profil → Vollwertiges Konto erstellen, um einen neuen Link zu senden.</string>
<string name="email_verified_failed_hint_registered">Öffne Profil → E-Mail, um die Änderung erneut anzufordern.</string>
<string name="email_verified_continue">Weiter</string>

<!-- Login -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@
<string name="close">Close</string>

<!-- Email Verification -->
<string name="verifying_account">Verifying account...</string>
<string name="email_verified_successfully">Email verified successfully!</string>
<string name="email_verified_failed">Failed to verify your email</string>
<string name="email_verified_successfully_desc">Your email has been verified. You can now log in with your credentials.</string>
<string name="email_verified_upgraded_desc">Your email has been verified. You stay signed in here, and can now log in with your credentials on any device.</string>
<string name="email_verified_failed_desc">Unable to verify your email. Maybe the link is invalid or has expired.</string>
<string name="verifying_account">Confirming your email…</string>
<string name="email_verified_title">Email confirmed</string>
<string name="email_verified_successfully_desc">Your email address is confirmed. Sign in to start using TabMates.</string>
<string name="email_verified_upgraded_title">You are all set</string>
<string name="email_verified_upgraded_desc">Your email address is confirmed and your groups now belong to your account. You stay signed in here, and can sign in with your credentials on any device.</string>
<string name="email_verified_changed_title">Email address updated</string>
<string name="email_verified_changed_desc">Your new address is confirmed. You have been signed out on purpose — sign in again with the new address.</string>
<string name="email_verified_failed">This link did not work</string>
<string name="email_verified_failed_desc">Your email could not be confirmed. The link is either invalid or has expired.</string>
<string name="email_verified_failed_hint_no_session">Sign in to send yourself a new confirmation email.</string>
<string name="email_verified_failed_hint_guest">Open Profile → Create a full account to send a new link.</string>
<string name="email_verified_failed_hint_registered">Open Profile → Email to request the change again.</string>
<string name="email_verified_continue">Continue</string>

<!-- Login -->
Expand Down
Loading