Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/src/main/java/com/doyoonkim/knutice/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavHostController
Expand Down Expand Up @@ -271,12 +272,11 @@ class MainActivity : ComponentActivity() {
}

if (showPermissionRationale) {
Box(
modifier = Modifier.fillMaxSize()
.clickable { /* CLICK TO DISMISS NOT ALLOWED */ }
Dialog(
onDismissRequest = { /* DO NOTHING. PERMISSION IS MANDATORY */ }
) {
PermissionRationaleComposable(
modifier = Modifier.align(Alignment.Center).padding(start = 20.dp, end = 20.dp),
modifier = Modifier,
permissionName = stringResource(R.string.title_alarm_and_reminder),
rationaleTitle = stringResource(R.string.text_rationale_title),
description = stringResource(R.string.text_rationale_description)
Expand Down
3 changes: 2 additions & 1 deletion common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(platform(libs.androidx.compose.bom))

debugImplementation(libs.ui.tooling)

testImplementation(libs.junit)

Expand All @@ -62,6 +62,7 @@ dependencies {
implementation(libs.dagger)
implementation(libs.dagger.android)
implementation(libs.dagger.android.support)
debugImplementation(libs.ui.tooling)
kapt(libs.dagger.compiler)
kapt(libs.dagger.android.processor)

Expand Down
4 changes: 4 additions & 0 deletions common/src/main/java/com/doyoonkim/common/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ val ColorScheme.containerBackgroundSolid: Color
@Composable
get() = if(isSystemInDarkTheme()) ContainerBlack else ContainerLight

val ColorScheme.containerGray: Color
@Composable
get() = if(isSystemInDarkTheme()) Color.Gray else Color.LightGray

val ColorScheme.bottomNavContainer: Color
@Composable
get() = if(isSystemInDarkTheme()) bottomNavBarBlack else bottomNavBarWhite
Expand Down
151 changes: 62 additions & 89 deletions common/src/main/java/com/doyoonkim/common/ui/DateTimePicker.kt
Original file line number Diff line number Diff line change
@@ -1,136 +1,109 @@
package com.doyoonkim.common.ui

import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.DisplayMode
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TimeInput
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.doyoonkim.common.R
import androidx.compose.ui.unit.sp
import com.doyoonkim.common.theme.containerGray
import com.doyoonkim.common.theme.title
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DateTimePicker(
fun DatePickerDialog(
modifier: Modifier = Modifier,
initialTime: Long = System.currentTimeMillis(),
onDateTimeConfirmed: (Long?) -> Unit
initialTime: Long? = null,
onDismissed: (Int, Int, Int) -> Unit
) {
val calendar = Calendar.getInstance().also {
it.timeInMillis = initialTime
}
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
.apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val localOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis())

val datePickerState = rememberDatePickerState(
initialDisplayMode = DisplayMode.Input,
initialSelectedDateMillis = initialTime
)
val timePickerState = rememberTimePickerState(
initialHour = calendar.get(Calendar.HOUR_OF_DAY),
initialMinute = calendar.get(Calendar.MINUTE),
is24Hour = false
initialDisplayMode = DisplayMode.Picker,
initialSelectedDateMillis = initialTime ?: (calendar.timeInMillis + localOffset)
)

var selectedDateTime by remember { mutableStateOf<Long?>(null) }
var confirmEnabled by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(
datePickerState.selectedDateMillis, timePickerState.hour, timePickerState.minute
) {
if (datePickerState.selectedDateMillis == null) {
confirmEnabled = false
} else {
val target = combineDateTime(
datePickerState.selectedDateMillis!!,
Pair(timePickerState.hour, timePickerState.minute)
)
confirmEnabled = target!! > (calendar.timeInMillis ?: (target + 1))
}
}
var pickerVisible by remember { mutableStateOf(false) }

Surface(
modifier = modifier.wrapContentSize().background(Color.Transparent),
color = MaterialTheme.colorScheme.surfaceContainer,
shape = RoundedCornerShape(15.dp)
Box(
modifier = modifier.wrapContentWidth()
) {
Column(
modifier = Modifier.wrapContentSize(),
verticalArrangement = Arrangement.spacedBy(3.dp),
horizontalAlignment = Alignment.CenterHorizontally
Surface(
modifier = Modifier.wrapContentSize()
.background(Color.Transparent)
.clip(RoundedCornerShape(10.dp))
.clickable { pickerVisible = !pickerVisible },
color = MaterialTheme.colorScheme.containerGray
) {
DatePicker(
state = datePickerState,
showModeToggle = false
Text(
text = datePickerState.selectedDateMillis!!.toFormattedString(),
fontSize = 15.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.title,
modifier = Modifier.padding(10.dp)
)
TimeInput(
state = timePickerState
)
TextButton(
onClick = {
selectedDateTime = combineDateTime(
datePickerState.selectedDateMillis!!,
Pair(timePickerState.hour, timePickerState.minute)
)
Log.d("DateTimePicker", "$selectedDateTime")
onDateTimeConfirmed(selectedDateTime)
},
enabled = confirmEnabled,
colors = ButtonDefaults.textButtonColors()
) { Text(stringResource(R.string.btn_confirm)) }
}

if (pickerVisible) {
DatePickerDialog(
onDismissRequest = {
datePickerState.selectedDateMillis?.let {
with(getInfo(it.toFormattedString())) {
onDismissed(this[0], this[1], this[2])
}
}.also { pickerVisible = !pickerVisible }
},
confirmButton = { }
) {
DatePicker(
state = datePickerState,
showModeToggle = false
)
}
}
}

}

private fun Long.toFormattedDate(f: SimpleDateFormat): String {
return f.format(Date(this).time)
}

private fun String.toMillis(f: SimpleDateFormat): Long? {
return f.parse(this)?.time
}
private fun Long.toFormattedString() =
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(this)

private fun combineDateTime(d: Long, t: Pair<Int, Int>): Long? {
val date = LocalDate.parse(d.toFormattedDate(SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())))
val time = LocalTime.of(t.first, t.second)
return LocalDateTime.of(date, time).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
}

private fun Long.dropSeconds(): Long? {
val dateFormat = SimpleDateFormat("yyyy/MM/dd a Hm", Locale.getDefault()).apply {
timeZone = TimeZone.getTimeZone(ZoneId.systemDefault())
}
return this.toFormattedDate(dateFormat).toMillis(dateFormat)
}
private fun getInfo(formattedDate: String) =
formattedDate.split("-").map { it.toInt() }

@Preview(showBackground = true)
@Composable
Expand Down
Loading
Loading