diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/HighlightedText.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/HighlightedText.kt new file mode 100644 index 000000000..e8138c6ca --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/HighlightedText.kt @@ -0,0 +1,186 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.swisstransfer.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.infomaniak.swisstransfer.R +import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme +import com.infomaniak.swisstransfer.ui.utils.PreviewLightAndDark +import kotlin.math.cos +import kotlin.math.sin + +private val VERTICAL_PADDING @Composable get() = with(LocalDensity.current) { 3.sp.toPx() } +private val HORIZONTAL_PADDING @Composable get() = with(LocalDensity.current) { 10.sp.toPx() } +private const val ROTATION_ANGLE_DEGREE = -3.0f + +@Composable +fun HighlightedText( + templateRes: Int, + argumentRes: Int, + style: TextStyle, + verticalPadding: Float = VERTICAL_PADDING, + horizontalPadding: Float = HORIZONTAL_PADDING, + angleDegrees: Float = ROTATION_ANGLE_DEGREE, +) { + val template = stringResource(templateRes) + val argument = stringResource(argumentRes) + val text = String.format(template, argument) + + val highlightedColor = SwissTransferTheme.colors.highlightedColor + + var highlightedPath by remember { mutableStateOf(null) } + + Text( + text = text, + style = style, + onTextLayout = { layoutResult -> + val boundingBoxes = layoutResult.getArgumentBoundingBoxes(text, argument) + highlightedPath = boundingBoxes.transformForHighlightedStyle(verticalPadding, horizontalPadding, angleDegrees) + }, + modifier = Modifier.drawBehind { + highlightedPath?.let { + drawPath(path = it, style = Fill, color = highlightedColor) + } + }, + ) +} + +private fun TextLayoutResult.getArgumentBoundingBoxes(text: String, argument: String): List { + val startIndex = text.indexOf(argument) + return getBoundingBoxesForRange(start = startIndex, end = startIndex + argument.count()) +} + +private fun List.transformForHighlightedStyle( + verticalPadding: Float, + horizontalPadding: Float, + angleDegrees: Float, +): Path = Path().apply { + forEach { boundingBox -> + addPath(boundingBox.transformForHighlightedStyle(verticalPadding, horizontalPadding, angleDegrees)) + } +} + +private fun Rect.transformForHighlightedStyle(verticalPadding: Float, horizontalPadding: Float, angleDegrees: Float): Path { + return getRotatedRectanglePath( + Rect( + left = left - horizontalPadding, + top = top - verticalPadding, + right = right + horizontalPadding, + bottom = bottom + verticalPadding, + ), + angleDegrees = angleDegrees, + ) +} + +private fun getRotatedRectanglePath(rect: Rect, angleDegrees: Float): Path { + val centerX = rect.left + (rect.width / 2) + val centerY = rect.top + (rect.height / 2) + + val angleRadians = Math.toRadians(angleDegrees.toDouble()) + + // Function to rotate a point around the center + fun rotatePoint(x: Float, y: Float, centerX: Float, centerY: Float, angleRadians: Double): Offset { + val dx = x - centerX + val dy = y - centerY + val cosAngle = cos(angleRadians) + val sinAngle = sin(angleRadians) + val newX = (dx * cosAngle - dy * sinAngle + centerX).toFloat() + val newY = (dx * sinAngle + dy * cosAngle + centerY).toFloat() + return Offset(newX, newY) + } + + val topLeft = rotatePoint(rect.left, rect.top, centerX, centerY, angleRadians) + val topRight = rotatePoint(rect.right, rect.top, centerX, centerY, angleRadians) + val bottomRight = rotatePoint(rect.right, rect.bottom, centerX, centerY, angleRadians) + val bottomLeft = rotatePoint(rect.left, rect.bottom, centerX, centerY, angleRadians) + + return Path().apply { + moveTo(topLeft.x, topLeft.y) + lineTo(topRight.x, topRight.y) + lineTo(bottomRight.x, bottomRight.y) + lineTo(bottomLeft.x, bottomLeft.y) + close() + } +} + +private fun TextLayoutResult.getBoundingBoxesForRange(start: Int, end: Int): List { + var previousRect: Rect? = null + var firstLineCharRect: Rect? = null + val boundingBoxes = mutableListOf() + + for (index in start..end) { + val rect = getBoundingBox(index) + val isLastRect = index == end + + // Single char case + if (isLastRect && firstLineCharRect == null) { + firstLineCharRect = rect + previousRect = rect + } + + // `rect.right` is zero for the last space in each line + // Might be an issue: https://issuetracker.google.com/issues/197146630 + if (!isLastRect && rect.right == 0.0f) continue + + if (firstLineCharRect == null) { + firstLineCharRect = rect + } else if (previousRect != null && (previousRect.bottom != rect.bottom || isLastRect)) { + boundingBoxes.add(firstLineCharRect.copy(right = previousRect.right)) + firstLineCharRect = rect + } + previousRect = rect + } + return boundingBoxes +} + +@PreviewLightAndDark +@Preview(locale = "fr") +@Preview(locale = "de") +@Preview(locale = "es") +@Preview(locale = "it") +@Composable +private fun Preview() { + SwissTransferTheme { + Surface { + Box(modifier = Modifier.padding(20.dp)) { + HighlightedText( + templateRes = R.string.uploadProgressTitleTemplate, + argumentRes = R.string.uploadProgressTitleArgument, + style = SwissTransferTheme.typography.bodyMedium + ) + } + } + } +} diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferTextField.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferTextField.kt index 2834145ef..1dc9e6ada 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferTextField.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferTextField.kt @@ -115,7 +115,7 @@ private fun getShowPasswordButton(shouldShowPassword: Boolean, onClick: () -> Un @Composable @Preview -fun Preview() { +private fun Preview() { SwissTransferTheme { Surface { Column(Modifier.padding(Margin.Medium)) { diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/components/AdHeader.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/components/AdHeader.kt index 87b53ba58..11cf43cdb 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/components/AdHeader.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/components/AdHeader.kt @@ -26,9 +26,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.infomaniak.swisstransfer.R +import com.infomaniak.swisstransfer.ui.components.HighlightedText import com.infomaniak.swisstransfer.ui.screen.newtransfer.upload.UploadProgressAdType import com.infomaniak.swisstransfer.ui.theme.Dimens import com.infomaniak.swisstransfer.ui.theme.Margin @@ -45,7 +45,13 @@ fun ColumnScope.AdHeader(adScreenType: UploadProgressAdType) { horizontalAlignment = Alignment.CenterHorizontally, ) { Spacer(modifier = Modifier.height(Margin.Giant)) - Text(stringResource(R.string.uploadSuccessTitle), style = SwissTransferTheme.typography.bodyMedium) + + HighlightedText( + templateRes = R.string.uploadProgressTitleTemplate, + argumentRes = R.string.uploadProgressTitleArgument, + style = SwissTransferTheme.typography.bodyMedium, + ) + Spacer(modifier = Modifier.height(Margin.Huge)) Text( text = adScreenType.description(), diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/ColorDark.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/ColorDark.kt index 4942c3543..65d2c6901 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/ColorDark.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/ColorDark.kt @@ -92,4 +92,5 @@ val CustomDarkColorScheme = CustomColorScheme( transferFilePreviewOverflow = Color(dark3), onTransferFilePreviewOverflow = Color(green_main), transferListStroke = Color(green_main), + highlightedColor = Color(green_dark), ) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/ColorLight.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/ColorLight.kt index b8557f460..a92f243d8 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/ColorLight.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/ColorLight.kt @@ -93,4 +93,5 @@ val CustomLightColorScheme = CustomColorScheme( transferFilePreviewOverflow = Color(green_dark), onTransferFilePreviewOverflow = Color(green_contrast), transferListStroke = Color(green_dark), + highlightedColor = Color(green_secondary), ) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/Theme.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/Theme.kt index ab86b0e88..0db11ea68 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/Theme.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/Theme.kt @@ -88,6 +88,7 @@ data class CustomColorScheme( val transferFilePreviewOverflow: Color = Color.Unspecified, val onTransferFilePreviewOverflow: Color = Color.Unspecified, val transferListStroke: Color = Color.Unspecified, + val highlightedColor: Color = Color.Unspecified, ) private val Shapes = Shapes( diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 73b607938..ae74cbcde 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -126,6 +126,8 @@ Wir entwickeln %s Ohne Kompromisse bei Ökologie, Privatsphäre und Menschen. Die Übertragung hat nicht funktioniert, gib ihr noch eine Chance. Wenn das Problem weiterhin besteht, wende dich an unseren Support. Was ist das denn? + zeichnet uns aus + Was %s? Der Download-Link wurde erfolgreich an den folgenden Empfänger gesendet: Der Download-Link wurde an die folgenden Empfänger gesendet: @@ -134,7 +136,6 @@ Lass diesen QR-Code in deiner Umgebung scannen, um deine Dateien weiterzugeben, oder kopiere den untenstehenden Link. Deine Überweisung ist fertig! Dein QR-Code ist fertig! - Was zeichnet uns aus? Transfer wird durchgeführt… https://www.infomaniak.com/de/about https://feedback.userreport.com/9abc7665-a78e-4fd2-aa41-a47a8b867fcd/#ideas/popular diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 43122de78..0f4596f8d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -126,6 +126,8 @@ Desarrollamos %s Sin comprometer la ecología, la privacidad ni a las personas. La transferencia no ha funcionado, dale otra oportunidad. Si el problema persiste, ponte en contacto con nuestro equipo de asistencia. ¿Pero qué, qué? + diferencia + ¿Qué nos %s? El enlace de descarga se ha enviado al siguiente destinatario: El enlace de descarga se ha enviado a los siguientes destinatarios: @@ -134,7 +136,6 @@ Escanea este código QR a tu alrededor para compartir tus archivos o copia el enlace que aparece a continuación. Su transferencia está lista Su código QR está listo - ¿Qué nos diferencia? Transferencia en curso… https://www.infomaniak.com/es/about https://feedback.userreport.com/1c462a20-7559-415e-a6e0-4b624dc38877/#ideas/popular diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index dd8b723c5..581fd1533 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -128,6 +128,8 @@ Nous développons %s Sans compromis sur l’écologie,
la vie privée et l’humain. Le transfert n’a pas fonctionné, donnes-lui une autre chance. Si le problème persiste, contacte notre support. Qu’est-ce que quoi ? + différent + Ce qui nous rend %s ? Le lien de téléchargement a bien été envoyé au destinataire suivant : Le lien de téléchargement a été envoyé aux destinataires suivants : @@ -136,7 +138,6 @@ Fais scanner ce QR code autour de toi pour partager tes fichiers ou copie le lien ci-dessous. Ton transfert est prêt ! Ton QR code est prêt ! - Ce qui nous rend différent ? Transfert en cours… https://www.infomaniak.com/fr/a-propos https://feedback.userreport.com/1c462a20-7559-415e-a6e0-4b624dc38877/#ideas/popular diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4d2927a82..717b481b4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -126,6 +126,8 @@ Stiamo sviluppando %s Senza compromettere l’ecologia, la privacy e le persone. Il trasferimento non ha funzionato, da un’altra possibilità. Se il problema persiste, contatta il nostro team di assistenza. Che cosa che… cosa? + diversi + Cosa ci rende %s? Il link per il download è stato inviato al seguente destinatario: Il link per il download è stato inviato ai seguenti destinatari: @@ -134,7 +136,6 @@ Scansiona questo codice QR intorno a voi per condividere i vostri file o copiate il link qui sotto. Il trasferimento è pronto! Il codice QR è pronto! - Cosa ci differenzia? Trasferimento in corso… https://www.infomaniak.com/it/about https://feedback.userreport.com/c85aa792-0f76-4923-8fe2-fae976cac9c2/#ideas/popular diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e240f0d4e..f21fc88e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -131,6 +131,8 @@ We develop %s Without compromising on ecology, privacy and people. The transfer didn’t work, give it another chance. If the problem persists, contact our support team. Wait, what just happened? + apart + What sets us %s? The download link has been sent to the following recipient: The download link has been sent to the following recipients: @@ -139,7 +141,6 @@ Scan this QR code around you to share your files, or copy the link below. Your transfer is ready! Your QR code is ready! - What sets us apart? Transfer in progress… https://www.infomaniak.com/en/about https://feedback.userreport.com/f12466ad-db5b-4f5c-b24c-a54b0a5117ca/#ideas/popular