diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5695a38cc..2316d8a8d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -268,6 +268,7 @@ dependencies { implementation(libs.bundles.kotlinx.serialization) implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.datetime) implementation(libs.jsoup) debugImplementation(libs.chucker) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryListFragment.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryListFragment.kt index 3dd037013..3b60657eb 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryListFragment.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryListFragment.kt @@ -42,6 +42,7 @@ import androidx.compose.material.icons.filled.Translate import androidx.compose.material.icons.outlined.Bookmarks import androidx.compose.material3.DismissDirection import androidx.compose.material3.DismissValue +import androidx.compose.material3.DisplayMode import androidx.compose.material3.ElevatedCard import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -50,6 +51,7 @@ import androidx.compose.material3.LeadingIconTab import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PrimaryTabRow +import androidx.compose.material3.SelectableDates import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState @@ -97,12 +99,6 @@ import androidx.paging.PagingSource import androidx.paging.PagingState import androidx.paging.cachedIn import androidx.paging.compose.collectAsLazyPagingItems -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.CalendarConstraints.DateValidator -import com.google.android.material.datepicker.CompositeDateValidator -import com.google.android.material.datepicker.DateValidatorPointBackward -import com.google.android.material.datepicker.DateValidatorPointForward -import com.google.android.material.datepicker.MaterialDatePicker import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings @@ -159,15 +155,18 @@ import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import java.io.File -import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneId -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter -import java.util.Locale import kotlin.math.roundToInt import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn +import kotlinx.datetime.minus +import kotlinx.datetime.toLocalDateTime +import kotlinx.datetime.todayIn import moe.tarsin.coroutines.runSuspendCatching import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyColumnState @@ -837,27 +836,28 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) { urlBuilder.setJumpTo(text) data.refresh() } else { - val local = LocalDateTime.of(2007, 3, 21, 0, 0) - val fromDate = - local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli() - val toDate = MaterialDatePicker.todayInUtcMilliseconds() - val listValidators = ArrayList() - listValidators.add(DateValidatorPointForward.from(fromDate)) - listValidators.add(DateValidatorPointBackward.before(toDate)) - val constraintsBuilder = CalendarConstraints.Builder() - .setStart(fromDate) - .setEnd(toDate) - .setValidator(CompositeDateValidator.allOf(listValidators)) - val datePicker = MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setTitleText(R.string.go_to) - .setSelection(toDate) - .build() - datePicker.show(activity.supportFragmentManager, "date-picker") - datePicker.addOnPositiveButtonClickListener { time -> - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.US).withZone(ZoneOffset.UTC) - val jumpTo = formatter.format(Instant.ofEpochMilli(time)) - urlBuilder.mJumpTo = jumpTo + val initial = LocalDate(2007, 3, 21) + val yesterday = Clock.System.todayIn(TimeZone.UTC).minus(1, DateTimeUnit.DAY) + val initialMillis = initial.atStartOfDayIn(TimeZone.UTC).toEpochMilliseconds() + val yesterdayMillis = yesterday.atStartOfDayIn(TimeZone.UTC).toEpochMilliseconds() + val dateRange = initialMillis..yesterdayMillis + coroutineScope.launch { + // TODO: Enable picker mode + // https://issuetracker.google.com/issues/306193893 + val dateMillis = dialogState.showDatePicker( + title = R.string.go_to, + yearRange = initial.year..yesterday.year, + initialDisplayMode = DisplayMode.Input, + selectableDates = object : SelectableDates { + override fun isSelectableDate(utcTimeMillis: Long): Boolean { + return utcTimeMillis in dateRange + } + }, + showModeToggle = false, + ) + urlBuilder.mJumpTo = dateMillis?.let { + Instant.fromEpochMilliseconds(it).toLocalDateTime(TimeZone.UTC).date.toString() + } data.refresh() } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt index 88255b11d..df066ac23 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/Dialog.kt @@ -4,6 +4,7 @@ import androidx.annotation.StringRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.defaultMinSize @@ -22,6 +23,10 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.Checkbox +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.DisplayMode import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem @@ -29,12 +34,14 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.RadioButton +import androidx.compose.material3.SelectableDates import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.compositionLocalOf @@ -270,6 +277,50 @@ class DialogState { } } + suspend fun showDatePicker( + @StringRes title: Int, + initialSelectedDateMillis: Long? = null, + initialDisplayedMonthMillis: Long? = initialSelectedDateMillis, + yearRange: IntRange = DatePickerDefaults.YearRange, + initialDisplayMode: DisplayMode = DisplayMode.Picker, + selectableDates: SelectableDates = DatePickerDefaults.AllDates, + showModeToggle: Boolean = true, + ): Long? { + return dialog { cont -> + val state = rememberDatePickerState( + initialSelectedDateMillis, + initialDisplayedMonthMillis, + yearRange, + initialDisplayMode, + selectableDates, + ) + DatePickerDialog( + onDismissRequest = { cont.cancel() }, + confirmButton = { + TextButton(onClick = { cont.resume(state.selectedDateMillis) }) { + Text(text = stringResource(id = android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = { cont.cancel() }) { + Text(text = stringResource(id = android.R.string.cancel)) + } + }, + ) { + DatePicker( + state = state, + title = { + Text( + text = stringResource(id = title), + modifier = Modifier.padding(DatePickerTitlePadding), + ) + }, + showModeToggle = showModeToggle, + ) + } + } + } + suspend fun showNoButton(respectDefaultWidth: Boolean = true, block: @Composable DismissDialogScope.() -> Unit): R { return dialog { cont -> val impl = remember(cont) { @@ -483,5 +534,6 @@ class DialogState { } private val IconWithTextCorner = RoundedCornerShape(8.dp) +private val DatePickerTitlePadding = PaddingValues(start = 24.dp, end = 12.dp, top = 16.dp) val LocalDialogState = compositionLocalOf { error("CompositionLocal LocalDialogState not present!") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 321c57ab8..653f5dce8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,6 +29,7 @@ google-accompanist = "0.32.0" google-android-material = "1.12.0-alpha01" kotlin = "2.0.0-Beta1" kotlinx-coroutines = "1.7.3" +kotlinx-datetime = "0.4.1" kotlinx-serialization = "1.6.1" ksp = "2.0.0-Beta1-1.0.14" ktlint = "1.0.1" @@ -105,6 +106,7 @@ insetter = "dev.chrisbanes.insetter:insetter:0.6.1" jsoup = "org.jsoup:jsoup:1.17.1" kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } +kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinx-serialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinx-serialization" }