From 5c0723e92a547e88d086c7a9943dfa0aaf7a7c23 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 20 Jan 2026 16:09:09 +0100 Subject: [PATCH 01/18] wip --- .../feature/claim/chat/data/ClaimIntent.kt | 38 +++++++------- .../feature/claim/chat/data/ClaimIntentExt.kt | 2 +- .../AudioRecordingStepSections.kt | 51 ++++++++++++------- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt index 32e444bfc4..a9d7396187 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt @@ -181,8 +181,6 @@ internal sealed interface StepContent { } sealed interface AudioRecordingStepState { - - data object NonDefined: AudioRecordingStepState data class FreeTextDescription( val showOverlay: Boolean, val errorType: FreeTextErrorType?, @@ -191,21 +189,27 @@ sealed interface AudioRecordingStepState { ) : AudioRecordingStepState sealed interface AudioRecording : AudioRecordingStepState { - data object NotRecording : AudioRecording - - data class Recording( - val amplitudes: List, - val startedAt: Instant, - val filePath: String, - ) : AudioRecording - - data class Playback( - val filePath: String, - val isPlaying: Boolean, - val isPrepared: Boolean, - val amplitudes: List, - val hasError: Boolean, - ) : AudioRecording + + data object NotOpened: AudioRecording + + sealed interface Opened: AudioRecording { + data object NotRecording : Opened + + data class Recording( + val amplitudes: List, + val startedAt: Instant, + val filePath: String, + ) : Opened + + data class Playback( + val filePath: String, + val isPlaying: Boolean, + val isPrepared: Boolean, + val amplitudes: List, + val hasError: Boolean, + ) : Opened + } + } } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt index 281cc67e09..6c69cb5084 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt @@ -81,7 +81,7 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): is AudioRecordingFragment -> StepContent.AudioRecording( uploadUri = uploadUri, isSkippable = isSkippable, - recordingState = AudioRecordingStepState.NonDefined, + recordingState = AudioRecordingStepState.AudioRecording.NotOpened, freeTextMinLength = freeTextMinLength, freeTextMaxLength = freeTextMaxLength, ) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 4d92a4db64..a9f6304aa6 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -72,28 +72,28 @@ internal fun AudioRecorderBubble( when (s) { is AudioRecordingStepState.AudioRecording -> "audio_recording" is AudioRecordingStepState.FreeTextDescription -> "freetext" - AudioRecordingStepState.NonDefined -> "non_defined" } }, ) { uiStateAnimated -> Column(modifier) { when (uiStateAnimated) { - is AudioRecordingStepState.AudioRecording -> { - AudioRecordingSection( - uiState = uiStateAnimated, - clock = clock, - shouldShowRequestPermissionRationale = onShouldShowRequestPermissionRationale, - startRecording = startRecording, - stopRecording = stopRecording, - submitAudioFile = submitAudioFile, - redo = redoRecording, - openAppSettings = openAppSettings, - allowFreeText = freeTextAvailable, - launchFreeText = onShowFreeText, - isCurrentStep = isCurrentStep, - continueButtonLoading = continueButtonLoading, - ) - } +// is AudioRecordingStepState.AudioRecording -> { +//// AudioRecordingSection( +//// uiState = uiStateAnimated, +//// clock = clock, +//// shouldShowRequestPermissionRationale = onShouldShowRequestPermissionRationale, +//// startRecording = startRecording, +//// stopRecording = stopRecording, +//// submitAudioFile = submitAudioFile, +//// redo = redoRecording, +//// openAppSettings = openAppSettings, +//// allowFreeText = freeTextAvailable, +//// launchFreeText = onShowFreeText, +//// isCurrentStep = isCurrentStep, +//// continueButtonLoading = continueButtonLoading, +//// ) +// +// } is AudioRecordingStepState.FreeTextDescription -> { FreeTextInputSection( @@ -109,8 +109,16 @@ internal fun AudioRecorderBubble( ) } - AudioRecordingStepState.NonDefined -> { + is AudioRecordingStepState.AudioRecording -> { Column(Modifier.fillMaxWidth()) { + if (uiStateAnimated is AudioRecordingStepState.AudioRecording.Opened) { + AudioRecordingBottomSheet( + uiStateAnimated, + onDismiss = { + // onShowUndefined() //todo + } + ) + } HedvigButton( enabled = true, text = stringResource(Res.string.CLAIM_CHAT_USE_AUDIO), @@ -145,6 +153,13 @@ internal fun AudioRecorderBubble( } } +@Composable +private fun AudioRecordingBottomSheet( + audioRecordingState: AudioRecordingStepState.AudioRecording, + onDismiss: () -> Unit +) { + //todo +} @Composable private fun FreeTextInputSection( From d5183f589fc29578b11d55f50812d9fe68c070a1 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 21 Jan 2026 12:02:11 +0100 Subject: [PATCH 02/18] add design foundation: BS, buttons, RestingAudioPlayer --- .../android/audio/player/HedvigAudioPlayer.kt | 41 +++ .../audio/player/internal/FakeAudioWaves.kt | 2 +- .../androidMain/res/values-sv-rSE/strings.xml | 9 +- .../src/androidMain/res/values/strings.xml | 9 +- .../values-sv-rSE/strings.xml | 9 +- .../composeResources/values/strings.xml | 9 +- .../feature/claim/chat/ClaimChatViewModel.kt | 8 +- .../feature/claim/chat/data/ClaimIntent.kt | 12 +- .../feature/claim/chat/data/ClaimIntentExt.kt | 2 +- .../claim/chat/ui/ClaimChatDestination.kt | 12 +- .../claim/chat/ui/ClaimChatUiComponents.kt | 9 +- .../AudioRecordingStepSections.kt | 334 ++++++++++++++++-- 12 files changed, 391 insertions(+), 65 deletions(-) diff --git a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt index 1b38e267b3..fe5c116deb 100644 --- a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt +++ b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt @@ -1,11 +1,28 @@ package com.hedvig.android.audio.player +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.audio.player.internal.FakeWaveAudioPlayerCard +import com.hedvig.android.audio.player.internal.waveWidthPercentOfSpaceAvailable +import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.audio.player.data.AudioPlayer +import kotlin.math.roundToInt /** * https://www.figma.com/file/e0lnWjMtp8x5Typlt5b33i/Claim-status-Android?node-id=1224%3A82&t=RgoySHgQiM6RyYNI-1 @@ -25,3 +42,27 @@ fun HedvigAudioPlayer(audioPlayer: AudioPlayer, modifier: Modifier = Modifier, o modifier = modifier, ) } + +@Composable +fun RestingAudioPlayer(modifier: Modifier = Modifier) { + BoxWithConstraints(modifier) { + val numberOfWaves = remember(maxWidth) { + (maxWidth / 5f).value.roundToInt() + } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth(), + ) { + repeat(numberOfWaves) { _ -> + Box( + modifier = Modifier + .size(2.dp) + .clip(CircleShape) + .background(HedvigTheme.colorScheme.fillPrimary), + ) + } + } + } +} diff --git a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/internal/FakeAudioWaves.kt b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/internal/FakeAudioWaves.kt index 033d2bdf1c..00c00d8f67 100644 --- a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/internal/FakeAudioWaves.kt +++ b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/internal/FakeAudioWaves.kt @@ -37,7 +37,7 @@ import kotlin.math.absoluteValue import kotlin.math.roundToInt import kotlin.random.Random -private const val waveWidthPercentOfSpaceAvailable = 0.5f +internal const val waveWidthPercentOfSpaceAvailable = 0.5f @Composable internal fun FakeAudioWaves( diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index 2c889c96d8..d929f2a3b5 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -50,6 +50,11 @@ Om du avslutar detta tillägg ingår inte det extra skyddet längre i din hemförsäkring. Avsluta %1$s OK + Lyssna + Skicka + Börja + Börja om + Stoppa Öppna BankID-appen på din telefon eller skanna QR-koden för att logga in Logga in med BankID Öppna BankID @@ -197,7 +202,7 @@ Tidigare svar kommer att rensas Dubbelkolla att du fyllt i alla obligatoriska fält Uppladdade filer - n/a + Du kan ladda upp bilder, PDF:er eller andra filer. Ladda bara upp filer som är relevanta för din skadeanmälan. Ladda upp filer Värdet får vara högst %d Värdet måste vara minst %d @@ -210,6 +215,7 @@ Vänligen ange telefonnummer ifall vi behöver kontakta dig Se till att vi har rätt telefonnummer ifall vi behöver kontakta dig Inspelning + If you don\'t remember the purchase date, you don\'t need to fill anything. Överhoppad Överhoppad Hoppa över @@ -221,6 +227,7 @@ Unknown step Starta röstinspelning Beskriv med text + If you don\'t know the exact date, enter an approximate date. Händelsedatum Skicka in din skadeanmälan Vilken försäkring gäller det? diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index 72e1758a85..e6ccc1cf80 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -50,6 +50,11 @@ By canceling this extended coverage, your home insurance will no longer include this extra protection. Cancel %1$s OK + Listen + Send + Start + Start over + Stop Open the BankID app on your phone or scan the QR-code to login Login with BankID Open BankID @@ -197,7 +202,7 @@ Previous answers will be reset Make sure you fill in all the required fields Uploaded files - n/a + You can upload images, PDFs or other files. Only include files that are relevant to your claim. Send files Value must be at most %d Value must be at least %d @@ -210,6 +215,7 @@ Please provide phone number in the case we need to contact you Make sure we have right phone number in the case we need to contact you Recording + If you don\'t remember the purchase date, you don\'t need to fill anything. Skipped Skipped Skip @@ -221,6 +227,7 @@ Unknown step Start voice recording Describe with text + If you don\'t know the exact date, enter an approximate date. Date of occurrence Submit your claim What insurance is it about? diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index 68a341bebe..d3903d062d 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -50,6 +50,11 @@ Om du avslutar detta tillägg ingår inte det extra skyddet längre i din hemförsäkring. Avsluta %1$s OK + Lyssna + Skicka + Börja + Börja om + Stoppa Öppna BankID-appen på din telefon eller skanna QR-koden för att logga in Logga in med BankID Öppna BankID @@ -197,7 +202,7 @@ Tidigare svar kommer att rensas Dubbelkolla att du fyllt i alla obligatoriska fält Uppladdade filer - n/a + Du kan ladda upp bilder, PDF:er eller andra filer. Ladda bara upp filer som är relevanta för din skadeanmälan. Ladda upp filer Värdet får vara högst %1$d Värdet måste vara minst %1$d @@ -210,6 +215,7 @@ Vänligen ange telefonnummer ifall vi behöver kontakta dig Se till att vi har rätt telefonnummer ifall vi behöver kontakta dig Inspelning + If you don't remember the purchase date, you don't need to fill anything. Överhoppad Överhoppad Hoppa över @@ -221,6 +227,7 @@ Unknown step Starta röstinspelning Beskriv med text + If you don't know the exact date, enter an approximate date. Händelsedatum Skicka in din skadeanmälan Vilken försäkring gäller det? diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index 1719670911..68adfbc28f 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -50,6 +50,11 @@ By canceling this extended coverage, your home insurance will no longer include this extra protection. Cancel %1$s OK + Listen + Send + Start + Start over + Stop Open the BankID app on your phone or scan the QR-code to login Login with BankID Open BankID @@ -197,7 +202,7 @@ Previous answers will be reset Make sure you fill in all the required fields Uploaded files - n/a + You can upload images, PDFs or other files. Only include files that are relevant to your claim. Send files Value must be at most %1$d Value must be at least %1$d @@ -210,6 +215,7 @@ Please provide phone number in the case we need to contact you Make sure we have right phone number in the case we need to contact you Recording + If you don't remember the purchase date, you don't need to fill anything. Skipped Skipped Skip @@ -221,6 +227,7 @@ Unknown step Start voice recording Describe with text + If you don't know the exact date, enter an approximate date. Date of occurrence Submit your claim What insurance is it about? diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt index 5d047a68c6..23366e572f 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt @@ -64,9 +64,9 @@ internal sealed interface ClaimChatEvent { data class RedoRecording(override val id: StepId) : AudioRecording - data class ShowFreeText(override val id: StepId) : AudioRecording + data class SwitchToFreeText(override val id: StepId) : AudioRecording - data class ShowAudioRecording(override val id: StepId) : AudioRecording + data class SwitchToAudioRecording(override val id: StepId) : AudioRecording } data class UpdateFreeText(val text: String?) : ClaimChatEvent @@ -359,7 +359,7 @@ internal class ClaimChatPresenter( } } - is ClaimChatEvent.AudioRecording.ShowFreeText -> { + is ClaimChatEvent.AudioRecording.SwitchToFreeText -> { val currentContent = currentStep?.stepContent as? StepContent.AudioRecording ?: return@CollectEvents val textTooShort = freeText?.length?.let { @@ -379,7 +379,7 @@ internal class ClaimChatPresenter( } } - is ClaimChatEvent.AudioRecording.ShowAudioRecording -> { + is ClaimChatEvent.AudioRecording.SwitchToAudioRecording -> { steps.updateStepWithSuccess(event.id) { step, content -> step.copy(stepContent = content.copy(recordingState = AudioRecording.NotRecording)) } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt index a9d7396187..376763e98c 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt @@ -189,17 +189,13 @@ sealed interface AudioRecordingStepState { ) : AudioRecordingStepState sealed interface AudioRecording : AudioRecordingStepState { - - data object NotOpened: AudioRecording - - sealed interface Opened: AudioRecording { - data object NotRecording : Opened + data object NotRecording : AudioRecording data class Recording( val amplitudes: List, val startedAt: Instant, val filePath: String, - ) : Opened + ) : AudioRecording data class Playback( val filePath: String, @@ -207,9 +203,7 @@ sealed interface AudioRecordingStepState { val isPrepared: Boolean, val amplitudes: List, val hasError: Boolean, - ) : Opened - } - + ) : AudioRecording } } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt index 6c69cb5084..a34aa01263 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt @@ -81,7 +81,7 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): is AudioRecordingFragment -> StepContent.AudioRecording( uploadUri = uploadUri, isSkippable = isSkippable, - recordingState = AudioRecordingStepState.AudioRecording.NotOpened, + recordingState = AudioRecordingStepState.AudioRecording.NotRecording, freeTextMinLength = freeTextMinLength, freeTextMaxLength = freeTextMaxLength, ) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index 36e2f574a5..ad5667c0c6 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -838,10 +838,10 @@ private fun StepBottomContent( item = stepItem, stepContent = stepItem.stepContent, onShowFreeText = { - onEvent(ClaimChatEvent.AudioRecording.ShowFreeText(stepItem.id)) + onEvent(ClaimChatEvent.AudioRecording.SwitchToFreeText(stepItem.id)) }, - onShowAudioRecording = { - onEvent(ClaimChatEvent.AudioRecording.ShowAudioRecording(stepItem.id)) + onSwitchToAudioRecording = { + onEvent(ClaimChatEvent.AudioRecording.SwitchToAudioRecording(stepItem.id)) }, onLaunchFullScreenEditText = { restrictions -> onEvent(ClaimChatEvent.OpenFreeTextOverlay(restrictions)) @@ -1385,7 +1385,7 @@ private fun AudioRecordingStep( freeText: String?, stepContent: StepContent.AudioRecording, onShowFreeText: () -> Unit, - onShowAudioRecording: () -> Unit, + onSwitchToAudioRecording: () -> Unit, onLaunchFullScreenEditText: (restrictions: FreeTextRestrictions) -> Unit, submitFreeText: () -> Unit, submitAudioFile: () -> Unit, @@ -1416,8 +1416,8 @@ private fun AudioRecordingStep( openAppSettings = openAppSettings, freeTextAvailable = true, submitFreeText = submitFreeText, - onShowFreeText = onShowFreeText, - onShowAudioRecording = onShowAudioRecording, + onSwitchToFreeText = onShowFreeText, + onSwitchToAudioRecording = onSwitchToAudioRecording, onLaunchFullScreenEditText = { onLaunchFullScreenEditText( FreeTextRestrictions( diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt index 49f466c03c..00ed9a3c20 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt @@ -2,7 +2,6 @@ package com.hedvig.feature.claim.chat.ui import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.Animatable import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -41,7 +40,6 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.semantics.selectableGroup import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.input.ImeAction @@ -86,7 +84,6 @@ import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.ThreeDotsLoading import com.hedvig.android.design.system.hedvig.a11y.FlowHeading import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState -import com.hedvig.android.design.system.hedvig.debugBorder import com.hedvig.android.design.system.hedvig.icon.Camera import com.hedvig.android.design.system.hedvig.icon.Close import com.hedvig.android.design.system.hedvig.icon.Document @@ -95,8 +92,6 @@ import com.hedvig.android.design.system.hedvig.icon.Image import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.design.system.hedvig.rememberPreviewImageLoader import com.hedvig.android.design.system.hedvig.show -import com.hedvig.android.logger.logcat -import com.hedvig.android.ui.claimflow.HedvigChip import com.hedvig.audio.player.data.PlayableAudioSource import com.hedvig.audio.player.data.SignedAudioUrl import com.hedvig.feature.claim.chat.data.AudioRecordingStepState @@ -983,8 +978,8 @@ private fun PreviewClaimChatComponents() { openAppSettings = {}, freeTextAvailable = true, submitFreeText = {}, - onShowFreeText = {}, - onShowAudioRecording = {}, + onSwitchToFreeText = {}, + onSwitchToAudioRecording = {}, onLaunchFullScreenEditText = {}, canSkip = true, onSkip = {}, diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index a9f6304aa6..156cdacdf4 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -1,41 +1,80 @@ package com.hedvig.feature.claim.chat.ui.audiorecording import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.graphics.Shape import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.hedvig.android.audio.player.HedvigAudioPlayer +import com.hedvig.android.audio.player.RestingAudioPlayer +import com.hedvig.android.audio.player.audioplayer.rememberAudioPlayer +import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.ButtonDefaults +import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigCircularProgressIndicator import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText -import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HorizontalDivider +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.IconButton import com.hedvig.android.design.system.hedvig.PermissionDialog import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.freetext.FreeTextDisplay +import com.hedvig.android.design.system.hedvig.icon.ArrowUp +import com.hedvig.android.design.system.hedvig.icon.Document +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Mic +import com.hedvig.android.design.system.hedvig.icon.Pause +import com.hedvig.android.design.system.hedvig.icon.Play +import com.hedvig.android.design.system.hedvig.icon.Refresh +import com.hedvig.android.design.system.hedvig.icon.Reload +import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState +import com.hedvig.audio.player.data.PlayableAudioSource import com.hedvig.feature.claim.chat.data.AudioRecordingStepState import com.hedvig.feature.claim.chat.data.FreeTextErrorType import com.hedvig.feature.claim.chat.ui.RoundCornersPill import com.hedvig.feature.claim.chat.ui.SkippedLabel +import hedvig.resources.AUDIO_RECORDER_LISTEN +import hedvig.resources.AUDIO_RECORDER_SEND +import hedvig.resources.AUDIO_RECORDER_START +import hedvig.resources.AUDIO_RECORDER_START_OVER +import hedvig.resources.AUDIO_RECORDER_STOP import hedvig.resources.CLAIMS_TEXT_INPUT_MIN_CHARACTERS_ERROR import hedvig.resources.CLAIMS_TEXT_INPUT_PLACEHOLDER import hedvig.resources.CLAIMS_USE_AUDIO_RECORDING import hedvig.resources.CLAIMS_USE_TEXT_INSTEAD import hedvig.resources.CLAIM_CHAT_USE_AUDIO +import hedvig.resources.CLAIM_TRIAGING_TITLE import hedvig.resources.PERMISSION_DIALOG_RECORD_AUDIO_MESSAGE import hedvig.resources.Res import hedvig.resources.SAVE_AND_CONTINUE_BUTTON_LABEL @@ -56,8 +95,8 @@ internal fun AudioRecorderBubble( openAppSettings: () -> Unit, freeTextAvailable: Boolean, submitFreeText: () -> Unit, - onShowFreeText: () -> Unit, - onShowAudioRecording: () -> Unit, + onSwitchToFreeText: () -> Unit, + onSwitchToAudioRecording: () -> Unit, onLaunchFullScreenEditText: () -> Unit, canSkip: Boolean, onSkip: () -> Unit, @@ -77,28 +116,11 @@ internal fun AudioRecorderBubble( ) { uiStateAnimated -> Column(modifier) { when (uiStateAnimated) { -// is AudioRecordingStepState.AudioRecording -> { -//// AudioRecordingSection( -//// uiState = uiStateAnimated, -//// clock = clock, -//// shouldShowRequestPermissionRationale = onShouldShowRequestPermissionRationale, -//// startRecording = startRecording, -//// stopRecording = stopRecording, -//// submitAudioFile = submitAudioFile, -//// redo = redoRecording, -//// openAppSettings = openAppSettings, -//// allowFreeText = freeTextAvailable, -//// launchFreeText = onShowFreeText, -//// isCurrentStep = isCurrentStep, -//// continueButtonLoading = continueButtonLoading, -//// ) -// -// } is AudioRecordingStepState.FreeTextDescription -> { FreeTextInputSection( submitFreeText = submitFreeText, - showAudioRecording = onShowAudioRecording, + showAudioRecording = onSwitchToAudioRecording, onLaunchFullScreenEditText = onLaunchFullScreenEditText, freeText = freeText, hasError = uiStateAnimated.hasError, @@ -111,18 +133,31 @@ internal fun AudioRecorderBubble( is AudioRecordingStepState.AudioRecording -> { Column(Modifier.fillMaxWidth()) { - if (uiStateAnimated is AudioRecordingStepState.AudioRecording.Opened) { - AudioRecordingBottomSheet( - uiStateAnimated, - onDismiss = { - // onShowUndefined() //todo - } - ) - } + val state = rememberHedvigBottomSheetState() + AudioRecordingBottomSheet( + audioRecordingState = uiStateAnimated, + onDismiss = { + state.dismiss() + }, + clock = clock, + shouldShowRequestPermissionRationale = onShouldShowRequestPermissionRationale, + startRecording = startRecording, + stopRecording = stopRecording, + submitAudioFile = submitAudioFile, + redo = redoRecording, + openAppSettings = openAppSettings, + allowFreeText = freeTextAvailable, + launchFreeText = onSwitchToFreeText, + isCurrentStep = isCurrentStep, + continueButtonLoading = continueButtonLoading, + bottomSheetState = state, + ) HedvigButton( enabled = true, text = stringResource(Res.string.CLAIM_CHAT_USE_AUDIO), - onClick = onShowAudioRecording, + onClick = { + state.show(Unit) + }, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(8.dp)) @@ -130,7 +165,7 @@ internal fun AudioRecorderBubble( enabled = true, buttonStyle = ButtonDefaults.ButtonStyle.Secondary, text = stringResource(Res.string.CLAIMS_USE_TEXT_INSTEAD), - onClick = onShowFreeText, + onClick = onSwitchToFreeText, modifier = Modifier.fillMaxWidth(), ) } @@ -155,10 +190,243 @@ internal fun AudioRecorderBubble( @Composable private fun AudioRecordingBottomSheet( + bottomSheetState: HedvigBottomSheetState, audioRecordingState: AudioRecordingStepState.AudioRecording, - onDismiss: () -> Unit + clock: Clock, + shouldShowRequestPermissionRationale: (String) -> Boolean, + startRecording: () -> Unit, + stopRecording: () -> Unit, + submitAudioFile: () -> Unit, + redo: () -> Unit, + openAppSettings: () -> Unit, + launchFreeText: () -> Unit, + allowFreeText: Boolean, + isCurrentStep: Boolean, + continueButtonLoading: Boolean, + modifier: Modifier = Modifier, + onDismiss: () -> Unit, ) { - //todo + var showPermissionDialog by remember { mutableStateOf(false) } + val recordAudioPermissionState = if (LocalInspectionMode.current) { + object : PermissionState { + override val permission: String = "" + override val status: PermissionStatus = PermissionStatus.Granted + + override fun launchPermissionRequest() {} + } + } else { + rememberPermissionState(RECORD_AUDIO_PERMISSION) { isGranted -> + if (isGranted) { + startRecording() + } else { + showPermissionDialog = true + } + } + } + if (showPermissionDialog) { + PermissionDialog( + permissionDescription = stringResource(Res.string.PERMISSION_DIALOG_RECORD_AUDIO_MESSAGE), + isPermanentlyDeclined = !shouldShowRequestPermissionRationale(RECORD_AUDIO_PERMISSION), + onDismiss = { showPermissionDialog = false }, + okClick = recordAudioPermissionState::launchPermissionRequest, + openAppSettings = openAppSettings, + ) + } + HedvigBottomSheet(bottomSheetState) { + Column { + HedvigText( + stringResource(Res.string.CLAIM_TRIAGING_TITLE), + modifier = Modifier.fillMaxWidth().semantics { + heading() + }, + textAlign = TextAlign.Center, + ) + Spacer(Modifier.height(24.dp)) + if (audioRecordingState is AudioRecordingStepState.AudioRecording.Playback) { + if (!audioRecordingState.isPrepared) { + HedvigCircularProgressIndicator() + } else { + val audioPlayer = rememberAudioPlayer(PlayableAudioSource.LocalFilePath(audioRecordingState.filePath)) + HedvigAudioPlayer( + audioPlayer = audioPlayer, + Modifier.padding( + horizontal = 45.dp, + vertical = 64.dp, + ), + ) + } + } else { + RestingAudioPlayer( + Modifier.padding( + horizontal = 45.dp, + vertical = 78.dp, + ), + ) + } + Row( + modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + AudioButton( + modifier = Modifier.weight(1f), + type = AudioButtonType.StartOver( + onStartOver = { + //todo + }, + isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback, + ), + ) + Spacer(Modifier.width(4.dp)) + AudioButton( + modifier = Modifier.weight(1f), + type = AudioButtonType.Control( + onStartRecording = { + //todo + }, + onStopRecording = { + //todo + }, + onListen = { + //todo + }, + onPause = { + //todo + }, + audioRecordingState = audioRecordingState, + ), + ) + Spacer(Modifier.width(4.dp)) + AudioButton( + modifier = Modifier.weight(1f), + type = AudioButtonType.Send( + onSend = { + //todo + }, + isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback, + ), + ) + } + Spacer(Modifier.height(16.dp)) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) + } + } +} + +@Composable +private fun AudioButton( + type: AudioButtonType, + modifier: Modifier = Modifier, +) { + Surface( + shape = HedvigTheme.shapes.cornerLarge, + modifier = modifier + .clip(HedvigTheme.shapes.cornerLarge) + .semantics(true) { + role = Role.Button + } + .clickable( + enabled = type.isEnabled, + onClick = { + when (type) { + is AudioButtonType.Control -> when (type.audioRecordingState) { + AudioRecordingStepState.AudioRecording.NotRecording -> type.onStartRecording + is AudioRecordingStepState.AudioRecording.Playback -> if (type.audioRecordingState.isPrepared) { + type.onListen + } else if (type.audioRecordingState.isPlaying) { + type.onPause + } //todo: else?? + is AudioRecordingStepState.AudioRecording.Recording -> type.onStopRecording + } + + is AudioButtonType.Send -> type.onSend + is AudioButtonType.StartOver -> type.onStartOver + } + }, + ), + ) { + Column( + modifier = Modifier.padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .clip(HedvigTheme.shapes.cornerXXLarge) + .background( + color = if (!type.isEnabled) HedvigTheme.colorScheme.surfaceSecondaryTransparent else when (type) { + is AudioButtonType.Control -> when (type.audioRecordingState) { + AudioRecordingStepState.AudioRecording.NotRecording -> HedvigTheme.colorScheme.signalRedElement + is AudioRecordingStepState.AudioRecording.Playback -> HedvigTheme.colorScheme.fillPrimary + is AudioRecordingStepState.AudioRecording.Recording -> HedvigTheme.colorScheme.signalRedElement + } + + is AudioButtonType.Send -> HedvigTheme.colorScheme.signalBlueElement + is AudioButtonType.StartOver -> HedvigTheme.colorScheme.fillPrimary + }, + ) + ) { + Icon( + modifier = Modifier.padding(4.dp).size(24.dp), + imageVector = when (type) { + is AudioButtonType.Control -> when (type.audioRecordingState) { + AudioRecordingStepState.AudioRecording.NotRecording -> HedvigIcons.Mic + is AudioRecordingStepState.AudioRecording.Playback -> + if (type.audioRecordingState.isPrepared) { + HedvigIcons.Play + } else if (type.audioRecordingState.isPlaying) { + HedvigIcons.Pause + } else HedvigIcons.Play //todo: check here + is AudioRecordingStepState.AudioRecording.Recording -> HedvigIcons.Pause + } + + is AudioButtonType.Send -> HedvigIcons.ArrowUp + is AudioButtonType.StartOver -> HedvigIcons.Reload + }, + contentDescription = EmptyContentDescription, + tint = if (!type.isEnabled) HedvigTheme.colorScheme.fillTertiary else HedvigTheme.colorScheme.fillNegative, + ) + } + Spacer(Modifier.height(8.dp)) + HedvigText( + text = when (type) { + is AudioButtonType.Control -> when (type.audioRecordingState) { + AudioRecordingStepState.AudioRecording.NotRecording -> stringResource(Res.string.AUDIO_RECORDER_START) + is AudioRecordingStepState.AudioRecording.Playback -> stringResource(Res.string.AUDIO_RECORDER_LISTEN) + is AudioRecordingStepState.AudioRecording.Recording -> stringResource(Res.string.AUDIO_RECORDER_STOP) + } + + is AudioButtonType.Send -> stringResource(Res.string.AUDIO_RECORDER_SEND) + is AudioButtonType.StartOver -> stringResource(Res.string.AUDIO_RECORDER_START_OVER) + }, + fontStyle = HedvigTheme.typography.label.fontStyle, + color = if (type.isEnabled) HedvigTheme.colorScheme.textPrimary else HedvigTheme.colorScheme.textTertiary, + ) + } + } +} + +private sealed interface AudioButtonType { + val isEnabled: Boolean + + class StartOver( + val onStartOver: () -> Unit, + override val isEnabled: Boolean, + ) : AudioButtonType + + class Control( + val onStartRecording: () -> Unit, + val onStopRecording: () -> Unit, + val onListen: () -> Unit, + val onPause: () -> Unit, + val audioRecordingState: AudioRecordingStepState.AudioRecording, + ) : AudioButtonType { + override val isEnabled: Boolean + get() = true + } + + class Send( + val onSend: () -> Unit, + override val isEnabled: Boolean, + ) : AudioButtonType } @Composable From 12130d174ce44bcdee89312130e2d2ae228f62bc Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 21 Jan 2026 15:00:58 +0100 Subject: [PATCH 03/18] color changes --- .../feature/claim/chat/ClaimChatViewModel.kt | 2 + .../AudioRecordingStepSections.kt | 194 ++++++++++-------- 2 files changed, 107 insertions(+), 89 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt index 23366e572f..cf2275db6a 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt @@ -338,6 +338,7 @@ internal class ClaimChatPresenter( is ClaimChatEvent.AudioRecording.StartRecording -> { audioRecordingManager.startRecording { recordingState -> + logcat { "Mariia: audioRecordingManager.startRecording started" } steps.updateStepWithSuccess(event.id) { step, content -> step.copy(stepContent = content.copy(recordingState = recordingState)) } @@ -346,6 +347,7 @@ internal class ClaimChatPresenter( is ClaimChatEvent.AudioRecording.StopRecording -> { audioRecordingManager.stopRecording { playbackState -> + logcat { "Mariia: audioRecordingManager.StopRecording stopped" } steps.updateStepWithSuccess(event.id) { step, content -> step.copy(stepContent = content.copy(recordingState = playbackState)) } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 156cdacdf4..95bebcf154 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -59,6 +59,8 @@ import com.hedvig.android.design.system.hedvig.icon.Play import com.hedvig.android.design.system.hedvig.icon.Refresh import com.hedvig.android.design.system.hedvig.icon.Reload import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState +import com.hedvig.android.logger.logcat +import com.hedvig.audio.player.data.AudioPlayerState import com.hedvig.audio.player.data.PlayableAudioSource import com.hedvig.feature.claim.chat.data.AudioRecordingStepState import com.hedvig.feature.claim.chat.data.FreeTextErrorType @@ -134,40 +136,50 @@ internal fun AudioRecorderBubble( is AudioRecordingStepState.AudioRecording -> { Column(Modifier.fillMaxWidth()) { val state = rememberHedvigBottomSheetState() - AudioRecordingBottomSheet( - audioRecordingState = uiStateAnimated, - onDismiss = { - state.dismiss() - }, - clock = clock, - shouldShowRequestPermissionRationale = onShouldShowRequestPermissionRationale, - startRecording = startRecording, - stopRecording = stopRecording, - submitAudioFile = submitAudioFile, - redo = redoRecording, - openAppSettings = openAppSettings, - allowFreeText = freeTextAvailable, - launchFreeText = onSwitchToFreeText, - isCurrentStep = isCurrentStep, - continueButtonLoading = continueButtonLoading, - bottomSheetState = state, - ) - HedvigButton( - enabled = true, - text = stringResource(Res.string.CLAIM_CHAT_USE_AUDIO), - onClick = { - state.show(Unit) - }, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(8.dp)) - HedvigButton( - enabled = true, - buttonStyle = ButtonDefaults.ButtonStyle.Secondary, - text = stringResource(Res.string.CLAIMS_USE_TEXT_INSTEAD), - onClick = onSwitchToFreeText, - modifier = Modifier.fillMaxWidth(), - ) + if (isCurrentStep) { + AudioRecordingBottomSheet( + audioRecordingState = uiStateAnimated, + clock = clock, + shouldShowRequestPermissionRationale = onShouldShowRequestPermissionRationale, + startRecording = startRecording, + stopRecording = stopRecording, + submitAudioFile = submitAudioFile, + redo = redoRecording, + openAppSettings = openAppSettings, + continueButtonLoading = continueButtonLoading, + bottomSheetState = state, + ) + HedvigButton( + enabled = true, + text = stringResource(Res.string.CLAIM_CHAT_USE_AUDIO), + onClick = { + state.show(Unit) + }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + HedvigButton( + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary, + text = stringResource(Res.string.CLAIMS_USE_TEXT_INSTEAD), + onClick = onSwitchToFreeText, + modifier = Modifier.fillMaxWidth(), + ) + } else { + if (uiStateAnimated is AudioRecordingStepState.AudioRecording.Playback) { + val audioPlayer = rememberAudioPlayer( + PlayableAudioSource.LocalFilePath(uiStateAnimated.filePath), + ) + HedvigAudioPlayer( + audioPlayer = audioPlayer, + Modifier.padding( + start = 45.dp + ), + ) + } else { + SkippedLabel() + } + } } } } @@ -199,12 +211,8 @@ private fun AudioRecordingBottomSheet( submitAudioFile: () -> Unit, redo: () -> Unit, openAppSettings: () -> Unit, - launchFreeText: () -> Unit, - allowFreeText: Boolean, - isCurrentStep: Boolean, continueButtonLoading: Boolean, modifier: Modifier = Modifier, - onDismiss: () -> Unit, ) { var showPermissionDialog by remember { mutableStateOf(false) } val recordAudioPermissionState = if (LocalInspectionMode.current) { @@ -246,12 +254,12 @@ private fun AudioRecordingBottomSheet( if (!audioRecordingState.isPrepared) { HedvigCircularProgressIndicator() } else { - val audioPlayer = rememberAudioPlayer(PlayableAudioSource.LocalFilePath(audioRecordingState.filePath)) - HedvigAudioPlayer( - audioPlayer = audioPlayer, + + // todo: fake waves + RestingAudioPlayer( Modifier.padding( horizontal = 45.dp, - vertical = 64.dp, + vertical = 78.dp, ), ) } @@ -270,9 +278,7 @@ private fun AudioRecordingBottomSheet( AudioButton( modifier = Modifier.weight(1f), type = AudioButtonType.StartOver( - onStartOver = { - //todo - }, + onStartOver = redo, isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback, ), ) @@ -280,18 +286,8 @@ private fun AudioRecordingBottomSheet( AudioButton( modifier = Modifier.weight(1f), type = AudioButtonType.Control( - onStartRecording = { - //todo - }, - onStopRecording = { - //todo - }, - onListen = { - //todo - }, - onPause = { - //todo - }, + onStartRecording = startRecording, + onStopRecording = stopRecording, audioRecordingState = audioRecordingState, ), ) @@ -299,10 +295,9 @@ private fun AudioRecordingBottomSheet( AudioButton( modifier = Modifier.weight(1f), type = AudioButtonType.Send( - onSend = { - //todo - }, - isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback, + onSend = submitAudioFile, + isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback + && !continueButtonLoading, ), ) } @@ -317,6 +312,12 @@ private fun AudioButton( type: AudioButtonType, modifier: Modifier = Modifier, ) { + val audioPlayer = ((type as? AudioButtonType.Control)?.audioRecordingState as? + AudioRecordingStepState.AudioRecording.Playback)?.let { + rememberAudioPlayer( + PlayableAudioSource.LocalFilePath( it.filePath)) + } + val audioPlayerState = audioPlayer?.audioPlayerState?.value Surface( shape = HedvigTheme.shapes.cornerLarge, modifier = modifier @@ -327,19 +328,27 @@ private fun AudioButton( .clickable( enabled = type.isEnabled, onClick = { + logcat { "Mariia: surface clicked, type: $type" } when (type) { is AudioButtonType.Control -> when (type.audioRecordingState) { - AudioRecordingStepState.AudioRecording.NotRecording -> type.onStartRecording - is AudioRecordingStepState.AudioRecording.Playback -> if (type.audioRecordingState.isPrepared) { - type.onListen - } else if (type.audioRecordingState.isPlaying) { - type.onPause - } //todo: else?? - is AudioRecordingStepState.AudioRecording.Recording -> type.onStopRecording + AudioRecordingStepState.AudioRecording.NotRecording -> { + logcat { "Mariia: type.onStartRecording clicked" } + type.onStartRecording() + } + + is AudioRecordingStepState.AudioRecording.Playback ->{ + val ready = audioPlayerState as? AudioPlayerState.Ready + if (ready?.readyState is AudioPlayerState.Ready.ReadyState.Playing) { + audioPlayer.pausePlayer() + } else { + audioPlayer?.startPlayer() + } + } + is AudioRecordingStepState.AudioRecording.Recording -> type.onStopRecording() } - is AudioButtonType.Send -> type.onSend - is AudioButtonType.StartOver -> type.onStartOver + is AudioButtonType.Send -> type.onSend() + is AudioButtonType.StartOver -> type.onStartOver() } }, ), @@ -348,33 +357,38 @@ private fun AudioButton( modifier = Modifier.padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { + + Box( modifier = Modifier .clip(HedvigTheme.shapes.cornerXXLarge) .background( - color = if (!type.isEnabled) HedvigTheme.colorScheme.surfaceSecondaryTransparent else when (type) { - is AudioButtonType.Control -> when (type.audioRecordingState) { - AudioRecordingStepState.AudioRecording.NotRecording -> HedvigTheme.colorScheme.signalRedElement - is AudioRecordingStepState.AudioRecording.Playback -> HedvigTheme.colorScheme.fillPrimary - is AudioRecordingStepState.AudioRecording.Recording -> HedvigTheme.colorScheme.signalRedElement - } + color = if (!type.isEnabled) HedvigTheme.colorScheme.surfaceSecondaryTransparent else when (type) { + is AudioButtonType.Control -> when (type.audioRecordingState) { + AudioRecordingStepState.AudioRecording.NotRecording -> HedvigTheme.colorScheme.signalRedElement + is AudioRecordingStepState.AudioRecording.Playback -> HedvigTheme.colorScheme.fillPrimary + is AudioRecordingStepState.AudioRecording.Recording -> HedvigTheme.colorScheme.signalRedElement + } - is AudioButtonType.Send -> HedvigTheme.colorScheme.signalBlueElement - is AudioButtonType.StartOver -> HedvigTheme.colorScheme.fillPrimary - }, - ) + is AudioButtonType.Send -> HedvigTheme.colorScheme.signalBlueElement + is AudioButtonType.StartOver -> HedvigTheme.colorScheme.surfaceSecondaryTransparent + }, + ), ) { Icon( modifier = Modifier.padding(4.dp).size(24.dp), imageVector = when (type) { is AudioButtonType.Control -> when (type.audioRecordingState) { AudioRecordingStepState.AudioRecording.NotRecording -> HedvigIcons.Mic - is AudioRecordingStepState.AudioRecording.Playback -> - if (type.audioRecordingState.isPrepared) { - HedvigIcons.Play - } else if (type.audioRecordingState.isPlaying) { + is AudioRecordingStepState.AudioRecording.Playback -> { + val ready = audioPlayerState as? AudioPlayerState.Ready + if (ready?.readyState is AudioPlayerState.Ready.ReadyState.Playing) { HedvigIcons.Pause - } else HedvigIcons.Play //todo: check here + } else { + HedvigIcons.Play + } + } + is AudioRecordingStepState.AudioRecording.Recording -> HedvigIcons.Pause } @@ -382,10 +396,13 @@ private fun AudioButton( is AudioButtonType.StartOver -> HedvigIcons.Reload }, contentDescription = EmptyContentDescription, - tint = if (!type.isEnabled) HedvigTheme.colorScheme.fillTertiary else HedvigTheme.colorScheme.fillNegative, + tint = if (!type.isEnabled) HedvigTheme.colorScheme.fillTertiary else { + if (type is AudioButtonType.StartOver) HedvigTheme.colorScheme.fillPrimary else + HedvigTheme.colorScheme.fillNegative + }, ) } - Spacer(Modifier.height(8.dp)) + Spacer(Modifier.height(4.dp)) HedvigText( text = when (type) { is AudioButtonType.Control -> when (type.audioRecordingState) { @@ -397,6 +414,7 @@ private fun AudioButton( is AudioButtonType.Send -> stringResource(Res.string.AUDIO_RECORDER_SEND) is AudioButtonType.StartOver -> stringResource(Res.string.AUDIO_RECORDER_START_OVER) }, + fontSize = HedvigTheme.typography.label.fontSize, fontStyle = HedvigTheme.typography.label.fontStyle, color = if (type.isEnabled) HedvigTheme.colorScheme.textPrimary else HedvigTheme.colorScheme.textTertiary, ) @@ -415,8 +433,6 @@ private sealed interface AudioButtonType { class Control( val onStartRecording: () -> Unit, val onStopRecording: () -> Unit, - val onListen: () -> Unit, - val onPause: () -> Unit, val audioRecordingState: AudioRecordingStepState.AudioRecording, ) : AudioButtonType { override val isEnabled: Boolean From a846011f444f92861c6965df683c54aba35a6fa2 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 21 Jan 2026 16:17:06 +0100 Subject: [PATCH 04/18] fix audio player --- .../chat/ui/audiorecording/AudioRecordingStepSections.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 95bebcf154..c48343da0f 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -317,7 +318,8 @@ private fun AudioButton( rememberAudioPlayer( PlayableAudioSource.LocalFilePath( it.filePath)) } - val audioPlayerState = audioPlayer?.audioPlayerState?.value + val audioPlayerState by audioPlayer?.audioPlayerState?.collectAsStateWithLifecycle() + ?: remember { mutableStateOf(null) } Surface( shape = HedvigTheme.shapes.cornerLarge, modifier = modifier @@ -339,7 +341,7 @@ private fun AudioButton( is AudioRecordingStepState.AudioRecording.Playback ->{ val ready = audioPlayerState as? AudioPlayerState.Ready if (ready?.readyState is AudioPlayerState.Ready.ReadyState.Playing) { - audioPlayer.pausePlayer() + audioPlayer?.pausePlayer() } else { audioPlayer?.startPlayer() } From db16649aa5eb57f4ed57e53e06e0fd4517355233 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 21 Jan 2026 16:53:23 +0100 Subject: [PATCH 05/18] add clock --- .../AudioRecordingStepSections.kt | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index c48343da0f..af747eeaac 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -28,6 +28,8 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics @@ -37,6 +39,7 @@ import com.hedvig.android.audio.player.HedvigAudioPlayer import com.hedvig.android.audio.player.RestingAudioPlayer import com.hedvig.android.audio.player.audioplayer.rememberAudioPlayer import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.core.uidata.DecimalFormatter import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton @@ -81,8 +84,10 @@ import hedvig.resources.CLAIM_TRIAGING_TITLE import hedvig.resources.PERMISSION_DIALOG_RECORD_AUDIO_MESSAGE import hedvig.resources.Res import hedvig.resources.SAVE_AND_CONTINUE_BUTTON_LABEL +import hedvig.resources.TALKBACK_RECORDING_DURATION import hedvig.resources.claims_skip_button import kotlin.time.Clock +import kotlin.time.Instant import org.jetbrains.compose.resources.stringResource @Composable @@ -158,14 +163,16 @@ internal fun AudioRecorderBubble( }, modifier = Modifier.fillMaxWidth(), ) - Spacer(Modifier.height(8.dp)) - HedvigButton( - enabled = true, - buttonStyle = ButtonDefaults.ButtonStyle.Secondary, - text = stringResource(Res.string.CLAIMS_USE_TEXT_INSTEAD), - onClick = onSwitchToFreeText, - modifier = Modifier.fillMaxWidth(), - ) + if (freeTextAvailable) { + Spacer(Modifier.height(8.dp)) + HedvigButton( + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary, + text = stringResource(Res.string.CLAIMS_USE_TEXT_INSTEAD), + onClick = onSwitchToFreeText, + modifier = Modifier.fillMaxWidth(), + ) + } } else { if (uiStateAnimated is AudioRecordingStepState.AudioRecording.Playback) { val audioPlayer = rememberAudioPlayer( @@ -250,6 +257,7 @@ private fun AudioRecordingBottomSheet( }, textAlign = TextAlign.Center, ) + DynamicClock(audioRecordingState, clock) Spacer(Modifier.height(24.dp)) if (audioRecordingState is AudioRecordingStepState.AudioRecording.Playback) { if (!audioRecordingState.isPrepared) { @@ -308,6 +316,38 @@ private fun AudioRecordingBottomSheet( } } +@Composable +private fun DynamicClock( + audioRecordingState: AudioRecordingStepState.AudioRecording, + clock: Clock +) { + val startedRecordingAt by remember { + mutableStateOf(null) + }.apply { + if (audioRecordingState is AudioRecordingStepState.AudioRecording.Recording) { + value = audioRecordingState.startedAt + } + } + + val twoDigitsFormat = remember { DecimalFormatter("00") } + val label = if (audioRecordingState is AudioRecordingStepState.AudioRecording.Recording) { + val diff = clock.now() - (startedRecordingAt ?: clock.now()) + "${twoDigitsFormat.format( + diff.inWholeMinutes)}:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" + } else null + val durationDescription =label?.let { stringResource(Res.string.TALKBACK_RECORDING_DURATION, + it)} + HedvigText( + text = label ?: "", + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth().clearAndSetSemantics { + if (durationDescription!=null) { + contentDescription = durationDescription + } + }, + color = HedvigTheme.colorScheme.textSecondary) +} + @Composable private fun AudioButton( type: AudioButtonType, From d9e12a8ee178b17e206ab2353ef98df5944be665 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 21 Jan 2026 17:02:45 +0100 Subject: [PATCH 06/18] block other buttons as well --- .../chat/ui/audiorecording/AudioRecordingStepSections.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index af747eeaac..26572d3b91 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -248,7 +248,7 @@ private fun AudioRecordingBottomSheet( openAppSettings = openAppSettings, ) } - HedvigBottomSheet(bottomSheetState) { + HedvigBottomSheet(bottomSheetState, modifier) { Column { HedvigText( stringResource(Res.string.CLAIM_TRIAGING_TITLE), @@ -288,7 +288,8 @@ private fun AudioRecordingBottomSheet( modifier = Modifier.weight(1f), type = AudioButtonType.StartOver( onStartOver = redo, - isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback, + isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback + && !continueButtonLoading, ), ) Spacer(Modifier.width(4.dp)) @@ -298,6 +299,7 @@ private fun AudioRecordingBottomSheet( onStartRecording = startRecording, onStopRecording = stopRecording, audioRecordingState = audioRecordingState, + isEnabled = !continueButtonLoading ), ) Spacer(Modifier.width(4.dp)) @@ -476,9 +478,8 @@ private sealed interface AudioButtonType { val onStartRecording: () -> Unit, val onStopRecording: () -> Unit, val audioRecordingState: AudioRecordingStepState.AudioRecording, + override val isEnabled: Boolean, ) : AudioButtonType { - override val isEnabled: Boolean - get() = true } class Send( From eeae8e126c7ef83d39f79383a18739171bc903da Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 22 Jan 2026 12:36:29 +0100 Subject: [PATCH 07/18] add AnimatedRecordingWaves --- .../android/audio/player/HedvigAudioPlayer.kt | 24 --- .../AudioRecordingStepSections.kt | 176 ++++++++++++++---- 2 files changed, 138 insertions(+), 62 deletions(-) diff --git a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt index fe5c116deb..09046c6fa4 100644 --- a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt +++ b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt @@ -42,27 +42,3 @@ fun HedvigAudioPlayer(audioPlayer: AudioPlayer, modifier: Modifier = Modifier, o modifier = modifier, ) } - -@Composable -fun RestingAudioPlayer(modifier: Modifier = Modifier) { - BoxWithConstraints(modifier) { - val numberOfWaves = remember(maxWidth) { - (maxWidth / 5f).value.roundToInt() - } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth(), - ) { - repeat(numberOfWaves) { _ -> - Box( - modifier = Modifier - .size(2.dp) - .clip(CircleShape) - .background(HedvigTheme.colorScheme.fillPrimary), - ) - } - } - } -} diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 26572d3b91..26ac06d6d8 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -1,14 +1,19 @@ package com.hedvig.feature.claim.chat.ui.audiorecording import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -16,16 +21,17 @@ import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics @@ -35,8 +41,8 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.audio.player.HedvigAudioPlayer -import com.hedvig.android.audio.player.RestingAudioPlayer import com.hedvig.android.audio.player.audioplayer.rememberAudioPlayer import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.core.uidata.DecimalFormatter @@ -47,20 +53,16 @@ import com.hedvig.android.design.system.hedvig.HedvigCircularProgressIndicator import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.HorizontalDivider import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.IconButton import com.hedvig.android.design.system.hedvig.PermissionDialog import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.freetext.FreeTextDisplay import com.hedvig.android.design.system.hedvig.icon.ArrowUp -import com.hedvig.android.design.system.hedvig.icon.Document import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Mic import com.hedvig.android.design.system.hedvig.icon.Pause import com.hedvig.android.design.system.hedvig.icon.Play -import com.hedvig.android.design.system.hedvig.icon.Refresh import com.hedvig.android.design.system.hedvig.icon.Reload import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.logger.logcat @@ -86,8 +88,10 @@ import hedvig.resources.Res import hedvig.resources.SAVE_AND_CONTINUE_BUTTON_LABEL import hedvig.resources.TALKBACK_RECORDING_DURATION import hedvig.resources.claims_skip_button +import kotlin.math.roundToInt import kotlin.time.Clock import kotlin.time.Instant +import kotlinx.coroutines.delay import org.jetbrains.compose.resources.stringResource @Composable @@ -181,7 +185,7 @@ internal fun AudioRecorderBubble( HedvigAudioPlayer( audioPlayer = audioPlayer, Modifier.padding( - start = 45.dp + start = 45.dp, ), ) } else { @@ -248,6 +252,7 @@ private fun AudioRecordingBottomSheet( openAppSettings = openAppSettings, ) } + HedvigBottomSheet(bottomSheetState, modifier) { Column { HedvigText( @@ -259,26 +264,39 @@ private fun AudioRecordingBottomSheet( ) DynamicClock(audioRecordingState, clock) Spacer(Modifier.height(24.dp)) - if (audioRecordingState is AudioRecordingStepState.AudioRecording.Playback) { - if (!audioRecordingState.isPrepared) { - HedvigCircularProgressIndicator() - } else { - - // todo: fake waves - RestingAudioPlayer( - Modifier.padding( - horizontal = 45.dp, - vertical = 78.dp, - ), - ) + + Column( + ) { + when (audioRecordingState) { + is AudioRecordingStepState.AudioRecording.Playback if !audioRecordingState.isPrepared -> { + HedvigCircularProgressIndicator() + } + + is AudioRecordingStepState.AudioRecording.Playback -> { + + //todo: playback + } + + is AudioRecordingStepState.AudioRecording.Recording -> { + AnimatedRecordingWaves( + Modifier + .padding( + horizontal = 45.dp, + vertical = 29.dp, + ).height(100.dp), + ) + } + + else -> { + RestingAudioPlayer( + Modifier + .padding( + horizontal = 45.dp, + vertical = 78.dp, + ), + ) + } } - } else { - RestingAudioPlayer( - Modifier.padding( - horizontal = 45.dp, - vertical = 78.dp, - ), - ) } Row( modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(), @@ -299,7 +317,7 @@ private fun AudioRecordingBottomSheet( onStartRecording = startRecording, onStopRecording = stopRecording, audioRecordingState = audioRecordingState, - isEnabled = !continueButtonLoading + isEnabled = !continueButtonLoading, ), ) Spacer(Modifier.width(4.dp)) @@ -321,7 +339,7 @@ private fun AudioRecordingBottomSheet( @Composable private fun DynamicClock( audioRecordingState: AudioRecordingStepState.AudioRecording, - clock: Clock + clock: Clock, ) { val startedRecordingAt by remember { mutableStateOf(null) @@ -332,22 +350,30 @@ private fun DynamicClock( } val twoDigitsFormat = remember { DecimalFormatter("00") } - val label = if (audioRecordingState is AudioRecordingStepState.AudioRecording.Recording) { + val label = if (audioRecordingState is AudioRecordingStepState.AudioRecording.Recording) { val diff = clock.now() - (startedRecordingAt ?: clock.now()) - "${twoDigitsFormat.format( - diff.inWholeMinutes)}:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" + "${ + twoDigitsFormat.format( + diff.inWholeMinutes, + ) + }:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" } else null - val durationDescription =label?.let { stringResource(Res.string.TALKBACK_RECORDING_DURATION, - it)} + val durationDescription = label?.let { + stringResource( + Res.string.TALKBACK_RECORDING_DURATION, + it, + ) + } HedvigText( text = label ?: "", textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().clearAndSetSemantics { - if (durationDescription!=null) { + if (durationDescription != null) { contentDescription = durationDescription } }, - color = HedvigTheme.colorScheme.textSecondary) + color = HedvigTheme.colorScheme.textSecondary, + ) } @Composable @@ -358,7 +384,8 @@ private fun AudioButton( val audioPlayer = ((type as? AudioButtonType.Control)?.audioRecordingState as? AudioRecordingStepState.AudioRecording.Playback)?.let { rememberAudioPlayer( - PlayableAudioSource.LocalFilePath( it.filePath)) + PlayableAudioSource.LocalFilePath(it.filePath), + ) } val audioPlayerState by audioPlayer?.audioPlayerState?.collectAsStateWithLifecycle() ?: remember { mutableStateOf(null) } @@ -380,7 +407,7 @@ private fun AudioButton( type.onStartRecording() } - is AudioRecordingStepState.AudioRecording.Playback ->{ + is AudioRecordingStepState.AudioRecording.Playback -> { val ready = audioPlayerState as? AudioPlayerState.Ready if (ready?.readyState is AudioPlayerState.Ready.ReadyState.Playing) { audioPlayer?.pausePlayer() @@ -388,6 +415,7 @@ private fun AudioButton( audioPlayer?.startPlayer() } } + is AudioRecordingStepState.AudioRecording.Recording -> type.onStopRecording() } @@ -613,6 +641,78 @@ private fun AudioRecordingSection( ) } +@Composable +private fun AnimatedRecordingWaves( + modifier: Modifier = Modifier, +) { + val numberOfWaves = 40 + val waveHeights = remember { + mutableStateListOf().apply { + repeat(numberOfWaves) { + add( + kotlin.random.Random.nextFloat() * 0.2f + ) + } + } + } + + LaunchedEffect(Unit) { + while (true) { + delay(50) + val indexToUpdate = kotlin.random.Random.nextInt(numberOfWaves) + waveHeights[indexToUpdate] = kotlin.random.Random.nextFloat() * 0.3f + 0.05f + } + } + + BoxWithConstraints(modifier) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().height(maxHeight), + ) { + waveHeights.forEachIndexed { _, height -> + val animatedHeight by animateFloatAsState( + targetValue = height, + animationSpec = tween(durationMillis = 200, easing = LinearEasing), + ) + Box( + modifier = Modifier + .width(WAVE_WIDTH) + .fillMaxHeight(fraction = animatedHeight) + .clip(CircleShape) + .background(HedvigTheme.colorScheme.fillPrimary.copy(alpha = 0.6f)), + ) + } + } + } +} + +@Composable +fun RestingAudioPlayer(modifier: Modifier = Modifier) { + BoxWithConstraints(modifier) { + val numberOfWaves = remember(maxWidth) { + (maxWidth / 5f).value.roundToInt() + } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth(), + ) { + repeat(numberOfWaves) { _ -> + Box( + modifier = Modifier + .size(WAVE_WIDTH) + .clip(CircleShape) + .background(HedvigTheme.colorScheme.fillPrimary), + ) + } + } + } +} + +private val WAVE_WIDTH = 2.dp + @HedvigPreview @Composable private fun PreviewFreeTextInput() { From b31caa82a2758f12a1530f34429d2d3b80cce4c1 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 22 Jan 2026 16:00:22 +0100 Subject: [PATCH 08/18] change of states --- .../AudioRecordingStepSections.kt | 223 +++++++++++++----- 1 file changed, 160 insertions(+), 63 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 26ac06d6d8..2b893dd038 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -4,6 +4,10 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -25,13 +29,14 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.graphics.Color +import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics @@ -41,6 +46,7 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.lerp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.audio.player.HedvigAudioPlayer import com.hedvig.android.audio.player.audioplayer.rememberAudioPlayer @@ -54,6 +60,7 @@ import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.LocalContentColor import com.hedvig.android.design.system.hedvig.PermissionDialog import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState @@ -66,8 +73,10 @@ import com.hedvig.android.design.system.hedvig.icon.Play import com.hedvig.android.design.system.hedvig.icon.Reload import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.logger.logcat +import com.hedvig.audio.player.data.AudioPlayer import com.hedvig.audio.player.data.AudioPlayerState import com.hedvig.audio.player.data.PlayableAudioSource +import com.hedvig.audio.player.data.ProgressPercentage import com.hedvig.feature.claim.chat.data.AudioRecordingStepState import com.hedvig.feature.claim.chat.data.FreeTextErrorType import com.hedvig.feature.claim.chat.ui.RoundCornersPill @@ -88,7 +97,9 @@ import hedvig.resources.Res import hedvig.resources.SAVE_AND_CONTINUE_BUTTON_LABEL import hedvig.resources.TALKBACK_RECORDING_DURATION import hedvig.resources.claims_skip_button +import kotlin.math.abs import kotlin.math.roundToInt +import kotlin.random.Random import kotlin.time.Clock import kotlin.time.Instant import kotlinx.coroutines.delay @@ -253,6 +264,13 @@ private fun AudioRecordingBottomSheet( ) } + val audioPlayer = (audioRecordingState as? + AudioRecordingStepState.AudioRecording.Playback)?.let { + rememberAudioPlayer( + PlayableAudioSource.LocalFilePath(it.filePath), + ) + } + HedvigBottomSheet(bottomSheetState, modifier) { Column { HedvigText( @@ -265,36 +283,68 @@ private fun AudioRecordingBottomSheet( DynamicClock(audioRecordingState, clock) Spacer(Modifier.height(24.dp)) - Column( - ) { - when (audioRecordingState) { - is AudioRecordingStepState.AudioRecording.Playback if !audioRecordingState.isPrepared -> { - HedvigCircularProgressIndicator() + AnimatedContent( + targetState = audioRecordingState, + transitionSpec = { + (fadeIn(animationSpec = tween(220, delayMillis = 90)) + .togetherWith(fadeOut(animationSpec = tween(90)))) + }, + contentKey = { state -> + when (state) { + is AudioRecordingStepState.AudioRecording.Playback -> { + if (state.isPrepared) "playback" else "loading" + } + is AudioRecordingStepState.AudioRecording.Recording -> "recording" + else -> "resting" } + }, + ) { target -> + Box( + modifier = Modifier.height(158.dp), + contentAlignment = Alignment.Center, + ) { + when (target) { + is AudioRecordingStepState.AudioRecording.Playback if !target.isPrepared -> { + HedvigCircularProgressIndicator() + } - is AudioRecordingStepState.AudioRecording.Playback -> { - - //todo: playback - } + is AudioRecordingStepState.AudioRecording.Playback -> { + val audioPlayerState by audioPlayer?.audioPlayerState?.collectAsStateWithLifecycle() + ?: remember { mutableStateOf(null) } + if (audioPlayerState is AudioPlayerState.Ready) { + AudioWaves( + animated = false, + progressPercentage = (audioPlayerState as AudioPlayerState.Ready).progressPercentage, + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 45.dp, + vertical = 29.dp, + ), + ) + } + } - is AudioRecordingStepState.AudioRecording.Recording -> { - AnimatedRecordingWaves( - Modifier - .padding( - horizontal = 45.dp, - vertical = 29.dp, - ).height(100.dp), - ) - } + is AudioRecordingStepState.AudioRecording.Recording -> { + AudioWaves( + animated = true, + progressPercentage = null, + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 45.dp, + vertical = 29.dp, + ), + ) + } - else -> { - RestingAudioPlayer( - Modifier - .padding( - horizontal = 45.dp, - vertical = 78.dp, - ), - ) + else -> { + RestingAudioPlayer( + Modifier + .fillMaxWidth() + .padding(horizontal = 45.dp), + ) + } } } } @@ -309,10 +359,12 @@ private fun AudioRecordingBottomSheet( isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback && !continueButtonLoading, ), + audioPlayer = null, ) Spacer(Modifier.width(4.dp)) AudioButton( modifier = Modifier.weight(1f), + audioPlayer = audioPlayer, type = AudioButtonType.Control( onStartRecording = startRecording, onStopRecording = stopRecording, @@ -328,6 +380,7 @@ private fun AudioRecordingBottomSheet( isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback && !continueButtonLoading, ), + audioPlayer = null, ) } Spacer(Modifier.height(16.dp)) @@ -379,14 +432,9 @@ private fun DynamicClock( @Composable private fun AudioButton( type: AudioButtonType, + audioPlayer: AudioPlayer?, modifier: Modifier = Modifier, ) { - val audioPlayer = ((type as? AudioButtonType.Control)?.audioRecordingState as? - AudioRecordingStepState.AudioRecording.Playback)?.let { - rememberAudioPlayer( - PlayableAudioSource.LocalFilePath(it.filePath), - ) - } val audioPlayerState by audioPlayer?.audioPlayerState?.collectAsStateWithLifecycle() ?: remember { mutableStateOf(null) } Surface( @@ -642,51 +690,100 @@ private fun AudioRecordingSection( } @Composable -private fun AnimatedRecordingWaves( +private fun AudioWaves( + animated: Boolean, + progressPercentage: ProgressPercentage?, modifier: Modifier = Modifier, ) { - val numberOfWaves = 40 - val waveHeights = remember { - mutableStateListOf().apply { - repeat(numberOfWaves) { - add( - kotlin.random.Random.nextFloat() * 0.2f - ) - } - } - } - - LaunchedEffect(Unit) { - while (true) { - delay(50) - val indexToUpdate = kotlin.random.Random.nextInt(numberOfWaves) - waveHeights[indexToUpdate] = kotlin.random.Random.nextFloat() * 0.3f + 0.05f - } - } + val playedColor = LocalContentColor.current + val notPlayedColor = LocalContentColor.current.copy(0.38f) + .compositeOver(HedvigTheme.colorScheme.surfacePrimary) + val fixedColor = HedvigTheme.colorScheme.fillPrimary.copy(alpha = 0.6f) BoxWithConstraints(modifier) { + val numberOfWaves = remember(maxWidth) { + (maxWidth / 5f).value.roundToInt() + } Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().height(maxHeight), ) { - waveHeights.forEachIndexed { _, height -> - val animatedHeight by animateFloatAsState( - targetValue = height, - animationSpec = tween(durationMillis = 200, easing = LinearEasing), - ) - Box( - modifier = Modifier - .width(WAVE_WIDTH) - .fillMaxHeight(fraction = animatedHeight) - .clip(CircleShape) - .background(HedvigTheme.colorScheme.fillPrimary.copy(alpha = 0.6f)), + repeat(numberOfWaves) { waveIndex -> + val baseHeight = remember(waveIndex, numberOfWaves) { + val wavePosition = waveIndex + 1 + val centerPoint = numberOfWaves / 2 + val distanceFromCenterPoint = abs(centerPoint - wavePosition) + val percentageToCenterPoint = + ((centerPoint - distanceFromCenterPoint).toFloat() / centerPoint) + val minWaveHeightFraction = 0.05f + val maxWaveHeightFractionForSideWaves = 0.05f + val maxWaveHeightFraction = 0.5f + val maxHeightFraction = lerp( + maxWaveHeightFractionForSideWaves, + maxWaveHeightFraction, + percentageToCenterPoint, + ) + if (maxHeightFraction <= minWaveHeightFraction) { + maxHeightFraction + } else { + Random.nextDouble(minWaveHeightFraction.toDouble(), maxHeightFraction.toDouble()) + .toFloat() + } + } + + val height = if (animated) { + var animatedHeight by remember { mutableStateOf(baseHeight) } + + LaunchedEffect(waveIndex) { + while (true) { + delay((50..150).random().toLong()) + val variation = Random.nextFloat() * 0.2f - 0.1f + animatedHeight = (baseHeight + variation).coerceIn(0.1f, 0.4f) + } + } + + val smoothHeight by animateFloatAsState( + targetValue = animatedHeight, + animationSpec = tween(durationMillis = 200, easing = LinearEasing), + ) + smoothHeight + } else { + baseHeight + } + + val backgroundColor = if (progressPercentage != null) { + val hasPlayedThisWave = remember(progressPercentage, numberOfWaves, waveIndex) { + progressPercentage.value * numberOfWaves > waveIndex + } + if (hasPlayedThisWave) playedColor else notPlayedColor + } else { + fixedColor + } + + WavePill( + heightFraction = height, + backgroundColor = backgroundColor, ) } } } } +@Composable +private fun WavePill( + heightFraction: Float, + backgroundColor: Color, +) { + Box( + modifier = Modifier + .width(WAVE_WIDTH) + .fillMaxHeight(fraction = heightFraction) + .clip(CircleShape) + .background(backgroundColor), + ) +} + @Composable fun RestingAudioPlayer(modifier: Modifier = Modifier) { BoxWithConstraints(modifier) { From e527555fa833c3a63a246776f19d8613c97312e1 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 22 Jan 2026 16:02:27 +0100 Subject: [PATCH 09/18] smoother transition --- .../chat/ui/audiorecording/AudioRecordingStepSections.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 2b893dd038..36012948d7 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -286,9 +286,9 @@ private fun AudioRecordingBottomSheet( AnimatedContent( targetState = audioRecordingState, transitionSpec = { - (fadeIn(animationSpec = tween(220, delayMillis = 90)) - .togetherWith(fadeOut(animationSpec = tween(90)))) - }, + fadeIn(animationSpec = tween(300)) + .togetherWith(fadeOut(animationSpec = tween(300))) + }, contentKey = { state -> when (state) { is AudioRecordingStepState.AudioRecording.Playback -> { From f9cdbcf2a581f7ddfb91f26a813d4a13f6948ecc Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 11:00:19 +0100 Subject: [PATCH 10/18] fix base height --- .../AudioRecordingStepSections.kt | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 36012948d7..92fd4abc9b 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -710,25 +710,30 @@ private fun AudioWaves( modifier = Modifier.fillMaxWidth().height(maxHeight), ) { repeat(numberOfWaves) { waveIndex -> - val baseHeight = remember(waveIndex, numberOfWaves) { - val wavePosition = waveIndex + 1 - val centerPoint = numberOfWaves / 2 - val distanceFromCenterPoint = abs(centerPoint - wavePosition) - val percentageToCenterPoint = - ((centerPoint - distanceFromCenterPoint).toFloat() / centerPoint) - val minWaveHeightFraction = 0.05f - val maxWaveHeightFractionForSideWaves = 0.05f - val maxWaveHeightFraction = 0.5f - val maxHeightFraction = lerp( - maxWaveHeightFractionForSideWaves, - maxWaveHeightFraction, - percentageToCenterPoint, - ) - if (maxHeightFraction <= minWaveHeightFraction) { - maxHeightFraction + val baseHeight = remember(waveIndex, numberOfWaves, progressPercentage) { + // When progressPercentage is null (recording state), start all waves at 2dp height + if (progressPercentage == null) { + 0.02f // 2dp out of 100dp container (after padding) } else { - Random.nextDouble(minWaveHeightFraction.toDouble(), maxHeightFraction.toDouble()) - .toFloat() + val wavePosition = waveIndex + 1 + val centerPoint = numberOfWaves / 2 + val distanceFromCenterPoint = abs(centerPoint - wavePosition) + val percentageToCenterPoint = + ((centerPoint - distanceFromCenterPoint).toFloat() / centerPoint) + val minWaveHeightFraction = 0.05f + val maxWaveHeightFractionForSideWaves = 0.05f + val maxWaveHeightFraction = 0.5f + val maxHeightFraction = lerp( + maxWaveHeightFractionForSideWaves, + maxWaveHeightFraction, + percentageToCenterPoint, + ) + if (maxHeightFraction <= minWaveHeightFraction) { + maxHeightFraction + } else { + Random.nextDouble(minWaveHeightFraction.toDouble(), maxHeightFraction.toDouble()) + .toFloat() + } } } @@ -738,8 +743,13 @@ private fun AudioWaves( LaunchedEffect(waveIndex) { while (true) { delay((50..150).random().toLong()) - val variation = Random.nextFloat() * 0.2f - 0.1f - animatedHeight = (baseHeight + variation).coerceIn(0.1f, 0.4f) + // For recording state (baseHeight = 0.02), generate random heights within animation range + animatedHeight = if (progressPercentage == null) { + Random.nextFloat() * 0.3f + 0.1f // Range: 0.1f to 0.4f + } else { + val variation = Random.nextFloat() * 0.2f - 0.1f + (baseHeight + variation).coerceIn(0.1f, 0.4f) + } } } From 3668f82041af1203f1a55934b520e47df8c02430 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 11:48:14 +0100 Subject: [PATCH 11/18] add clock label placeholder --- .../AudioRecordingStepSections.kt | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 92fd4abc9b..e4feaaa057 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -280,7 +280,7 @@ private fun AudioRecordingBottomSheet( }, textAlign = TextAlign.Center, ) - DynamicClock(audioRecordingState, clock) + DynamicClock(audioRecordingState, clock, audioPlayer) Spacer(Modifier.height(24.dp)) AnimatedContent( @@ -393,6 +393,7 @@ private fun AudioRecordingBottomSheet( private fun DynamicClock( audioRecordingState: AudioRecordingStepState.AudioRecording, clock: Clock, + audioPlayer: AudioPlayer?, ) { val startedRecordingAt by remember { mutableStateOf(null) @@ -403,20 +404,29 @@ private fun DynamicClock( } val twoDigitsFormat = remember { DecimalFormatter("00") } - val label = if (audioRecordingState is AudioRecordingStepState.AudioRecording.Recording) { - val diff = clock.now() - (startedRecordingAt ?: clock.now()) - "${ - twoDigitsFormat.format( - diff.inWholeMinutes, - ) - }:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" - } else null + + // Compute label based on state + val label = when (audioRecordingState) { + // Recording state: show elapsed recording time + is AudioRecordingStepState.AudioRecording.Recording -> { + val diff = clock.now() - (startedRecordingAt ?: clock.now()) + "${twoDigitsFormat.format(diff.inWholeMinutes)}:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" + } + // Playback state: show duration placeholder (actual duration not available from current data structures) + is AudioRecordingStepState.AudioRecording.Playback -> { + // TODO: Get actual audio duration from file or state + "00:00" + } + else -> null + } + val durationDescription = label?.let { stringResource( Res.string.TALKBACK_RECORDING_DURATION, it, ) } + HedvigText( text = label ?: "", textAlign = TextAlign.Center, From 3db575c3d9cd01fa31556d405333029fb0931901 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 11:56:04 +0100 Subject: [PATCH 12/18] fix height animation in playback --- .../chat/ui/audiorecording/AudioRecordingStepSections.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index e4feaaa057..3d7c2238c8 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -405,14 +405,11 @@ private fun DynamicClock( val twoDigitsFormat = remember { DecimalFormatter("00") } - // Compute label based on state val label = when (audioRecordingState) { - // Recording state: show elapsed recording time is AudioRecordingStepState.AudioRecording.Recording -> { val diff = clock.now() - (startedRecordingAt ?: clock.now()) "${twoDigitsFormat.format(diff.inWholeMinutes)}:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" } - // Playback state: show duration placeholder (actual duration not available from current data structures) is AudioRecordingStepState.AudioRecording.Playback -> { // TODO: Get actual audio duration from file or state "00:00" @@ -720,9 +717,10 @@ private fun AudioWaves( modifier = Modifier.fillMaxWidth().height(maxHeight), ) { repeat(numberOfWaves) { waveIndex -> - val baseHeight = remember(waveIndex, numberOfWaves, progressPercentage) { + val isRecording = progressPercentage == null + val baseHeight = remember(waveIndex, numberOfWaves, isRecording) { // When progressPercentage is null (recording state), start all waves at 2dp height - if (progressPercentage == null) { + if (isRecording) { 0.02f // 2dp out of 100dp container (after padding) } else { val wavePosition = waveIndex + 1 From d3ce96ed57d7bfad9cad304ad700afa29b7e9dce Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 11:59:19 +0100 Subject: [PATCH 13/18] fix height animation in playback --- .../chat/ui/audiorecording/AudioRecordingStepSections.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 3d7c2238c8..b92bb10acb 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -753,7 +753,13 @@ private fun AudioWaves( delay((50..150).random().toLong()) // For recording state (baseHeight = 0.02), generate random heights within animation range animatedHeight = if (progressPercentage == null) { - Random.nextFloat() * 0.3f + 0.1f // Range: 0.1f to 0.4f + // Side waves (first and last ~5%) have smaller max height + val isSideWave = waveIndex < numberOfWaves * 0.05 || waveIndex > numberOfWaves * 0.95 + if (isSideWave) { + Random.nextFloat() * 0.05f + 0.1f // Range: 0.1f to 0.15f for side waves + } else { + Random.nextFloat() * 0.3f + 0.1f // Range: 0.1f to 0.4f for center waves + } } else { val variation = Random.nextFloat() * 0.2f - 0.1f (baseHeight + variation).coerceIn(0.1f, 0.4f) From 806e846b30ca2042a64423d09fefbf444112e732 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 12:03:34 +0100 Subject: [PATCH 14/18] fix height animation in playback --- .../chat/ui/audiorecording/AudioRecordingStepSections.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index b92bb10acb..b3466fc741 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -756,9 +756,9 @@ private fun AudioWaves( // Side waves (first and last ~5%) have smaller max height val isSideWave = waveIndex < numberOfWaves * 0.05 || waveIndex > numberOfWaves * 0.95 if (isSideWave) { - Random.nextFloat() * 0.05f + 0.1f // Range: 0.1f to 0.15f for side waves + Random.nextFloat() * 0.05f + 0.01f // Range: 0.1f to 0.15f for side waves } else { - Random.nextFloat() * 0.3f + 0.1f // Range: 0.1f to 0.4f for center waves + Random.nextFloat() * 0.3f + 0.01f // Range: 0.1f to 0.4f for center waves } } else { val variation = Random.nextFloat() * 0.2f - 0.1f From 53a9a6063517b123da20442743f95b7797a69d00 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 12:24:49 +0100 Subject: [PATCH 15/18] show duration in playback state --- .../audio/player/data/AudioPlayerState.kt | 1 + .../audio/player/CommonMediaPlayer.android.kt | 3 +++ .../android/audio/player/CommonMediaPlayer.kt | 1 + .../player/audioplayer/AudioPlayerImpl.kt | 20 +++++++++++++++++-- .../audio/player/CommonMediaPlayer.native.kt | 3 +++ .../AudioRecordingStepSections.kt | 8 ++++++-- 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/audio-player-data/src/commonMain/kotlin/com/hedvig/audio/player/data/AudioPlayerState.kt b/app/audio-player-data/src/commonMain/kotlin/com/hedvig/audio/player/data/AudioPlayerState.kt index 1fa6289b0c..4288833a75 100644 --- a/app/audio-player-data/src/commonMain/kotlin/com/hedvig/audio/player/data/AudioPlayerState.kt +++ b/app/audio-player-data/src/commonMain/kotlin/com/hedvig/audio/player/data/AudioPlayerState.kt @@ -8,6 +8,7 @@ sealed interface AudioPlayerState { data class Ready( val readyState: ReadyState, val progressPercentage: ProgressPercentage = ProgressPercentage(0f), + val durationMillis: Int = 0, ) : AudioPlayerState { sealed interface ReadyState { object NotStarted : ReadyState diff --git a/app/audio-player-ui/src/androidMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.android.kt b/app/audio-player-ui/src/androidMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.android.kt index fcd84e7729..1f9d0d3d6e 100644 --- a/app/audio-player-ui/src/androidMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.android.kt +++ b/app/audio-player-ui/src/androidMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.android.kt @@ -25,6 +25,9 @@ private class AndroidMediaPlayer( override val isPlaying: Boolean get() = mediaPlayer.isPlaying + override val duration: Int + get() = mediaPlayer.duration + override fun pause() { mediaPlayer.pause() } diff --git a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.kt b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.kt index c97addd4f3..ca70132518 100644 --- a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.kt +++ b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.kt @@ -4,6 +4,7 @@ import com.hedvig.audio.player.data.ProgressPercentage interface CommonMediaPlayer { val isPlaying: Boolean + val duration: Int fun pause() diff --git a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/audioplayer/AudioPlayerImpl.kt b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/audioplayer/AudioPlayerImpl.kt index e9e13fbf40..9b21755cbd 100644 --- a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/audioplayer/AudioPlayerImpl.kt +++ b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/audioplayer/AudioPlayerImpl.kt @@ -125,8 +125,24 @@ private class AudioPlayerImpl( _audioPlayerState.update { AudioPlayerState.Failed } true } - setOnPreparedListener { _audioPlayerState.update { AudioPlayerState.Ready.notStarted() } } - setOnCompletionListener { _audioPlayerState.update { AudioPlayerState.Ready.done() } } + setOnPreparedListener { + _audioPlayerState.update { + AudioPlayerState.Ready( + readyState = AudioPlayerState.Ready.ReadyState.NotStarted, + progressPercentage = ProgressPercentage(0f), + durationMillis = duration, + ) + } + } + setOnCompletionListener { + _audioPlayerState.update { + AudioPlayerState.Ready( + readyState = AudioPlayerState.Ready.ReadyState.Done, + progressPercentage = ProgressPercentage(1f), + durationMillis = duration, + ) + } + } prepareAsync() } } diff --git a/app/audio-player-ui/src/nativeMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.native.kt b/app/audio-player-ui/src/nativeMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.native.kt index f86227ef35..e9dcb182ce 100644 --- a/app/audio-player-ui/src/nativeMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.native.kt +++ b/app/audio-player-ui/src/nativeMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.native.kt @@ -8,6 +8,9 @@ actual fun CommonMediaPlayer(dataSourceUrl: String): CommonMediaPlayer { override val isPlaying: Boolean get() = false + override val duration: Int + get() = 0 + override fun pause() { } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index b3466fc741..f3ee4eaafd 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -403,6 +403,9 @@ private fun DynamicClock( } } + val audioPlayerState by audioPlayer?.audioPlayerState?.collectAsStateWithLifecycle() + ?: remember { mutableStateOf(null) } + val twoDigitsFormat = remember { DecimalFormatter("00") } val label = when (audioRecordingState) { @@ -411,8 +414,9 @@ private fun DynamicClock( "${twoDigitsFormat.format(diff.inWholeMinutes)}:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" } is AudioRecordingStepState.AudioRecording.Playback -> { - // TODO: Get actual audio duration from file or state - "00:00" + val durationMillis = (audioPlayerState as? AudioPlayerState.Ready)?.durationMillis ?: 0 + val durationSeconds = durationMillis / 1000 + "${twoDigitsFormat.format(durationSeconds / 60)}:${twoDigitsFormat.format(durationSeconds % 60)}" } else -> null } From 2f1d3a578013cd1c754abe289bf93173c49c6e7e Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 12:28:11 +0100 Subject: [PATCH 16/18] don't flicker 00:00 --- .../ui/audiorecording/AudioRecordingStepSections.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index f3ee4eaafd..3917944ad5 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -414,9 +414,14 @@ private fun DynamicClock( "${twoDigitsFormat.format(diff.inWholeMinutes)}:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" } is AudioRecordingStepState.AudioRecording.Playback -> { - val durationMillis = (audioPlayerState as? AudioPlayerState.Ready)?.durationMillis ?: 0 - val durationSeconds = durationMillis / 1000 - "${twoDigitsFormat.format(durationSeconds / 60)}:${twoDigitsFormat.format(durationSeconds % 60)}" + + val ready = audioPlayerState as? AudioPlayerState.Ready + if (ready != null) { + val durationSeconds = ready.durationMillis / 1000 + "${twoDigitsFormat.format(durationSeconds / 60)}:${twoDigitsFormat.format(durationSeconds % 60)}" + } else { + null + } } else -> null } From 0ace4f1fc3a63e176f566cdbb5d771c5253980c0 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 12:40:42 +0100 Subject: [PATCH 17/18] ktlint --- .../AndroidAccessTokenProviderTest.kt | 47 ++++--- .../authlib/internal/KtorConfiguration.kt | 4 +- .../core/buildconstants/AppBuildConfig.kt | 2 +- .../CommonHedvigBuildConstants.kt | 6 + .../core/datastore/DeviceIdFetcher.android.kt | 2 +- .../core/datastore/DeviceIdFetcher.jvm.kt | 2 +- .../android/core/fileupload/FileService.kt | 5 +- .../android/data/claimflow/OdysseyService.kt | 5 +- .../design/system/hedvig/HedvigDatePicker.kt | 2 +- .../android/design/system/hedvig/Shapes.kt | 2 +- .../feature/claim/chat/ClaimChatNavGraph.kt | 3 +- .../chat/ui/PlatformBlurContainer.android.kt | 8 +- .../feature/claim/chat/ClaimChatViewModel.kt | 89 +++++++----- .../feature/claim/chat/data/ClaimIntent.kt | 47 +++---- .../feature/claim/chat/data/ClaimIntentExt.kt | 8 +- .../chat/ui/BlurredGradientBackground.kt | 9 +- .../claim/chat/ui/ClaimChatDestination.kt | 26 ++-- .../claim/chat/ui/ClaimChatUiComponents.kt | 60 ++++---- .../chat/ui/audiorecording/AudioRecorder.kt | 1 - .../AudioRecordingStepSections.kt | 132 +++++------------- .../outcome/ClaimOutcomeDeflectDestination.kt | 7 +- .../ClaimOutcomeNewClaimDestination.kt | 4 +- .../claim/chat/data/file/FileService.jvm.kt | 5 +- .../details/di/FeatureClaimDetailsModule.kt | 2 +- .../claim/details/ui/AddFilesViewModel.kt | 2 +- .../claim/details/ui/ClaimDetailsViewModel.kt | 2 +- .../feature/home/home/ui/HomeDestination.kt | 8 +- .../feature/login/swedishlogin/BankIdState.kt | 2 +- .../feature/odyssey/di/OdysseyModule.kt | 2 +- .../step/fileupload/FileUploadViewModel.kt | 2 +- .../android/logger/AndroidLogcatLogger.kt | 24 +++- .../network/clients/di/NetworkModule.kt | 6 +- .../android/shareddi/SharedModule.native.kt | 6 +- 33 files changed, 243 insertions(+), 289 deletions(-) diff --git a/app/auth/auth-core-public/src/test/kotlin/com/hedvig/android/auth/interceptor/AndroidAccessTokenProviderTest.kt b/app/auth/auth-core-public/src/test/kotlin/com/hedvig/android/auth/interceptor/AndroidAccessTokenProviderTest.kt index a10c6eb70f..e16bc7503d 100644 --- a/app/auth/auth-core-public/src/test/kotlin/com/hedvig/android/auth/interceptor/AndroidAccessTokenProviderTest.kt +++ b/app/auth/auth-core-public/src/test/kotlin/com/hedvig/android/auth/interceptor/AndroidAccessTokenProviderTest.kt @@ -67,31 +67,32 @@ class AndroidAccessTokenProviderTest { } @Test - fun `when the access token is expired, and the refresh token is not expired, refresh and return new token`() = runTest { - val clock = TestClock() - val authTokenStorage = authTokenStorage(clock) - authTokenStorage.updateTokens( - AccessToken("", 10.minutes.inWholeSeconds), - RefreshToken("", 1.hours.inWholeSeconds), - ) - val authRepository = FakeAuthRepository() - val authTokenService = authTokenService(authTokenStorage, authRepository) - val accessTokenProvider = AndroidAccessTokenProvider(authTokenService, clock) + fun `when the access token is expired, and the refresh token is not expired, refresh and return new token`() = + runTest { + val clock = TestClock() + val authTokenStorage = authTokenStorage(clock) + authTokenStorage.updateTokens( + AccessToken("", 10.minutes.inWholeSeconds), + RefreshToken("", 1.hours.inWholeSeconds), + ) + val authRepository = FakeAuthRepository() + val authTokenService = authTokenService(authTokenStorage, authRepository) + val accessTokenProvider = AndroidAccessTokenProvider(authTokenService, clock) - clock.advanceTimeBy(30.minutes) - authRepository.exchangeResponse.add( - AuthTokenResult.Success( - AccessToken("refreshedToken", 10.minutes.inWholeSeconds), - RefreshToken("refreshedRefreshToken", 0), - ), - ) - val token = accessTokenProvider.provide() + clock.advanceTimeBy(30.minutes) + authRepository.exchangeResponse.add( + AuthTokenResult.Success( + AccessToken("refreshedToken", 10.minutes.inWholeSeconds), + RefreshToken("refreshedRefreshToken", 0), + ), + ) + val token = accessTokenProvider.provide() - val storedAuthTokens = authTokenStorage.getTokens().first()!! - assertThat(storedAuthTokens.accessToken.token).isEqualTo("refreshedToken") - assertThat(storedAuthTokens.refreshToken.token).isEqualTo("refreshedRefreshToken") - assertThat(token).isEqualTo("refreshedToken") - } + val storedAuthTokens = authTokenStorage.getTokens().first()!! + assertThat(storedAuthTokens.accessToken.token).isEqualTo("refreshedToken") + assertThat(storedAuthTokens.refreshToken.token).isEqualTo("refreshedRefreshToken") + assertThat(token).isEqualTo("refreshedToken") + } @Test fun `when the access token and the refresh token are expired, clear tokens and return null`() = runTest { diff --git a/app/authlib/src/commonMain/kotlin/com/hedvig/authlib/internal/KtorConfiguration.kt b/app/authlib/src/commonMain/kotlin/com/hedvig/authlib/internal/KtorConfiguration.kt index 78b89c432c..1baaec6816 100644 --- a/app/authlib/src/commonMain/kotlin/com/hedvig/authlib/internal/KtorConfiguration.kt +++ b/app/authlib/src/commonMain/kotlin/com/hedvig/authlib/internal/KtorConfiguration.kt @@ -13,9 +13,7 @@ import io.ktor.client.request.header import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json -internal fun buildKtorClient( - additionalHttpHeadersProvider: () -> Map, -): HttpClient { +internal fun buildKtorClient(additionalHttpHeadersProvider: () -> Map): HttpClient { val httpClientConfig: HttpClientConfig<*>.() -> Unit = { commonKtorConfiguration(additionalHttpHeadersProvider).invoke(this) } diff --git a/app/core/core-build-constants/src/commonMain/kotlin/com/hedvig/android/core/buildconstants/AppBuildConfig.kt b/app/core/core-build-constants/src/commonMain/kotlin/com/hedvig/android/core/buildconstants/AppBuildConfig.kt index 5ecd5593be..b1c2e99393 100644 --- a/app/core/core-build-constants/src/commonMain/kotlin/com/hedvig/android/core/buildconstants/AppBuildConfig.kt +++ b/app/core/core-build-constants/src/commonMain/kotlin/com/hedvig/android/core/buildconstants/AppBuildConfig.kt @@ -18,5 +18,5 @@ interface AppBuildConfig { enum class Flavor { Production, Staging, - Develop + Develop, } diff --git a/app/core/core-build-constants/src/commonMain/kotlin/com/hedvig/android/core/buildconstants/CommonHedvigBuildConstants.kt b/app/core/core-build-constants/src/commonMain/kotlin/com/hedvig/android/core/buildconstants/CommonHedvigBuildConstants.kt index 6e4158540b..c30489d7ee 100644 --- a/app/core/core-build-constants/src/commonMain/kotlin/com/hedvig/android/core/buildconstants/CommonHedvigBuildConstants.kt +++ b/app/core/core-build-constants/src/commonMain/kotlin/com/hedvig/android/core/buildconstants/CommonHedvigBuildConstants.kt @@ -53,10 +53,15 @@ private fun makeUserAgent(languageBCP47: String, appBuildConfig: AppBuildConfig) private interface UrlHolder { fun urlGraphqlOctopus(flavor: Flavor): String + fun urlBaseWeb(flavor: Flavor): String + fun urlOdyssey(flavor: Flavor): String + fun urlBotService(flavor: Flavor): String + fun urlClaimsService(flavor: Flavor): String + fun deepLinkHosts(flavor: Flavor): List } @@ -125,6 +130,7 @@ private class AppConfigUrlHolder(private val appBuildConfig: AppBuildConfig) : U } private fun deepLinkDomainPathPrefix(): String = "/deeplink" + private fun deepLinkDomainHostOld(flavor: Flavor): String = when (flavor) { Production -> "hedvig.page.link" Staging -> "hedvigtest.page.link" diff --git a/app/core/core-datastore-public/src/androidMain/kotlin/com/hedvig/android/core/datastore/DeviceIdFetcher.android.kt b/app/core/core-datastore-public/src/androidMain/kotlin/com/hedvig/android/core/datastore/DeviceIdFetcher.android.kt index e8e4ab0db0..2644e06708 100644 --- a/app/core/core-datastore-public/src/androidMain/kotlin/com/hedvig/android/core/datastore/DeviceIdFetcher.android.kt +++ b/app/core/core-datastore-public/src/androidMain/kotlin/com/hedvig/android/core/datastore/DeviceIdFetcher.android.kt @@ -3,7 +3,7 @@ package com.hedvig.android.core.datastore import kotlinx.coroutines.flow.firstOrNull internal class AndroidDeviceIdFetcher( - private val deviceIdDataStore: DeviceIdDataStore + private val deviceIdDataStore: DeviceIdDataStore, ) : DeviceIdFetcher { override suspend fun fetch(): String? { return deviceIdDataStore.observeDeviceId().firstOrNull() diff --git a/app/core/core-datastore-public/src/jvmMain/kotlin/com/hedvig/android/core/datastore/DeviceIdFetcher.jvm.kt b/app/core/core-datastore-public/src/jvmMain/kotlin/com/hedvig/android/core/datastore/DeviceIdFetcher.jvm.kt index 0adf1aa8d9..2faec33ccc 100644 --- a/app/core/core-datastore-public/src/jvmMain/kotlin/com/hedvig/android/core/datastore/DeviceIdFetcher.jvm.kt +++ b/app/core/core-datastore-public/src/jvmMain/kotlin/com/hedvig/android/core/datastore/DeviceIdFetcher.jvm.kt @@ -3,7 +3,7 @@ package com.hedvig.android.core.datastore import kotlinx.coroutines.flow.firstOrNull internal class JvmDeviceIdFetcher( - private val deviceIdDataStore: DeviceIdDataStore + private val deviceIdDataStore: DeviceIdDataStore, ) : DeviceIdFetcher { override suspend fun fetch(): String? { return deviceIdDataStore.observeDeviceId().firstOrNull() diff --git a/app/core/core-file-upload/src/main/kotlin/com/hedvig/android/core/fileupload/FileService.kt b/app/core/core-file-upload/src/main/kotlin/com/hedvig/android/core/fileupload/FileService.kt index 37cd438cd7..f3c8f94935 100644 --- a/app/core/core-file-upload/src/main/kotlin/com/hedvig/android/core/fileupload/FileService.kt +++ b/app/core/core-file-upload/src/main/kotlin/com/hedvig/android/core/fileupload/FileService.kt @@ -79,9 +79,10 @@ class FileService( private fun getFileExtension(path: String): String = MimeTypeMap.getFileExtensionFromUrl(path) } - class BackendFileLimitException(message: String) : IOException(message) { - constructor(uri: Uri) : this("Failed to upload with uri:$uri. Content size above backend limit:$backendContentSizeLimit") + constructor( + uri: Uri, + ) : this("Failed to upload with uri:$uri. Content size above backend limit:$backendContentSizeLimit") } private const val backendContentSizeLimit = 512 * 1024 * 1024 // 512 Mb diff --git a/app/data/data-claim-flow/src/main/kotlin/com/hedvig/android/data/claimflow/OdysseyService.kt b/app/data/data-claim-flow/src/main/kotlin/com/hedvig/android/data/claimflow/OdysseyService.kt index fccc12faf0..beb253492a 100644 --- a/app/data/data-claim-flow/src/main/kotlin/com/hedvig/android/data/claimflow/OdysseyService.kt +++ b/app/data/data-claim-flow/src/main/kotlin/com/hedvig/android/data/claimflow/OdysseyService.kt @@ -30,10 +30,7 @@ internal class OdysseyService( get() = "${hedvigBuildConstants.urlOdyssey}/api/flows/" context(_: Raise) - suspend fun uploadAudioRecordingFile( - flowId: String, - file: File, - ): UploadAudioRecordingResult { + suspend fun uploadAudioRecordingFile(flowId: String, file: File): UploadAudioRecordingResult { logcat { "OdysseyService: Uploading audio file ${file.name} for flowId: $flowId" } val response = httpClient.post("$baseUrl$flowId/audio-recording") { diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/HedvigDatePicker.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/HedvigDatePicker.kt index 6cda752b72..f0b2dd1106 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/HedvigDatePicker.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/HedvigDatePicker.kt @@ -10,8 +10,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterProvider -import com.hedvig.android.core.locale.previewCommonLocale import com.hedvig.android.core.locale.CommonLocale +import com.hedvig.android.core.locale.previewCommonLocale import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Medium import com.hedvig.android.design.system.hedvig.api.HedvigDisplayMode import com.hedvig.android.design.system.hedvig.api.HedvigSelectableDates diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Shapes.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Shapes.kt index 6a245739f4..27ce2e3111 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Shapes.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Shapes.kt @@ -139,7 +139,7 @@ enum class FigmaShapeDirection { All, TopOnly, BottomOnly, - EndOnly + EndOnly, } private fun RoundedPolygon.toPath( diff --git a/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatNavGraph.kt b/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatNavGraph.kt index ca477d53d0..e465b1979d 100644 --- a/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatNavGraph.kt +++ b/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatNavGraph.kt @@ -83,8 +83,7 @@ fun NavGraphBuilder.claimChatGraph( navigateUp = navController::navigateUp, ) } - navdestination(ClaimOutcomeDeflectDestination) - { backStackEntry -> + navdestination(ClaimOutcomeDeflectDestination) { backStackEntry -> ClaimOutcomeDeflectDestination( deflect = deflect, imageLoader = imageLoader, diff --git a/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/ui/PlatformBlurContainer.android.kt b/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/ui/PlatformBlurContainer.android.kt index 80e6557600..cde64bdbb0 100644 --- a/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/ui/PlatformBlurContainer.android.kt +++ b/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/ui/PlatformBlurContainer.android.kt @@ -24,9 +24,9 @@ import androidx.compose.ui.unit.dp import kotlin.math.cos import kotlin.math.sin -//todo: leaving it here since we want to have gradient in the future -//@Composable -//internal actual fun BlurredGradientBackground(modifier: Modifier, radius: Int) { +// todo: leaving it here since we want to have gradient in the future +// @Composable +// internal actual fun BlurredGradientBackground(modifier: Modifier, radius: Int) { // Box( // modifier = modifier.graphicsLayer { // compositingStrategy = CompositingStrategy.Offscreen @@ -91,4 +91,4 @@ import kotlin.math.sin // } // } // } -//} +// } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt index cf2275db6a..63728a02be 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt @@ -150,23 +150,23 @@ internal class ClaimChatViewModel( regretStepUseCase: RegretStepUseCase, fileService: FileService, ) : MoleculeViewModel( - ClaimChatUiState.Initializing, - ClaimChatPresenter( - developmentFlow, - startClaimIntentUseCase, - getClaimIntentUseCase, - submitTaskUseCase, - submitAudioRecordingUseCase, - submitFileUploadUseCase, - submitFormUseCase, - submitSelectUseCase, - submitSummaryUseCase, - skipStepUseCase, - audioRecordingManager, - fileService, - regretStepUseCase, - ), -) + ClaimChatUiState.Initializing, + ClaimChatPresenter( + developmentFlow, + startClaimIntentUseCase, + getClaimIntentUseCase, + submitTaskUseCase, + submitAudioRecordingUseCase, + submitFileUploadUseCase, + submitFormUseCase, + submitSelectUseCase, + submitSummaryUseCase, + skipStepUseCase, + audioRecordingManager, + fileService, + regretStepUseCase, + ), + ) internal class ClaimChatPresenter( private val developmentFlow: Boolean, @@ -240,7 +240,9 @@ internal class ClaimChatPresenter( ObserveIncompleteTaskEffect(getClaimIntentUseCase, currentStep, { claimIntentId }, steps) SubmitCompleteTaskEffect(submitTaskUseCase, currentStep) { claimIntent -> handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } } @@ -273,7 +275,9 @@ internal class ClaimChatPresenter( } currentContinueButtonLoading = false handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } }, ) @@ -307,7 +311,9 @@ internal class ClaimChatPresenter( audioRecordingManager.cleanup() currentContinueButtonLoading = false handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } }, ) @@ -329,7 +335,9 @@ internal class ClaimChatPresenter( ifRight = { claimIntent -> currentContinueButtonLoading = false handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } }, ) @@ -338,7 +346,6 @@ internal class ClaimChatPresenter( is ClaimChatEvent.AudioRecording.StartRecording -> { audioRecordingManager.startRecording { recordingState -> - logcat { "Mariia: audioRecordingManager.startRecording started" } steps.updateStepWithSuccess(event.id) { step, content -> step.copy(stepContent = content.copy(recordingState = recordingState)) } @@ -347,7 +354,6 @@ internal class ClaimChatPresenter( is ClaimChatEvent.AudioRecording.StopRecording -> { audioRecordingManager.stopRecording { playbackState -> - logcat { "Mariia: audioRecordingManager.StopRecording stopped" } steps.updateStepWithSuccess(event.id) { step, content -> step.copy(stepContent = content.copy(recordingState = playbackState)) } @@ -469,7 +475,9 @@ internal class ClaimChatPresenter( ifRight = { claimIntent -> currentContinueButtonLoading = false handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } }, ) @@ -521,7 +529,9 @@ internal class ClaimChatPresenter( if (!steps.updateStepWithSuccess(event.id) { step -> step.clearContent() }) return@launch currentSkipButtonLoading = false handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } }, ) @@ -543,9 +553,13 @@ internal class ClaimChatPresenter( stepContent = content.copy( recordingState = recordingState.copy( hasError = textTooShort, - errorType = if (textTooShort) TooShort( - currentContent.freeTextMinLength, - ) else null, + errorType = if (textTooShort) { + TooShort( + currentContent.freeTextMinLength, + ) + } else { + null + }, canSubmit = canSubmit, ), ), @@ -569,7 +583,9 @@ internal class ClaimChatPresenter( ifRight = { claimIntent -> currentContinueButtonLoading = false handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } }, ) @@ -603,7 +619,9 @@ internal class ClaimChatPresenter( currentContinueButtonLoading = false currentSkipButtonLoading = false handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } }, ) @@ -621,7 +639,7 @@ internal class ClaimChatPresenter( FieldType.BINARY, FieldType.SINGLE_SELECT, null, - -> field.copy( + -> field.copy( selectedOptions = event.answer?.let { listOf(it) } ?: emptyList(), @@ -687,7 +705,9 @@ internal class ClaimChatPresenter( ifRight = { claimIntent -> currentContinueButtonLoading = false handleNext( - steps, setOutcome, claimIntent, + steps, + setOutcome, + claimIntent, ) { progress = it } }, ) @@ -711,7 +731,7 @@ internal class ClaimChatPresenter( } is ClaimChatEvent.HandledDeflectNavigation -> { - //todo or remove + // todo or remove } ClaimChatEvent.DismissConfirmEditDialog -> showConfirmEditDialogForStep = null @@ -888,7 +908,7 @@ private fun ClaimIntentStep.clearContent(): ClaimIntentStep = when (val content is StepContent.Task, is StepContent.Deflect, StepContent.Unknown, - -> this + -> this } private fun MutableList.removeLastIf(predicate: (T) -> Boolean) { @@ -899,7 +919,6 @@ private fun MutableList.removeLastIf(predicate: (T) -> Boolean) { } private fun validateField(field: Field): Field { - if (field.isRequired) { val isMissing = when (field.type) { FieldType.DATE -> field.datePickerUiState?.datePickerState?.selectedDateMillis == null diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt index 376763e98c..d4d2fa9e4a 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt @@ -1,6 +1,5 @@ package com.hedvig.feature.claim.chat.data -import androidx.compose.runtime.Immutable import com.hedvig.android.core.uidata.UiFile import com.hedvig.android.design.system.hedvig.DatePickerUiState import kotlin.jvm.JvmInline @@ -13,7 +12,7 @@ value class ClaimIntentId(val value: String) internal data class ClaimIntent( val id: ClaimIntentId, val next: Next, - val progress: Float? + val progress: Float?, ) { sealed interface Next { val step: Step? @@ -33,7 +32,7 @@ internal data class ClaimIntentStep( val text: String?, val stepContent: StepContent, val isRegrettable: Boolean, - val hint: String? + val hint: String?, ) @Serializable @@ -53,7 +52,7 @@ internal sealed interface StepContent { override val isSkippable: Boolean, val recordingState: AudioRecordingStepState, val freeTextMinLength: Int, - val freeTextMaxLength: Int + val freeTextMaxLength: Int, ) : StepContent data class FileUpload( @@ -73,7 +72,6 @@ internal sealed interface StepContent { val fields: List, override val isSkippable: Boolean, ) : StepContent { - data class Field( val id: FieldId, val isRequired: Boolean, @@ -86,12 +84,14 @@ internal sealed interface StepContent { val options: List, val selectedOptions: List, val datePickerUiState: DatePickerUiState?, - val hasError: FieldError? = null + val hasError: FieldError? = null, ) sealed interface FieldError { data object BiggerThanMaxValue : FieldError + data object LessThanMinValue : FieldError + data object Missing : FieldError } @@ -108,7 +108,6 @@ internal sealed interface StepContent { MULTI_SELECT, BINARY, } - } data class ContentSelect( @@ -125,7 +124,7 @@ internal sealed interface StepContent { enum class ContentSelectStyle { PILL, - BINARY + BINARY, } data class Summary( @@ -152,7 +151,7 @@ internal sealed interface StepContent { val partnersInfo: InfoBlock?, val content: InfoBlock, val faq: List, - val buttonText: String + val buttonText: String, ) : StepContent { override val isSkippable: Boolean = false @@ -189,21 +188,21 @@ sealed interface AudioRecordingStepState { ) : AudioRecordingStepState sealed interface AudioRecording : AudioRecordingStepState { - data object NotRecording : AudioRecording - - data class Recording( - val amplitudes: List, - val startedAt: Instant, - val filePath: String, - ) : AudioRecording - - data class Playback( - val filePath: String, - val isPlaying: Boolean, - val isPrepared: Boolean, - val amplitudes: List, - val hasError: Boolean, - ) : AudioRecording + data object NotRecording : AudioRecording + + data class Recording( + val amplitudes: List, + val startedAt: Instant, + val filePath: String, + ) : AudioRecording + + data class Playback( + val filePath: String, + val isPlaying: Boolean, + val isPrepared: Boolean, + val amplitudes: List, + val hasError: Boolean, + ) : AudioRecording } } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt index a34aa01263..4966e3567b 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt @@ -41,7 +41,7 @@ internal fun ClaimIntentFragment.toClaimIntent(locale: CommonLocale): ClaimInten createdClaim != null -> ClaimIntent.Next.Outcome(createdClaim!!.toClaimIntentOutcome()) else -> error("ClaimIntentFragment contained null currentStep and null outcome") }, - progress = progress?.toFloat() + progress = progress?.toFloat(), ) } @@ -69,8 +69,8 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): style = when (style) { ClaimIntentStepContentSelectStyle.PILL -> StepContent.ContentSelectStyle.PILL ClaimIntentStepContentSelectStyle.BINARY -> StepContent.ContentSelectStyle.BINARY - ClaimIntentStepContentSelectStyle.UNKNOWN__ -> StepContent.ContentSelectStyle.PILL - } + ClaimIntentStepContentSelectStyle.UNKNOWN__ -> StepContent.ContentSelectStyle.PILL + }, ) is TaskFragment -> StepContent.Task( @@ -128,7 +128,7 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): partnersInfo = partnersInfo?.toInfoBlock(), content = content.toInfoBlock(), faq = faq.map { it.toInfoBlock() }, - buttonText = buttonTitle + buttonText = buttonTitle, ) } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/BlurredGradientBackground.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/BlurredGradientBackground.kt index 7b4a24e8df..20eac20170 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/BlurredGradientBackground.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/BlurredGradientBackground.kt @@ -16,15 +16,16 @@ import org.jetbrains.compose.resources.painterResource internal fun BlurredGradientBackground(modifier: Modifier = Modifier) { val isDarkTheme = isSystemInDarkTheme() if (isDarkTheme) { - Surface(modifier = modifier.fillMaxSize(), - color = HedvigTheme.colorScheme.backgroundPrimary){} + Surface( + modifier = modifier.fillMaxSize(), + color = HedvigTheme.colorScheme.backgroundPrimary, + ) {} } else { Image( painter = painterResource(Res.drawable.blur_background), contentDescription = null, contentScale = ContentScale.Crop, - modifier = modifier.fillMaxSize() + modifier = modifier.fillMaxSize(), ) } - } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index ad5667c0c6..0b3c9b05a3 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -473,9 +473,9 @@ private fun ClaimChatScrollableContent( contentType = { it.stepContent::class }, ) { item -> val isCurrentStep = item.id == uiState.steps.lastOrNull()?.id - val showAnimationSequence = isCurrentStep - && item.stepContent !is StepContent.Task - && !uiState.stepsWithShownAnimations.contains(item.id) + val showAnimationSequence = isCurrentStep && + item.stepContent !is StepContent.Task && + !uiState.stepsWithShownAnimations.contains(item.id) val isLastItem = item == uiState.steps.lastOrNull() val heightModifier = if (isLastItem) { @@ -513,10 +513,7 @@ private fun ClaimChatScrollableContent( } @Composable -private fun ScrollToBottomButton( - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { +private fun ScrollToBottomButton(onClick: () -> Unit, modifier: Modifier = Modifier) { IconButton( onClick = onClick, modifier = modifier.size(50.dp), @@ -560,7 +557,6 @@ private fun ColumnScope.StepContentSection( openAppSettings: () -> Unit, onResponseHeightChanged: (IntSize) -> Unit, ) { - // AnimationSequence has 3 stages one after another: // 1) fake ai dot // 2) top part of content @@ -746,11 +742,9 @@ private fun StepTopContent( } } -//to align blinking dot, task step and animated and not-animated questions to appear in the same place vertically +// to align blinking dot, task step and animated and not-animated questions to appear in the same place vertically @Composable -private fun CommonPaddingWrapper( - content: @Composable () -> Unit, -) { +private fun CommonPaddingWrapper(content: @Composable () -> Unit) { Row( verticalAlignment = Alignment.CenterVertically, ) { @@ -1064,10 +1058,7 @@ private fun DeflectStep( } @Composable -private fun TaskStep( - taskContent: StepContent.Task, - modifier: Modifier = Modifier, -) { +private fun TaskStep(taskContent: StepContent.Task, modifier: Modifier = Modifier) { val taskContentDescription = stringResource(Res.string.CLAIM_CHAT_TASK_CONTENT_DESCRIPTION) Column( modifier.clearAndSetSemantics { contentDescription = taskContentDescription }, @@ -1370,7 +1361,8 @@ private fun EditButton(canBeChanged: Boolean, onRegret: () -> Unit, modifier: Mo ) Spacer(Modifier.width(6.dp)) Icon( - HedvigIcons.ChevronDown, null, + HedvigIcons.ChevronDown, + null, tint = HedvigTheme.colorScheme.fillTertiaryTransparent, ) } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt index 00ed9a3c20..7fdf5035a6 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt @@ -134,35 +134,43 @@ internal fun ContentSelectChips( for (item in options) { key(item) { RoundCornersPill( - isSelected = item.id== selectedOptionId, + isSelected = item.id == selectedOptionId, onClick = { onOptionClick(item) - }) {contentColor -> - HedvigText(item.title, - color = contentColor) + }, + ) { contentColor -> + HedvigText( + item.title, + color = contentColor, + ) } } } } } StepContent.ContentSelectStyle.BINARY -> { - Row(Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly) { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { for (item in options) { RoundCornersPill( onClick = { onOptionClick(item) }, - isSelected = item.id== selectedOptionId, - modifier = Modifier.weight(1f).padding(horizontal = 4.dp) + isSelected = item.id == selectedOptionId, + modifier = Modifier.weight(1f).padding(horizontal = 4.dp), ) { contentColor -> Row( Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center + horizontalArrangement = Arrangement.Center, ) { - HedvigText(item.title, textAlign = TextAlign.Center, + HedvigText( + item.title, + textAlign = TextAlign.Center, modifier = Modifier.padding(top = 2.dp, bottom = 2.dp), - color = contentColor) + color = contentColor, + ) } } } @@ -176,7 +184,7 @@ internal fun RoundCornersPill( modifier: Modifier = Modifier, isSelected: Boolean = false, onClick: (() -> Unit)?, - content: @Composable ( contentColor: Color) -> Unit, + content: @Composable (contentColor: Color) -> Unit, ) { val surfaceColor by animateColorAsState( if (isSelected) { @@ -268,7 +276,7 @@ internal fun YesNoBubble( answerSelected: String?, onSelect: (String) -> Unit, modifier: Modifier = Modifier, - errorText: String? = null + errorText: String? = null, ) { val options = listOf( StepContent.ContentSelect.Option( @@ -297,7 +305,7 @@ internal fun YesNoBubble( onSelect(option.title) }, style = StepContent.ContentSelectStyle.BINARY, - selectedOptionId = options.firstOrNull { it.title==answerSelected }?.id + selectedOptionId = options.firstOrNull { it.title == answerSelected }?.id, ) } AnimatedVisibility(errorText != null) { @@ -315,7 +323,6 @@ internal fun YesNoBubble( color = HedvigTheme.colorScheme.textSecondaryTranslucent, ) } - } } } @@ -361,7 +368,7 @@ internal fun SingleSelectBubbleWithDialog( errorText, style = HedvigTheme.typography.label, color = HedvigTheme.colorScheme.textSecondaryTranslucent, - modifier = Modifier.padding(start = 16.dp) + modifier = Modifier.padding(start = 16.dp), ) } } @@ -409,13 +416,12 @@ internal fun MultiSelectBubbleWithDialog( errorText, style = HedvigTheme.typography.label, color = HedvigTheme.colorScheme.textSecondaryTranslucent, - modifier = Modifier.padding(start = 16.dp) + modifier = Modifier.padding(start = 16.dp), ) } } } } - } @Composable @@ -700,9 +706,9 @@ internal fun DateSelectBubble( modifier = modifier, ) AnimatedVisibility( - errorText != null - && datePickerState.datePickerState.selectedDateMillis == null, - //adding this since datePickerState handles update internally + errorText != null && + datePickerState.datePickerState.selectedDateMillis == null, + // adding this since datePickerState handles update internally // and it's hard to clear the error state as with other fields ) { Column { @@ -712,13 +718,12 @@ internal fun DateSelectBubble( errorText, style = HedvigTheme.typography.label, color = HedvigTheme.colorScheme.textSecondaryTranslucent, - modifier = Modifier.padding(start = 16.dp) + modifier = Modifier.padding(start = 16.dp), ) } } } } - } @Composable @@ -749,8 +754,11 @@ internal fun TextInputBubble( labelText = questionLabel, modifier = modifier.focusRequester(focusRequester), enabled = true, - errorState = if (errorText != null) HedvigTextFieldDefaults.ErrorState.Error.WithMessage(errorText) - else HedvigTextFieldDefaults.ErrorState.NoError, + errorState = if (errorText != null) { + HedvigTextFieldDefaults.ErrorState.Error.WithMessage(errorText) + } else { + HedvigTextFieldDefaults.ErrorState.NoError + }, suffix = { Row(verticalAlignment = Alignment.CenterVertically) { if (suffix != null) { @@ -851,7 +859,7 @@ internal fun ChatClaimSummaryTopContent( RoundCornersPill( modifier = Modifier.fillMaxWidth(), onClick = null, - isSelected = false + isSelected = false, ) { HedvigText(string) } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecorder.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecorder.kt index 96d5f01430..ad5c29cf2b 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecorder.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecorder.kt @@ -257,7 +257,6 @@ private fun Playback( Modifier.padding(start = 48.dp), ), ) - } else { HedvigAudioPlayer( audioPlayer = audioPlayer, diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt index 3917944ad5..dea4338250 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/audiorecording/AudioRecordingStepSections.kt @@ -6,7 +6,6 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -72,7 +71,6 @@ import com.hedvig.android.design.system.hedvig.icon.Pause import com.hedvig.android.design.system.hedvig.icon.Play import com.hedvig.android.design.system.hedvig.icon.Reload import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState -import com.hedvig.android.logger.logcat import com.hedvig.audio.player.data.AudioPlayer import com.hedvig.audio.player.data.AudioPlayerState import com.hedvig.audio.player.data.PlayableAudioSource @@ -139,7 +137,6 @@ internal fun AudioRecorderBubble( ) { uiStateAnimated -> Column(modifier) { when (uiStateAnimated) { - is AudioRecordingStepState.FreeTextDescription -> { FreeTextInputSection( submitFreeText = submitFreeText, @@ -264,8 +261,10 @@ private fun AudioRecordingBottomSheet( ) } - val audioPlayer = (audioRecordingState as? - AudioRecordingStepState.AudioRecording.Playback)?.let { + val audioPlayer = ( + audioRecordingState as? + AudioRecordingStepState.AudioRecording.Playback + )?.let { rememberAudioPlayer( PlayableAudioSource.LocalFilePath(it.filePath), ) @@ -294,6 +293,7 @@ private fun AudioRecordingBottomSheet( is AudioRecordingStepState.AudioRecording.Playback -> { if (state.isPrepared) "playback" else "loading" } + is AudioRecordingStepState.AudioRecording.Recording -> "recording" else -> "resting" } @@ -356,8 +356,8 @@ private fun AudioRecordingBottomSheet( modifier = Modifier.weight(1f), type = AudioButtonType.StartOver( onStartOver = redo, - isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback - && !continueButtonLoading, + isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback && + !continueButtonLoading, ), audioPlayer = null, ) @@ -377,8 +377,8 @@ private fun AudioRecordingBottomSheet( modifier = Modifier.weight(1f), type = AudioButtonType.Send( onSend = submitAudioFile, - isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback - && !continueButtonLoading, + isEnabled = audioRecordingState is AudioRecordingStepState.AudioRecording.Playback && + !continueButtonLoading, ), audioPlayer = null, ) @@ -413,8 +413,8 @@ private fun DynamicClock( val diff = clock.now() - (startedRecordingAt ?: clock.now()) "${twoDigitsFormat.format(diff.inWholeMinutes)}:${twoDigitsFormat.format(diff.inWholeSeconds % 60)}" } - is AudioRecordingStepState.AudioRecording.Playback -> { + is AudioRecordingStepState.AudioRecording.Playback -> { val ready = audioPlayerState as? AudioPlayerState.Ready if (ready != null) { val durationSeconds = ready.durationMillis / 1000 @@ -423,6 +423,7 @@ private fun DynamicClock( null } } + else -> null } @@ -446,11 +447,7 @@ private fun DynamicClock( } @Composable -private fun AudioButton( - type: AudioButtonType, - audioPlayer: AudioPlayer?, - modifier: Modifier = Modifier, -) { +private fun AudioButton(type: AudioButtonType, audioPlayer: AudioPlayer?, modifier: Modifier = Modifier) { val audioPlayerState by audioPlayer?.audioPlayerState?.collectAsStateWithLifecycle() ?: remember { mutableStateOf(null) } Surface( @@ -463,11 +460,9 @@ private fun AudioButton( .clickable( enabled = type.isEnabled, onClick = { - logcat { "Mariia: surface clicked, type: $type" } when (type) { is AudioButtonType.Control -> when (type.audioRecordingState) { AudioRecordingStepState.AudioRecording.NotRecording -> { - logcat { "Mariia: type.onStartRecording clicked" } type.onStartRecording() } @@ -493,21 +488,23 @@ private fun AudioButton( modifier = Modifier.padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - - Box( modifier = Modifier .clip(HedvigTheme.shapes.cornerXXLarge) .background( - color = if (!type.isEnabled) HedvigTheme.colorScheme.surfaceSecondaryTransparent else when (type) { - is AudioButtonType.Control -> when (type.audioRecordingState) { - AudioRecordingStepState.AudioRecording.NotRecording -> HedvigTheme.colorScheme.signalRedElement - is AudioRecordingStepState.AudioRecording.Playback -> HedvigTheme.colorScheme.fillPrimary - is AudioRecordingStepState.AudioRecording.Recording -> HedvigTheme.colorScheme.signalRedElement - } + color = if (!type.isEnabled) { + HedvigTheme.colorScheme.surfaceSecondaryTransparent + } else { + when (type) { + is AudioButtonType.Control -> when (type.audioRecordingState) { + AudioRecordingStepState.AudioRecording.NotRecording -> HedvigTheme.colorScheme.signalRedElement + is AudioRecordingStepState.AudioRecording.Playback -> HedvigTheme.colorScheme.fillPrimary + is AudioRecordingStepState.AudioRecording.Recording -> HedvigTheme.colorScheme.signalRedElement + } - is AudioButtonType.Send -> HedvigTheme.colorScheme.signalBlueElement - is AudioButtonType.StartOver -> HedvigTheme.colorScheme.surfaceSecondaryTransparent + is AudioButtonType.Send -> HedvigTheme.colorScheme.signalBlueElement + is AudioButtonType.StartOver -> HedvigTheme.colorScheme.surfaceSecondaryTransparent + } }, ), ) { @@ -532,9 +529,14 @@ private fun AudioButton( is AudioButtonType.StartOver -> HedvigIcons.Reload }, contentDescription = EmptyContentDescription, - tint = if (!type.isEnabled) HedvigTheme.colorScheme.fillTertiary else { - if (type is AudioButtonType.StartOver) HedvigTheme.colorScheme.fillPrimary else + tint = if (!type.isEnabled) { + HedvigTheme.colorScheme.fillTertiary + } else { + if (type is AudioButtonType.StartOver) { + HedvigTheme.colorScheme.fillPrimary + } else { HedvigTheme.colorScheme.fillNegative + } }, ) } @@ -571,8 +573,7 @@ private sealed interface AudioButtonType { val onStopRecording: () -> Unit, val audioRecordingState: AudioRecordingStepState.AudioRecording, override val isEnabled: Boolean, - ) : AudioButtonType { - } + ) : AudioButtonType class Send( val onSend: () -> Unit, @@ -640,7 +641,6 @@ private fun FreeTextInputSection( HedvigText(freeText, textAlign = TextAlign.End) } } - } else { SkippedLabel() } @@ -649,68 +649,7 @@ private fun FreeTextInputSection( } @Composable -private fun AudioRecordingSection( - uiState: AudioRecordingStepState.AudioRecording, - clock: Clock, - shouldShowRequestPermissionRationale: (String) -> Boolean, - startRecording: () -> Unit, - stopRecording: () -> Unit, - submitAudioFile: () -> Unit, - redo: () -> Unit, - openAppSettings: () -> Unit, - launchFreeText: () -> Unit, - allowFreeText: Boolean, - isCurrentStep: Boolean, - continueButtonLoading: Boolean, - modifier: Modifier = Modifier, -) { - var showPermissionDialog by remember { mutableStateOf(false) } - val recordAudioPermissionState = if (LocalInspectionMode.current) { - object : PermissionState { - override val permission: String = "" - override val status: PermissionStatus = PermissionStatus.Granted - - override fun launchPermissionRequest() {} - } - } else { - rememberPermissionState(RECORD_AUDIO_PERMISSION) { isGranted -> - if (isGranted) { - startRecording() - } else { - showPermissionDialog = true - } - } - } - if (showPermissionDialog) { - PermissionDialog( - permissionDescription = stringResource(Res.string.PERMISSION_DIALOG_RECORD_AUDIO_MESSAGE), - isPermanentlyDeclined = !shouldShowRequestPermissionRationale(RECORD_AUDIO_PERMISSION), - onDismiss = { showPermissionDialog = false }, - okClick = recordAudioPermissionState::launchPermissionRequest, - openAppSettings = openAppSettings, - ) - } - AudioRecorder( - uiState = uiState, - startRecording = recordAudioPermissionState::launchPermissionRequest, - clock = clock, - stopRecording = stopRecording, - submitAudioFile = submitAudioFile, - redo = redo, - modifier = modifier, - allowFreeText = allowFreeText, - onLaunchFreeText = launchFreeText, - isCurrentStep = isCurrentStep, - continueButtonLoading = continueButtonLoading, - ) -} - -@Composable -private fun AudioWaves( - animated: Boolean, - progressPercentage: ProgressPercentage?, - modifier: Modifier = Modifier, -) { +private fun AudioWaves(animated: Boolean, progressPercentage: ProgressPercentage?, modifier: Modifier = Modifier) { val playedColor = LocalContentColor.current val notPlayedColor = LocalContentColor.current.copy(0.38f) .compositeOver(HedvigTheme.colorScheme.surfacePrimary) @@ -804,10 +743,7 @@ private fun AudioWaves( } @Composable -private fun WavePill( - heightFraction: Float, - backgroundColor: Color, -) { +private fun WavePill(heightFraction: Float, backgroundColor: Color) { Box( modifier = Modifier .width(WAVE_WIDTH) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/outcome/ClaimOutcomeDeflectDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/outcome/ClaimOutcomeDeflectDestination.kt index 2e2180aa71..0617431c2a 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/outcome/ClaimOutcomeDeflectDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/outcome/ClaimOutcomeDeflectDestination.kt @@ -247,7 +247,7 @@ internal fun ClaimOutcomeDeflectDestination( HedvigButton( text = stringResource(Res.string.DASHBOARD_OPEN_CHAT), onClick = dropUnlessResumed { onNavigateToNewConversation() }, - buttonSize = ButtonDefaults.ButtonSize.Medium, + buttonSize = ButtonDefaults.ButtonSize.Medium, enabled = true, modifier = Modifier .padding(horizontal = 16.dp) @@ -264,10 +264,7 @@ internal fun ClaimOutcomeDeflectDestination( } @Composable -private fun QuestionsAndAnswers( - faqList: List, - modifier: Modifier = Modifier, -) { +private fun QuestionsAndAnswers(faqList: List, modifier: Modifier = Modifier) { Column(modifier) { AccordionList( faqList.map { faq -> diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/outcome/ClaimOutcomeNewClaimDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/outcome/ClaimOutcomeNewClaimDestination.kt index d711196b06..46563580af 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/outcome/ClaimOutcomeNewClaimDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/outcome/ClaimOutcomeNewClaimDestination.kt @@ -32,9 +32,7 @@ import hedvig.resources.general_done_button import org.jetbrains.compose.resources.stringResource @Composable -internal fun ClaimOutcomeNewClaimDestination( - closeSuccessScreen: () -> Unit, -) { +internal fun ClaimOutcomeNewClaimDestination(closeSuccessScreen: () -> Unit) { Surface( color = HedvigTheme.colorScheme.backgroundPrimary, modifier = Modifier.fillMaxSize(), diff --git a/app/feature/feature-claim-chat/src/jvmMain/kotlin/com/hedvig/feature/claim/chat/data/file/FileService.jvm.kt b/app/feature/feature-claim-chat/src/jvmMain/kotlin/com/hedvig/feature/claim/chat/data/file/FileService.jvm.kt index 6a96063a5e..5bccd63bec 100644 --- a/app/feature/feature-claim-chat/src/jvmMain/kotlin/com/hedvig/feature/claim/chat/data/file/FileService.jvm.kt +++ b/app/feature/feature-claim-chat/src/jvmMain/kotlin/com/hedvig/feature/claim/chat/data/file/FileService.jvm.kt @@ -20,8 +20,9 @@ internal class JvmFile( internal class JvmFileService : FileService { override fun convertToCommonFile(uri: Uri): CommonFile { return JvmFile( - "todo", "todo", - mimeType = "todo" + "todo", + "todo", + mimeType = "todo", ) } diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt index 19f4fdf730..5712938434 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt @@ -1,8 +1,8 @@ package com.hedvig.android.feature.claim.details.di import com.apollographql.apollo.ApolloClient -import com.hedvig.android.core.fileupload.DownloadPdfUseCase import com.hedvig.android.core.fileupload.ClaimsServiceUploadFileUseCase +import com.hedvig.android.core.fileupload.DownloadPdfUseCase import com.hedvig.android.data.cross.sell.after.claim.closed.CrossSellAfterClaimClosedRepository import com.hedvig.android.feature.claim.details.data.GetClaimDetailUiStateUseCase import com.hedvig.android.feature.claim.details.ui.AddFilesViewModel diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/AddFilesViewModel.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/AddFilesViewModel.kt index a98841b5c6..96429a1923 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/AddFilesViewModel.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/AddFilesViewModel.kt @@ -5,8 +5,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import arrow.core.raise.either import com.hedvig.android.apollo.NetworkCacheManager -import com.hedvig.android.core.fileupload.FileService import com.hedvig.android.core.fileupload.ClaimsServiceUploadFileUseCase +import com.hedvig.android.core.fileupload.FileService import com.hedvig.android.core.uidata.UiFile import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt index f9067d8638..b14b4a8d42 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt @@ -9,8 +9,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import arrow.fx.coroutines.parMap -import com.hedvig.android.core.fileupload.DownloadPdfUseCase import com.hedvig.android.core.fileupload.ClaimsServiceUploadFileUseCase +import com.hedvig.android.core.fileupload.DownloadPdfUseCase import com.hedvig.android.core.uidata.UiFile import com.hedvig.android.data.display.items.DisplayItem import com.hedvig.android.feature.claim.details.data.GetClaimDetailUiStateUseCase diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index b775aa4b5d..0b2a28db50 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -404,7 +404,7 @@ private fun StartClaimBottomSheet( enabled = isChecked, onClick = dropUnlessResumed { state.dismiss { - if (isExperimentalClaimChatEnabled) { + if (isExperimentalClaimChatEnabled) { navigateToClaimChat() } else { navigateToOldClaimFlow() @@ -471,11 +471,7 @@ private fun StartClaimBottomSheet( } @Composable -private fun ImportantInfoCheckBox( - isChecked: Boolean, - onCheckedChange: () -> Unit, - modifier: Modifier = Modifier, -) { +private fun ImportantInfoCheckBox(isChecked: Boolean, onCheckedChange: () -> Unit, modifier: Modifier = Modifier) { Surface( shape = HedvigTheme.shapes.cornerLarge, modifier = modifier, diff --git a/app/feature/feature-login/src/main/kotlin/com/hedvig/android/feature/login/swedishlogin/BankIdState.kt b/app/feature/feature-login/src/main/kotlin/com/hedvig/android/feature/login/swedishlogin/BankIdState.kt index 4efee5358c..4bacc9f4c6 100644 --- a/app/feature/feature-login/src/main/kotlin/com/hedvig/android/feature/login/swedishlogin/BankIdState.kt +++ b/app/feature/feature-login/src/main/kotlin/com/hedvig/android/feature/login/swedishlogin/BankIdState.kt @@ -61,7 +61,7 @@ private class BankIdStateImpl( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { packageManager.getPackageInfo( BankIdAppPackageName, - PackageManager.PackageInfoFlags.of(0) + PackageManager.PackageInfoFlags.of(0), ) } else { @Suppress("DEPRECATION") diff --git a/app/feature/feature-odyssey/src/main/kotlin/com/hedvig/android/feature/odyssey/di/OdysseyModule.kt b/app/feature/feature-odyssey/src/main/kotlin/com/hedvig/android/feature/odyssey/di/OdysseyModule.kt index 55b39702f9..5bc2bf1193 100644 --- a/app/feature/feature-odyssey/src/main/kotlin/com/hedvig/android/feature/odyssey/di/OdysseyModule.kt +++ b/app/feature/feature-odyssey/src/main/kotlin/com/hedvig/android/feature/odyssey/di/OdysseyModule.kt @@ -1,7 +1,7 @@ package com.hedvig.android.feature.odyssey.di -import com.hedvig.android.core.fileupload.FileService import com.hedvig.android.core.fileupload.ClaimsServiceUploadFileUseCase +import com.hedvig.android.core.fileupload.FileService import com.hedvig.android.data.claimflow.ClaimFlowDestination import com.hedvig.android.data.claimflow.ClaimFlowRepository import com.hedvig.android.data.claimflow.LocationOption diff --git a/app/feature/feature-odyssey/src/main/kotlin/com/hedvig/android/feature/odyssey/step/fileupload/FileUploadViewModel.kt b/app/feature/feature-odyssey/src/main/kotlin/com/hedvig/android/feature/odyssey/step/fileupload/FileUploadViewModel.kt index 098c348e32..12e2a37567 100644 --- a/app/feature/feature-odyssey/src/main/kotlin/com/hedvig/android/feature/odyssey/step/fileupload/FileUploadViewModel.kt +++ b/app/feature/feature-odyssey/src/main/kotlin/com/hedvig/android/feature/odyssey/step/fileupload/FileUploadViewModel.kt @@ -4,8 +4,8 @@ import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import arrow.core.raise.either -import com.hedvig.android.core.fileupload.FileService import com.hedvig.android.core.fileupload.ClaimsServiceUploadFileUseCase +import com.hedvig.android.core.fileupload.FileService import com.hedvig.android.core.uidata.UiFile import com.hedvig.android.data.claimflow.ClaimFlowRepository import com.hedvig.android.data.claimflow.ClaimFlowStep diff --git a/app/logging/logging-public/src/androidMain/kotlin/com/hedvig/android/logger/AndroidLogcatLogger.kt b/app/logging/logging-public/src/androidMain/kotlin/com/hedvig/android/logger/AndroidLogcatLogger.kt index 0a12bff970..222d1d516e 100644 --- a/app/logging/logging-public/src/androidMain/kotlin/com/hedvig/android/logger/AndroidLogcatLogger.kt +++ b/app/logging/logging-public/src/androidMain/kotlin/com/hedvig/android/logger/AndroidLogcatLogger.kt @@ -35,7 +35,9 @@ class AndroidLogcatLogger : LogcatLogger { } private fun v(throwable: Throwable?, tag: String?, message: () -> String) { - if (tag != null) { Timber.tag(tag) } + if (tag != null) { + Timber.tag(tag) + } if (throwable != null) { v(throwable, message) } else { @@ -44,7 +46,9 @@ private fun v(throwable: Throwable?, tag: String?, message: () -> String) { } private fun d(throwable: Throwable?, tag: String?, message: () -> String) { - if (tag != null) { Timber.tag(tag) } + if (tag != null) { + Timber.tag(tag) + } if (throwable != null) { d(throwable, message) } else { @@ -53,7 +57,9 @@ private fun d(throwable: Throwable?, tag: String?, message: () -> String) { } private fun i(throwable: Throwable?, tag: String?, message: () -> String) { - if (tag != null) { Timber.tag(tag) } + if (tag != null) { + Timber.tag(tag) + } if (throwable != null) { i(throwable, message) } else { @@ -62,7 +68,9 @@ private fun i(throwable: Throwable?, tag: String?, message: () -> String) { } private fun w(throwable: Throwable?, tag: String?, message: () -> String) { - if (tag != null) { Timber.tag(tag) } + if (tag != null) { + Timber.tag(tag) + } if (throwable != null) { w(throwable, message) } else { @@ -71,7 +79,9 @@ private fun w(throwable: Throwable?, tag: String?, message: () -> String) { } private fun e(throwable: Throwable?, tag: String?, message: () -> String) { - if (tag != null) { Timber.tag(tag) } + if (tag != null) { + Timber.tag(tag) + } if (throwable != null) { e(throwable, message) } else { @@ -80,7 +90,9 @@ private fun e(throwable: Throwable?, tag: String?, message: () -> String) { } private fun wtf(throwable: Throwable?, tag: String?, message: () -> String) { - if (tag != null) { Timber.tag(tag) } + if (tag != null) { + Timber.tag(tag) + } if (throwable != null) { wtf(throwable, message) } else { diff --git a/app/network/network-clients/src/commonMain/kotlin/com/hedvig/android/network/clients/di/NetworkModule.kt b/app/network/network-clients/src/commonMain/kotlin/com/hedvig/android/network/clients/di/NetworkModule.kt index 7c4a5d76de..85ea20df6b 100644 --- a/app/network/network-clients/src/commonMain/kotlin/com/hedvig/android/network/clients/di/NetworkModule.kt +++ b/app/network/network-clients/src/commonMain/kotlin/com/hedvig/android/network/clients/di/NetworkModule.kt @@ -100,9 +100,7 @@ private fun DefaultRequest.DefaultRequestBuilder.commonHeaders( } } -private fun HttpClient.addAuthPlugin( - accessTokenFetcher: AccessTokenFetcher, -) { +private fun HttpClient.addAuthPlugin(accessTokenFetcher: AccessTokenFetcher) { plugin(HttpSend).intercept { request -> val accessToken = accessTokenFetcher.fetch() execute( @@ -110,7 +108,7 @@ private fun HttpClient.addAuthPlugin( request.apply { header("Authorization", "Bearer ${accessTokenFetcher.fetch()}") } } else { request - } + }, ) } } diff --git a/app/shareddi/src/nativeMain/kotlin/com/hedvig/android/shareddi/SharedModule.native.kt b/app/shareddi/src/nativeMain/kotlin/com/hedvig/android/shareddi/SharedModule.native.kt index 462303a47f..f567bad381 100644 --- a/app/shareddi/src/nativeMain/kotlin/com/hedvig/android/shareddi/SharedModule.native.kt +++ b/app/shareddi/src/nativeMain/kotlin/com/hedvig/android/shareddi/SharedModule.native.kt @@ -8,16 +8,12 @@ import org.koin.core.module.Module import org.koin.dsl.module internal actual val platformModule: Module = module { - } /** * Like [platformModule] but allows for dynamic input, for pieces that need to be injected from iOS */ -internal fun iosPlatformModule( - accessTokenFetcher: AccessTokenFetcher, - deviceIdFetcher: DeviceIdFetcher, -) = module { +internal fun iosPlatformModule(accessTokenFetcher: AccessTokenFetcher, deviceIdFetcher: DeviceIdFetcher) = module { single { accessTokenFetcher } From 9ce070dfdc755c44a7f443e76e201f8ee351ea4c Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 23 Jan 2026 12:41:57 +0100 Subject: [PATCH 18/18] remove unused --- .../android/audio/player/HedvigAudioPlayer.kt | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt index 09046c6fa4..1b38e267b3 100644 --- a/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt +++ b/app/audio-player-ui/src/commonMain/kotlin/com/hedvig/android/audio/player/HedvigAudioPlayer.kt @@ -1,28 +1,11 @@ package com.hedvig.android.audio.player -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.audio.player.internal.FakeWaveAudioPlayerCard -import com.hedvig.android.audio.player.internal.waveWidthPercentOfSpaceAvailable -import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.audio.player.data.AudioPlayer -import kotlin.math.roundToInt /** * https://www.figma.com/file/e0lnWjMtp8x5Typlt5b33i/Claim-status-Android?node-id=1224%3A82&t=RgoySHgQiM6RyYNI-1