Composables
Docs Login
Pairel

Discovery preferences

Jetpack Compose Screen

Download
package com.composables.pairel

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
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.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.composables.pairel.components.Card
import com.composables.pairel.components.CenteredAppBar
import com.composables.pairel.components.ChoiceButton
import com.composables.pairel.components.Icon
import com.composables.pairel.components.IconButton
import com.composables.pairel.components.PreferenceToggle
import com.composables.pairel.components.ScreenScaffold
import com.composables.pairel.components.Text
import com.composables.pairel.theme.ArrowLeft
import com.composables.pairel.theme.Icons
import com.composables.pairel.theme.Theme
import com.composeunstyled.UnstyledSlider
import kotlin.math.roundToInt

sealed interface WhoToSee {
    data object Men : WhoToSee

    data object Women : WhoToSee

    data object Everyone : WhoToSee
}

sealed interface DatingIntent {
    data object LongTerm : DatingIntent

    data object OpenToEither : DatingIntent

    data object Casual : DatingIntent
}

data class DiscoveryPreferences(
    val minimumAge: Int = 26,
    val maximumAge: Int = 38,
    val maximumDistanceKm: Int = 40,
    val whoToSee: WhoToSee = WhoToSee.Women,
    val datingIntent: DatingIntent = DatingIntent.OpenToEither,
    val broadenAge: Boolean = true,
    val broadenDistance: Boolean = true,
)

@Composable
fun DiscoveryPreferencesScreen(
    navigateBack: () -> Unit,
    modifier: Modifier = Modifier,
    initialPreferences: DiscoveryPreferences = DiscoveryPreferences(),
) {
    var preferences by remember(initialPreferences) { mutableStateOf(initialPreferences) }

    ScreenScaffold(
        modifier = modifier,
        topBar = {
            CenteredAppBar(
                title = { Text("Preferences") },
                navigation = {
                    IconButton(
                        onClick = navigateBack,
                        modifier = Modifier.testTag("preferences-back"),
                    ) {
                        Icon(
                            Icons.ArrowLeft,
                            contentDescription = "Back",
                            modifier = Modifier.size(24.dp),
                        )
                    }
                },
            )
        },
    ) {
        Column(
            Modifier.fillMaxSize()
                .verticalScroll(rememberScrollState())
                .padding(top = 20.dp, bottom = 24.dp)
                .windowInsetsPadding(WindowInsets.navigationBars)
                .padding(horizontal = 20.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            Text("A little more you", style = Theme.typography.headline)
            Text(
                "Choose who you'd like to meet. You can change these anytime.",
                color = Theme.colors.onBackgroundMuted,
            )

            DiscoveryPreferencesForm(
                preferences = preferences,
                onPreferencesChange = { preferences = it },
                modifier = Modifier.fillMaxWidth(),
            )
        }
    }
}

@Composable
private fun PreferenceSlider(
    label: String,
    value: Int,
    range: ClosedFloatingPointRange<Float>,
    onValueChange: (Int) -> Unit,
    modifier: Modifier = Modifier,
) {
    Column(modifier) {
        Text(label, style = Theme.typography.caption, color = Theme.colors.onBackgroundMuted)
        UnstyledSlider(
            value = value.toFloat(),
            onValueChange = { onValueChange(it.roundToInt()) },
            valueRange = range,
            modifier =
                Modifier.fillMaxWidth()
                    .padding(top = 8.dp)
                    .semantics { contentDescription = "$label: $value" }
                    .testTag(label.lowercase().replace(' ', '-')),
            track = { state ->
                Box(
                    Modifier.fillMaxWidth()
                        .height(4.dp)
                        .clip(CircleShape)
                        .background(Theme.colors.surfaceVariant)
                ) {
                    Box(
                        Modifier.fillMaxWidth(state.fraction)
                            .height(4.dp)
                            .background(Theme.colors.accent)
                    )
                }
            },
            thumb = { Box(Modifier.size(24.dp).clip(CircleShape).background(Theme.colors.accent)) },
        )
    }
}

@Composable
internal fun DiscoveryPreferencesForm(
    preferences: DiscoveryPreferences,
    onPreferencesChange: (DiscoveryPreferences) -> Unit,
    modifier: Modifier = Modifier,
    includeDatingIntent: Boolean = true,
) {
    Column(modifier, verticalArrangement = Arrangement.spacedBy(16.dp)) {
        Card(Modifier.fillMaxWidth()) {
            Text("Who would you like to see?", style = Theme.typography.label)
            Row(
                Modifier.fillMaxWidth().padding(top = 16.dp),
                horizontalArrangement = Arrangement.spacedBy(8.dp),
            ) {
                for ((choice, label) in
                    listOf(
                        WhoToSee.Men to "Men",
                        WhoToSee.Women to "Women",
                        WhoToSee.Everyone to "Everyone",
                    )) {
                    ChoiceButton(
                        selected = preferences.whoToSee == choice,
                        onClick = { onPreferencesChange(preferences.copy(whoToSee = choice)) },
                        modifier = Modifier.weight(1f),
                    ) {
                        Text(label)
                    }
                }
            }
        }

        Card(Modifier.fillMaxWidth()) {
            Text("Age range", style = Theme.typography.label)
            Text(
                "${preferences.minimumAge} to ${preferences.maximumAge}",
                modifier = Modifier.padding(top = 4.dp),
                color = Theme.colors.onBackgroundMuted,
            )
            PreferenceSlider(
                label = "Minimum age",
                value = preferences.minimumAge,
                range = 18f..(preferences.maximumAge - 1).toFloat(),
                onValueChange = { onPreferencesChange(preferences.copy(minimumAge = it)) },
                modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
            )
            PreferenceSlider(
                label = "Maximum age",
                value = preferences.maximumAge,
                range = (preferences.minimumAge + 1).toFloat()..70f,
                onValueChange = { onPreferencesChange(preferences.copy(maximumAge = it)) },
                modifier = Modifier.fillMaxWidth().padding(top = 12.dp),
            )
            PreferenceToggle(
                label = "Show people slightly outside this range",
                initialChecked = preferences.broadenAge,
                onCheckedChange = { onPreferencesChange(preferences.copy(broadenAge = it)) },
                modifier = Modifier.fillMaxWidth().padding(top = 20.dp),
            )
        }

        Card(Modifier.fillMaxWidth()) {
            Text("Distance", style = Theme.typography.label)
            Text(
                "Up to ${preferences.maximumDistanceKm} km away",
                modifier = Modifier.padding(top = 4.dp),
                color = Theme.colors.onBackgroundMuted,
            )
            PreferenceSlider(
                label = "Maximum distance",
                value = preferences.maximumDistanceKm,
                range = 5f..100f,
                onValueChange = { onPreferencesChange(preferences.copy(maximumDistanceKm = it)) },
                modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
            )
            PreferenceToggle(
                label = "Show people farther away if I run out",
                initialChecked = preferences.broadenDistance,
                onCheckedChange = { onPreferencesChange(preferences.copy(broadenDistance = it)) },
                modifier = Modifier.fillMaxWidth().padding(top = 20.dp),
            )
        }

        if (includeDatingIntent) {
            Card(Modifier.fillMaxWidth()) {
                Text("What are you looking for?", style = Theme.typography.label)
                Column(
                    Modifier.fillMaxWidth().padding(top = 16.dp),
                    verticalArrangement = Arrangement.spacedBy(8.dp),
                ) {
                    for ((choice, label) in
                        listOf(
                            DatingIntent.LongTerm to "A long-term relationship",
                            DatingIntent.OpenToEither to "Open to seeing where it goes",
                            DatingIntent.Casual to "Something casual",
                        )) {
                        ChoiceButton(
                            selected = preferences.datingIntent == choice,
                            onClick = {
                                onPreferencesChange(preferences.copy(datingIntent = choice))
                            },
                            modifier = Modifier.fillMaxWidth(),
                        ) {
                            Text(label)
                        }
                    }
                }
            }
        }
    }
}

More screens from Pairel

by Alex Styl

Search screens and components

Screen flows

Screens

UI Elements