diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d7cdde9b..4e1ea457 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -79,7 +79,7 @@ dependencies { // UI implementation("androidx.activity:activity-compose:1.8.2") - implementation("androidx.compose.ui:ui:1.6.5") + implementation("androidx.compose.ui:ui:1.7.5") implementation("androidx.compose.ui:ui-tooling-preview:1.6.5") implementation("androidx.navigation:navigation-compose:2.7.7") diff --git a/app/src/main/java/com/bnyro/wallpaper/ext/Context.kt b/app/src/main/java/com/bnyro/wallpaper/ext/Context.kt index e4ceb61b..e9df85d9 100644 --- a/app/src/main/java/com/bnyro/wallpaper/ext/Context.kt +++ b/app/src/main/java/com/bnyro/wallpaper/ext/Context.kt @@ -8,4 +8,6 @@ import kotlinx.coroutines.withContext suspend fun Context.toastFromMainThread(text: String?, length: Int = Toast.LENGTH_SHORT) = withContext(Dispatchers.Main) { Toast.makeText(this@toastFromMainThread, text, length).show() -} \ No newline at end of file +} + +fun Context.toast(text: String?, length: Int = Toast.LENGTH_SHORT) = Toast.makeText(this, text, length).show() \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/wallpaper/ext/FormatTime.kt b/app/src/main/java/com/bnyro/wallpaper/ext/FormatTime.kt new file mode 100644 index 00000000..44f845f4 --- /dev/null +++ b/app/src/main/java/com/bnyro/wallpaper/ext/FormatTime.kt @@ -0,0 +1,11 @@ +package com.bnyro.wallpaper.ext + +import android.text.format.DateUtils +import androidx.compose.runtime.Composable + +@Composable +fun Long.formatTime(): String { + val formatted = DateUtils.formatElapsedTime(this / 1000) + + return formatted.substring(0, 5).trimEnd(':') +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/wallpaper/obj/WallpaperConfig.kt b/app/src/main/java/com/bnyro/wallpaper/obj/WallpaperConfig.kt index a563ac0d..d3e8fd9b 100644 --- a/app/src/main/java/com/bnyro/wallpaper/obj/WallpaperConfig.kt +++ b/app/src/main/java/com/bnyro/wallpaper/obj/WallpaperConfig.kt @@ -18,7 +18,9 @@ data class WallpaperConfig( var source: WallpaperSource = WallpaperSource.ONLINE, var applyImageFilters: Boolean = true, var selectedApiRoutes: List = listOf(DrawerScreens.apiScreens[0].route), - var localFolderUris: List = listOf() + var localFolderUris: List = listOf(), + var startTimeMillis: Long? = null, + var endTimeMillis: Long? = null, ) { fun getSummary(context: Context): String { val targetString = when (target) { diff --git a/app/src/main/java/com/bnyro/wallpaper/ui/components/WallpaperChangerPref.kt b/app/src/main/java/com/bnyro/wallpaper/ui/components/WallpaperChangerPref.kt index 5b2e6c08..c1695d30 100644 --- a/app/src/main/java/com/bnyro/wallpaper/ui/components/WallpaperChangerPref.kt +++ b/app/src/main/java/com/bnyro/wallpaper/ui/components/WallpaperChangerPref.kt @@ -1,8 +1,10 @@ package com.bnyro.wallpaper.ui.components +import android.text.format.DateUtils import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -10,9 +12,14 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowForward +import androidx.compose.material.icons.filled.AccessTimeFilled import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -35,6 +42,10 @@ import com.bnyro.wallpaper.obj.WallpaperConfig import com.bnyro.wallpaper.enums.WallpaperSource import com.bnyro.wallpaper.enums.WallpaperTarget import com.bnyro.wallpaper.ext.formatMinutes +import com.bnyro.wallpaper.ext.formatTime +import com.bnyro.wallpaper.ext.toast +import com.bnyro.wallpaper.ext.toastFromMainThread +import com.bnyro.wallpaper.ui.components.dialogs.TimePickerDialog import com.bnyro.wallpaper.ui.components.prefs.CheckboxPref import com.bnyro.wallpaper.ui.components.prefs.MultiSelectionBlockPreference import com.bnyro.wallpaper.ui.components.prefs.ListPreference @@ -85,6 +96,12 @@ fun WallpaperChangerPrefDialog( var applyImageFilters by remember { mutableStateOf(config.applyImageFilters) } + var startTimeMillis by remember { + mutableStateOf(config.startTimeMillis) + } + var endTimeMillis by remember { + mutableStateOf(config.endTimeMillis) + } AlertDialog( onDismissRequest = onDismissRequest, @@ -95,6 +112,12 @@ fun WallpaperChangerPrefDialog( }, confirmButton = { DialogButton(stringResource(android.R.string.ok)) { + val hasStartAndEndTime = startTimeMillis != null && endTimeMillis != null + if (hasStartAndEndTime && startTimeMillis!! == endTimeMillis!!) { + context.toast(context.getString(R.string.invalid_time_interval)) + return@DialogButton + } + val newConfig = WallpaperConfig( id = config.id, changeIntervalMinutes = changeInterval, @@ -103,7 +126,9 @@ fun WallpaperChangerPrefDialog( source = wallpaperSource, selectedApiRoutes = wallpaperEnginesIndices.map { DrawerScreens.apiScreens[it].route }, localFolderUris = localFolderUris, - applyImageFilters = applyImageFilters + applyImageFilters = applyImageFilters, + startTimeMillis = if (hasStartAndEndTime) startTimeMillis else null, + endTimeMillis = if (hasStartAndEndTime) endTimeMillis else null, ) onConfigChange(newConfig) onDismissRequest() @@ -233,6 +258,81 @@ fun WallpaperChangerPrefDialog( ) { newValue -> applyImageFilters = newValue } + + var customTimeInterval by remember { + mutableStateOf(endTimeMillis != null && startTimeMillis != null) + } + CheckboxPref( + prefKey = null, + title = stringResource(R.string.time_interval), + defaultValue = customTimeInterval + ) { newValue -> + customTimeInterval = newValue + + if (!customTimeInterval) { + startTimeMillis = null + endTimeMillis = null + } else { + startTimeMillis = 0 + endTimeMillis = 0 + } + } + AnimatedVisibility(visible = customTimeInterval) { + var showStartTimePicker by remember { + mutableStateOf(false) + } + + var showEndTimePicker by remember { + mutableStateOf(false) + } + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.AccessTimeFilled, + contentDescription = null + ) + + Spacer(modifier = Modifier.width(10.dp)) + + Button( + onClick = { showStartTimePicker = true } + ) { + Text((startTimeMillis ?: 0).formatTime()) + } + + Icon( + modifier = Modifier.padding(horizontal = 10.dp), + imageVector = Icons.AutoMirrored.Default.ArrowForward, + contentDescription = null + ) + + Button( + onClick = { showEndTimePicker = true } + ) { + Text((endTimeMillis ?: 0).formatTime()) + } + } + + if (showStartTimePicker) { + TimePickerDialog( + startTimeMillis ?: 0, + onTimeChange = { startTimeMillis = it } + ) { + showStartTimePicker = false + } + } + + if (showEndTimePicker) { + TimePickerDialog( + endTimeMillis ?: 0, + onTimeChange = { endTimeMillis = it } + ) { + showEndTimePicker = false + } + } + } } } ) diff --git a/app/src/main/java/com/bnyro/wallpaper/ui/components/dialogs/TimePickerDialog.kt b/app/src/main/java/com/bnyro/wallpaper/ui/components/dialogs/TimePickerDialog.kt new file mode 100644 index 00000000..48446207 --- /dev/null +++ b/app/src/main/java/com/bnyro/wallpaper/ui/components/dialogs/TimePickerDialog.kt @@ -0,0 +1,41 @@ +package com.bnyro.wallpaper.ui.components.dialogs + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TimePicker +import androidx.compose.material3.TimePickerLayoutType +import androidx.compose.material3.rememberTimePickerState +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.bnyro.wallpaper.ui.components.DialogButton + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TimePickerDialog( + initialMillis: Long, + onTimeChange: (Long) -> Unit, + onDismissRequest: () -> Unit +) { + val timePickerState = rememberTimePickerState( + initialHour = (initialMillis / 1000 / 60 / 60).toInt(), + initialMinute = (initialMillis / 1000 / 60 % 60).toInt(), + ) + + AlertDialog( + onDismissRequest = onDismissRequest, + dismissButton = { + DialogButton(stringResource(android.R.string.cancel)) { + onDismissRequest() + } + }, + confirmButton = { + DialogButton(stringResource(android.R.string.ok)) { + onTimeChange((timePickerState.hour * 60L + timePickerState.minute) * 60 * 1000) + onDismissRequest() + } + }, + text = { + TimePicker(timePickerState, layoutType = TimePickerLayoutType.Vertical) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/bnyro/wallpaper/util/BackgroundWorker.kt b/app/src/main/java/com/bnyro/wallpaper/util/BackgroundWorker.kt index bc084818..80062158 100644 --- a/app/src/main/java/com/bnyro/wallpaper/util/BackgroundWorker.kt +++ b/app/src/main/java/com/bnyro/wallpaper/util/BackgroundWorker.kt @@ -2,7 +2,6 @@ package com.bnyro.wallpaper.util import android.content.Context import android.graphics.Bitmap -import android.util.Log import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.bnyro.wallpaper.db.DatabaseHolder @@ -24,7 +23,11 @@ class BackgroundWorker( it.id == configId } ?: return Result.success() - Log.e("wallpaper changer", "found appropriate wallpaper config") + val nowMillis = TimeHelper.timeTodayInMillis() + if (config.startTimeMillis != null && config.endTimeMillis != null && + !TimeHelper.isInTimeRange(nowMillis, config.startTimeMillis!!, config.endTimeMillis!!) + ) return Result.success() + return if (runWallpaperChanger(config)) Result.success() else Result.retry() } diff --git a/app/src/main/java/com/bnyro/wallpaper/util/TimeHelper.kt b/app/src/main/java/com/bnyro/wallpaper/util/TimeHelper.kt new file mode 100644 index 00000000..70b7d82a --- /dev/null +++ b/app/src/main/java/com/bnyro/wallpaper/util/TimeHelper.kt @@ -0,0 +1,20 @@ +package com.bnyro.wallpaper.util + +import java.util.Calendar + +object TimeHelper { + fun isInTimeRange(currentTimeMillis: Long, startTimeMillis: Long, endTimeMillis: Long): Boolean { + // start and end time are on two different days in this case + if (endTimeMillis < startTimeMillis) { + return currentTimeMillis <= endTimeMillis || currentTimeMillis >= startTimeMillis + } + + return currentTimeMillis in startTimeMillis..endTimeMillis + } + + fun timeTodayInMillis(): Long { + val calendar = Calendar.getInstance() + + return (calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)) * 60 * 1000L + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b94ac433..fc7fc753 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,6 +59,8 @@ Invert the wallpaper to be dark on dark themes and light on light themes. Apply image filters Add wallpaper changer rule + Time interval + Invalid time interval Network type All networks diff --git a/app/src/test/java/com/bnyro/wallpaper/ExampleUnitTest.kt b/app/src/test/java/com/bnyro/wallpaper/ExampleUnitTest.kt index 4d01e0b0..ad1c49cb 100644 --- a/app/src/test/java/com/bnyro/wallpaper/ExampleUnitTest.kt +++ b/app/src/test/java/com/bnyro/wallpaper/ExampleUnitTest.kt @@ -1,5 +1,6 @@ package com.bnyro.wallpaper +import com.bnyro.wallpaper.util.TimeHelper import org.junit.Assert.assertEquals import org.junit.Test @@ -13,4 +14,11 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } + + @Test + fun testTimeRange() { + assert(TimeHelper.isInTimeRange(500, 200, 700)) + assert(!TimeHelper.isInTimeRange(500, 600, 700)) + assert(TimeHelper.isInTimeRange(500, 700, 600)) + } }