From e1065cda8f6df6d70be56c4d5f6f8e040de0948a Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Tue, 29 Oct 2024 15:27:30 +0200 Subject: [PATCH 01/11] Trigger the Login button when Enter is pressed. --- .../io/spine/examples/pingh/desktop/Login.kt | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt index 21cac06f..caf4b41a 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt @@ -68,6 +68,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.testTag @@ -139,6 +142,10 @@ private fun UsernameEnteringPage( var username by remember { mutableStateOf("") } var wasChanged by remember { mutableStateOf(false) } val isError = remember { mutableStateOf(false) } + val requestUserCode = { + val name = Username::class.of(username) + flow.requestUserCode(name) + } Column( modifier = Modifier .fillMaxSize() @@ -154,15 +161,18 @@ private fun UsernameEnteringPage( username = value wasChanged = true }, + onEnterPressed = { + if (wasChanged && !isError.value) { + requestUserCode() + } + }, isError = isError ) Spacer(Modifier.height(10.dp)) LoginButton( - enabled = wasChanged && !isError.value - ) { - val name = Username::class.of(username) - flow.requestUserCode(name) - } + enabled = wasChanged && !isError.value, + onClick = requestUserCode + ) } } @@ -213,12 +223,14 @@ private fun ApplicationInfo() { * * @param value The current value of the input field. * @param onChange Called when input value is changed. + * @param onEnterPressed Called when this input is focused and the "Enter" key is pressed. * @param isError Indicates if the input's current value is in error. */ @Composable private fun UsernameInput( value: String, onChange: (String) -> Unit, + onEnterPressed: () -> Unit, isError: MutableState ) { val interactionSource = remember { MutableInteractionSource() } @@ -238,6 +250,14 @@ private fun UsernameInput( modifier = Modifier .width(180.dp) .height(52.dp) + .onKeyEvent { event -> + if (event.key == Key.Enter) { + onEnterPressed() + true + } else { + false + } + } .testTag("username-input"), textStyle = MaterialTheme.typography.bodyLarge.copy( color = MaterialTheme.colorScheme.onSecondary From d0d6fc61fc7274a1b326116f2ab345083fdcbc51 Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Tue, 29 Oct 2024 17:35:10 +0200 Subject: [PATCH 02/11] Create window border and shadow. --- .../io/spine/examples/pingh/desktop/Window.kt | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Window.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Window.kt index 682f4234..5ab00ed2 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Window.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Window.kt @@ -26,16 +26,21 @@ package io.spine.examples.pingh.desktop +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.unit.dp @@ -88,8 +93,8 @@ private fun PlatformWindow( content: @Composable FrameWindowScope.() -> Unit ) { val windowState = rememberWindowState( - width = 240.dp, - height = 426.dp, + width = 460.dp, + height = 740.dp, position = WindowPosition(1200.dp, 30.dp) ) ComposeWindow( @@ -111,11 +116,26 @@ private fun PlatformWindow( @Composable private fun WindowContent(app: PinghApplication) { Box( - modifier = Modifier - .fillMaxSize() - .clip(MaterialTheme.shapes.small) + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.TopCenter ) { - CurrentPage(app) + Box( + modifier = Modifier + .width(420.dp) + .height(700.dp) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.onBackground, + shape = MaterialTheme.shapes.small + ) + .shadow( + elevation = 10.dp, + shape = MaterialTheme.shapes.small + ) + .clip(MaterialTheme.shapes.small) + ) { + CurrentPage(app) + } } } From 74dd1ea70811957e3050f8eebe7b1dfcb47ea3ab Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Wed, 30 Oct 2024 10:46:59 +0200 Subject: [PATCH 03/11] Update version. --- .../src/main/kotlin/io/spine/internal/dependency/Pingh.kt | 2 +- version.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/buildSrc/src/main/kotlin/io/spine/internal/dependency/Pingh.kt b/desktop/buildSrc/src/main/kotlin/io/spine/internal/dependency/Pingh.kt index 58d811e2..6054755f 100644 --- a/desktop/buildSrc/src/main/kotlin/io/spine/internal/dependency/Pingh.kt +++ b/desktop/buildSrc/src/main/kotlin/io/spine/internal/dependency/Pingh.kt @@ -28,7 +28,7 @@ package io.spine.internal.dependency // https://github.com/spine-examples/Pingh public object Pingh { - private const val version = "1.0.0-SNAPSHOT.15" + private const val version = "1.0.0-SNAPSHOT.16" private const val group = "io.spine.examples.pingh" public const val client: String = "$group:client:$version" diff --git a/version.gradle.kts b/version.gradle.kts index b1aa5587..ec80871d 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -27,4 +27,4 @@ /** * The version of the `Pingh` to publish. */ -val pinghVersion: String by extra("1.0.0-SNAPSHOT.15") +val pinghVersion: String by extra("1.0.0-SNAPSHOT.16") From bc192b4375ed2fd878c987766a54c9ab2366a5de Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Wed, 30 Oct 2024 11:10:16 +0200 Subject: [PATCH 04/11] Redesign Login page for bigger window. --- .../io/spine/examples/pingh/desktop/Login.kt | 52 +++++++++---------- .../io/spine/examples/pingh/desktop/Theme.kt | 12 ++--- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt index caf4b41a..4cfd6198 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt @@ -168,7 +168,7 @@ private fun UsernameEnteringPage( }, isError = isError ) - Spacer(Modifier.height(10.dp)) + Spacer(Modifier.height(15.dp)) LoginButton( enabled = wasChanged && !isError.value, onClick = requestUserCode @@ -194,7 +194,7 @@ private fun ApplicationInfo() { Icon( painter = Icons.pingh, contentDescription = null, - modifier = Modifier.size(40.dp), + modifier = Modifier.size(50.dp), tint = MaterialTheme.colorScheme.onSecondary ) Spacer(Modifier.width(10.dp)) @@ -206,11 +206,11 @@ private fun ApplicationInfo() { style = MaterialTheme.typography.displayLarge ) } - Spacer(Modifier.height(10.dp)) + Spacer(Modifier.height(20.dp)) Text( text = "Pingh is a GitHub app that looks up mentions on behalf of the user. " + "It requires authentication via GitHub.", - modifier = Modifier.width(180.dp), + modifier = Modifier.width(240.dp), color = MaterialTheme.colorScheme.onSecondaryContainer, textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyMedium @@ -248,8 +248,8 @@ private fun UsernameInput( isError.value = !isValidUsername(changedValue) }, modifier = Modifier - .width(180.dp) - .height(52.dp) + .width(240.dp) + .height(57.dp) .onKeyEvent { event -> if (event.key == Key.Enter) { onEnterPressed() @@ -294,8 +294,8 @@ private fun InputContainer( ) { Row( modifier = Modifier - .width(180.dp) - .height(40.dp) + .width(240.dp) + .height(45.dp) .border(border = border, shape = MaterialTheme.shapes.medium) .background( color = MaterialTheme.colorScheme.secondary, @@ -325,9 +325,9 @@ private fun InputContainer( private fun Label(color: Color) { Box( modifier = Modifier - .width(90.dp) - .height(10.dp) - .absoluteOffset(x = 10.dp, y = (-5).dp) + .width(110.dp) + .height(12.dp) + .absoluteOffset(x = 12.dp, y = (-6).dp) ) { Text( text = "GitHub username", @@ -370,7 +370,7 @@ private fun ErrorMessage(isShown: Boolean) { modifier = Modifier .width(155.dp) .height(30.dp) - .absoluteOffset(x = 15.dp, y = 44.dp), + .absoluteOffset(x = 15.dp, y = 49.dp), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall ) @@ -392,8 +392,8 @@ private fun LoginButton( Button( onClick = onClick, modifier = Modifier - .width(180.dp) - .height(40.dp) + .width(240.dp) + .height(45.dp) .testTag("login-button"), enabled = enabled, colors = ButtonDefaults.buttonColors( @@ -445,7 +445,7 @@ private fun VerificationPage( userCode = userCode, isExpired = isUserCodeExpired ) - Spacer(Modifier.height(10.dp)) + Spacer(Modifier.height(15.dp)) if (isUserCodeExpired) { Spacer(Modifier.height(5.dp)) CodeExpiredErrorMessage(flow) @@ -499,7 +499,7 @@ private fun UserCodeField( Text( text = userCode.value, color = color, - fontSize = 28.sp, + fontSize = 34.sp, letterSpacing = 3.sp, style = MaterialTheme.typography.displayLarge ) @@ -521,14 +521,14 @@ private fun CopyToClipboardIcon( ) { val clipboardManager = LocalClipboardManager.current Box( - modifier = Modifier.offset(x = 103.dp) + modifier = Modifier.offset(x = 130.dp) ) { IconButton( icon = Icons.copy, onClick = { clipboardManager.setText(AnnotatedString(userCode.value)) }, - modifier = Modifier.size(30.dp), + modifier = Modifier.size(40.dp), colors = IconButtonDefaults.iconButtonColors( contentColor = MaterialTheme.colorScheme.onSecondaryContainer ) @@ -635,8 +635,8 @@ private fun SubmitButton( } Box( modifier = Modifier - .width(210.dp) - .height(32.dp), + .width(240.dp) + .height(40.dp), contentAlignment = Alignment.TopCenter ) { Button( @@ -679,8 +679,8 @@ private fun NoResponseErrorMessage(flow: VerifyLogin) { clickablePartOfText = "start over", onClick = flow::requestNewUserCode, modifier = Modifier - .width(180.dp) - .offset(y = 40.dp) + .width(220.dp) + .offset(y = 45.dp) .testTag("no-response-message") ) } @@ -757,11 +757,11 @@ private fun FailedPage(flow: LoginFailed) { ) { Text( text = flow.errorMessage.value, - modifier = Modifier.width(210.dp), + modifier = Modifier.width(240.dp), textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyLarge ) - Spacer(Modifier.height(20.dp)) + Spacer(Modifier.height(25.dp)) RestartButton(flow) } } @@ -776,8 +776,8 @@ private fun RestartButton(flow: LoginFailed) { Button( onClick = flow::restartLogin, modifier = Modifier - .width(210.dp) - .height(32.dp), + .width(240.dp) + .height(40.dp), colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Theme.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Theme.kt index 14f88237..2b3126c9 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Theme.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Theme.kt @@ -104,26 +104,26 @@ private val sanFrancisco = FontFamily( private val typography = Typography( displayLarge = TextStyle( fontFamily = sanFrancisco, - fontSize = 16.sp + fontSize = 18.sp ), displayMedium = TextStyle( fontFamily = sanFrancisco, - fontSize = 14.sp + fontSize = 16.sp ), displaySmall = TextStyle( fontFamily = sanFrancisco, - fontSize = 12.sp + fontSize = 14.sp ), bodyLarge = TextStyle( fontFamily = sanFrancisco, - fontSize = 12.sp + fontSize = 14.sp ), bodyMedium = TextStyle( fontFamily = sanFrancisco, - fontSize = 10.sp + fontSize = 12.sp ), bodySmall = TextStyle( fontFamily = sanFrancisco, - fontSize = 8.sp + fontSize = 10.sp ) ) From 8ab02bd8a1ac0114c11f5fe153c50989d1724fb0 Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Wed, 30 Oct 2024 11:29:26 +0200 Subject: [PATCH 05/11] Redesign Mentions page for bigger window. --- .../spine/examples/pingh/desktop/Mentions.kt | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt index 519ae843..c97a2a25 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt @@ -108,7 +108,7 @@ private fun ToolBar( Row( modifier = Modifier .fillMaxWidth() - .height(48.dp) + .height(60.dp) .background(MaterialTheme.colorScheme.secondary) .drawBehind { drawLine( @@ -118,20 +118,21 @@ private fun ToolBar( strokeWidth = 1.dp.toPx() ) } - .padding(horizontal = 5.dp, vertical = 4.dp), + .padding(horizontal = 30.dp), verticalAlignment = Alignment.CenterVertically ) { IconButton( icon = Icons.pingh, onClick = toSettingsPage, - modifier = Modifier.size(40.dp).testTag("settings-button"), + modifier = Modifier.size(50.dp).testTag("settings-button"), colors = IconButtonDefaults.iconButtonColors( contentColor = contentColor ) ) + Spacer(Modifier.width(10.dp)) Text( text = "Recent mentions", - modifier = Modifier.width(140.dp), + modifier = Modifier.width(250.dp), color = contentColor, style = MaterialTheme.typography.displayLarge ) @@ -140,7 +141,7 @@ private fun ToolBar( onClick = { flow.updateMentions() }, - modifier = Modifier.size(40.dp), + modifier = Modifier.size(50.dp), colors = IconButtonDefaults.iconButtonColors( contentColor = contentColor ) @@ -165,17 +166,17 @@ private fun MentionCards( Column( Modifier .fillMaxSize() - .padding(horizontal = 5.dp) + .padding(horizontal = 10.dp) .verticalScroll(scrollState) .background(MaterialTheme.colorScheme.background) .testTag("mention-cards"), ) { mentions.sorted() .forEach { mention -> - Spacer(Modifier.height(5.dp)) + Spacer(Modifier.height(10.dp)) MentionCard(flow, mention) } - Spacer(Modifier.height(5.dp)) + Spacer(Modifier.height(10.dp)) } } @@ -216,7 +217,7 @@ private fun MentionCard( modifier = Modifier .fillMaxWidth() .testTag("mention-card-${mention.id}") - .height(50.dp), + .height(60.dp), interactionSource = interactionSource, colors = CardDefaults.elevatedCardColors( containerColor = containerColor, @@ -225,16 +226,16 @@ private fun MentionCard( ) { Row( modifier = Modifier - .padding(vertical = 3.dp, horizontal = 10.dp), + .padding(horizontal = 20.dp), verticalAlignment = Alignment.CenterVertically ) { Avatar( url = mention.whoMentioned.avatarUrl, - modifier = Modifier.size(40.dp) + modifier = Modifier.size(50.dp) ) - Spacer(Modifier.width(5.dp)) + Spacer(Modifier.width(10.dp)) MentionCardText(mention, isHovered) - Spacer(Modifier.width(5.dp)) + Spacer(Modifier.width(10.dp)) SnoozeButton(flow, mention) } } @@ -258,7 +259,7 @@ private fun MentionCardText( Column( modifier = Modifier .fillMaxHeight() - .width(120.dp), + .width(240.dp), verticalArrangement = Arrangement.Center ) { Text( @@ -267,7 +268,7 @@ private fun MentionCardText( maxLines = 1, style = MaterialTheme.typography.bodyLarge ) - Spacer(Modifier.height(2.dp)) + Spacer(Modifier.height(4.dp)) Text( text = "$time, by ${mention.whoMentioned.username.value}", overflow = TextOverflow.Ellipsis, @@ -298,7 +299,7 @@ private fun SnoozeButton( onClick = { flow.snooze(mention.id) }, - modifier = Modifier.size(40.dp).testTag("snooze-button"), + modifier = Modifier.size(50.dp).testTag("snooze-button"), colors = IconButtonDefaults.iconButtonColors( contentColor = MaterialTheme.colorScheme.onSecondary ) @@ -307,7 +308,7 @@ private fun SnoozeButton( MentionStatus.SNOOZED -> Text( text = "Snoozed", - modifier = Modifier.size(40.dp) + modifier = Modifier.size(50.dp) .wrapContentSize(Alignment.Center), style = MaterialTheme.typography.bodySmall ) From 6a32668c3c248ee0417ded87c00a855e411a6493 Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Wed, 30 Oct 2024 12:59:11 +0200 Subject: [PATCH 06/11] Redesign Settings page for bigger window. --- .../spine/examples/pingh/desktop/Settings.kt | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Settings.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Settings.kt index 01ff2859..104b6b7a 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Settings.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Settings.kt @@ -62,6 +62,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.Role @@ -118,14 +119,14 @@ private fun SettingsHeader( Row( modifier = Modifier .fillMaxWidth() - .height(48.dp) - .padding(horizontal = 5.dp, vertical = 4.dp), + .height(60.dp) + .padding(horizontal = 10.dp), verticalAlignment = Alignment.CenterVertically ) { IconButton( icon = Icons.back, onClick = toMentionsPage, - modifier = Modifier.size(30.dp), + modifier = Modifier.size(35.dp), colors = IconButtonDefaults.iconButtonColors( contentColor = MaterialTheme.colorScheme.onSecondary ) @@ -153,7 +154,7 @@ private fun SettingsBox( Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) - .padding(5.dp), + .padding(10.dp), ) { Card( modifier = Modifier.fillMaxSize(), @@ -165,8 +166,8 @@ private fun SettingsBox( Column( modifier = Modifier .fillMaxSize() - .padding(vertical = 5.dp), - verticalArrangement = Arrangement.spacedBy(15.dp), + .padding(horizontal = 20.dp, vertical = 10.dp), + verticalArrangement = Arrangement.spacedBy(25.dp), content = content ) } @@ -189,7 +190,7 @@ private fun Profile( modifier = Modifier .fillMaxWidth() .height(60.dp) - .padding(horizontal = 10.dp, vertical = 4.dp), + .padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { Avatar( @@ -246,7 +247,7 @@ private fun LogOutButton( ) { OutlinedButton( onClick = onClick, - modifier = Modifier.height(20.dp).testTag("logout-button"), + modifier = Modifier.height(22.dp).testTag("logout-button"), colors = ButtonDefaults.outlinedButtonColors( containerColor = MaterialTheme.colorScheme.secondary, contentColor = MaterialTheme.colorScheme.onSecondary @@ -272,7 +273,7 @@ private fun SnoozeTimeOption(state: SettingsState) { Option( title = "Snooze time", description = "Time after which the notification is repeated.", - titleWight = 68.dp + titleWight = 150.dp ) { SnoozeTimeSegmentedButtonRow(state) } @@ -293,7 +294,7 @@ private fun DndOption( Option( title = "Do not disturb", description = "Turn off notifications for new mentions or snooze expirations.", - titleWight = 174.dp + titleWight = 324.dp ) { Switch( checked = enabledDndMode, @@ -333,9 +334,7 @@ private fun Option( control: @Composable () -> Unit ) { Column( - Modifier - .fillMaxWidth() - .padding(horizontal = 10.dp) + Modifier.fillMaxWidth() ) { Row( modifier = Modifier.fillMaxWidth(), @@ -344,14 +343,14 @@ private fun Option( Text( text = title, modifier = Modifier.width(titleWight), - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyLarge ) control() } - Spacer(Modifier.height(5.dp)) + Spacer(Modifier.height(10.dp)) Text( text = description, - modifier = Modifier.width(170.dp), + modifier = Modifier.width(360.dp), color = MaterialTheme.colorScheme.secondaryContainer, style = MaterialTheme.typography.bodySmall ) @@ -423,7 +422,7 @@ private fun SegmentedButton( selected = selected, onClick = onClick, modifier = Modifier - .width(48.dp) + .width(70.dp) .height(20.dp) .semantics { role = Role.RadioButton }, shape = shape, From 3058029a7de2a8607372f60ca58e2e15d9f2966a Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Wed, 30 Oct 2024 16:50:45 +0200 Subject: [PATCH 07/11] Redesign Login verification page. --- .../io/spine/examples/pingh/desktop/Login.kt | 70 +++++++++++-------- .../spine/examples/pingh/desktop/Mentions.kt | 8 +-- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt index 4cfd6198..c0d8cdcc 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt @@ -396,6 +396,7 @@ private fun LoginButton( .height(45.dp) .testTag("login-button"), enabled = enabled, + shape = MaterialTheme.shapes.medium, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary @@ -440,12 +441,12 @@ private fun VerificationPage( horizontalAlignment = Alignment.CenterHorizontally ) { VerificationTitle() - Spacer(Modifier.height(15.dp)) + Spacer(Modifier.height(25.dp)) UserCodeField( userCode = userCode, isExpired = isUserCodeExpired ) - Spacer(Modifier.height(15.dp)) + Spacer(Modifier.height(25.dp)) if (isUserCodeExpired) { Spacer(Modifier.height(5.dp)) CodeExpiredErrorMessage(flow) @@ -454,7 +455,7 @@ private fun VerificationPage( verificationUrl = verificationUrl, expiresIn = expiresIn ) - Spacer(Modifier.height(20.dp)) + Spacer(Modifier.height(25.dp)) SubmitButton( flow = flow, toMentionsPage = toMentionsPage @@ -470,7 +471,7 @@ private fun VerificationPage( private fun VerificationTitle() { Text( text = "Verify your login", - fontSize = 18.sp, + fontSize = 20.sp, style = MaterialTheme.typography.displayLarge ) } @@ -491,15 +492,32 @@ private fun UserCodeField( } else { MaterialTheme.colorScheme.onSecondary } - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.Center + Row( + modifier = Modifier + .width(260.dp) + .height(46.dp) + .run { + if (isExpired) { + this + } else { + border( + width = 1.dp, + color = MaterialTheme.colorScheme.onBackground, + shape = MaterialTheme.shapes.medium + ) + } + } + .padding(horizontal = 15.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically ) { SelectionContainer { Text( text = userCode.value, + modifier = Modifier.width(200.dp), color = color, - fontSize = 34.sp, + fontSize = 30.sp, + textAlign = if (isExpired) TextAlign.Center else TextAlign.Start, letterSpacing = 3.sp, style = MaterialTheme.typography.displayLarge ) @@ -520,20 +538,16 @@ private fun CopyToClipboardIcon( userCode: UserCode ) { val clipboardManager = LocalClipboardManager.current - Box( - modifier = Modifier.offset(x = 130.dp) - ) { - IconButton( - icon = Icons.copy, - onClick = { - clipboardManager.setText(AnnotatedString(userCode.value)) - }, - modifier = Modifier.size(40.dp), - colors = IconButtonDefaults.iconButtonColors( - contentColor = MaterialTheme.colorScheme.onSecondaryContainer - ) + IconButton( + icon = Icons.copy, + onClick = { + clipboardManager.setText(AnnotatedString(userCode.value)) + }, + modifier = Modifier.size(30.dp), + colors = IconButtonDefaults.iconButtonColors( + contentColor = MaterialTheme.colorScheme.onSecondaryContainer ) - } + ) } /** @@ -563,17 +577,16 @@ private fun VerificationText( expiresIn: Duration ) { Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier.width(260.dp) ) { Text( text = "Enter this code at", color = MaterialTheme.colorScheme.onSecondaryContainer, style = MaterialTheme.typography.bodyLarge ) - Spacer(Modifier.height(3.dp)) + Spacer(Modifier.height(5.dp)) VerificationUrlButton(verificationUrl) - Spacer(Modifier.height(3.dp)) + Spacer(Modifier.height(12.dp)) Text( text = "The code is valid for ${toMinutes(expiresIn)} minutes.", color = MaterialTheme.colorScheme.onSecondaryContainer, @@ -635,8 +648,8 @@ private fun SubmitButton( } Box( modifier = Modifier - .width(240.dp) - .height(40.dp), + .width(260.dp) + .height(46.dp), contentAlignment = Alignment.TopCenter ) { Button( @@ -644,6 +657,7 @@ private fun SubmitButton( modifier = Modifier.fillMaxSize() .testTag("submit-button"), enabled = enabled, + shape = MaterialTheme.shapes.medium, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary @@ -651,7 +665,7 @@ private fun SubmitButton( ) { Text( text = "I have entered the code", - style = MaterialTheme.typography.displayMedium + style = MaterialTheme.typography.displaySmall ) } if (!enabled) { diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt index c97a2a25..4faa4fbe 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt @@ -108,7 +108,7 @@ private fun ToolBar( Row( modifier = Modifier .fillMaxWidth() - .height(60.dp) + .height(68.dp) .background(MaterialTheme.colorScheme.secondary) .drawBehind { drawLine( @@ -118,18 +118,18 @@ private fun ToolBar( strokeWidth = 1.dp.toPx() ) } - .padding(horizontal = 30.dp), + .padding(start = 28.dp), verticalAlignment = Alignment.CenterVertically ) { IconButton( icon = Icons.pingh, onClick = toSettingsPage, - modifier = Modifier.size(50.dp).testTag("settings-button"), + modifier = Modifier.size(54.dp).testTag("settings-button"), colors = IconButtonDefaults.iconButtonColors( contentColor = contentColor ) ) - Spacer(Modifier.width(10.dp)) + Spacer(Modifier.width(8.dp)) Text( text = "Recent mentions", modifier = Modifier.width(250.dp), From 935a2fc7b8fc73b864148c1dc07bdfbb5254b27f Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Wed, 30 Oct 2024 17:00:49 +0200 Subject: [PATCH 08/11] Clean up. --- .../src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt index c0d8cdcc..701ff7d4 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt @@ -693,8 +693,8 @@ private fun NoResponseErrorMessage(flow: VerifyLogin) { clickablePartOfText = "start over", onClick = flow::requestNewUserCode, modifier = Modifier - .width(220.dp) - .offset(y = 45.dp) + .width(240.dp) + .offset(y = 50.dp) .testTag("no-response-message") ) } From 8bf1b1076d82f262587951703c379d535a7c8d3f Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Wed, 30 Oct 2024 17:12:43 +0200 Subject: [PATCH 09/11] Update `RestartButton`. --- desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt index 701ff7d4..bdf21e76 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Login.kt @@ -792,6 +792,7 @@ private fun RestartButton(flow: LoginFailed) { modifier = Modifier .width(240.dp) .height(40.dp), + shape = MaterialTheme.shapes.medium, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary From 48a5cca38983b1928a19f249e50e492765cc3fc9 Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Wed, 30 Oct 2024 19:37:53 +0200 Subject: [PATCH 10/11] Improve design of Mentions page. --- .../spine/examples/pingh/desktop/Mentions.kt | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt index 4faa4fbe..1d9177c3 100644 --- a/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt +++ b/desktop/src/main/kotlin/io/spine/examples/pingh/desktop/Mentions.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import io.spine.examples.pingh.client.MentionsFlow import io.spine.examples.pingh.client.howMuchTimeHasPassed import io.spine.examples.pingh.client.sorted @@ -105,34 +106,36 @@ private fun ToolBar( toSettingsPage: () -> Unit ) { val contentColor = MaterialTheme.colorScheme.onSecondary + val borderColor = MaterialTheme.colorScheme.onBackground Row( modifier = Modifier .fillMaxWidth() - .height(68.dp) + .height(72.dp) .background(MaterialTheme.colorScheme.secondary) .drawBehind { drawLine( - color = contentColor, + color = borderColor, start = Offset(0f, size.height), end = Offset(size.width, size.height), strokeWidth = 1.dp.toPx() ) } - .padding(start = 28.dp), + .padding(start = 27.dp), verticalAlignment = Alignment.CenterVertically ) { IconButton( icon = Icons.pingh, onClick = toSettingsPage, - modifier = Modifier.size(54.dp).testTag("settings-button"), + modifier = Modifier.size(56.dp).testTag("settings-button"), colors = IconButtonDefaults.iconButtonColors( contentColor = contentColor ) ) - Spacer(Modifier.width(8.dp)) + Spacer(Modifier.width(7.dp)) Text( text = "Recent mentions", modifier = Modifier.width(250.dp), + fontSize = 20.sp, color = contentColor, style = MaterialTheme.typography.displayLarge ) @@ -236,7 +239,7 @@ private fun MentionCard( Spacer(Modifier.width(10.dp)) MentionCardText(mention, isHovered) Spacer(Modifier.width(10.dp)) - SnoozeButton(flow, mention) + SnoozeButton(flow, mention, isHovered.value) } } } @@ -256,10 +259,15 @@ private fun MentionCardText( val time = mention.whenMentioned.run { if (isHovered.value) toDatetime() else howMuchTimeHasPassed() } + val textWidth = if (isHovered.value || mention.status == MentionStatus.SNOOZED) { + 240.dp + } else { + 300.dp + } Column( modifier = Modifier .fillMaxHeight() - .width(240.dp), + .width(textWidth), verticalArrangement = Arrangement.Center ) { Text( @@ -286,14 +294,16 @@ private fun MentionCardText( * * @param flow The flow for managing the lifecycle of mentions. * @param mention The mention whose information is displayed. + * @param isParentHovered Whether the parent mention card is being hovered. */ @Composable private fun SnoozeButton( flow: MentionsFlow, - mention: MentionView + mention: MentionView, + isParentHovered: Boolean ) { - when (mention.status) { - MentionStatus.UNREAD -> + when { + isParentHovered && mention.status == MentionStatus.UNREAD -> IconButton( icon = Icons.snooze, onClick = { @@ -305,14 +315,12 @@ private fun SnoozeButton( ) ) - MentionStatus.SNOOZED -> + mention.status == MentionStatus.SNOOZED -> Text( text = "Snoozed", modifier = Modifier.size(50.dp) .wrapContentSize(Alignment.Center), style = MaterialTheme.typography.bodySmall ) - - else -> {} } } From 9ea21ae0c6014127f0c4670735db6f84e9223089 Mon Sep 17 00:00:00 2001 From: MykytaPimonovTD Date: Thu, 31 Oct 2024 11:23:24 +0200 Subject: [PATCH 11/11] Add hover effect before clicking snooze button in testing. --- .../pingh/desktop/MentionsPageUiTest.kt | 17 ++++++++++++++--- .../examples/pingh/desktop/given/UiTestEnv.kt | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/desktop/src/test/kotlin/io/spine/examples/pingh/desktop/MentionsPageUiTest.kt b/desktop/src/test/kotlin/io/spine/examples/pingh/desktop/MentionsPageUiTest.kt index 355c4f67..e782f423 100644 --- a/desktop/src/test/kotlin/io/spine/examples/pingh/desktop/MentionsPageUiTest.kt +++ b/desktop/src/test/kotlin/io/spine/examples/pingh/desktop/MentionsPageUiTest.kt @@ -42,6 +42,7 @@ import io.kotest.matchers.floats.shouldBeLessThan import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual import io.kotest.matchers.shouldBe import io.spine.examples.pingh.desktop.given.DelayedFactAssertion.Companion.awaitFact +import io.spine.examples.pingh.desktop.given.performHover import io.spine.examples.pingh.desktop.given.testTag import kotlin.test.Test import org.junit.jupiter.api.DisplayName @@ -70,8 +71,13 @@ internal class MentionsPageUiTest : UiTest() { logIn() awaitFact { mentionCards().size shouldBeGreaterThanOrEqual 1 } val tag = mentionCards().random().testTag + onNodeWithTag(tag).performHover() + awaitFact { onSnoozeButtonWithParentTag(tag).assertExists() } onSnoozeButtonWithParentTag(tag).performClick() - awaitFact { onSnoozeButtonWithParentTag(tag).assertDoesNotExist() } + awaitFact { + onNodeWithTag(tag).performHover() + onSnoozeButtonWithParentTag(tag).assertDoesNotExist() + } } @Test @@ -82,7 +88,10 @@ internal class MentionsPageUiTest : UiTest() { awaitFact { mentionCards().size shouldBeGreaterThanOrEqual 1 } val tag = mentionCards().random().testTag onNodeWithTag(tag).performClick() - awaitFact { onSnoozeButtonWithParentTag(tag).assertDoesNotExist() } + awaitFact { + onNodeWithTag(tag).performHover() + onSnoozeButtonWithParentTag(tag).assertDoesNotExist() + } } @Test @@ -94,8 +103,10 @@ internal class MentionsPageUiTest : UiTest() { val mentionsCards = mentionCards().sortedBy { it.positionInRoot.y } val readMentionTag = mentionsCards[0].testTag val snoozedMentionTag = mentionsCards[1].testTag - onNodeWithTag(readMentionTag).performClick() + onNodeWithTag(snoozedMentionTag).performHover() + awaitFact { onSnoozeButtonWithParentTag(snoozedMentionTag).assertExists() } onSnoozeButtonWithParentTag(snoozedMentionTag).performClick() + onNodeWithTag(readMentionTag).performClick() awaitFact { val mentions = mentionCards() val readMention = mentions.first { it.testTag == readMentionTag } diff --git a/desktop/src/test/kotlin/io/spine/examples/pingh/desktop/given/UiTestEnv.kt b/desktop/src/test/kotlin/io/spine/examples/pingh/desktop/given/UiTestEnv.kt index 13effe40..e324bc7b 100644 --- a/desktop/src/test/kotlin/io/spine/examples/pingh/desktop/given/UiTestEnv.kt +++ b/desktop/src/test/kotlin/io/spine/examples/pingh/desktop/given/UiTestEnv.kt @@ -26,8 +26,12 @@ package io.spine.examples.pingh.desktop.given +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.performMouseInput import com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly import java.util.concurrent.TimeUnit import kotlin.time.Duration @@ -46,3 +50,13 @@ internal val SemanticsNode.testTag: String get() = config.getOrElse(SemanticsProperties.TestTag) { throw IllegalStateException("This node does not have a `TestTag` specified.") } + +/** + * Hovers the mouse pointer over the center of the element to trigger a hover event. + */ +@OptIn(ExperimentalTestApi::class) +internal fun SemanticsNodeInteraction.performHover() { + val size = this.fetchSemanticsNode().size + val middle = Offset(size.width / 2f, size.height / 2f) + this.performMouseInput { this.moveTo(middle) } +}