diff --git a/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/ResExtensions.kt b/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/ResExtensions.kt
new file mode 100644
index 000000000..eafd8517d
--- /dev/null
+++ b/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/ResExtensions.kt
@@ -0,0 +1,19 @@
+package github.tornaco.android.thanos.module.compose.common
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+
+@Composable
+fun Int.toDp(): Dp {
+ return with(LocalDensity.current) {
+ this@toDp.toDp()
+ }
+}
+
+@Composable
+fun Dp.toPx(): Float {
+ return with(LocalDensity.current) {
+ this@toPx.toPx()
+ }
+}
diff --git a/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/widget/Cards.kt b/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/widget/Cards.kt
new file mode 100644
index 000000000..7d970def6
--- /dev/null
+++ b/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/widget/Cards.kt
@@ -0,0 +1,38 @@
+/*
+ * (C) Copyright 2022 Thanox
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package github.tornaco.android.thanos.module.compose.common.widget
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import github.tornaco.android.thanos.module.compose.common.theme.ColorDefaults
+
+@Composable
+fun CardContainer(content: @Composable () -> Unit) {
+ androidx.compose.material.Card(
+ modifier = Modifier
+ .padding(horizontal = 16.dp, vertical = 8.dp),
+ backgroundColor = ColorDefaults.backgroundSurfaceColor(),
+ shape = RoundedCornerShape(12.dp),
+ elevation = 0.dp
+ ) {
+ content()
+ }
+}
\ No newline at end of file
diff --git a/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/widget/Dialog.kt b/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/widget/Dialog.kt
index 803219148..e3a599df8 100644
--- a/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/widget/Dialog.kt
+++ b/android/modules/module_common/src/main/java/github/tornaco/android/thanos/module/compose/common/widget/Dialog.kt
@@ -17,15 +17,19 @@
package github.tornaco.android.thanos.module.compose.common.widget
-import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.*
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
+import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@OptIn(ExperimentalComposeUiApi::class)
@@ -66,4 +70,43 @@ fun ThanoxAlertDialog(
usePlatformDefaultWidth = false
)
)
+}
+
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun ThanoxDialog(
+ onDismissRequest: () -> Unit,
+ properties: DialogProperties = DialogProperties(),
+ title: @Composable () -> Unit = {},
+ buttons: @Composable RowScope.() -> Unit = {},
+ content: @Composable ColumnScope.() -> Unit,
+) {
+ Dialog(onDismissRequest = onDismissRequest,
+ properties = DialogProperties(
+ dismissOnBackPress = properties.dismissOnBackPress,
+ dismissOnClickOutside = properties.dismissOnClickOutside,
+ securePolicy = properties.securePolicy,
+ usePlatformDefaultWidth = false
+ )) {
+ Surface(modifier = Modifier.fillMaxWidth(fraction = 0.82f),
+ shape = AlertDialogDefaults.shape,
+ color = AlertDialogDefaults.containerColor,
+ tonalElevation = AlertDialogDefaults.TonalElevation) {
+
+ Column(modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)) {
+ title()
+ Spacer(modifier = Modifier.size(16.dp))
+ content()
+ Row(modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically) {
+ buttons()
+ }
+ }
+
+ }
+ }
}
\ No newline at end of file
diff --git a/android/modules/module_profile/src/main/AndroidManifest.xml b/android/modules/module_profile/src/main/AndroidManifest.xml
index b51b95f61..6e489041c 100644
--- a/android/modules/module_profile/src/main/AndroidManifest.xml
+++ b/android/modules/module_profile/src/main/AndroidManifest.xml
@@ -37,9 +37,6 @@
-
diff --git a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/AlarmSelectDialog.kt b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/AlarmSelectDialog.kt
new file mode 100644
index 000000000..d6ca58ef1
--- /dev/null
+++ b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/AlarmSelectDialog.kt
@@ -0,0 +1,208 @@
+package github.tornaco.thanos.android.module.profile.engine
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Tag
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight.Companion.W600
+import androidx.compose.ui.unit.dp
+import github.tornaco.android.thanos.core.alarm.Alarm
+import github.tornaco.android.thanos.core.alarm.Repeat
+import github.tornaco.android.thanos.core.alarm.TimeOfADay
+import github.tornaco.android.thanos.core.alarm.WeekDay
+import github.tornaco.android.thanos.module.compose.common.theme.ColorDefaults
+import github.tornaco.android.thanos.module.compose.common.widget.StandardSpacer
+import github.tornaco.android.thanos.module.compose.common.widget.ThanoxDialog
+import github.tornaco.thanos.android.module.profile.R
+import github.tornaco.thanos.android.module.profile.engine.timepicker.BottomTimePicker
+import java.time.LocalTime
+
+class AlarmSelectorState(
+ initialTime: LocalTime,
+ initialRepeat: Array,
+ val selected: (Alarm) -> Unit
+) {
+ private var _isShow by mutableStateOf(false)
+ val isShow get() = _isShow
+
+ var time: LocalTime = initialTime
+
+ var repeat = mutableStateOf(initialRepeat.toList())
+
+ var tag by mutableStateOf("")
+
+ fun addRepeat(weekDay: WeekDay) {
+ repeat.value = repeat.value.toMutableList().apply {
+ add(weekDay)
+ }
+ }
+
+ fun removeRepeat(weekDay: WeekDay) {
+ repeat.value = repeat.value.toMutableList().apply {
+ remove(weekDay)
+ }
+ }
+
+ fun show() {
+ _isShow = true
+ }
+
+ fun dismiss() {
+ _isShow = false
+ }
+}
+
+@Composable
+fun rememberAlarmSelectorState(
+ initialTime: LocalTime = LocalTime.now(),
+ initialRepeat: Array = emptyArray(),
+ selected: (Alarm) -> Unit
+): AlarmSelectorState {
+ return remember {
+ AlarmSelectorState(initialTime, initialRepeat, selected)
+ }
+}
+
+@Composable
+fun AlarmSelector(state: AlarmSelectorState) {
+ if (state.isShow) {
+ ThanoxDialog(onDismissRequest = { state.dismiss() }, title = {
+ DialogTitle(text = stringResource(id = R.string.module_profile_date_time_alarm))
+ }, buttons = {
+ TextButton(onClick = {
+ state.selected(
+ Alarm(
+ label = state.tag,
+ triggerAt = TimeOfADay(
+ hour = state.time.hour,
+ minutes = state.time.minute,
+ seconds = state.time.second
+ ),
+ repeat = Repeat(state.repeat.value)
+ )
+ )
+ state.dismiss()
+ }) {
+ Text(text = stringResource(id = android.R.string.ok))
+ }
+ }, content = {
+ AlarmContent(state)
+ })
+ }
+}
+
+@Composable
+private fun AlarmContent(state: AlarmSelectorState) {
+ Column(
+ verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Tag(state = state)
+
+ BottomTimePicker(is24TimeFormat = true, currentTime = state.time) {
+ state.time = it
+ }
+ RepeatSelector(state)
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun Tag(state: AlarmSelectorState) {
+ Column(verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start) {
+ TextField(
+ label = {
+ Text(text = "Tag")
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Filled.Tag,
+ contentDescription = "tag"
+ )
+ },
+ value = state.tag, onValueChange = {
+ state.tag = it
+ })
+ Text(
+ text = stringResource(
+ id = R.string.module_profile_date_time_tag,
+ "\ncondition: \"timeTick && tag == \"${state.tag}\"\""
+ ), style = MaterialTheme.typography.labelSmall
+ )
+ }
+ StandardSpacer()
+}
+
+@Composable
+private fun RepeatSelector(state: AlarmSelectorState) {
+ Column(
+ modifier = Modifier.padding(top = 24.dp, bottom = 32.dp),
+ horizontalAlignment = Alignment.Start
+ ) {
+ Text(
+ text = "Repeat",
+ style = MaterialTheme.typography.bodySmall
+ )
+ StandardSpacer()
+ Row(
+ modifier = Modifier
+ .horizontalScroll(rememberScrollState()),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ arrayOf(
+ WeekDay.SUNDAY,
+ WeekDay.MONDAY,
+ WeekDay.TUESDAY,
+ WeekDay.WEDNESDAY,
+ WeekDay.THURSDAY,
+ WeekDay.FRIDAY,
+ WeekDay.SATURDAY
+ ).forEach { weekDay ->
+ val isSelected = state.repeat.value.contains(weekDay)
+ Column(
+ modifier = Modifier
+ .width(36.dp)
+ .height(36.dp)
+ .padding(2.dp)
+ .clip(CircleShape)
+ .background(color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else ColorDefaults.backgroundSurfaceColor())
+ .clickable {
+ if (isSelected) {
+ state.removeRepeat(weekDay)
+ } else {
+ state.addRepeat(weekDay)
+ }
+ }
+ .padding(4.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = weekDay.name.first().toString().uppercase(),
+ style = MaterialTheme.typography.bodySmall.copy(fontWeight = W600)
+ )
+ }
+ }
+ }
+ }
+}
+
+
+@Composable
+fun DialogTitle(text: String) {
+ Text(
+ modifier = Modifier.padding(8.dp),
+ text = text,
+ style = MaterialTheme.typography.titleLarge
+ )
+}
\ No newline at end of file
diff --git a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/DateTimeEngineScreen.kt b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/DateTimeEngineScreen.kt
index 1477eca6a..abb4469db 100644
--- a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/DateTimeEngineScreen.kt
+++ b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/DateTimeEngineScreen.kt
@@ -23,7 +23,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material.icons.filled.Tag
import androidx.compose.material.icons.filled.Timer
import androidx.compose.material3.*
@@ -45,6 +44,9 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import dev.enro.core.compose.registerForNavigationResult
import github.tornaco.android.thanos.core.alarm.AlarmRecord
+import github.tornaco.android.thanos.core.alarm.TimeOfADay
+import github.tornaco.android.thanos.core.alarm.WeekDay
+import github.tornaco.android.thanos.core.util.DateUtils
import github.tornaco.android.thanos.module.compose.common.theme.TypographyDefaults
import github.tornaco.android.thanos.module.compose.common.widget.*
import github.tornaco.android.thanos.module.compose.common.widget.Switch
@@ -59,10 +61,10 @@ private const val ID_REGULAR_INTERVAL = "ri"
sealed class BottomNavItem(var title: String, var icon: Int, var screenRoute: String) {
object TimeOfADay :
- BottomNavItem("TimeOfADay", R.drawable.ic_account_circle_fill, "TimeOfADay")
+ BottomNavItem("TimeOfADay", R.drawable.ic_remix_time_fill, "TimeOfADay")
object RegularInterval :
- BottomNavItem("RegularInterval", R.drawable.ic_alipay_fill, "RegularInterval")
+ BottomNavItem("RegularInterval", R.drawable.ic_remix_24_hours_fill, "RegularInterval")
}
@Composable
@@ -83,34 +85,9 @@ fun Activity.DateTimeEngineScreen() {
)
}
- val newAlarmHandle = registerForNavigationResult { alarm ->
- viewModel.addAlarm(alarm.alarm)
- }
-
- val typeSelectDialogState =
- rememberSingleChoiceDialogState(title = stringResource(id = R.string.module_profile_rule_new),
- items = listOf(
- SingleChoiceItem(
- id = ID_TIME_OF_A_DAY,
- icon = Icons.Filled.Schedule,
- label = stringResource(id = R.string.module_profile_date_time_alarm)
- ),
- SingleChoiceItem(
- id = ID_REGULAR_INTERVAL,
- icon = Icons.Filled.Timer,
- label = stringResource(id = R.string.module_profile_date_time_regular_interval)
- ),
- ),
- onItemClick = {
- when (it) {
- ID_REGULAR_INTERVAL -> {
- newRegularIntervalHandle.open(NewRegularInterval)
- }
- ID_TIME_OF_A_DAY -> {
- newAlarmHandle.open(NewAlarm)
- }
- }
- })
+ val alarmDialogState = rememberAlarmSelectorState(selected = {
+ viewModel.addAlarm(it)
+ })
val navController = rememberNavController()
@@ -137,16 +114,28 @@ fun Activity.DateTimeEngineScreen() {
contentDescription = stringResource(id = R.string.module_profile_rule_new)
)
}) {
- typeSelectDialogState.show()
+
+ when (navController.currentBackStackEntry?.destination?.route) {
+ BottomNavItem.TimeOfADay.screenRoute -> {
+ alarmDialogState.show()
+ }
+ BottomNavItem.RegularInterval.screenRoute -> {
+ newRegularIntervalHandle.open(NewRegularInterval)
+ }
+ else -> {}
+ }
}
}) { contentPadding ->
- Column(modifier = Modifier
- .fillMaxSize()
- .padding(contentPadding)) {
- SingleChoiceDialog(state = typeSelectDialogState)
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(contentPadding)
+ ) {
NavigationGraph(navController = navController, viewModel = viewModel, state = state)
}
+
+ AlarmSelector(state = alarmDialogState)
}
}
@@ -164,8 +153,10 @@ private fun NavigationContent(
items.forEach { item ->
NavigationBarItem(
icon = {
- Icon(painterResource(id = item.icon),
- contentDescription = item.title)
+ Icon(
+ painterResource(id = item.icon),
+ contentDescription = item.title
+ )
},
label = {
Text(text = item.title, fontSize = 9.sp)
@@ -196,18 +187,18 @@ fun NavigationGraph(
) {
NavHost(navController, startDestination = BottomNavItem.TimeOfADay.screenRoute) {
composable(BottomNavItem.TimeOfADay.screenRoute) {
- WorkList(state = state.workStates) {
- viewModel.deleteWorkById(it)
- }
- }
-
- composable(BottomNavItem.RegularInterval.screenRoute) {
AlarmList(state = state.alarms,
delete = { viewModel.deleteAlarm(it) },
onCheckChange = { record, checked ->
viewModel.setAlarmEnabled(record.alarm, checked)
})
}
+
+ composable(BottomNavItem.RegularInterval.screenRoute) {
+ WorkList(state = state.workStates) {
+ viewModel.deleteWorkById(it)
+ }
+ }
}
}
@@ -219,84 +210,7 @@ private fun WorkList(
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(state) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp)
- .heightIn(min = 64.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
- ) {
- Column(
- modifier = Modifier
- ) {
- val typeText = if (it.type == Type.Periodic) {
- stringResource(id = R.string.module_profile_date_time_regular_interval)
- } else {
- "Unknown"
- }
- val valueText = if (it.type == Type.Periodic) {
- it.value.toDuration(DurationUnit.MILLISECONDS).toString()
- } else {
- ""
- }
-
- Text(
- text = typeText, style = MaterialTheme.typography.titleMedium,
- maxLines = 1
- )
- TinySpacer()
- Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(
- imageVector = Icons.Filled.Tag,
- contentDescription = "tag"
- )
- TinySpacer()
- Text(
- text = it.tag.substring(0, min(18, it.tag.length)),
- style = MaterialTheme.typography.labelMedium,
- maxLines = 1
- )
- }
-
- Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(
- imageVector = Icons.Filled.Timer,
- contentDescription = "interval"
- )
- TinySpacer()
- Text(
- text = valueText, style = MaterialTheme.typography.labelMedium,
- maxLines = 1
- )
- }
- }
-
- IconButton(onClick = {
- delete(it.id)
- }) {
- Icon(
- painter = painterResource(id = R.drawable.module_profile_ic_delete_bin_fill),
- contentDescription = "Remove"
- )
- }
- }
-
- }
- }
-}
-
-
-@Composable
-private fun AlarmList(
- state: List,
- delete: (alarm: AlarmRecord) -> Unit,
- onCheckChange: (alarm: AlarmRecord, checked: Boolean) -> Unit,
-) {
- LazyColumn(modifier = Modifier.fillMaxSize()) {
- items(state) { record ->
- Column(modifier = Modifier
- .fillMaxWidth()) {
+ CardContainer {
Row(
modifier = Modifier
.fillMaxWidth()
@@ -306,10 +220,18 @@ private fun AlarmList(
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(
- modifier = Modifier.weight(1f, fill = false)
+ modifier = Modifier
) {
- val typeText = stringResource(id = R.string.module_profile_date_time_alarm)
- val valueText = record.alarm.triggerAt.toString()
+ val typeText = if (it.type == Type.Periodic) {
+ stringResource(id = R.string.module_profile_date_time_regular_interval)
+ } else {
+ "Unknown"
+ }
+ val valueText = if (it.type == Type.Periodic) {
+ it.value.toDuration(DurationUnit.MILLISECONDS).toString()
+ } else {
+ ""
+ }
Text(
text = typeText, style = MaterialTheme.typography.titleMedium,
@@ -323,13 +245,14 @@ private fun AlarmList(
)
TinySpacer()
Text(
- text = record.alarm.label.substring(0,
- min(18, record.alarm.label.length)),
+ text = it.tag.substring(0, min(18, it.tag.length)),
style = MaterialTheme.typography.labelMedium,
maxLines = 1
)
}
+ StandardSpacer()
+
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Filled.Timer,
@@ -343,24 +266,9 @@ private fun AlarmList(
}
}
- Switch(
- modifier = Modifier,
- checked = record.isEnabled,
- onCheckedChange = { checked ->
- onCheckChange(record, checked)
- })
- }
-
- Box(modifier = Modifier
- .fillMaxWidth()
- .padding(top = 6.dp)) {
- IconButton(
- modifier = Modifier
- .align(Alignment.CenterEnd)
- .padding(end = 16.dp),
- onClick = {
- delete(record)
- }) {
+ IconButton(onClick = {
+ delete(it.id)
+ }) {
Icon(
painter = painterResource(id = R.drawable.module_profile_ic_delete_bin_fill),
contentDescription = "Remove"
@@ -368,6 +276,143 @@ private fun AlarmList(
}
}
}
+
+ }
+ }
+}
+
+
+@Composable
+private fun AlarmList(
+ state: List,
+ delete: (alarm: AlarmRecord) -> Unit,
+ onCheckChange: (alarm: AlarmRecord, checked: Boolean) -> Unit,
+) {
+ LazyColumn(modifier = Modifier.fillMaxSize()) {
+ items(state) { record ->
+ CardContainer {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ .heightIn(min = 64.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Column(
+ modifier = Modifier.weight(1f, fill = false)
+ ) {
+ val typeText =
+ stringResource(id = R.string.module_profile_date_time_alarm)
+
+ val repeatText = if (record.alarm.repeat.isNo) {
+ "No repeat"
+ } else if (record.alarm.repeat.isEveryDay) {
+ "Repeat every day"
+ } else {
+ "Repeat at ${
+ record.alarm.repeat.days.joinToString(
+ " "
+ ) {
+ getLongLabelForWeekDay(weekDay = it)
+ }
+ }"
+ }
+
+ val valueText =
+ "${record.alarm.triggerAt.toDisplayTime()}\n${repeatText}"
+
+ Text(
+ text = typeText, style = MaterialTheme.typography.titleMedium,
+ maxLines = 1
+ )
+ TinySpacer()
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ imageVector = Icons.Filled.Tag,
+ contentDescription = "tag"
+ )
+ TinySpacer()
+ Text(
+ text = record.alarm.label.substring(
+ 0,
+ min(18, record.alarm.label.length)
+ ),
+ style = MaterialTheme.typography.labelMedium,
+ maxLines = 1
+ )
+ }
+ StandardSpacer()
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ imageVector = Icons.Filled.Timer,
+ contentDescription = "Time"
+ )
+ TinySpacer()
+ Text(
+ text = valueText, style = MaterialTheme.typography.labelMedium,
+ )
+ }
+ }
+
+ Switch(
+ modifier = Modifier,
+ checked = record.isEnabled,
+ onCheckedChange = { checked ->
+ onCheckChange(record, checked)
+ })
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 6.dp)
+ ) {
+ IconButton(
+ modifier = Modifier
+ .align(Alignment.CenterEnd)
+ .padding(end = 16.dp),
+ onClick = {
+ delete(record)
+ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.module_profile_ic_delete_bin_fill),
+ contentDescription = "Remove"
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private fun TimeOfADay.toDisplayTime(): String {
+ val date = Calendar.getInstance().apply {
+ set(Calendar.HOUR_OF_DAY, hour)
+ set(Calendar.MINUTE, minutes)
+ set(Calendar.SECOND, 0)
+ }.time
+ return DateUtils.formatShortForMessageTime(date.time)
+}
+
+
+private fun getLongLabelForWeekDay(weekDay: WeekDay): String {
+ return when (weekDay) {
+ WeekDay.MONDAY -> "Mon"
+ WeekDay.TUESDAY -> "Tue"
+ WeekDay.WEDNESDAY -> "Wed"
+ WeekDay.THURSDAY -> "Thu"
+ WeekDay.FRIDAY -> "Fri"
+ WeekDay.SATURDAY -> "Sat"
+ WeekDay.SUNDAY -> "Sun"
+ else -> {
+ "N/A"
}
}
}
\ No newline at end of file
diff --git a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/NewAlarmActivity.kt b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/NewAlarmActivity.kt
deleted file mode 100644
index 624f060ec..000000000
--- a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/NewAlarmActivity.kt
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * (C) Copyright 2022 Thanox
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-@file:OptIn(ExperimentalMaterial3Api::class)
-
-package github.tornaco.thanos.android.module.profile.engine
-
-import android.os.Parcelable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material.icons.filled.Remove
-import androidx.compose.material.icons.filled.Tag
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import dagger.hilt.android.AndroidEntryPoint
-import dev.enro.annotations.NavigationDestination
-import dev.enro.core.NavigationKey
-import dev.enro.core.close
-import dev.enro.core.compose.navigationHandle
-import dev.enro.core.result.closeWithResult
-import github.tornaco.android.thanos.core.alarm.Alarm
-import github.tornaco.android.thanos.core.alarm.Repeat
-import github.tornaco.android.thanos.core.alarm.TimeOfADay
-import github.tornaco.android.thanos.module.compose.common.ComposeThemeActivity
-import github.tornaco.android.thanos.module.compose.common.theme.TypographyDefaults
-import github.tornaco.android.thanos.module.compose.common.widget.*
-import github.tornaco.thanos.android.module.profile.R
-import kotlinx.parcelize.Parcelize
-import kotlin.math.min
-
-@Parcelize
-object NewAlarm : NavigationKey.WithResult
-
-@Parcelize
-data class NewAlarmResult(
- val alarm: Alarm,
-) : Parcelable
-
-@AndroidEntryPoint
-@NavigationDestination(NewAlarm::class)
-class NewAlarmActivity : ComposeThemeActivity() {
- override fun isF(): Boolean {
- return true
- }
-
- override fun isADVF(): Boolean {
- return true
- }
-
- @Composable
- override fun Content() {
- NewAlarmContent()
- }
-
-
- @Composable
- private fun NewAlarmContent() {
- val state = AlarmState()
- val navHandle = navigationHandle()
-
- ThanoxSmallAppBarScaffold(title = {
- Text(
- text = stringResource(id = R.string.module_profile_date_time_alarm),
- style = TypographyDefaults.appBarTitleTextStyle()
- )
- },
- onBackPressed = { navHandle.close() },
- actions = {
- }, floatingActionButton = {
- ExtendableFloatingActionButton(
- extended = true,
- text = { Text(text = stringResource(id = R.string.module_profile_rule_edit_action_save)) },
- icon = {
- Icon(
- imageVector = Icons.Filled.Check,
- contentDescription = stringResource(id = R.string.module_profile_rule_edit_action_save)
- )
- }) {
- val alarm = Alarm(
- label = state.tag,
- triggerAt = TimeOfADay(
- state.h.value,
- state.m.value,
- state.s.value
- ),
- repeat = Repeat()
- )
- navHandle.closeWithResult(
- NewAlarmResult(
- alarm = alarm
- )
- )
- }
- }) { contentPadding ->
- Column(
- modifier = Modifier
- .padding(contentPadding)
- .padding(16.dp),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.Start
- ) {
- Hour(state)
- Minute(state)
- Second(state)
- Text(
- text = stringResource(
- id = R.string.module_profile_date_time_min_interval
- ), style = MaterialTheme.typography.labelSmall
- )
-
- LargeSpacer()
- OutlinedTextField(
- label = {
- Text(text = "Tag")
- },
- leadingIcon = {
- Icon(
- imageVector = Icons.Filled.Tag,
- contentDescription = "tag"
- )
- },
- value = state.tag,
- maxLines = 1,
- onValueChange = {
- state.tag = it.substring(0, min(16, it.length)).replace("-", "_")
- .trim()
- })
-
- TinySpacer()
- Text(
- text = stringResource(
- id = R.string.module_profile_date_time_tag,
- "\ncondition: \"timeTick && tag == \"${state.tag}\"\""
- ), style = MaterialTheme.typography.labelSmall
- )
- }
- }
- }
-
-
- @Composable
- private fun Hour(durationState: AlarmState) {
- Field(
- durationState.h,
- "Hour",
- 0,
- 24
- )
- }
-
- @Composable
- private fun Minute(durationState: AlarmState) {
- Field(
- durationState.m,
- "Minute",
- 15,
- 59
- )
- }
-
- @Composable
- private fun Second(durationState: AlarmState) {
- Field(
- durationState.s,
- "Second",
- 0,
- 59
- )
- }
-
- @Composable
- private fun Field(fieldValue: MutableState, label: String, min: Int = 0, max: Int) {
- var value by fieldValue
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
- ) {
- Text(text = label)
- LargeSpacer()
-
- // -
- IconButton(onClick = {
- if (value > min) {
- value -= 1
- } else {
- value = max
- }
- }) {
- Icon(
- imageVector = Icons.Filled.Remove,
- contentDescription = "-"
- )
- }
-
- StandardSpacer()
- Text(text = "$value")
- StandardSpacer()
-
- // +
- IconButton(onClick = {
- if (value < max) {
- value += 1
- } else {
- value = min
- }
- }) {
- Icon(
- imageVector = Icons.Filled.Add,
- contentDescription = "+"
- )
- }
- }
- }
-
- private class AlarmState {
- var h = mutableStateOf(0)
- var m = mutableStateOf(0)
- var s = mutableStateOf(0)
-
- var tag by mutableStateOf("")
- }
-
-}
diff --git a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/BottomTimePicker.kt b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/BottomTimePicker.kt
new file mode 100644
index 000000000..62d4c87de
--- /dev/null
+++ b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/BottomTimePicker.kt
@@ -0,0 +1,55 @@
+package github.tornaco.thanos.android.module.profile.engine.timepicker
+
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import github.tornaco.android.thanos.module.compose.common.theme.ColorDefaults
+import java.time.LocalTime
+
+
+@Composable
+fun BottomTimePicker(
+ currentTime: LocalTime? = null,
+ is24TimeFormat: Boolean,
+ onTimeChanged: (LocalTime) -> Unit
+) {
+
+ var time by remember { mutableStateOf(currentTime ?: LocalTime.now()) }
+
+ PickerContainer(
+ modifier = Modifier.padding(18.dp),
+ backgroundColor = ColorDefaults.backgroundSurfaceColor(),
+ cornerRadius = 16.dp,
+ fadingEdgeLength = 60.dp
+ ) {
+ TimePicker(
+ modifier = Modifier.height(130.dp),
+ itemHeight = 40.dp,
+ is24TimeFormat = is24TimeFormat,
+ itemStyles = ItemStyles(
+ defaultTextStyle = TextStyle(
+ MaterialTheme.colorScheme.onSurface,
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Medium
+ ),
+ selectedTextStyle = TextStyle(
+ MaterialTheme.colorScheme.primary,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Black
+ )
+ ),
+ onTimeChanged = {
+ time = it
+ onTimeChanged(it)
+ },
+ currentTime = time
+ )
+ }
+}
\ No newline at end of file
diff --git a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/PickerContainer.kt b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/PickerContainer.kt
new file mode 100644
index 000000000..831b8a4dd
--- /dev/null
+++ b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/PickerContainer.kt
@@ -0,0 +1,65 @@
+package github.tornaco.thanos.android.module.profile.engine.timepicker
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import github.tornaco.android.thanos.module.compose.common.toPx
+
+
+// https://github.com/KirillVolkov/Compose-TimePicker
+@Composable
+fun PickerContainer(
+ modifier: Modifier = Modifier,
+ backgroundColor: Color = Color.Transparent,
+ cornerRadius: Dp = 0.dp,
+ fadingEdgeLength: Dp = 0.dp,
+ content: @Composable () -> Unit
+) {
+
+ val corners = cornerRadius.toPx()
+ var height by remember { mutableStateOf(0) }
+
+ Box(
+ modifier = modifier
+ .background(color = backgroundColor, shape = RoundedCornerShape(corners))
+ .onGloballyPositioned {
+ if (height == 0) {
+ height = it.size.height
+ }
+ }
+ .drawWithContent {
+ drawContent()
+ if (fadingEdgeLength > 0.dp) {
+ drawRoundRect(
+ cornerRadius = CornerRadius(corners, corners),
+ brush = Brush.verticalGradient(
+ listOf(backgroundColor, Color.Transparent),
+ startY = 0f,
+ endY = fadingEdgeLength.toPx()
+ )
+ )
+ drawRoundRect(
+ cornerRadius = CornerRadius(corners, corners),
+ brush = Brush.verticalGradient(
+ listOf(Color.Transparent, backgroundColor),
+ startY = height - fadingEdgeLength.toPx(),
+ endY = height.toFloat()
+ )
+ )
+ }
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ content()
+ }
+}
diff --git a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/TimePicker.kt b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/TimePicker.kt
new file mode 100644
index 000000000..bbbd9e02b
--- /dev/null
+++ b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/TimePicker.kt
@@ -0,0 +1,227 @@
+package github.tornaco.thanos.android.module.profile.engine.timepicker
+
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.Divider
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+import java.time.LocalTime
+
+@Composable
+fun TimePicker(
+ modifier: Modifier = Modifier,
+ is24TimeFormat: Boolean,
+ itemHeight: Dp = 32.dp,
+ divider: NumberPickerDivider = NumberPickerDivider(),
+ itemStyles: ItemStyles = ItemStyles(),
+ currentTime: LocalTime,
+ onTimeChanged: (LocalTime) -> Unit
+) {
+
+ val pickerTime by remember {
+ mutableStateOf(
+ parseTime(
+ currentTime,
+ is24TimeFormat = is24TimeFormat
+ )
+ )
+ }
+
+ val hours = (if (is24TimeFormat) (0..23) else (1..12)).toList()
+ val minutes = (0..59).toList()
+
+ Row(horizontalArrangement = Arrangement.Center, modifier = modifier.fillMaxWidth(1f)) {
+ WheelPicker(
+ itemHeight = itemHeight,
+ divider = divider,
+ itemStyles = itemStyles,
+ items = hours,
+ selectedItem = pickerTime.hours,
+ itemToString = { if (is24TimeFormat) String.format("%02d", it) else it.toString() },
+ modifier = Modifier
+ .fillMaxHeight(1f)
+ .fillMaxWidth(.3f),
+ onItemChanged = {
+ pickerTime.hours = it
+ onTimeChanged(pickerTime.toLocalTime())
+ }
+ )
+ WheelPicker(
+ items = minutes,
+ selectedItem = pickerTime.minutes,
+ itemHeight = itemHeight,
+ divider = divider,
+ itemStyles = itemStyles,
+ itemToString = { String.format("%02d", it) },
+ modifier = Modifier
+ .fillMaxHeight(1f)
+ .fillMaxWidth(.3f),
+ onItemChanged = {
+ pickerTime.minutes = it
+ onTimeChanged(pickerTime.toLocalTime())
+ }
+
+ )
+ if (is24TimeFormat.not()) {
+ AmPmPicker(
+ itemHeight = itemHeight,
+ divider = divider,
+ itemStyles = itemStyles,
+ modifier = Modifier
+ .fillMaxHeight(1f)
+ .fillMaxWidth(.3f),
+ selectedItem = pickerTime.timesOfDay!!,
+ onItemChanged = {
+ pickerTime.timesOfDay = it
+ onTimeChanged(pickerTime.toLocalTime())
+ }
+ )
+ }
+ }
+}
+
+@Composable
+fun AmPmPicker(
+ modifier: Modifier = Modifier,
+ selectedItem: TimesOfDay,
+ itemHeight: Dp = 32.dp,
+ divider: NumberPickerDivider = NumberPickerDivider(showed = true, Color.Black),
+ itemStyles: ItemStyles = ItemStyles(),
+ onItemChanged: (TimesOfDay) -> Unit = {}
+) {
+ val scope = rememberCoroutineScope()
+ var currItem by remember { mutableStateOf(selectedItem) }
+ var listHeightInPixels by remember { mutableStateOf(0) }
+ var itemHeightInPixels by remember { mutableStateOf(0) }
+
+ val items = TimesOfDay.values()
+
+ val listState =
+ rememberLazyListState(initialFirstVisibleItemIndex = items.indexOf(selectedItem))
+
+ if (listState.isScrollInProgress.not() && itemHeightInPixels > 0 && listHeightInPixels > itemHeightInPixels) {
+ if (listState.firstVisibleItemScrollOffset != 0) {
+ LaunchedEffect(key1 = listState) {
+ scope.launch {
+ listState.animateScrollBy(
+ (if (listState.firstVisibleItemScrollOffset <= itemHeightInPixels / 2)
+ -listState.firstVisibleItemScrollOffset - itemHeightInPixels
+ else listState.firstVisibleItemScrollOffset + itemHeightInPixels).toFloat()
+ )
+ }
+ }
+ } else {
+ if (items[listState.firstVisibleItemIndex] != currItem) {
+ currItem = items[listState.firstVisibleItemIndex]
+ onItemChanged(currItem)
+ }
+ }
+ }
+
+ Box(modifier = modifier, contentAlignment = Alignment.Center) {
+ if (items.isNotEmpty()) {
+ LazyColumn(
+ contentPadding = PaddingValues(top = itemHeight, bottom = itemHeight),
+ state = listState,
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .height(itemHeight * (items.size + 1))
+ .onGloballyPositioned {
+ if (listHeightInPixels < itemHeightInPixels) {
+ listHeightInPixels = it.size.height
+
+ scope.launch {
+ listState.scrollToItem(items.indexOf(selectedItem))
+ }
+ }
+ }
+ ) {
+ items(items.size) { i ->
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .height(itemHeight)
+ .onGloballyPositioned {
+ if (itemHeightInPixels == 0) {
+ itemHeightInPixels = it.size.height
+ }
+ }
+ ) {
+ Text(
+ text = items[i].string,
+ style = if (i == listState.firstVisibleItemIndex) itemStyles.selectedTextStyle else itemStyles.defaultTextStyle
+ )
+ }
+ }
+ }
+ }
+ if (divider.showed) {
+ Divider(
+ color = divider.color,
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .offset(y = -itemHeight / 2)
+ .offset(x = -divider.indent),
+ thickness = divider.thickness,
+ startIndent = divider.indent * 2
+ )
+ Divider(
+ color = divider.color, modifier = Modifier
+ .fillMaxWidth(1f)
+ .offset(y = itemHeight / 2)
+ .offset(x = -divider.indent),
+ thickness = divider.thickness,
+ startIndent = divider.indent * 2
+ )
+ }
+ }
+}
+
+enum class TimesOfDay(val string: String) {
+ AM("AM"), PM("PM")
+}
+
+
+fun parseTime(time: LocalTime, is24TimeFormat: Boolean): PickerTime {
+ return PickerTime(
+ hours = if (is24TimeFormat.not() && time.hour > 12) time.hour - 12 else time.hour,
+ minutes = time.minute,
+ if (is24TimeFormat) null else if (time.hour > 12) TimesOfDay.PM else TimesOfDay.AM
+ )
+}
+
+fun PickerTime.toLocalTime(): LocalTime {
+ return LocalTime.of(
+ when (timesOfDay) {
+ TimesOfDay.AM -> hours % 12
+ TimesOfDay.PM -> hours % 12 + 12
+ else -> hours
+ },
+ minutes
+ )
+}
+
+class PickerTime(
+ var hours: Int,
+ var minutes: Int,
+ var timesOfDay: TimesOfDay? = null
+) {
+ override fun toString(): String {
+ return "${String.format("%02d", hours)}:${
+ String.format(
+ "%02d",
+ minutes
+ )
+ } ${timesOfDay?.string ?: ""}"
+ }
+}
diff --git a/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/WheelPicker.kt b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/WheelPicker.kt
new file mode 100644
index 000000000..c35f2e760
--- /dev/null
+++ b/android/modules/module_profile/src/main/java/github/tornaco/thanos/android/module/profile/engine/timepicker/WheelPicker.kt
@@ -0,0 +1,133 @@
+package github.tornaco.thanos.android.module.profile.engine.timepicker
+
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.material.Divider
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import github.tornaco.android.thanos.module.compose.common.toDp
+import kotlinx.coroutines.launch
+import kotlin.math.abs
+
+@Composable
+internal fun WheelPicker(
+ modifier: Modifier = Modifier,
+ items: List = emptyList(),
+ selectedItem: T? = null,
+ itemHeight: Dp = 32.dp,
+ divider: NumberPickerDivider = NumberPickerDivider(),
+ itemStyles: ItemStyles = ItemStyles(),
+ onItemChanged: (T) -> Unit = {},
+ itemToString: (T) -> String = { it.toString() }
+) {
+ val scope = rememberCoroutineScope()
+ var currItem by remember { mutableStateOf(selectedItem) }
+ var listHeightInPixels by remember { mutableStateOf(0) }
+ var itemHeightInPixels by remember { mutableStateOf(0) }
+
+ val listState = rememberSaveable(saver = LazyListState.Saver) {
+ if (items.isNotEmpty()) {
+ val centerList = Int.MAX_VALUE / 2 - (Int.MAX_VALUE / 2) % items.size
+ val selectedIndex = selectedItem?.let { items.indexOf(selectedItem) } ?: 0
+ LazyListState(
+ firstVisibleItemIndex = centerList + selectedIndex
+ )
+ } else {
+ LazyListState()
+ }
+ }
+
+ if (listState.isScrollInProgress.not() && itemHeightInPixels > 0) {
+ val needScrollTop = -listState.firstVisibleItemScrollOffset + itemHeightInPixels / 2
+
+ if (abs(listState.firstVisibleItemScrollOffset - itemHeightInPixels / 2) > 1) {
+
+ LaunchedEffect(key1 = listState) {
+ scope.launch {
+ listState.animateScrollBy(needScrollTop.toFloat())
+ if (items[listState.firstVisibleItemIndex % items.size] != currItem) {
+ currItem = items[listState.firstVisibleItemIndex % items.size]
+ onItemChanged(currItem!!)
+ }
+ }
+ }
+ }
+ }
+
+ Box(modifier = modifier, contentAlignment = Alignment.Center) {
+ if (items.isNotEmpty()) {
+ LazyColumn(
+ contentPadding = PaddingValues(top = (listHeightInPixels / 2).toDp()),
+ state = listState,
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .onGloballyPositioned {
+ if (listHeightInPixels < itemHeightInPixels) {
+ listHeightInPixels = it.size.height
+ }
+ }
+ ) {
+ items(Int.MAX_VALUE) { i ->
+ val index = i % items.size
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .height(itemHeight)
+ .onGloballyPositioned {
+ if (itemHeightInPixels == 0) {
+ itemHeightInPixels = it.size.height
+ }
+ }
+ ) {
+ Text(
+ text = itemToString(items[index]),
+ style = if (i == listState.firstVisibleItemIndex) itemStyles.selectedTextStyle else itemStyles.defaultTextStyle
+ )
+ }
+ }
+ }
+ }
+ if (divider.showed) {
+ Divider(
+ color = divider.color,
+ modifier = Modifier
+ .fillMaxWidth(1f)
+ .offset(y = -itemHeight / 2)
+ .offset(x = -divider.indent),
+ thickness = divider.thickness,
+ startIndent = divider.indent * 2
+ )
+ Divider(
+ color = divider.color, modifier = Modifier
+ .fillMaxWidth(1f)
+ .offset(y = itemHeight / 2)
+ .offset(x = -divider.indent),
+ thickness = divider.thickness,
+ startIndent = divider.indent * 2
+ )
+ }
+ }
+}
+
+class NumberPickerDivider(
+ val showed: Boolean = false,
+ val color: Color = Color.Transparent,
+ val thickness: Dp = 1.dp,
+ val indent: Dp = 0.dp
+)
+
+class ItemStyles(
+ val defaultTextStyle: TextStyle = TextStyle.Default,
+ val selectedTextStyle: TextStyle = TextStyle.Default
+)
diff --git a/android/modules/module_profile/src/main/res/values-zh-rCN/strings.xml b/android/modules/module_profile/src/main/res/values-zh-rCN/strings.xml
index 39c94713d..a748901c6 100644
--- a/android/modules/module_profile/src/main/res/values-zh-rCN/strings.xml
+++ b/android/modules/module_profile/src/main/res/values-zh-rCN/strings.xml
@@ -24,7 +24,7 @@
安排一些重复或定时任务
固定时间间隔
一天中的时间
- Tag用于情景模式的条件判断。使用举例:%s
+ Tag用于情景模式的条件判断。\n使用举例:%s
当前支持的最短时间间隔为15分钟
保存
diff --git a/android/modules/module_profile/src/main/res/values-zh-rTW/strings.xml b/android/modules/module_profile/src/main/res/values-zh-rTW/strings.xml
index 3d7231326..1471df2fe 100644
--- a/android/modules/module_profile/src/main/res/values-zh-rTW/strings.xml
+++ b/android/modules/module_profile/src/main/res/values-zh-rTW/strings.xml
@@ -24,7 +24,7 @@
安排一些重複或定時任務
固定時間間隔
一天中的時間
- Tag用於情景模式的條件判斷。使用舉例:%s
+ Tag用於情景模式的條件判斷。\n使用舉例:%s
當前支持的最短時間間隔為15分鐘
保存
diff --git a/android/modules/module_profile/src/main/res/values/strings.xml b/android/modules/module_profile/src/main/res/values/strings.xml
index bf74cb1fb..3d492419d 100644
--- a/android/modules/module_profile/src/main/res/values/strings.xml
+++ b/android/modules/module_profile/src/main/res/values/strings.xml
@@ -24,7 +24,7 @@
Schedule some repeating or timed tasks
Regular interval
Time of a day
- Tag is for condition check. Example of use: %s
+ Tag is for condition check. \nExample of use: %s
Min interval is 15m
Save