Skip to content

Commit

Permalink
Ivy switch (#54)
Browse files Browse the repository at this point in the history
* Implement IvySwitch component and use it in SettingsContent

* Improve switch UI in Settings

* Handle terms of use and privacy policy click events

* Improve Settings screen's UI

* Improve Settings screen's UI & SettingsButton

* Implement DeleteAccountConfirmationDialog composable

* Improve UI/UX

* Fix event handling

* Fix setting button titles

* Remove not needed imports

* Support button loading state
  • Loading branch information
nicolegeorgieva authored Nov 30, 2024
1 parent 7b22c8c commit 13f722f
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 63 deletions.
29 changes: 22 additions & 7 deletions composeApp/src/commonMain/kotlin/component/button/IvyButton.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package component.button

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -32,19 +29,37 @@ sealed interface ButtonStyle {
fun IvyButton(
appearance: ButtonAppearance,
modifier: Modifier = Modifier,
loading: Boolean = false,
enabled: Boolean = true,
text: @Composable (RowScope.() -> Unit)? = null,
icon: @Composable (RowScope.() -> Unit)? = null,
iconRight: @Composable (RowScope.() -> Unit)? = null,
onClick: () -> Unit,
) {
val contentPadding = if (text == null) {
PaddingValues(all = 8.dp)
} else {
PaddingValues(
horizontal = 16.dp,
vertical = 8.dp
)
}

ButtonWrapper(
appearance = appearance,
modifier = modifier,
enabled = enabled,
appearance = appearance,
contentPadding = contentPadding,
enabled = enabled && !loading,
onClick = onClick
) {
when {
loading -> {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = LocalContentColor.current
)
}

icon != null && text != null -> {
icon()
Spacer(Modifier.width(8.dp))
Expand All @@ -71,12 +86,12 @@ fun IvyButton(
@Composable
private fun ButtonWrapper(
appearance: ButtonAppearance,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
enabled: Boolean = true,
onClick: () -> Unit,
content: @Composable (RowScope.() -> Unit)
) {
val contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
val colors = appearance.buttonColors()

when (appearance) {
Expand Down
49 changes: 49 additions & 0 deletions composeApp/src/commonMain/kotlin/component/button/IvySwitch.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package component.button

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Switch
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import ui.theme.colorsExt

@Composable
fun IvySwitch(
checked: Boolean,
modifier: Modifier = Modifier,
onCheckedChange: (Boolean) -> Unit,
text: @Composable () -> Unit,
) {
Row(
modifier = modifier
.clip(MaterialTheme.shapes.medium)
.clickable(
onClick = {
onCheckedChange(!checked)
}
)
.background(
color = MaterialTheme.colorsExt.backgroundVariant,
shape = MaterialTheme.shapes.medium
)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
text()
Spacer(Modifier.weight(1f))
Switch(
checked = checked,
onCheckedChange = onCheckedChange
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import component.*
import component.button.SecondaryButton
import component.button.ButtonAppearance
import component.button.ButtonStyle
import component.button.IvyButton
import ivy.model.CourseId
import ui.screen.home.HomeItemViewState
import ui.screen.home.HomeViewEvent
Expand All @@ -32,11 +35,8 @@ fun HomeContent(
),
title = "Learn",
actions = {
// TODO - update button
SecondaryButton(
text = "",
icon = Icons.Filled.Settings,
onClick = {
SettingsButton(
onSettingsClick = {
onEvent(HomeViewEvent.OnSettingsClick)
}
)
Expand Down Expand Up @@ -101,4 +101,18 @@ fun HomeContent(
}
}
}
}

@Composable
private fun SettingsButton(onSettingsClick: () -> Unit) {
IvyButton(
appearance = ButtonAppearance.Filled(style = ButtonStyle.Secondary),
icon = {
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = null
)
},
onClick = onSettingsClick
)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package ui.screen.settings

import androidx.compose.runtime.*
import androidx.compose.ui.platform.UriHandler
import ivy.IvyUrls
import navigation.Navigation
import ui.ComposeViewModel

class SettingsViewModel(
private val navigation: Navigation,
private val uriHandler: UriHandler
) : ComposeViewModel<SettingsViewState, SettingsViewEvent> {
private var soundEnabled by mutableStateOf(true)
private var deleteDialogVisible by mutableStateOf(false)
private var deleteDialog by mutableStateOf<DeleteDialogViewState?>(null)

@Composable
override fun viewState(): SettingsViewState {
Expand All @@ -17,7 +20,7 @@ class SettingsViewModel(
}
return SettingsViewState(
soundEnabled = getSoundEnabled(),
deleteDialogVisible = getDeleteDialogVisible()
deleteDialog = getDeleteDialogState()
)
}

Expand All @@ -27,8 +30,8 @@ class SettingsViewModel(
}

@Composable
private fun getDeleteDialogVisible(): Boolean {
return deleteDialogVisible
private fun getDeleteDialogState(): DeleteDialogViewState? {
return deleteDialog
}

override fun onEvent(event: SettingsViewEvent) {
Expand All @@ -37,11 +40,12 @@ class SettingsViewModel(
SettingsViewEvent.OnPremiumClick -> handlePremiumClick()
is SettingsViewEvent.OnSoundEnabledChange -> handleSoundEnabledChange(event)
SettingsViewEvent.OnPrivacyClick -> handlePrivacyClick()
SettingsViewEvent.OnLogOutClick -> handleLogOutClick()
SettingsViewEvent.OnDeleteAccountClick -> handleDeleteAccountClick()
SettingsViewEvent.OnTermsOfUseClick -> handleTermsOfUseClick()
SettingsViewEvent.OnPrivacyPolicyClick -> handlePrivacyPolicyClick()
SettingsViewEvent.OnCancelDeleteAccountClick -> handleConfirmDeleteAccountClick()
SettingsViewEvent.OnConfirmDeleteAccountClick -> handleCancelDeleteAccountClick()
SettingsViewEvent.OnCancelDeleteAccountClick -> handleCancelDeleteAccountClick()
SettingsViewEvent.OnConfirmDeleteAccountClick -> handleConfirmDeleteAccountClick()
}
}

Expand All @@ -61,23 +65,28 @@ class SettingsViewModel(
// TODO - handle event
}

private fun handleDeleteAccountClick() {
deleteDialogVisible = true
private fun handleLogOutClick() {
// TODO - handle event
}

private fun handleTermsOfUseClick() {
// TODO - handle event
uriHandler.openUri(IvyUrls.tos)
}

private fun handlePrivacyPolicyClick() {
// TODO - handle event
uriHandler.openUri(IvyUrls.privacy)
}

private fun handleDeleteAccountClick() {
deleteDialog = DeleteDialogViewState(ctaLoading = false)
}

private fun handleConfirmDeleteAccountClick() {
deleteDialog = DeleteDialogViewState(ctaLoading = true)
// TODO - handle event
}

private fun handleCancelDeleteAccountClick() {
// TODO - handle event
deleteDialog = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import androidx.compose.runtime.Immutable
@Immutable
data class SettingsViewState(
val soundEnabled: Boolean,
val deleteDialogVisible: Boolean,
val deleteDialog: DeleteDialogViewState?
)

@Immutable
data class DeleteDialogViewState(
val ctaLoading: Boolean
)

sealed interface SettingsViewEvent {
data object OnBackClick : SettingsViewEvent
data object OnPremiumClick : SettingsViewEvent
data class OnSoundEnabledChange(val enabled: Boolean) : SettingsViewEvent
data object OnPrivacyClick : SettingsViewEvent
data object OnLogOutClick : SettingsViewEvent
data object OnDeleteAccountClick : SettingsViewEvent
data object OnTermsOfUseClick : SettingsViewEvent
data object OnPrivacyPolicyClick : SettingsViewEvent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ui.screen.settings.composable

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.AlertDialog
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import component.button.ButtonAppearance
import component.button.ButtonStyle
import component.button.IvyButton
import ui.screen.settings.DeleteDialogViewState
import ui.theme.colorsExt

@Composable
fun DeleteAccountConfirmationDialog(
viewState: DeleteDialogViewState,
modifier: Modifier = Modifier,
onConfirmDeleteAccountClick: () -> Unit,
onCancelDeleteAccountClick: () -> Unit
) {
AlertDialog(
modifier = modifier,
title = {
Text(
text = "Confirm Account Deletion",
style = MaterialTheme.typography.subtitle1
)
},
text = {
Text(
text = "By proceeding, you confirm your request to permanently delete your account and all " +
"associated data. This action is irreversible and cannot be undone. Please review our " +
"Privacy Policy and Terms of Service for further details.",
style = MaterialTheme.typography.subtitle2
)
},
onDismissRequest = onCancelDeleteAccountClick,
confirmButton = {
IvyButton(
appearance = ButtonAppearance.Filled(ButtonStyle.Destructive),
loading = viewState.ctaLoading,
onClick = onConfirmDeleteAccountClick,
text = {
Text(text = "DELETE ACCOUNT")
}
)
},
dismissButton = {
IvyButton(
appearance = ButtonAppearance.Filled(ButtonStyle.Neutral),
onClick = onCancelDeleteAccountClick,
text = {
Text(text = "Cancel")
}
)
},
backgroundColor = MaterialTheme.colorsExt.backgroundVariant,
contentColor = MaterialTheme.colorsExt.onBackgroundVariant,
shape = RoundedCornerShape(8.dp)
)
}
Loading

0 comments on commit 13f722f

Please sign in to comment.