Skip to content

Commit

Permalink
feat: login screen
Browse files Browse the repository at this point in the history
VEEEEEEEEEERY basic it doesn't even have persistency
this'll be added in a few commits
I'll be adding localization first
  • Loading branch information
abdallahmehiz committed Jul 21, 2024
1 parent 54f5686 commit 44c1737
Show file tree
Hide file tree
Showing 29 changed files with 780 additions and 66 deletions.

This file was deleted.

12 changes: 9 additions & 3 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,27 @@ kotlin {
}

sourceSets {

androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
}
commonMain.dependencies {
implementation(project(":domain"))

implementation(compose.ui)
implementation(compose.runtime)
implementation(compose.material3)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.materialIconsExtended)
implementation(compose.components.uiToolingPreview)

implementation(libs.immutable.collections)
implementation(libs.kotlinx.datetime)

api(libs.bundles.kodein)
api(libs.bundles.datastore)
implementation(libs.bundles.voyager)
}
}
}
Expand Down
7 changes: 0 additions & 7 deletions composeApp/src/androidMain/kotlin/Platform.android.kt

This file was deleted.

Binary file not shown.
35 changes: 6 additions & 29 deletions composeApp/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
@@ -1,38 +1,15 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material3.MaterialTheme
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 org.jetbrains.compose.resources.painterResource
import cafe.adriel.voyager.navigator.Navigator
import org.jetbrains.compose.ui.tooling.preview.Preview
import progres.composeapp.generated.resources.Res
import progres.composeapp.generated.resources.compose_multiplatform
import ui.onboarding.LoginScreen

@Composable
@Preview
fun App() {
MaterialTheme {
var showContent by remember { mutableStateOf(false) }
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { showContent = !showContent }) {
Text("Click me!")
}
AnimatedVisibility(showContent) {
val greeting = remember { Greeting().greet() }
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greeting")
}
}
}
Navigator(
screen = LoginScreen
)
}
}
7 changes: 0 additions & 7 deletions composeApp/src/commonMain/kotlin/Greeting.kt

This file was deleted.

5 changes: 0 additions & 5 deletions composeApp/src/commonMain/kotlin/Platform.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package di

import mehiz.abdallah.progres.domain.DomainModule
import org.kodein.di.DI

fun initKodein(
datastorePath: String,
): DI {
return DI.from(
listOf(
PreferencesModule(datastorePath)
PreferencesModule(datastorePath),
DomainModule
)
)
}
Expand Down
249 changes: 249 additions & 0 deletions composeApp/src/commonMain/kotlin/presentation/WheelPicker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package presentation

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlin.math.absoluteValue

@Composable
fun WheelNumberPicker(
items: ImmutableList<Number>,
modifier: Modifier = Modifier,
startIndex: Int = 0,
size: DpSize = DpSize(128.dp, 128.dp),
onSelectionChanged: (index: Int) -> Unit = {},
backgroundContent: (@Composable (size: DpSize) -> Unit)? = {
WheelPickerDefaults.Background(size = it)
},
) {
WheelPicker(
modifier = modifier,
startIndex = startIndex,
items = items,
size = size,
onSelectionChanged = onSelectionChanged,
manualInputType = KeyboardType.Number,
backgroundContent = backgroundContent,
) {
WheelPickerDefaults.Item(text = "$it")
}
}

@Composable
fun WheelTextPicker(
items: ImmutableList<String>,
modifier: Modifier = Modifier,
startIndex: Int = 0,
size: DpSize = DpSize(128.dp, 128.dp),
onSelectionChanged: (index: Int) -> Unit = {},
backgroundContent: (@Composable (size: DpSize) -> Unit)? = {
WheelPickerDefaults.Background(size = it)
},
) {
WheelPicker(
modifier = modifier,
startIndex = startIndex,
items = items,
size = size,
onSelectionChanged = onSelectionChanged,
backgroundContent = backgroundContent,
) {
WheelPickerDefaults.Item(text = it)
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun <T> WheelPicker(
items: ImmutableList<T>,
modifier: Modifier = Modifier,
startIndex: Int = 0,
size: DpSize = DpSize(128.dp, 128.dp),
onSelectionChanged: (index: Int) -> Unit = {},
manualInputType: KeyboardType? = null,
backgroundContent: (@Composable (size: DpSize) -> Unit)? = {
WheelPickerDefaults.Background(size = it)
},
itemContent: @Composable LazyItemScope.(item: T) -> Unit,
) {
val haptic = LocalHapticFeedback.current
val lazyListState = rememberLazyListState(startIndex)

var internalIndex by remember { mutableIntStateOf(startIndex) }
val internalOnSelectionChanged: (Int) -> Unit = {
internalIndex = it
onSelectionChanged(it)
}

LaunchedEffect(lazyListState, onSelectionChanged) {
snapshotFlow { lazyListState.firstVisibleItemScrollOffset }
.map { calculateSnappedItemIndex(lazyListState) }
.distinctUntilChanged()
.collectLatest {
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
internalOnSelectionChanged(it)
}
}

Box(
modifier = modifier
.height(size.height)
.width(size.width),
contentAlignment = Alignment.Center,
) {
backgroundContent?.invoke(size)

var showManualInput by remember { mutableStateOf(false) }
if (showManualInput) {
var value by remember {
val currentString = items[internalIndex].toString()
mutableStateOf(TextFieldValue(text = currentString, selection = TextRange(currentString.length)))
}

val scope = rememberCoroutineScope()
BasicTextField(
modifier = Modifier
.align(Alignment.Center),
value = value,
onValueChange = { value = it },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = manualInputType!!,
imeAction = ImeAction.Done,
),
textStyle = MaterialTheme.typography.titleMedium +
TextStyle(
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
),
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
)
} else {
LazyColumn(
modifier = Modifier,
state = lazyListState,
contentPadding = PaddingValues(vertical = size.height / RowCount * ((RowCount - 1) / 2)),
flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState),
) {
itemsIndexed(items) { index, item ->
Box(
modifier = Modifier
.height(size.height / RowCount)
.width(size.width)
.alpha(
calculateAnimatedAlpha(
lazyListState = lazyListState,
index = index,
),
),
contentAlignment = Alignment.Center,
) {
itemContent(item)
}
}
}
}
}
}

private fun LazyListState.snapOffsetForItem(itemInfo: LazyListItemInfo): Int {
val startScrollOffset = 0
val endScrollOffset = layoutInfo.let { it.viewportEndOffset - it.afterContentPadding }
return startScrollOffset + (endScrollOffset - startScrollOffset - itemInfo.size) / 2
}

private fun LazyListState.distanceToSnapForIndex(index: Int): Int {
val itemInfo = layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }
if (itemInfo != null) {
return itemInfo.offset - snapOffsetForItem(itemInfo)
}
return 0
}

private fun calculateAnimatedAlpha(
lazyListState: LazyListState,
index: Int,
): Float {
val distanceToIndexSnap = lazyListState.distanceToSnapForIndex(index).absoluteValue
val viewPortHeight = lazyListState.layoutInfo.viewportSize.height.toFloat()
val singleViewPortHeight = viewPortHeight / RowCount
return if (distanceToIndexSnap in 0..singleViewPortHeight.toInt()) {
1.2f - (distanceToIndexSnap / singleViewPortHeight)
} else {
0.2f
}
}

private fun calculateSnappedItemIndex(lazyListState: LazyListState): Int {
return lazyListState.layoutInfo.visibleItemsInfo
.maxBy { calculateAnimatedAlpha(lazyListState, it.index) }
.index
}

object WheelPickerDefaults {
@Composable
fun Background(size: DpSize) {
androidx.compose.material3.Surface(
modifier = Modifier
.size(size.width, size.height / RowCount),
shape = RoundedCornerShape(24.dp),
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary),
content = {},
)
}

@Composable
fun Item(text: String) {
Text(
text = text,
style = MaterialTheme.typography.titleMedium,
maxLines = 1,
)
}
}

private const val RowCount = 3
18 changes: 18 additions & 0 deletions composeApp/src/commonMain/kotlin/ui/home/HomeScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ui.home

import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import cafe.adriel.voyager.core.screen.Screen

object HomeScreen: Screen {
@Composable
override fun Content() {
Box(
contentAlignment = Alignment.Center
) {
Text("Home Screen")
}
}
}
Loading

0 comments on commit 44c1737

Please sign in to comment.