From 02c124c74ab873a2c8b83978c0db8184b40ac1fa Mon Sep 17 00:00:00 2001 From: Kaushik Meesala Date: Wed, 3 Dec 2025 13:41:55 -0800 Subject: [PATCH 1/8] fix material icons lib --- gradle/libs.versions.toml | 11 +- microapps/ArFlyoverApp/app/build.gradle.kts | 1 + .../ArWorldScaleApp/app/build.gradle.kts | 1 + .../FeatureFormsApp/app/build.gradle.kts | 1 + .../app/build.gradle.kts | 1 + .../app/build.gradle.kts | 1 + .../app/build.gradle.kts | 1 + .../OfflineMapAreasApp/app/build.gradle.kts | 1 + .../app/build.gradle.kts | 1 + .../app/build.gradle.kts | 1 + .../app/build.gradle.kts | 1 + toolkit/ar/build.gradle.kts | 1 + toolkit/authentication/build.gradle.kts | 2 +- toolkit/featureforms/build.gradle.kts | 2 +- .../datetime/picker/DateTimePicker.kt | 53 +- .../datetime/picker/DateTimePickerState.kt | 11 +- .../datetime/picker/date/CalenderModel.kt | 531 ----- .../datetime/picker/date/DateInput.kt | 359 ---- .../datetime/picker/date/DatePicker.kt | 1874 ----------------- .../datetime/picker/date/DatePickerTokens.kt | 105 - .../datetime/picker/date/SnapFlingBehavior.kt | 357 ---- toolkit/indoors/build.gradle.kts | 2 +- toolkit/offline/build.gradle.kts | 2 +- toolkit/popup/build.gradle.kts | 2 +- toolkit/utilitynetworks/build.gradle.kts | 2 +- 25 files changed, 69 insertions(+), 3255 deletions(-) delete mode 100644 toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/CalenderModel.kt delete mode 100644 toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DateInput.kt delete mode 100644 toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DatePicker.kt delete mode 100644 toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DatePickerTokens.kt delete mode 100644 toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/SnapFlingBehavior.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccbf30865..5c7d00974 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,12 +4,11 @@ androidGradlePlugin = "8.12.2" androidXBrowser = "1.9.0" coil3 = "3.3.0" coilBOM = "2.7.0" -composeBOM = "2025.08.01" +composeBOM = "2025.11.01" androidxCamera = "1.4.2" androidxCore = "1.17.0" androidxEspresso = "3.7.0" androidxHiltNavigationCompose = "1.2.0" -androidxMaterialIcons = "1.7.8" androidxTestExt = "1.3.0" androidXTestRunner = "1.7.0" androidXTestRules = "1.7.0" @@ -63,7 +62,8 @@ androidx-datastore-preferences = { module = "androidx.datastore:datastore-prefer androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose"} androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose"} -androidx-material-icons = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "androidxMaterialIcons"} +androidx-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" } +androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" } androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Exoplayer" } androidx-media3-exoplayer-dash = { module = "androidx.media3:media3-exoplayer-dash", version.ref = "media3Exoplayer" } @@ -146,6 +146,11 @@ debug = [ "androidx-compose-ui-test-manifest" ] +icons = [ + "androidx-material-icons-core", + "androidx-material-icons-extended" +] + unitTest = [ "junit", "kotlinx-coroutines-test", diff --git a/microapps/ArFlyoverApp/app/build.gradle.kts b/microapps/ArFlyoverApp/app/build.gradle.kts index cb041d2a7..c393f2aca 100644 --- a/microapps/ArFlyoverApp/app/build.gradle.kts +++ b/microapps/ArFlyoverApp/app/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { implementation(project(":ar")) implementation(libs.arcore) implementation(arcgis.mapsSdk) + implementation(libs.androidx.material.icons.core) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) diff --git a/microapps/ArWorldScaleApp/app/build.gradle.kts b/microapps/ArWorldScaleApp/app/build.gradle.kts index 671023ab5..2e9a1dca4 100644 --- a/microapps/ArWorldScaleApp/app/build.gradle.kts +++ b/microapps/ArWorldScaleApp/app/build.gradle.kts @@ -79,6 +79,7 @@ dependencies { implementation(project(":ar")) implementation(libs.arcore) implementation(arcgis.mapsSdk) + implementation(libs.androidx.material.icons.core) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) diff --git a/microapps/FeatureFormsApp/app/build.gradle.kts b/microapps/FeatureFormsApp/app/build.gradle.kts index f00f1616a..266ae77ff 100644 --- a/microapps/FeatureFormsApp/app/build.gradle.kts +++ b/microapps/FeatureFormsApp/app/build.gradle.kts @@ -100,6 +100,7 @@ dependencies { implementation(libs.coil3.compose) // compose implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.material.icons.core) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) diff --git a/microapps/MapViewGeometryEditorApp/app/build.gradle.kts b/microapps/MapViewGeometryEditorApp/app/build.gradle.kts index 0102a9318..18d81271e 100644 --- a/microapps/MapViewGeometryEditorApp/app/build.gradle.kts +++ b/microapps/MapViewGeometryEditorApp/app/build.gradle.kts @@ -78,6 +78,7 @@ dependencies { implementation(project(":geoview-compose")) implementation(project(":microapps-lib")) implementation(arcgis.mapsSdk) + implementation(libs.androidx.material.icons.core) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) diff --git a/microapps/MapViewLocationDisplayApp/app/build.gradle.kts b/microapps/MapViewLocationDisplayApp/app/build.gradle.kts index 3e5f5262d..3a5354110 100644 --- a/microapps/MapViewLocationDisplayApp/app/build.gradle.kts +++ b/microapps/MapViewLocationDisplayApp/app/build.gradle.kts @@ -78,6 +78,7 @@ dependencies { implementation(project(":geoview-compose")) implementation(project(":microapps-lib")) implementation(arcgis.mapsSdk) + implementation(libs.androidx.material.icons.core) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) diff --git a/microapps/MapViewSetViewpointApp/app/build.gradle.kts b/microapps/MapViewSetViewpointApp/app/build.gradle.kts index 14b6578df..414bd3af9 100644 --- a/microapps/MapViewSetViewpointApp/app/build.gradle.kts +++ b/microapps/MapViewSetViewpointApp/app/build.gradle.kts @@ -78,6 +78,7 @@ dependencies { implementation(project(":geoview-compose")) implementation(project(":microapps-lib")) implementation(arcgis.mapsSdk) + implementation(libs.androidx.material.icons.core) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) diff --git a/microapps/OfflineMapAreasApp/app/build.gradle.kts b/microapps/OfflineMapAreasApp/app/build.gradle.kts index 555452842..3cfed0699 100644 --- a/microapps/OfflineMapAreasApp/app/build.gradle.kts +++ b/microapps/OfflineMapAreasApp/app/build.gradle.kts @@ -79,6 +79,7 @@ dependencies { implementation(project(":offline")) implementation(project(":microapps-lib")) implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.material.icons.core) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) diff --git a/microapps/SceneViewCameraControllerApp/app/build.gradle.kts b/microapps/SceneViewCameraControllerApp/app/build.gradle.kts index c38bf89c5..b4af94613 100644 --- a/microapps/SceneViewCameraControllerApp/app/build.gradle.kts +++ b/microapps/SceneViewCameraControllerApp/app/build.gradle.kts @@ -79,6 +79,7 @@ dependencies { implementation(project(":microapps-lib")) implementation(arcgis.mapsSdk) implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.material.icons.core) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) diff --git a/microapps/SceneViewLightingOptionsApp/app/build.gradle.kts b/microapps/SceneViewLightingOptionsApp/app/build.gradle.kts index f07487a4b..bc4e790a8 100644 --- a/microapps/SceneViewLightingOptionsApp/app/build.gradle.kts +++ b/microapps/SceneViewLightingOptionsApp/app/build.gradle.kts @@ -79,6 +79,7 @@ dependencies { implementation(project(":microapps-lib")) implementation(arcgis.mapsSdk) implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.material.icons.core) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) diff --git a/microapps/UtilityNetworkTraceApp/app/build.gradle.kts b/microapps/UtilityNetworkTraceApp/app/build.gradle.kts index 4d2b0207e..44f4bfe44 100644 --- a/microapps/UtilityNetworkTraceApp/app/build.gradle.kts +++ b/microapps/UtilityNetworkTraceApp/app/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { implementation(project(":utilitynetworks")) implementation(arcgis.mapsSdk) implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.material.icons.core) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) diff --git a/toolkit/ar/build.gradle.kts b/toolkit/ar/build.gradle.kts index 44862377b..3ae93879d 100644 --- a/toolkit/ar/build.gradle.kts +++ b/toolkit/ar/build.gradle.kts @@ -103,6 +103,7 @@ dependencies { implementation(project(":geoview-compose")) implementation(libs.arcore) api(arcgis.mapsSdk) + implementation(libs.androidx.material.icons.core) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) diff --git a/toolkit/authentication/build.gradle.kts b/toolkit/authentication/build.gradle.kts index 80bb6fa4f..634e86e43 100644 --- a/toolkit/authentication/build.gradle.kts +++ b/toolkit/authentication/build.gradle.kts @@ -108,7 +108,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) implementation(libs.androidx.browser) - implementation(libs.androidx.material.icons) + implementation(libs.bundles.icons) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) debugImplementation(libs.bundles.debug) diff --git a/toolkit/featureforms/build.gradle.kts b/toolkit/featureforms/build.gradle.kts index 3779425c4..31ccc8a28 100644 --- a/toolkit/featureforms/build.gradle.kts +++ b/toolkit/featureforms/build.gradle.kts @@ -183,7 +183,7 @@ dependencies { implementation(libs.androidx.compose.ui.util) implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) - implementation(libs.androidx.material.icons) + implementation(libs.bundles.icons) implementation(libs.bundles.camerax) implementation(libs.mlkit.barcode.scanning) implementation(libs.androidx.compose.navigation) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt index cf2eb6879..37b99f4ca 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt @@ -35,12 +35,14 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AccessTime import androidx.compose.material.icons.rounded.CalendarMonth import androidx.compose.material3.AlertDialog +import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.key @@ -62,10 +64,6 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.arcgismaps.toolkit.featureforms.R import com.arcgismaps.toolkit.featureforms.internal.components.base.ValidationErrorState -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date.DatePicker -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date.DatePickerDefaults -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date.DatePickerState -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date.DisplayMode import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.time.TimePicker import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.time.TimePickerState import java.time.Instant @@ -165,15 +163,23 @@ internal fun DateTimePicker( val dateTime by state.dateTime // create and remember a DatePickerState - val datePickerState = rememberSaveable(dateTime, saver = DatePickerState.Saver()) { - DatePickerState( - initialSelectedDateMillis = dateTime.dateForPicker, - initialDisplayedMonthMillis = dateTime.dateForPicker - ?: (state.minDateTime?.toEpochMilli() ?: state.maxDateTime?.toEpochMilli()), - datePickerRange, - DisplayMode.Picker - ) - } +// val datePickerState = rememberSaveable(dateTime, saver = DatePickerState.Saver()) { +// DatePickerState( +// initialSelectedDateMillis = dateTime.dateForPicker, +// initialDisplayedMonthMillis = dateTime.dateForPicker +// ?: (state.minDateTime?.toEpochMilli() ?: state.maxDateTime?.toEpochMilli()), +// datePickerRange, +// DisplayMode.Picker +// ) +// } + val datePickerState = rememberDatePickerState( + initialSelectedDateMillis = dateTime.dateForPicker, + initialDisplayedMonthMillis = dateTime.dateForPicker + ?: (state.minDateTime?.toEpochMilli() ?: state.maxDateTime?.toEpochMilli()), + yearRange = datePickerRange, + initialDisplayMode = androidx.compose.material3.DisplayMode.Picker, + selectableDates = state + ) // create a DateTimePickerDialog DateTimePickerDialog( onDismissRequest = onDismissRequest @@ -229,7 +235,7 @@ private fun (ColumnScope).PickerContent( label: String, description: String, state: DateTimePickerState, - datePickerState: DatePickerState, + datePickerState: androidx.compose.material3.DatePickerState, timePickerState: TimePickerState, style: DateTimePickerStyle, picker: DateTimePickerInput, @@ -258,12 +264,13 @@ private fun (ColumnScope).PickerContent( TimePicker(state = timePickerState, modifier = Modifier.padding(10.dp)) } else { key(state.dateTime.value) { - DatePicker( + androidx.compose.material3.DatePicker( state = datePickerState, - dateValidator = { timeStamp -> - state.dateValidator(timeStamp) - }, - title = { title(if (style == DateTimePickerStyle.Date) null else Icons.Rounded.AccessTime) } +// dateValidator = { timeStamp -> +// state.dateValidator(timeStamp) +// }, + title = { title(if (style == DateTimePickerStyle.Date) null else Icons.Rounded.AccessTime) }, + ) } } @@ -435,3 +442,11 @@ private fun DateTimePickerPreview() { ) DateTimePicker(state = state, {}, {}, {}) } + + +@Composable +@Preview +private fun DatePr() { + val state = rememberDatePickerState() + androidx.compose.material3.DatePicker(state) +} diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePickerState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePickerState.kt index 1f6155815..44dd0abf1 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePickerState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePickerState.kt @@ -18,6 +18,7 @@ package com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker +import androidx.compose.material3.SelectableDates import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf @@ -119,7 +120,7 @@ internal class UtcDateTime private constructor( /** * State for [DateTimePicker]. Use factory [DateTimePicker()] to create an instance. */ -internal interface DateTimePickerState { +internal interface DateTimePickerState : SelectableDates { /** * Minimum date time allowed. This should be null if no range restriction is needed. @@ -309,6 +310,14 @@ private class DateTimePickerStateImpl( minute ) } + + override fun isSelectableDate(utcTimeMillis: Long): Boolean { + return dateValidator(utcTimeMillis) + } + + override fun isSelectableYear(year: Int): Boolean { + return true + } } /** diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/CalenderModel.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/CalenderModel.kt deleted file mode 100644 index fb4c17eac..000000000 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/CalenderModel.kt +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * 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. - * - * - * Modifications copyright (C) 2024 Esri Inc - */ - -package com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date - -import android.os.Build -import androidx.annotation.RequiresApi -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.ui.platform.LocalConfiguration -import androidx.core.os.ConfigurationCompat -import java.time.DayOfWeek -import java.time.Instant -import java.time.LocalDate -import java.time.LocalTime -import java.time.ZoneId -import java.time.ZoneOffset -import java.time.chrono.Chronology -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeFormatterBuilder -import java.time.format.DateTimeParseException -import java.time.format.DecimalStyle -import java.time.format.FormatStyle -import java.time.format.TextStyle -import java.time.temporal.WeekFields -import java.util.Locale - -/** - * Creates a [CalendarModel] to be used by the date picker. - */ -internal fun CalendarModel(): CalendarModel { - return CalendarModelImpl() -} - -/** - * Formats a UTC timestamp into a string with a given date format skeleton. - * - * A skeleton is similar to, and uses the same format characters as described in - * [Unicode Technical Standard #35](https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) - * - * One difference is that order is irrelevant. For example, "MMMMd" will return "MMMM d" in the - * en_US locale, but "d. MMMM" in the de_CH locale. - * - * @param utcTimeMillis a UTC timestamp to format (milliseconds from epoch) - * @param skeleton a date format skeleton - * @param locale the [Locale] to use when formatting the given timestamp - */ -internal fun formatWithSkeleton( - utcTimeMillis: Long, - skeleton: String, - locale: Locale = Locale.getDefault() -): String { - val pattern = android.text.format.DateFormat.getBestDateTimePattern(locale, skeleton) - return CalendarModelImpl.formatWithPattern(utcTimeMillis, pattern, locale) -} - -/** - * A composable function that returns the default [Locale]. - * - * When running on an Android platform, it will be recomposed when the `Configuration` gets updated. - */ -@Composable -@ReadOnlyComposable -internal fun defaultLocale(): Locale { - return ConfigurationCompat.getLocales(LocalConfiguration.current).get(0) ?: Locale.getDefault() -} - - -internal interface CalendarModel { - - /** - * A [CalendarDate] representing the current day. - */ - val today: CalendarDate - - /** - * Hold the first day of the week at the current `Locale` as an integer. The integer value - * follows the ISO-8601 standard and refer to Monday as 1, and Sunday as 7. - */ - val firstDayOfWeek: Int - - /** - * Holds a list of weekday names, starting from Monday as the first day in the list. - * - * Each item in this list is a [Pair] that holds the full name of the day, and its short - * abbreviation letter(s). - * - * Newer APIs (i.e. API 26+), a [Pair] will hold a full name and the first letter of the - * day. - * Older APIs that predate API 26 will hold a full name and the first three letters of the day. - */ - val weekdayNames: List> - - /** - * Returns a [DateInputFormat] for the given [Locale]. - * - * The input format represents the date with two digits for the day and the month, and - * four digits for the year. - * - * For example, the input format patterns, including delimiters, will hold 10-characters strings - * in one of the following variations: - * - yyyy/MM/dd - * - yyyy-MM-dd - * - yyyy.MM.dd - * - dd/MM/yyyy - * - dd-MM-yyyy - * - dd.MM.yyyy - * - MM/dd/yyyy - */ - fun getDateInputFormat(locale: Locale = Locale.getDefault()): DateInputFormat - - /** - * Returns a [CalendarDate] from a given _UTC_ time in milliseconds. - * - * The returned date will hold milliseconds value that represent the start of the day, which may - * be different than the one provided to this function. - * - * @param timeInMillis UTC milliseconds from the epoch - */ - fun getCanonicalDate(timeInMillis: Long): CalendarDate - - /** - * Returns a [CalendarMonth] from a given _UTC_ time in milliseconds. - * - * @param timeInMillis UTC milliseconds from the epoch for the first day the month - */ - fun getMonth(timeInMillis: Long): CalendarMonth - - /** - * Returns a [CalendarMonth] from a given [CalendarDate]. - * - * Note: This function ignores the [CalendarDate.dayOfMonth] value and just uses the date's - * year and month to resolve a [CalendarMonth]. - * - * @param date a [CalendarDate] to resolve into a month - */ - fun getMonth(date: CalendarDate): CalendarMonth - - /** - * Returns a [CalendarMonth] from a given [year] and [month]. - * - * @param year the month's year - * @param month an integer representing a month (e.g. JANUARY as 1, December as 12) - */ - fun getMonth(year: Int, /* @IntRange(from = 1, to = 12) */ month: Int): CalendarMonth - - /** - * Returns a day of week from a given [CalendarDate]. - * - * @param date a [CalendarDate] to resolve - */ - fun getDayOfWeek(date: CalendarDate): Int - - /** - * Returns a [CalendarMonth] that is computed by adding a number of months, given as - * [addedMonthsCount], to a given month. - * - * @param from the [CalendarMonth] to add to - * @param addedMonthsCount the number of months to add - */ - fun plusMonths(from: CalendarMonth, addedMonthsCount: Int): CalendarMonth - - /** - * Returns a [CalendarMonth] that is computed by subtracting a number of months, given as - * [subtractedMonthsCount], from a given month. - * - * @param from the [CalendarMonth] to subtract from - * @param subtractedMonthsCount the number of months to subtract - */ - fun minusMonths(from: CalendarMonth, subtractedMonthsCount: Int): CalendarMonth - - /** - * Formats a [CalendarMonth] into a string with a given date format skeleton. - * - * @param month a [CalendarMonth] to format - * @param skeleton a date format skeleton - * @param locale the [Locale] to use when formatting the given month - */ - fun formatWithSkeleton( - month: CalendarMonth, - skeleton: String, - locale: Locale = Locale.getDefault() - ): String = - formatWithSkeleton(month.startUtcTimeMillis, skeleton, locale) - - /** - * Formats a [CalendarDate] into a string with a given date format skeleton. - * - * @param date a [CalendarDate] to format - * @param skeleton a date format skeleton - * @param locale the [Locale] to use when formatting the given date - */ - fun formatWithSkeleton( - date: CalendarDate, - skeleton: String, - locale: Locale = Locale.getDefault() - ): String = formatWithSkeleton(date.utcTimeMillis, skeleton, locale) - - /** - * Formats a UTC timestamp into a string with a given date format pattern. - * - * @param utcTimeMillis a UTC timestamp to format (milliseconds from epoch) - * @param pattern a date format pattern - * @param locale the [Locale] to use when formatting the given timestamp - */ - fun formatWithPattern(utcTimeMillis: Long, pattern: String, locale: Locale): String - - /** - * Parses a date string into a [CalendarDate]. - * - * @param date a date string - * @param pattern the expected date pattern to be used for parsing the date string - * @return a [CalendarDate], or a `null` in case the parsing failed - */ - fun parse(date: String, pattern: String): CalendarDate? -} - -/** - * Represents a calendar date. - * - * @param year the date's year - * @param month the date's month - * @param dayOfMonth the date's day of month - * @param utcTimeMillis the date representation in _UTC_ milliseconds from the epoch - */ -internal data class CalendarDate( - val year: Int, - val month: Int, - val dayOfMonth: Int, - val utcTimeMillis: Long -) : Comparable { - override operator fun compareTo(other: CalendarDate): Int = - this.utcTimeMillis.compareTo(other.utcTimeMillis) - - /** - * Formats the date into a string with the given skeleton format and a [Locale]. - */ - fun format( - calendarModel: CalendarModel, - skeleton: String, - locale: Locale = Locale.getDefault() - ): String = - calendarModel.formatWithSkeleton(this, skeleton, locale) -} - -/** - * Represents a calendar month. - * - * @param year the month's year - * @param month the calendar month as an integer (e.g. JANUARY as 1, December as 12) - * @param numberOfDays the number of days in the month - * @param daysFromStartOfWeekToFirstOfMonth the number of days from the start of the week to the - * first day of the month - * @param startUtcTimeMillis the first day of the month in _UTC_ milliseconds from the epoch - */ -internal data class CalendarMonth( - val year: Int, - val month: Int, - val numberOfDays: Int, - val daysFromStartOfWeekToFirstOfMonth: Int, - val startUtcTimeMillis: Long -) { - - /** - * The last _UTC_ milliseconds from the epoch of the month (i.e. the last millisecond of the - * last day of the month) - */ - val endUtcTimeMillis: Long = startUtcTimeMillis + (numberOfDays * MillisecondsIn24Hours) - 1 - - /** - * Returns the position of a [CalendarMonth] within given years range. - */ - fun indexIn(years: IntRange): Int { - return (year - years.first) * 12 + month - 1 - } - - /** - * Formats the month into a string with the given skeleton format and a [Locale]. - */ - fun format( - calendarModel: CalendarModel, - skeleton: String, - locale: Locale = Locale.getDefault() - ): String = - calendarModel.formatWithSkeleton(this, skeleton, locale) -} - -/** - * Holds the date input format pattern information. - * - * This data class hold the delimiter that is used by the current [Locale] when representing dates - * in a short format, as well as a date pattern with and without a delimiter. - */ -@Immutable -internal data class DateInputFormat( - val patternWithDelimiters: String, - val delimiter: Char -) { - val patternWithoutDelimiters: String = patternWithDelimiters.replace(delimiter.toString(), "") -} - -/** - * Receives a given local date format string and returns a string that can be displayed to the user - * and parsed by the date parser. - * - * This function: - * - Removes all characters that don't match `d`, `M` and `y`, or any of the date format delimiters - * `.`, `/` and `-`. - * - Ensures that the format is for two digits day and month, and four digits year. - * - * The output of this cleanup is always a 10 characters string in one of the following variations: - * - yyyy/MM/dd - * - yyyy-MM-dd - * - yyyy.MM.dd - * - dd/MM/yyyy - * - dd-MM-yyyy - * - dd.MM.yyyy - * - MM/dd/yyyy - */ -internal fun datePatternAsInputFormat(localeFormat: String): DateInputFormat { - val patternWithDelimiters = localeFormat.replace(Regex("[^dMy/\\-.]"), "") - .replace(Regex("d{1,2}"), "dd") - .replace(Regex("M{1,2}"), "MM") - .replace(Regex("y{1,4}"), "yyyy") - .replace("My", "M/y") // Edge case for the Kako locale - .removeSuffix(".") // Removes a dot suffix that appears in some formats - - val delimiterRegex = Regex("[/\\-.]") - val delimiterMatchResult = delimiterRegex.find(patternWithDelimiters) - val delimiterIndex = delimiterMatchResult!!.groups[0]!!.range.first - val delimiter = patternWithDelimiters.substring(delimiterIndex, delimiterIndex + 1) - return DateInputFormat( - patternWithDelimiters = patternWithDelimiters, - delimiter = delimiter[0] - ) -} - -internal const val DaysInWeek: Int = 7 -internal const val MillisecondsIn24Hours = 86400000L - -/** - * A [CalendarModel] implementation for API >= 26. - */ -internal class CalendarModelImpl : CalendarModel { - - override val today - get(): CalendarDate { - val systemLocalDate = LocalDate.now() - return CalendarDate( - year = systemLocalDate.year, - month = systemLocalDate.monthValue, - dayOfMonth = systemLocalDate.dayOfMonth, - utcTimeMillis = systemLocalDate.atTime(LocalTime.MIDNIGHT) - .atZone(utcTimeZoneId).toInstant().toEpochMilli() - ) - } - - override val firstDayOfWeek: Int = WeekFields.of(Locale.getDefault()).firstDayOfWeek.value - - override val weekdayNames: List> = - // This will start with Monday as the first day, according to ISO-8601. - with(Locale.getDefault()) { - DayOfWeek.entries.map { - it.getDisplayName( - TextStyle.FULL, - /* locale = */ this - ) to it.getDisplayName( - TextStyle.NARROW, - /* locale = */ this - ) - } - } - - override fun getDateInputFormat(locale: Locale): DateInputFormat { - return datePatternAsInputFormat( - DateTimeFormatterBuilder.getLocalizedDateTimePattern( - /* dateStyle = */ FormatStyle.SHORT, - /* timeStyle = */ null, - /* chrono = */ Chronology.ofLocale(locale), - /* locale = */ locale - ) - ) - } - - override fun getCanonicalDate(timeInMillis: Long): CalendarDate { - val localDate = - Instant.ofEpochMilli(timeInMillis).atZone(utcTimeZoneId).toLocalDate() - return CalendarDate( - year = localDate.year, - month = localDate.monthValue, - dayOfMonth = localDate.dayOfMonth, - utcTimeMillis = localDate.atStartOfDay().toEpochSecond(ZoneOffset.UTC) * 1000 - ) - } - - override fun getMonth(timeInMillis: Long): CalendarMonth { - return getMonth( - Instant - .ofEpochMilli(timeInMillis) - .atZone(utcTimeZoneId) - .withDayOfMonth(1) - .toLocalDate() - ) - } - - override fun getMonth(date: CalendarDate): CalendarMonth { - return getMonth(LocalDate.of(date.year, date.month, 1)) - } - - override fun getMonth(year: Int, month: Int): CalendarMonth { - return getMonth(LocalDate.of(year, month, 1)) - } - - override fun getDayOfWeek(date: CalendarDate): Int { - return date.toLocalDate().dayOfWeek.value - } - - override fun plusMonths(from: CalendarMonth, addedMonthsCount: Int): CalendarMonth { - if (addedMonthsCount <= 0) return from - - val firstDayLocalDate = from.toLocalDate() - val laterMonth = firstDayLocalDate.plusMonths(addedMonthsCount.toLong()) - return getMonth(laterMonth) - } - - override fun minusMonths(from: CalendarMonth, subtractedMonthsCount: Int): CalendarMonth { - if (subtractedMonthsCount <= 0) return from - - val firstDayLocalDate = from.toLocalDate() - val earlierMonth = firstDayLocalDate.minusMonths(subtractedMonthsCount.toLong()) - return getMonth(earlierMonth) - } - - override fun formatWithPattern(utcTimeMillis: Long, pattern: String, locale: Locale): String = - Companion.formatWithPattern(utcTimeMillis, pattern, locale) - - override fun parse(date: String, pattern: String): CalendarDate? { - // TODO: A DateTimeFormatter can be reused. - val formatter = DateTimeFormatter.ofPattern(pattern) - return try { - val localDate = LocalDate.parse(date, formatter) - CalendarDate( - year = localDate.year, - month = localDate.month.value, - dayOfMonth = localDate.dayOfMonth, - utcTimeMillis = localDate.atTime(LocalTime.MIDNIGHT) - .atZone(utcTimeZoneId).toInstant().toEpochMilli() - ) - } catch (pe: DateTimeParseException) { - null - } - } - - override fun toString(): String { - return "CalendarModel" - } - - companion object { - - /** - * Formats a UTC timestamp into a string with a given date format pattern. - * - * @param utcTimeMillis a UTC timestamp to format (milliseconds from epoch) - * @param pattern a date format pattern - * @param locale the [Locale] to use when formatting the given timestamp - */ - fun formatWithPattern(utcTimeMillis: Long, pattern: String, locale: Locale): String { - val formatter: DateTimeFormatter = - DateTimeFormatter.ofPattern(pattern, locale) - .withDecimalStyle(DecimalStyle.of(locale)) - return Instant - .ofEpochMilli(utcTimeMillis) - .atZone(utcTimeZoneId) - .toLocalDate() - .format(formatter) - } - - /** - * Holds a UTC [ZoneId]. - */ - internal val utcTimeZoneId: ZoneId = ZoneId.of("UTC") - } - - private fun getMonth(firstDayLocalDate: LocalDate): CalendarMonth { - val difference = firstDayLocalDate.dayOfWeek.value - firstDayOfWeek - val daysFromStartOfWeekToFirstOfMonth = if (difference < 0) { - difference + DaysInWeek - } else { - difference - } - val firstDayEpochMillis = - firstDayLocalDate.atTime(LocalTime.MIDNIGHT).atZone(utcTimeZoneId).toInstant() - .toEpochMilli() - return CalendarMonth( - year = firstDayLocalDate.year, - month = firstDayLocalDate.monthValue, - numberOfDays = firstDayLocalDate.lengthOfMonth(), - daysFromStartOfWeekToFirstOfMonth = daysFromStartOfWeekToFirstOfMonth, - startUtcTimeMillis = firstDayEpochMillis - ) - } - - private fun CalendarMonth.toLocalDate(): LocalDate { - return Instant.ofEpochMilli(startUtcTimeMillis).atZone(utcTimeZoneId).toLocalDate() - } - - private fun CalendarDate.toLocalDate(): LocalDate { - return LocalDate.of( - this.year, - this.month, - this.dayOfMonth - ) - } -} diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DateInput.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DateInput.kt deleted file mode 100644 index 9d57bd0ce..000000000 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DateInput.kt +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * 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. - * - * - * Modifications copyright (C) 2024 Esri Inc - */ - -package com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.clearAndSetSemantics -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.OffsetMapping -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.text.input.TransformedText -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.unit.dp -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.Strings -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.getString -import java.util.Locale - -@Composable -internal fun DateInputContent( - stateData: StateData, - dateFormatter: DatePickerFormatter, - dateValidator: (Long) -> Boolean, -) { - // Obtain the DateInputFormat for the default Locale. - val defaultLocale = defaultLocale() - val dateInputFormat = remember(defaultLocale) { - stateData.calendarModel.getDateInputFormat(defaultLocale) - } - val errorDatePattern = getString(Strings.DateInputInvalidForPattern) - val errorDateOutOfYearRange = getString(Strings.DateInputInvalidYearRange) - val errorInvalidNotAllowed = getString(Strings.DateInputInvalidNotAllowed) - val dateInputValidator = remember(dateInputFormat, dateFormatter) { - DateInputValidator( - stateData = stateData, - dateInputFormat = dateInputFormat, - dateFormatter = dateFormatter, - dateValidator = dateValidator, - errorDatePattern = errorDatePattern, - errorDateOutOfYearRange = errorDateOutOfYearRange, - errorInvalidNotAllowed = errorInvalidNotAllowed, - errorInvalidRangeInput = "" // Not used for a single date input - ) - } - val pattern = dateInputFormat.patternWithDelimiters.uppercase() - val labelText = getString(string = Strings.DateInputLabel) - DateInputTextField( - modifier = Modifier - .fillMaxWidth() - .padding(InputTextFieldPadding), - label = { - Text( - labelText, - modifier = Modifier.semantics { contentDescription = "$labelText, $pattern" }) - }, - placeholder = { Text(pattern, modifier = Modifier.clearAndSetSemantics { }) }, - stateData = stateData, - initialDate = stateData.selectedStartDate.value, - onDateChanged = { date -> stateData.selectedStartDate.value = date }, - inputIdentifier = InputIdentifier.SingleDateInput, - dateInputValidator = dateInputValidator, - dateInputFormat = dateInputFormat, - locale = defaultLocale - ) -} - -@Composable -internal fun DateInputTextField( - modifier: Modifier, - label: @Composable (() -> Unit)?, - placeholder: @Composable (() -> Unit)?, - stateData: StateData, - initialDate: CalendarDate?, - onDateChanged: (CalendarDate?) -> Unit, - inputIdentifier: InputIdentifier, - dateInputValidator: DateInputValidator, - dateInputFormat: DateInputFormat, - locale: Locale -) { - val errorText = rememberSaveable { mutableStateOf("") } - var text by rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf( - TextFieldValue( - text = with(stateData) { - initialDate?.let { - calendarModel.formatWithPattern( - it.utcTimeMillis, - dateInputFormat.patternWithoutDelimiters, - locale - ) - } ?: "" - }, - TextRange(0, 0) - ) - ) - } - - OutlinedTextField( - value = text, - onValueChange = { input -> - if (input.text.length <= dateInputFormat.patternWithoutDelimiters.length && - input.text.all { it.isDigit() } - ) { - text = input - val trimmedText = input.text.trim() - if (trimmedText.isEmpty() || - trimmedText.length < dateInputFormat.patternWithoutDelimiters.length - ) { - errorText.value = "" - onDateChanged(null) - } else { - val parsedDate = stateData.calendarModel.parse( - trimmedText, - dateInputFormat.patternWithoutDelimiters - ) - errorText.value = dateInputValidator.validate( - calendarDate = parsedDate, - inputIdentifier = inputIdentifier, - locale = locale - ) - // Set the parsed date only if the error validation returned an empty string. - // Otherwise, set it to null, as the validation failed. - onDateChanged(if (errorText.value.isEmpty()) parsedDate else null) - } - } - }, - modifier = modifier - // Add bottom padding when there is no error. Otherwise, remove it as the error text - // will take additional height. - .padding( - bottom = if (errorText.value.isNotBlank()) { - 0.dp - } else { - InputTextNonErroneousBottomPadding - } - ) - .semantics { - if (errorText.value.isNotBlank()) error(errorText.value) - }, - label = label, - placeholder = placeholder, - supportingText = { if (errorText.value.isNotBlank()) Text(errorText.value) }, - isError = errorText.value.isNotBlank(), - visualTransformation = DateVisualTransformation(dateInputFormat), - keyboardOptions = KeyboardOptions( - autoCorrectEnabled = false, - keyboardType = KeyboardType.Number, - imeAction = ImeAction.Done - ), - singleLine = true - ) -} - -/** - * A date input validator class. - * - * @param stateData the [StateData] that holds the selected dates info - * @param dateInputFormat a [DateInputFormat] that holds date patterns information - * @param dateFormatter a [DatePickerFormatter] - * @param dateValidator a lambda that takes a date timestamp and return true if the date is a valid - * one for selection. - * @param errorDatePattern a string for displaying an error message when an input does not match the - * expected date pattern. The string expects a date pattern string as an argument to be formatted - * into it. - * @param errorDateOutOfYearRange a string for displaying an error message when an input date - * exceeds the year-range defined at the DateInput's state. The string expects a start and end year - * as arguments to be formatted into it. - * @param errorInvalidNotAllowed a string for displaying an error message when an input date does - * not pass the DateInput's validator check. The string expects a date argument to be formatted into - * it. - * @param errorInvalidRangeInput a string for displaying an error message when in a range input mode - * and one of the input dates is out of order (i.e. the user inputs a start date that is after the - * end date, or an end date that is before the start date) - */ -@Stable -internal class DateInputValidator( - private val stateData: StateData, - private val dateInputFormat: DateInputFormat, - private val dateFormatter: DatePickerFormatter, - private val dateValidator: (Long) -> Boolean, - private val errorDatePattern: String, - private val errorDateOutOfYearRange: String, - private val errorInvalidNotAllowed: String, - private val errorInvalidRangeInput: String -) { - - /** - * Validates a [CalendarDate] input and returns an error string in case an issue with the given - * date is detected, or an empty string in case there are no issues. - * - * @param calendarDate a [CalendarDate] input - * @param inputIdentifier an [InputIdentifier] that provides information about the input field - * that is supposed to hold the date. - * @param locale the current [Locale] - */ - fun validate( - calendarDate: CalendarDate?, - inputIdentifier: InputIdentifier, - locale: Locale - ): String { - if (calendarDate == null) { - return errorDatePattern.format(dateInputFormat.patternWithDelimiters.uppercase()) - } - // Check that the date is within the valid range of years. - if (!stateData.yearRange.contains(calendarDate.year)) { - return errorDateOutOfYearRange.format( - stateData.yearRange.first.toLocalString(), - stateData.yearRange.last.toLocalString() - ) - } - // Check that the provided date validator allows this date to be selected. - if (!dateValidator.invoke(calendarDate.utcTimeMillis)) { - return errorInvalidNotAllowed.format( - dateFormatter.formatDate( - date = calendarDate, - calendarModel = stateData.calendarModel, - locale = locale - ) - ) - } - - // Additional validation when the InputIdentifier is for start of end dates in a range input - if ((inputIdentifier == InputIdentifier.StartDateInput && - calendarDate.utcTimeMillis >= (stateData.selectedEndDate.value?.utcTimeMillis - ?: Long.MAX_VALUE)) || - (inputIdentifier == InputIdentifier.EndDateInput && - calendarDate.utcTimeMillis <= (stateData.selectedStartDate.value?.utcTimeMillis - ?: Long.MIN_VALUE)) - ) { - // The input start date is after the end date, or the end date is before the start date. - return errorInvalidRangeInput - } - - return "" - } -} - -/** - * Represents different input identifiers for the [DateInputTextField]. An `InputIdentifier` is used - * when validating the user input, and especially when validating an input range. - */ -@Immutable -@JvmInline -internal value class InputIdentifier internal constructor(internal val value: Int) { - - companion object { - /** Single date input */ - val SingleDateInput = InputIdentifier(0) - - /** A start date input */ - val StartDateInput = InputIdentifier(1) - - /** An end date input */ - val EndDateInput = InputIdentifier(2) - } - - override fun toString() = when (this) { - SingleDateInput -> "SingleDateInput" - StartDateInput -> "StartDateInput" - EndDateInput -> "EndDateInput" - else -> "Unknown" - } -} - -/** - * A [VisualTransformation] for date input. The transformation will automatically display the date - * delimiters provided by the [DateInputFormat] as the date is being entered into the text field. - */ -private class DateVisualTransformation(private val dateInputFormat: DateInputFormat) : - VisualTransformation { - - private val firstDelimiterOffset: Int = - dateInputFormat.patternWithDelimiters.indexOf(dateInputFormat.delimiter) - private val secondDelimiterOffset: Int = - dateInputFormat.patternWithDelimiters.lastIndexOf(dateInputFormat.delimiter) - private val dateFormatLength: Int = dateInputFormat.patternWithoutDelimiters.length - - private val dateOffsetTranslator = object : OffsetMapping { - - override fun originalToTransformed(offset: Int): Int { - return when { - offset < firstDelimiterOffset -> offset - offset < secondDelimiterOffset -> offset + 1 - offset <= dateFormatLength -> offset + 2 - else -> dateFormatLength + 2 // 10 - } - } - - override fun transformedToOriginal(offset: Int): Int { - return when { - offset <= firstDelimiterOffset - 1 -> offset - offset <= secondDelimiterOffset - 1 -> offset - 1 - offset <= dateFormatLength + 1 -> offset - 2 - else -> dateFormatLength // 8 - } - } - } - - override fun filter(text: AnnotatedString): TransformedText { - val trimmedText = - if (text.text.length > dateFormatLength) { - text.text.substring(0 until dateFormatLength) - } else { - text.text - } - var transformedText = "" - trimmedText.forEachIndexed { index, char -> - transformedText += char - if (index + 1 == firstDelimiterOffset || index + 2 == secondDelimiterOffset) { - transformedText += dateInputFormat.delimiter - } - } - return TransformedText(AnnotatedString(transformedText), dateOffsetTranslator) - } -} - -internal val InputTextFieldPadding = PaddingValues( - start = 24.dp, - end = 24.dp, - top = 10.dp -) - -// An optional padding that will only be added to the bottom of the date input text field when it's -// not showing an error message. -private val InputTextNonErroneousBottomPadding = 16.dp diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DatePicker.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DatePicker.kt deleted file mode 100644 index bc7541bf5..000000000 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DatePicker.kt +++ /dev/null @@ -1,1874 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * 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. - * - * - * Modifications copyright (C) 2024 Esri Inc - */ - -package com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date - -import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.DecayAnimationSpec -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.exponentialDecay -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.FlingBehavior -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredHeight -import androidx.compose.foundation.layout.requiredSize -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.sizeIn -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridState -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.material.icons.filled.DateRange -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.DatePicker -import androidx.compose.material3.DatePickerDialog -import androidx.compose.material3.DateRangePickerState -import androidx.compose.material3.DividerDefaults -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalAbsoluteTonalElevation -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ProvideTextStyle -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.minimumInteractiveComponentSize -import androidx.compose.material3.rememberDatePickerState -import androidx.compose.material3.surfaceColorAtElevation -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.listSaver -import androidx.compose.runtime.saveable.rememberSaveable -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.clipToBounds -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.semantics.CustomAccessibilityAction -import androidx.compose.ui.semantics.LiveRegionMode -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.ScrollAxisRange -import androidx.compose.ui.semantics.clearAndSetSemantics -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.customActions -import androidx.compose.ui.semantics.horizontalScrollAxisRange -import androidx.compose.ui.semantics.isTraversalGroup -import androidx.compose.ui.semantics.liveRegion -import androidx.compose.ui.semantics.paneTitle -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.text -import androidx.compose.ui.semantics.verticalScrollAxisRange -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.Strings -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.fromToken -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.getString -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.toColor -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.toShape -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.text.NumberFormat -import java.util.Locale - -@Composable -internal fun DatePicker( - state: DatePickerState, - modifier: Modifier = Modifier, - dateFormatter: DatePickerFormatter = remember { DatePickerFormatter() }, - dateValidator: (Long) -> Boolean = { true }, - title: (@Composable () -> Unit)? = { - DatePickerDefaults.DatePickerTitle( - state, - modifier = Modifier.padding(DatePickerTitlePadding) - ) - }, - headline: (@Composable () -> Unit)? = { - DatePickerDefaults.DatePickerHeadline( - state, - dateFormatter, - modifier = Modifier.padding(DatePickerHeadlinePadding) - ) - }, - showModeToggle: Boolean = true, - colors: DatePickerColors = DatePickerDefaults.colors() -) { - DateEntryContainer( - modifier = modifier, - title = title, - headline = headline, - modeToggleButton = if (showModeToggle) { - { - DisplayModeToggleButton( - modifier = Modifier.padding(DatePickerModeTogglePadding), - displayMode = state.displayMode, - onDisplayModeChange = { displayMode -> - state.stateData.switchDisplayMode( - displayMode - ) - }, - ) - } - } else { - null - }, - headlineTextStyle = MaterialTheme.typography.fromToken( - DatePickerModalTokens.HeaderHeadlineFont - ), - headerMinHeight = DatePickerModalTokens.HeaderContainerHeight, - colors = colors - ) { - SwitchableDateEntryContent( - state = state, - dateFormatter = dateFormatter, - dateValidator = dateValidator, - colors = colors - ) - } -} - -/** - * Creates a [DatePickerState] for a [DatePicker] that is remembered across compositions. - * - * @param initialSelectedDateMillis timestamp in _UTC_ milliseconds from the epoch that represents - * an initial selection of a date. Provide a `null` to indicate no selection. - * @param initialDisplayedMonthMillis timestamp in _UTC_ milliseconds from the epoch that represents - * an initial selection of a month to be displayed to the user. By default, in case an - * `initialSelectedDateMillis` is provided, the initial displayed month would be the month of the - * selected date. Otherwise, in case `null` is provided, the displayed month would be the - * current one. - * @param yearRange an [IntRange] that holds the year range that the date picker will be limited to - * @param initialDisplayMode an initial [DisplayMode] that this state will hold - */ -@Composable -internal fun rememberDatePickerState( - @Suppress("AutoBoxing") initialSelectedDateMillis: Long? = null, - @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long? = initialSelectedDateMillis, - yearRange: IntRange = DatePickerDefaults.YearRange, - initialDisplayMode: DisplayMode = DisplayMode.Picker -): DatePickerState = rememberSaveable( - saver = DatePickerState.Saver() -) { - DatePickerState( - initialSelectedDateMillis = initialSelectedDateMillis, - initialDisplayedMonthMillis = initialDisplayedMonthMillis, - yearRange = yearRange, - initialDisplayMode = initialDisplayMode - ) -} - -/** - * A state object that can be hoisted to observe the date picker state. See - * [rememberDatePickerState]. - * - * The state's [selectedDateMillis] will provide a timestamp that represents the _start_ of the day. - */ -@Stable -internal class DatePickerState private constructor(internal val stateData: StateData) { - - /** - * Constructs a DatePickerState. - * - * @param initialSelectedDateMillis timestamp in _UTC_ milliseconds from the epoch that - * represents an initial selection of a date. Provide a `null` to indicate no selection. Note - * that the state's - * [selectedDateMillis] will provide a timestamp that represents the _start_ of the day, which - * may be different than the provided initialSelectedDateMillis. - * @param initialDisplayedMonthMillis timestamp in _UTC_ milliseconds from the epoch that - * represents an initial selection of a month to be displayed to the user. In case `null` is - * provided, the displayed month would be the current one. - * @param yearRange an [IntRange] that holds the year range that the date picker will be limited - * to - * @param initialDisplayMode an initial [DisplayMode] that this state will hold - * @see rememberDatePickerState - * @throws [IllegalArgumentException] if the initial selected date or displayed month represent - * a year that is out of the year range. - */ - constructor( - @Suppress("AutoBoxing") initialSelectedDateMillis: Long?, - @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long?, - yearRange: IntRange, - initialDisplayMode: DisplayMode - ) : this( - StateData( - initialSelectedStartDateMillis = initialSelectedDateMillis, - initialSelectedEndDateMillis = null, - initialDisplayedMonthMillis = initialDisplayedMonthMillis, - yearRange = yearRange, - initialDisplayMode = initialDisplayMode, - ) - ) - - /** - * A timestamp that represents the _start_ of the day of the selected date in _UTC_ milliseconds - * from the epoch. - * - * In case no date was selected or provided, the state will hold a `null` value. - * - * @see [setSelection] - */ - val selectedDateMillis: Long? - @Suppress("AutoBoxing") get() = stateData.selectedStartDate.value?.utcTimeMillis - - /** - * Sets the selected date. - * - * @param dateMillis timestamp in _UTC_ milliseconds from the epoch that represents the date - * selection, or `null` to indicate no selection. - * - * @throws IllegalArgumentException in case the given timestamps do not fall within the year - * range this state was created with. - */ - fun setSelection(@Suppress("AutoBoxing") dateMillis: Long?) { - stateData.setSelection(startDateMillis = dateMillis, endDateMillis = null) - } - - /** - * A mutable state of [DisplayMode] that represents the current display mode of the UI - * (i.e. picker or input). - */ - var displayMode by stateData.displayMode - - companion object { - /** - * The default [Saver] implementation for [DatePickerState]. - */ - fun Saver(): Saver = Saver( - save = { with(StateData.Saver()) { save(it.stateData) } }, - restore = { value -> DatePickerState(with(StateData.Saver()) { restore(value)!! }) } - ) - } -} - -/** - * Contains default values used by the date pickers. - */ -@Stable -internal object DatePickerDefaults { - - /** - * Creates a [DatePickerColors] that will potentially animate between the provided colors - * according to the Material specification. - * - * @param containerColor the color used for the date picker's background - * @param titleContentColor the color used for the date picker's title - * @param headlineContentColor the color used for the date picker's headline - * @param weekdayContentColor the color used for the weekday letters - * @param subheadContentColor the color used for the month and year subhead labels that appear - * when the date picker is scrolling calendar months vertically - * @param yearContentColor the color used for the year item when selecting a year - * @param currentYearContentColor the color used for the current year content when selecting a - * year - * @param selectedYearContentColor the color used for the selected year content when selecting a - * year - * @param selectedYearContainerColor the color used for the selected year container when - * selecting a year - * @param dayContentColor the color used for days content - * @param disabledDayContentColor the color used for disabled days content - * @param selectedDayContentColor the color used for selected days content - * @param disabledSelectedDayContentColor the color used for disabled selected days content - * @param selectedDayContainerColor the color used for a selected day container - * @param disabledSelectedDayContainerColor the color used for a disabled selected day container - * @param todayContentColor the color used for the day that marks the current date - * @param todayDateBorderColor the color used for the border of the day that marks the current - * date - * @param dayInSelectionRangeContentColor the content color used for days that are within a date - * range selection - * @param dayInSelectionRangeContainerColor the container color used for days that are within a - * date range selection - */ - @Composable - fun colors( - containerColor: Color = DatePickerModalTokens.ContainerColor.toColor(), - titleContentColor: Color = DatePickerModalTokens.HeaderSupportingTextColor.toColor(), - headlineContentColor: Color = DatePickerModalTokens.HeaderHeadlineColor.toColor(), - weekdayContentColor: Color = DatePickerModalTokens.WeekdaysLabelTextColor.toColor(), - subheadContentColor: Color = - DatePickerModalTokens.RangeSelectionMonthSubheadColor.toColor(), - yearContentColor: Color = - DatePickerModalTokens.SelectionYearUnselectedLabelTextColor.toColor(), - currentYearContentColor: Color = DatePickerModalTokens.DateTodayLabelTextColor.toColor(), - selectedYearContentColor: Color = - DatePickerModalTokens.SelectionYearSelectedLabelTextColor.toColor(), - selectedYearContainerColor: Color = - DatePickerModalTokens.SelectionYearSelectedContainerColor.toColor(), - dayContentColor: Color = DatePickerModalTokens.DateUnselectedLabelTextColor.toColor(), - // TODO: Missing token values for the disabled colors. - disabledDayContentColor: Color = dayContentColor.copy(alpha = 0.38f), - selectedDayContentColor: Color = DatePickerModalTokens.DateSelectedLabelTextColor.toColor(), - // TODO: Missing token values for the disabled colors. - disabledSelectedDayContentColor: Color = selectedDayContentColor.copy(alpha = 0.38f), - selectedDayContainerColor: Color = - DatePickerModalTokens.DateSelectedContainerColor.toColor(), - // TODO: Missing token values for the disabled colors. - disabledSelectedDayContainerColor: Color = selectedDayContainerColor.copy(alpha = 0.38f), - todayContentColor: Color = DatePickerModalTokens.DateTodayLabelTextColor.toColor(), - todayDateBorderColor: Color = - DatePickerModalTokens.DateTodayContainerOutlineColor.toColor(), - dayInSelectionRangeContentColor: Color = - DatePickerModalTokens.SelectionDateInRangeLabelTextColor.toColor(), - dayInSelectionRangeContainerColor: Color = - DatePickerModalTokens.RangeSelectionActiveIndicatorContainerColor.toColor() - ): DatePickerColors = - DatePickerColors( - containerColor = containerColor, - titleContentColor = titleContentColor, - headlineContentColor = headlineContentColor, - weekdayContentColor = weekdayContentColor, - subheadContentColor = subheadContentColor, - yearContentColor = yearContentColor, - currentYearContentColor = currentYearContentColor, - selectedYearContentColor = selectedYearContentColor, - selectedYearContainerColor = selectedYearContainerColor, - dayContentColor = dayContentColor, - disabledDayContentColor = disabledDayContentColor, - selectedDayContentColor = selectedDayContentColor, - disabledSelectedDayContentColor = disabledSelectedDayContentColor, - selectedDayContainerColor = selectedDayContainerColor, - disabledSelectedDayContainerColor = disabledSelectedDayContainerColor, - todayContentColor = todayContentColor, - todayDateBorderColor = todayDateBorderColor, - dayInSelectionRangeContentColor = dayInSelectionRangeContentColor, - dayInSelectionRangeContainerColor = dayInSelectionRangeContainerColor - ) - - /** - * A default date picker title composable. - * - * @param state a [DatePickerState] that will help determine the title's content - * @param modifier a [Modifier] to be applied for the title - */ - @Composable - fun DatePickerTitle(state: DatePickerState, modifier: Modifier = Modifier) { - when (state.displayMode) { - DisplayMode.Picker -> Text( - text = getString(string = Strings.DatePickerTitle), - modifier = modifier - ) - - DisplayMode.Input -> Text( - text = getString(string = Strings.DateInputTitle), - modifier = modifier - ) - } - } - - /** - * A default date picker headline composable that displays a default headline text when there is - * no date selection, and an actual date string when there is. - * - * @param state a [DatePickerState] that will help determine the title's headline - * @param dateFormatter a [DatePickerFormatter] - * @param modifier a [Modifier] to be applied for the headline - */ - @Composable - fun DatePickerHeadline( - state: DatePickerState, - dateFormatter: DatePickerFormatter, - modifier: Modifier = Modifier - ) { - with(state.stateData) { - val defaultLocale = defaultLocale() - val formattedDate = dateFormatter.formatDate( - date = selectedStartDate.value, - calendarModel = calendarModel, - locale = defaultLocale - ) - val verboseDateDescription = dateFormatter.formatDate( - date = selectedStartDate.value, - calendarModel = calendarModel, - locale = defaultLocale, - forContentDescription = true - ) ?: when (displayMode.value) { - DisplayMode.Picker -> getString(Strings.DatePickerNoSelectionDescription) - DisplayMode.Input -> getString(Strings.DateInputNoInputDescription) - else -> "" - } - - val headlineText = formattedDate ?: when (displayMode.value) { - DisplayMode.Picker -> getString(Strings.DatePickerHeadline) - DisplayMode.Input -> getString(Strings.DateInputHeadline) - else -> "" - } - - val headlineDescription = when (displayMode.value) { - DisplayMode.Picker -> getString(Strings.DatePickerHeadlineDescription) - DisplayMode.Input -> getString(Strings.DateInputHeadlineDescription) - else -> "" - }.format(verboseDateDescription) - - Text( - text = headlineText, - modifier = modifier.semantics { - liveRegion = LiveRegionMode.Polite - contentDescription = headlineDescription - }, - maxLines = 1 - ) - } - } - - /** - * Creates and remembers a [FlingBehavior] that will represent natural fling curve with snap to - * the most visible month in the months list. - * - * @param lazyListState a [LazyListState] - * @param decayAnimationSpec the decay to use - */ - @Composable - internal fun rememberSnapFlingBehavior( - lazyListState: LazyListState, - decayAnimationSpec: DecayAnimationSpec = exponentialDecay() - ): FlingBehavior { - val density = LocalDensity.current - return remember(density) { - SnapFlingBehavior( - lazyListState = lazyListState, - decayAnimationSpec = decayAnimationSpec, - snapAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), - density = density - ) - } - } - - /** The range of years for the date picker dialogs. */ - val YearRange: IntRange = IntRange(1900, 2100) - - /** The default tonal elevation used for [DatePickerDialog]. */ - val TonalElevation: Dp = DatePickerModalTokens.ContainerElevation - - /** The default shape for date picker dialogs. */ - val shape: Shape @Composable get() = DatePickerModalTokens.ContainerShape.toShape() - - /** - * A date format skeleton used to format the date picker's year selection menu button (e.g. - * "March 2021") - */ - const val YearMonthSkeleton: String = "yMMMM" - - /** - * A date format skeleton used to format a selected date (e.g. "Mar 27, 2021") - */ - const val YearAbbrMonthDaySkeleton: String = "yMMMd" - - /** - * A date format skeleton used to format a selected date to be used as content description for - * screen readers (e.g. "Saturday, March 27, 2021") - */ - const val YearMonthWeekdayDaySkeleton: String = "yMMMMEEEEd" -} - -/** - * Represents the colors used by the date picker. - * - * See [DatePickerDefaults.colors] for the default implementation that follows Material - * specifications. - */ -@Immutable -internal class DatePickerColors internal constructor( - internal val containerColor: Color, - internal val titleContentColor: Color, - internal val headlineContentColor: Color, - internal val weekdayContentColor: Color, - internal val subheadContentColor: Color, - private val yearContentColor: Color, - private val currentYearContentColor: Color, - private val selectedYearContentColor: Color, - private val selectedYearContainerColor: Color, - private val dayContentColor: Color, - private val disabledDayContentColor: Color, - private val selectedDayContentColor: Color, - private val disabledSelectedDayContentColor: Color, - private val selectedDayContainerColor: Color, - private val disabledSelectedDayContainerColor: Color, - private val todayContentColor: Color, - internal val todayDateBorderColor: Color, - internal val dayInSelectionRangeContainerColor: Color, - private val dayInSelectionRangeContentColor: Color, -) { - /** - * Represents the content color for a calendar day. - * - * @param isToday indicates that the color is for a date that represents today - * @param selected indicates that the color is for a selected day - * @param inRange indicates that the day is part of a selection range of days - * @param enabled indicates that the day is enabled for selection - */ - @Composable - internal fun dayContentColor( - isToday: Boolean, - selected: Boolean, - inRange: Boolean, - enabled: Boolean - ): State { - val target = when { - selected && enabled -> selectedDayContentColor - selected && !enabled -> disabledSelectedDayContentColor - inRange && enabled -> dayInSelectionRangeContentColor - inRange && !enabled -> disabledDayContentColor - isToday -> todayContentColor - enabled -> dayContentColor - else -> disabledDayContentColor - } - - return if (inRange) { - rememberUpdatedState(target) - } else { - // Animate the content color only when the day is not in a range. - animateColorAsState( - target, - tween(durationMillis = MotionTokens.DurationShort2.toInt()) - ) - } - } - - /** - * Represents the container color for a calendar day. - * - * @param selected indicates that the color is for a selected day - * @param enabled indicates that the day is enabled for selection - * @param animate whether or not to animate a container color change - */ - @Composable - internal fun dayContainerColor( - selected: Boolean, - enabled: Boolean, - animate: Boolean - ): State { - val target = if (selected) { - if (enabled) selectedDayContainerColor else disabledSelectedDayContainerColor - } else { - Color.Transparent - } - return if (animate) { - animateColorAsState( - target, - tween(durationMillis = MotionTokens.DurationShort2.toInt()) - ) - } else { - rememberUpdatedState(target) - } - } - - /** - * Represents the content color for a calendar year. - * - * @param currentYear indicates that the color is for a year that represents the current year - * @param selected indicates that the color is for a selected year - */ - @Composable - internal fun yearContentColor(currentYear: Boolean, selected: Boolean): State { - val target = if (selected) { - selectedYearContentColor - } else if (currentYear) { - currentYearContentColor - } else { - yearContentColor - } - - return animateColorAsState( - target, - tween(durationMillis = MotionTokens.DurationShort2.toInt()) - ) - } - - /** - * Represents the container color for a calendar year. - * - * @param selected indicates that the color is for a selected day - */ - @Composable - internal fun yearContainerColor(selected: Boolean): State { - val target = if (selected) selectedYearContainerColor else Color.Transparent - return animateColorAsState( - target, - tween(durationMillis = MotionTokens.DurationShort2.toInt()) - ) - } - - override fun equals(other: Any?): Boolean { - if (other !is DatePickerColors) return false - if (containerColor != other.containerColor) return false - if (titleContentColor != other.titleContentColor) return false - if (headlineContentColor != other.headlineContentColor) return false - if (weekdayContentColor != other.weekdayContentColor) return false - if (subheadContentColor != other.subheadContentColor) return false - if (yearContentColor != other.yearContentColor) return false - if (currentYearContentColor != other.currentYearContentColor) return false - if (selectedYearContentColor != other.selectedYearContentColor) return false - if (selectedYearContainerColor != other.selectedYearContainerColor) return false - if (dayContentColor != other.dayContentColor) return false - if (disabledDayContentColor != other.disabledDayContentColor) return false - if (selectedDayContentColor != other.selectedDayContentColor) return false - if (disabledSelectedDayContentColor != other.disabledSelectedDayContentColor) return false - if (selectedDayContainerColor != other.selectedDayContainerColor) return false - if (disabledSelectedDayContainerColor != other.disabledSelectedDayContainerColor) { - return false - } - if (todayContentColor != other.todayContentColor) return false - if (todayDateBorderColor != other.todayDateBorderColor) return false - if (dayInSelectionRangeContainerColor != other.dayInSelectionRangeContainerColor) { - return false - } - if (dayInSelectionRangeContentColor != other.dayInSelectionRangeContentColor) return false - - return true - } - - override fun hashCode(): Int { - var result = containerColor.hashCode() - result = 31 * result + titleContentColor.hashCode() - result = 31 * result + headlineContentColor.hashCode() - result = 31 * result + weekdayContentColor.hashCode() - result = 31 * result + subheadContentColor.hashCode() - result = 31 * result + yearContentColor.hashCode() - result = 31 * result + currentYearContentColor.hashCode() - result = 31 * result + selectedYearContentColor.hashCode() - result = 31 * result + selectedYearContainerColor.hashCode() - result = 31 * result + dayContentColor.hashCode() - result = 31 * result + disabledDayContentColor.hashCode() - result = 31 * result + selectedDayContentColor.hashCode() - result = 31 * result + disabledSelectedDayContentColor.hashCode() - result = 31 * result + selectedDayContainerColor.hashCode() - result = 31 * result + disabledSelectedDayContainerColor.hashCode() - result = 31 * result + todayContentColor.hashCode() - result = 31 * result + todayDateBorderColor.hashCode() - result = 31 * result + dayInSelectionRangeContainerColor.hashCode() - result = 31 * result + dayInSelectionRangeContentColor.hashCode() - return result - } -} - -/** - * A date formatter used by [DatePicker]. - * - * The date formatter will apply the best possible localized form of the given skeleton and Locale. - * A skeleton is similar to, and uses the same format characters as, a Unicode - * UTS #35 pattern. - * - * One difference is that order is irrelevant. For example, "MMMMd" will return "MMMM d" in the - * `en_US` locale, but "d. MMMM" in the `de_CH` locale. - * - * @param yearSelectionSkeleton a date format skeleton used to format the date picker's year - * selection menu button (e.g. "March 2021"). - * @param selectedDateSkeleton a date format skeleton used to format a selected date (e.g. - * "Mar 27, 2021") - * @param selectedDateDescriptionSkeleton a date format skeleton used to format a selected date to - * be used as content description for screen readers (e.g. "Saturday, March 27, 2021") - */ -@Immutable -internal class DatePickerFormatter constructor( - internal val yearSelectionSkeleton: String = DatePickerDefaults.YearMonthSkeleton, - internal val selectedDateSkeleton: String = DatePickerDefaults.YearAbbrMonthDaySkeleton, - internal val selectedDateDescriptionSkeleton: String = - DatePickerDefaults.YearMonthWeekdayDaySkeleton -) { - - internal fun formatMonthYear( - month: CalendarMonth?, - calendarModel: CalendarModel, - locale: Locale - ): String? { - if (month == null) return null - return calendarModel.formatWithSkeleton(month, yearSelectionSkeleton, locale) - } - - internal fun formatDate( - date: CalendarDate?, - calendarModel: CalendarModel, - locale: Locale, - forContentDescription: Boolean = false - ): String? { - if (date == null) return null - return calendarModel.formatWithSkeleton( - date, if (forContentDescription) { - selectedDateDescriptionSkeleton - } else { - selectedDateSkeleton - }, - locale - ) - } - - override fun equals(other: Any?): Boolean { - if (other !is DatePickerFormatter) return false - - if (yearSelectionSkeleton != other.yearSelectionSkeleton) return false - if (selectedDateSkeleton != other.selectedDateSkeleton) return false - if (selectedDateDescriptionSkeleton != other.selectedDateDescriptionSkeleton) return false - - return true - } - - override fun hashCode(): Int { - var result = yearSelectionSkeleton.hashCode() - result = 31 * result + selectedDateSkeleton.hashCode() - result = 31 * result + selectedDateDescriptionSkeleton.hashCode() - return result - } -} - -/** - * Represents the different modes that a date picker can be at. - */ -@Immutable -@JvmInline -internal value class DisplayMode internal constructor(internal val value: Int) { - - companion object { - /** Date picker mode */ - val Picker = DisplayMode(0) - - /** Date text input mode */ - val Input = DisplayMode(1) - } - - override fun toString() = when (this) { - Picker -> "Picker" - Input -> "Input" - else -> "Unknown" - } -} - -/** - * Holds the state's data for the date picker. - * - * Note that the internal representation is capable of holding a start and end date. However, the - * the [DatePickerState] and the [DateRangePickerState] that use this class will only expose - * publicly the relevant functionality for their purpose. - * - * @param initialSelectedStartDateMillis timestamp in _UTC_ milliseconds from the epoch that - * represents an initial selection of a start date. Provide a `null` to indicate no selection. - * @param initialSelectedEndDateMillis timestamp in _UTC_ milliseconds from the epoch that - * represents an initial selection of an end date. Provide a `null` to indicate no selection. This - * value will be ignored in case it's smaller or equals to the initial start value. - * @param initialDisplayedMonthMillis timestamp in _UTC_ milliseconds from the epoch that represents - * an initial selection of a month to be displayed to the user. In case `null` is provided, the - * displayed month would be the current one. - * @param yearRange an [IntRange] that holds the year range that the date picker will be limited to - * @param initialDisplayMode an initial [DisplayMode] that this state will hold - * @see rememberDatePickerState - */ -@Stable -internal class StateData constructor( - initialSelectedStartDateMillis: Long?, - initialSelectedEndDateMillis: Long?, - initialDisplayedMonthMillis: Long?, - val yearRange: IntRange, - initialDisplayMode: DisplayMode, -) { - - val calendarModel: CalendarModel = CalendarModel() - - /** - * A mutable state of [CalendarDate] that represents the start date for a selection. - */ - var selectedStartDate = mutableStateOf(null) - - /** - * A mutable state of [CalendarDate] that represents the end date for a selection. - * - * Single date selection states that use this [StateData] should always have this as `null`. - */ - var selectedEndDate = mutableStateOf(null) - - /** - * Initialize the state with the provided initial selections. - */ - init { - setSelection( - startDateMillis = initialSelectedStartDateMillis, - endDateMillis = initialSelectedEndDateMillis - ) - } - - /** - * A mutable state for the month that is displayed to the user. In case an initial month was not - * provided, the current month will be the one to be displayed. - */ - var displayedMonth by mutableStateOf( - if (initialDisplayedMonthMillis != null) { - val month = calendarModel.getMonth(initialDisplayedMonthMillis) - require(yearRange.contains(month.year)) { - "The initial display month's year (${month.year}) is out of the years range of " + - "$yearRange." - } - month - } else { - currentMonth - } - ) - - /** - * The current [CalendarMonth] that represents the present's day month. - */ - val currentMonth: CalendarMonth - get() = calendarModel.getMonth(calendarModel.today) - - /** - * A mutable state of [DisplayMode] that represents the current display mode of the UI - * (i.e. picker or input). - */ - var displayMode = mutableStateOf(initialDisplayMode) - - /** - * The displayed month index within the total months at the defined years range. - * - * @see [displayedMonth] - * @see [yearRange] - */ - val displayedMonthIndex: Int - get() = displayedMonth.indexIn(yearRange) - - /** - * The total month count for the defined years range. - * - * @see [yearRange] - */ - val totalMonthsInRange: Int - get() = (yearRange.last - yearRange.first + 1) * 12 - - /** - * Sets a start and end selection dates. - * - * The function expects the dates to be within the state's year-range, and for the start date to - * appear before, or be equal, the end date. Also, if an end date is provided (e.g. not `null`), - * a start date is also expected to be provided. In any other case, an - * [IllegalArgumentException] is thrown. - * - * @param startDateMillis timestamp in _UTC_ milliseconds from the epoch that represents the - * start date selection. Provide a `null` to indicate no selection. - * @param endDateMillis timestamp in _UTC_ milliseconds from the epoch that represents the - * end date selection. Provide a `null` to indicate no selection. - * @throws IllegalArgumentException in case the given timestamps do not comply with the expected - * values specified above. - */ - fun setSelection(startDateMillis: Long?, endDateMillis: Long?) { - val startDate = if (startDateMillis != null) { - calendarModel.getCanonicalDate(startDateMillis) - } else { - null - } - val endDate = if (endDateMillis != null) { - calendarModel.getCanonicalDate(endDateMillis) - } else { - null - } - // Validate that both dates are within the valid years range. - startDate?.let { - require(yearRange.contains(it.year)) { - "The provided start date year (${it.year}) is out of the years range of $yearRange." - } - } - endDate?.let { - require(yearRange.contains(it.year)) { - "The provided end date year (${it.year}) is out of the years range of $yearRange." - } - } - // Validate that an end date cannot be set without a start date. - if (endDate != null) { - requireNotNull(startDate) { - "An end date was provided without a start date." - } - // Validate that the end date appears on or after the start date. - require(startDate.utcTimeMillis <= endDate.utcTimeMillis) { - "The provided end date appears before the start date." - } - } - selectedStartDate.value = startDate - selectedEndDate.value = endDate - } - - fun switchDisplayMode(displayMode: DisplayMode) { - // Update the displayed month, if needed, and change the mode to a date-picker. - selectedStartDate.value?.let { - displayedMonth = calendarModel.getMonth(it) - } - // When toggling back from an input mode, it's possible that the user input an invalid - // start date and a valid end date. If this is the case, and the start date is null, ensure - // that the end date is also null. - if (selectedStartDate.value == null && selectedEndDate.value != null) { - selectedEndDate.value = null - } - this.displayMode.value = displayMode - } - - companion object { - /** - * A [Saver] implementation for [StateData]. - */ - fun Saver(): Saver = listSaver( - save = { - listOf( - it.selectedStartDate.value?.utcTimeMillis, - it.selectedEndDate.value?.utcTimeMillis, - it.displayedMonth.startUtcTimeMillis, - it.yearRange.first, - it.yearRange.last, - it.displayMode.value.value - ) - }, - restore = { value -> - StateData( - initialSelectedStartDateMillis = value[0] as Long?, - initialSelectedEndDateMillis = value[1] as Long?, - initialDisplayedMonthMillis = value[2] as Long?, - yearRange = IntRange(value[3] as Int, value[4] as Int), - initialDisplayMode = DisplayMode(value[5] as Int) - ) - } - ) - } -} - -/** - * A base container for the date picker and the date input. This container composes the top common - * area of the UI, and accepts [content] for the actual calendar picker or text field input. - */ -@Composable -internal fun DateEntryContainer( - modifier: Modifier, - title: (@Composable () -> Unit)?, - headline: (@Composable () -> Unit)?, - modeToggleButton: (@Composable () -> Unit)?, - colors: DatePickerColors, - headlineTextStyle: TextStyle, - headerMinHeight: Dp, - content: @Composable () -> Unit -) { - Column( - modifier = modifier - .sizeIn(minWidth = DatePickerModalTokens.ContainerWidth) - .semantics { isTraversalGroup = true } - ) { - DatePickerHeader( - modifier = Modifier, - title = title, - titleContentColor = colors.titleContentColor, - headlineContentColor = colors.headlineContentColor, - minHeight = headerMinHeight - ) { - Column(modifier = Modifier.fillMaxWidth()) { - val horizontalArrangement = when { - headline != null && modeToggleButton != null -> Arrangement.SpaceBetween - headline != null -> Arrangement.Start - else -> Arrangement.End - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = horizontalArrangement, - verticalAlignment = Alignment.CenterVertically - ) { - if (headline != null) { - ProvideTextStyle(value = headlineTextStyle) { - Box(modifier = Modifier.weight(1f)) { - headline() - } - } - } - modeToggleButton?.invoke() - } - // Display a divider only when there is a title, headline, or a mode toggle. - if (title != null || headline != null || modeToggleButton != null) { - HorizontalDivider() - } - } - } - content() - } -} - -@Composable -internal fun DisplayModeToggleButton( - modifier: Modifier, - displayMode: DisplayMode, - onDisplayModeChange: (DisplayMode) -> Unit -) { - if (displayMode == DisplayMode.Picker) { - IconButton(onClick = { onDisplayModeChange(DisplayMode.Input) }, modifier = modifier) { - Icon( - imageVector = Icons.Filled.Edit, - contentDescription = getString(Strings.DatePickerSwitchToInputMode) - ) - } - } else { - IconButton(onClick = { onDisplayModeChange(DisplayMode.Picker) }, modifier = modifier) { - Icon( - imageVector = Icons.Filled.DateRange, - contentDescription = getString(Strings.DatePickerSwitchToCalendarMode) - ) - } - } -} - -/** - * Date entry content that displays a [DatePickerContent] or a [DateInputContent] according to the - * state's display mode. - */ -@Composable -private fun SwitchableDateEntryContent( - state: DatePickerState, - dateFormatter: DatePickerFormatter, - dateValidator: (Long) -> Boolean, - colors: DatePickerColors -) { - // TODO(b/266480386): Apply the motion spec for this once we have it. Consider replacing this - // with AnimatedContent when it's out of experimental. - Crossfade( - targetState = state.displayMode, - animationSpec = spring(), - modifier = Modifier.semantics { isTraversalGroup = true }) { mode -> - when (mode) { - DisplayMode.Picker -> DatePickerContent( - stateData = state.stateData, - dateFormatter = dateFormatter, - dateValidator = dateValidator, - colors = colors - ) - - DisplayMode.Input -> DateInputContent( - stateData = state.stateData, - dateFormatter = dateFormatter, - dateValidator = dateValidator, - ) - } - } -} - -@Composable -private fun DatePickerContent( - stateData: StateData, - dateFormatter: DatePickerFormatter, - dateValidator: (Long) -> Boolean, - colors: DatePickerColors -) { - val monthsListState = - rememberLazyListState(initialFirstVisibleItemIndex = stateData.displayedMonthIndex) - val coroutineScope = rememberCoroutineScope() - - val onDateSelected = { dateInMillis: Long -> - stateData.selectedStartDate.value = - stateData.calendarModel.getCanonicalDate(dateInMillis) - } - - var yearPickerVisible by rememberSaveable { mutableStateOf(false) } - val defaultLocale = defaultLocale() - Column { - MonthsNavigation( - modifier = Modifier.padding(horizontal = DatePickerHorizontalPadding), - nextAvailable = monthsListState.canScrollForward, - previousAvailable = monthsListState.canScrollBackward, - yearPickerVisible = yearPickerVisible, - yearPickerText = dateFormatter.formatMonthYear( - month = stateData.displayedMonth, - calendarModel = stateData.calendarModel, - locale = defaultLocale - ) ?: "-", - onNextClicked = { - coroutineScope.launch { - monthsListState.animateScrollToItem( - monthsListState.firstVisibleItemIndex + 1 - ) - } - }, - onPreviousClicked = { - coroutineScope.launch { - monthsListState.animateScrollToItem( - monthsListState.firstVisibleItemIndex - 1 - ) - } - }, - onYearPickerButtonClicked = { yearPickerVisible = !yearPickerVisible } - ) - - Box { - Column(modifier = Modifier.padding(horizontal = DatePickerHorizontalPadding)) { - WeekDays(colors, stateData.calendarModel) - HorizontalMonthsList( - onDateSelected = onDateSelected, - stateData = stateData, - lazyListState = monthsListState, - dateFormatter = dateFormatter, - dateValidator = dateValidator, - colors = colors - ) - } - androidx.compose.animation.AnimatedVisibility( - visible = yearPickerVisible, - modifier = Modifier.clipToBounds(), - enter = expandVertically() + fadeIn(initialAlpha = 0.6f), - exit = shrinkVertically() + fadeOut() - ) { - // Apply a paneTitle to make the screen reader focus on a relevant node after this - // column is hidden and disposed. - // TODO(b/186443263): Have the screen reader focus on a year in the list when the - // list is revealed. - val yearsPaneTitle = getString(Strings.DatePickerYearPickerPaneTitle) - Column(modifier = Modifier.semantics { paneTitle = yearsPaneTitle }) { - YearPicker( - // Keep the height the same as the monthly calendar + weekdays height, and - // take into account the thickness of the divider that will be composed - // below it. - modifier = Modifier - .requiredHeight( - RecommendedSizeForAccessibility * (MaxCalendarRows + 1) - - DividerDefaults.Thickness - ) - .padding(horizontal = DatePickerHorizontalPadding), - onYearSelected = { year -> - // Switch back to the monthly calendar and scroll to the selected year. - yearPickerVisible = !yearPickerVisible - coroutineScope.launch { - // Scroll to the selected year (maintaining the month of year). - // A LaunchEffect at the MonthsList will take care of rest and will - // update the state's displayedMonth to the month we scrolled to. - with(stateData) { - monthsListState.scrollToItem( - (year - yearRange.first) * 12 + displayedMonth.month - 1 - ) - } - } - }, - colors = colors, - stateData = stateData - ) - HorizontalDivider() - } - } - } - } -} - -@Composable -internal fun DatePickerHeader( - modifier: Modifier, - title: (@Composable () -> Unit)?, - titleContentColor: Color, - headlineContentColor: Color, - minHeight: Dp, - content: @Composable () -> Unit -) { - // Apply a defaultMinSize only when the title is not null. - val heightModifier = - if (title != null) { - Modifier.defaultMinSize(minHeight = minHeight) - } else { - Modifier - } - Column( - modifier - .fillMaxWidth() - .then(heightModifier), - verticalArrangement = Arrangement.SpaceBetween - ) { - if (title != null) { - CompositionLocalProvider(LocalContentColor provides titleContentColor) { - val textStyle = - MaterialTheme.typography.fromToken( - DatePickerModalTokens.HeaderSupportingTextFont - ) - ProvideTextStyle(textStyle) { - Box(contentAlignment = Alignment.BottomStart) { - title() - } - } - } - } - CompositionLocalProvider( - LocalContentColor provides headlineContentColor, content = content - ) - } -} - -/** - * Composes a horizontal pageable list of months. - */ -@Composable -private fun HorizontalMonthsList( - onDateSelected: (dateInMillis: Long) -> Unit, - stateData: StateData, - lazyListState: LazyListState, - dateFormatter: DatePickerFormatter, - dateValidator: (Long) -> Boolean, - colors: DatePickerColors, -) { - val today = stateData.calendarModel.today - val firstMonth = remember(stateData.yearRange) { - stateData.calendarModel.getMonth( - year = stateData.yearRange.first, - month = 1 // January - ) - } - LazyRow( - // Apply this to prevent the screen reader from scrolling to the next or previous month, and - // instead, traverse outside the Month composable when swiping from a focused first or last - // day of the month. - modifier = Modifier.semantics { - horizontalScrollAxisRange = ScrollAxisRange(value = { 0f }, maxValue = { 0f }) - }, - state = lazyListState, - // TODO(b/264687693): replace with the framework's rememberSnapFlingBehavior(lazyListState) - // when promoted to stable - flingBehavior = DatePickerDefaults.rememberSnapFlingBehavior(lazyListState) - ) { - items(stateData.totalMonthsInRange) { - val month = - stateData.calendarModel.plusMonths( - from = firstMonth, - addedMonthsCount = it - ) - Box( - modifier = Modifier.fillParentMaxWidth() - ) { - Month( - month = month, - onDateSelected = onDateSelected, - today = today, - stateData = stateData, - rangeSelectionEnabled = false, - dateValidator = dateValidator, - dateFormatter = dateFormatter, - colors = colors - ) - } - } - } - - LaunchedEffect(lazyListState) { - updateDisplayedMonth(lazyListState, stateData) - } -} - -internal suspend fun updateDisplayedMonth( - lazyListState: LazyListState, - stateData: StateData -) { - snapshotFlow { lazyListState.firstVisibleItemIndex }.collect { - val yearOffset = lazyListState.firstVisibleItemIndex / 12 - val month = lazyListState.firstVisibleItemIndex % 12 + 1 - with(stateData) { - if (displayedMonth.month != month || - displayedMonth.year != yearRange.first + yearOffset - ) { - displayedMonth = calendarModel.getMonth( - year = yearRange.first + yearOffset, - month = month - ) - } - } - } -} - -/** - * Composes the weekdays letters. - */ -@Composable -internal fun WeekDays(colors: DatePickerColors, calendarModel: CalendarModel) { - val firstDayOfWeek = calendarModel.firstDayOfWeek - val weekdays = calendarModel.weekdayNames - val dayNames = arrayListOf>() - // Start with firstDayOfWeek - 1 as the days are 1-based. - for (i in firstDayOfWeek - 1 until weekdays.size) { - dayNames.add(weekdays[i]) - } - for (i in 0 until firstDayOfWeek - 1) { - dayNames.add(weekdays[i]) - } - CompositionLocalProvider(LocalContentColor provides colors.weekdayContentColor) { - val textStyle = - MaterialTheme.typography.fromToken(DatePickerModalTokens.WeekdaysLabelTextFont) - ProvideTextStyle(value = textStyle) { - Row( - modifier = Modifier - .defaultMinSize( - minHeight = RecommendedSizeForAccessibility - ) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically - ) { - dayNames.forEach { - Box( - modifier = Modifier - .clearAndSetSemantics { contentDescription = it.first } - .size( - width = RecommendedSizeForAccessibility, - height = RecommendedSizeForAccessibility - ), - contentAlignment = Alignment.Center) { - Text( - text = it.second, - modifier = Modifier.wrapContentSize(), - textAlign = TextAlign.Center - ) - } - } - } - } - } -} - -/** - * A composable that renders a calendar month and displays a date selection. - */ -@Composable -internal fun Month( - month: CalendarMonth, - onDateSelected: (dateInMillis: Long) -> Unit, - today: CalendarDate, - stateData: StateData, - rangeSelectionEnabled: Boolean, - dateValidator: (Long) -> Boolean, - dateFormatter: DatePickerFormatter, - colors: DatePickerColors -) { - val rangeSelectionDrawModifier = if (rangeSelectionEnabled) { - Modifier.drawWithContent { - drawContent() - } - } else { - Modifier - } - - val defaultLocale = defaultLocale() - val startSelection = stateData.selectedStartDate - val endSelection = stateData.selectedEndDate - ProvideTextStyle( - MaterialTheme.typography.fromToken(DatePickerModalTokens.DateLabelTextFont) - ) { - var cellIndex = 0 - Column( - modifier = Modifier - .requiredHeight(RecommendedSizeForAccessibility * MaxCalendarRows) - .then(rangeSelectionDrawModifier), - verticalArrangement = Arrangement.SpaceEvenly - ) { - repeat(MaxCalendarRows) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically - ) { - repeat(DaysInWeek) { - if (cellIndex < month.daysFromStartOfWeekToFirstOfMonth || - cellIndex >= - (month.daysFromStartOfWeekToFirstOfMonth + month.numberOfDays) - ) { - // Empty cell - Spacer( - modifier = Modifier.requiredSize( - width = RecommendedSizeForAccessibility, - height = RecommendedSizeForAccessibility - ) - ) - } else { - val dayNumber = cellIndex - month.daysFromStartOfWeekToFirstOfMonth - val dateInMillis = month.startUtcTimeMillis + - (dayNumber * MillisecondsIn24Hours) - val isToday = dateInMillis == today.utcTimeMillis - val startDateSelected = - dateInMillis == startSelection.value?.utcTimeMillis - val endDateSelected = dateInMillis == endSelection.value?.utcTimeMillis - val inRange = remember(rangeSelectionEnabled, dateInMillis) { - derivedStateOf { - with(stateData) { - rangeSelectionEnabled && - dateInMillis >= (selectedStartDate.value?.utcTimeMillis - ?: Long.MAX_VALUE) && - dateInMillis <= (selectedEndDate.value?.utcTimeMillis - ?: Long.MIN_VALUE) - } - } - } - val dayContentDescription = dayContentDescription( - rangeSelectionEnabled = rangeSelectionEnabled, - isToday = isToday, - isStartDate = startDateSelected, - isEndDate = endDateSelected, - isInRange = inRange.value - ) - val formattedDateDescription = formatWithSkeleton( - dateInMillis, - dateFormatter.selectedDateDescriptionSkeleton, - defaultLocale - ) - Day( - modifier = Modifier, - selected = startDateSelected || endDateSelected, - onClick = { onDateSelected(dateInMillis) }, - // Only animate on the first selected day. This is important to - // disable when drawing a range marker behind the days on an - // end-date selection. - animateChecked = startDateSelected, - enabled = remember(dateInMillis) { - dateValidator.invoke(dateInMillis) - }, - today = isToday, - inRange = inRange.value, - description = if (dayContentDescription != null) { - "$dayContentDescription, $formattedDateDescription" - } else { - formattedDateDescription - }, - colors = colors - ) { - Text( - text = (dayNumber + 1).toLocalString(), - // The semantics are set at the Day level. - modifier = Modifier.clearAndSetSemantics { }, - textAlign = TextAlign.Center - ) - } - } - cellIndex++ - } - } - } - } - } -} - -@Composable -private fun dayContentDescription( - rangeSelectionEnabled: Boolean, - isToday: Boolean, - isStartDate: Boolean, - isEndDate: Boolean, - isInRange: Boolean -): String? { - val descriptionBuilder = StringBuilder() - if (rangeSelectionEnabled) { - when { - isStartDate -> descriptionBuilder.append( - getString(string = Strings.DateRangePickerStartHeadline) - ) - - isEndDate -> descriptionBuilder.append( - getString(string = Strings.DateRangePickerEndHeadline) - ) - - isInRange -> descriptionBuilder.append( - getString(string = Strings.DateRangePickerDayInRange) - ) - } - } - if (isToday) { - if (descriptionBuilder.isNotEmpty()) descriptionBuilder.append(", ") - descriptionBuilder.append(getString(string = Strings.DatePickerTodayDescription)) - } - return if (descriptionBuilder.isEmpty()) null else descriptionBuilder.toString() -} - -@Composable -private fun Day( - modifier: Modifier, - selected: Boolean, - onClick: () -> Unit, - animateChecked: Boolean, - enabled: Boolean, - today: Boolean, - inRange: Boolean, - description: String, - colors: DatePickerColors, - content: @Composable () -> Unit -) { - Surface( - selected = selected, - onClick = onClick, - modifier = modifier - .minimumInteractiveComponentSize() - .requiredSize( - DatePickerModalTokens.DateStateLayerWidth, - DatePickerModalTokens.DateStateLayerHeight - ) - // Apply and merge semantics here. This will ensure that when scrolling the list the - // entire Day surface is treated as one unit and holds the date semantics even when it's - // not completely visible atm. - .semantics(mergeDescendants = true) { - text = AnnotatedString(description) - role = Role.Button - }, - enabled = enabled, - shape = DatePickerModalTokens.DateContainerShape.toShape(), - color = colors.dayContainerColor( - selected = selected, - enabled = enabled, - animate = animateChecked - ).value, - contentColor = colors.dayContentColor( - isToday = today, - selected = selected, - inRange = inRange, - enabled = enabled, - ).value, - border = if (today && !selected) { - BorderStroke( - DatePickerModalTokens.DateTodayContainerOutlineWidth, - colors.todayDateBorderColor - ) - } else { - null - } - ) { - Box(contentAlignment = Alignment.Center) { - content() - } - } -} - -@Composable -private fun YearPicker( - modifier: Modifier, - onYearSelected: (year: Int) -> Unit, - colors: DatePickerColors, - stateData: StateData -) { - ProvideTextStyle( - value = MaterialTheme.typography.fromToken(DatePickerModalTokens.SelectionYearLabelTextFont) - ) { - val currentYear = stateData.currentMonth.year - val displayedYear = stateData.displayedMonth.year - val lazyGridState = - rememberLazyGridState( - // Set the initial index to a few years before the current year to allow quicker - // selection of previous years. - initialFirstVisibleItemIndex = Integer.max( - 0, displayedYear - stateData.yearRange.first - YearsInRow - ) - ) - // Match the years container color to any elevated surface color that is composed under it. - val containerColor = if (colors.containerColor == MaterialTheme.colorScheme.surface) { - MaterialTheme.colorScheme.surfaceColorAtElevation(LocalAbsoluteTonalElevation.current) - } else { - colors.containerColor - } - val coroutineScope = rememberCoroutineScope() - val scrollToEarlierYearsLabel = getString(Strings.DatePickerScrollToShowEarlierYears) - val scrollToLaterYearsLabel = getString(Strings.DatePickerScrollToShowLaterYears) - LazyVerticalGrid( - columns = GridCells.Fixed(YearsInRow), - modifier = modifier - .background(containerColor) - // Apply this to have the screen reader traverse outside the visible list of years - // and not scroll them by default. - .semantics { - verticalScrollAxisRange = ScrollAxisRange(value = { 0f }, maxValue = { 0f }) - }, - state = lazyGridState, - horizontalArrangement = Arrangement.SpaceEvenly, - verticalArrangement = Arrangement.spacedBy(YearsVerticalPadding) - ) { - items(stateData.yearRange.count()) { - val selectedYear = it + stateData.yearRange.first - val localizedYear = selectedYear.toLocalString() - Year( - modifier = Modifier - .requiredSize( - width = DatePickerModalTokens.SelectionYearContainerWidth, - height = DatePickerModalTokens.SelectionYearContainerHeight - ) - .semantics { - // Apply a11y custom actions to the first and last items in the years - // grid. The actions will suggest to scroll to earlier or later years in - // the grid. - customActions = if (lazyGridState.firstVisibleItemIndex == it || - lazyGridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index == it - ) { - customScrollActions( - state = lazyGridState, - coroutineScope = coroutineScope, - scrollUpLabel = scrollToEarlierYearsLabel, - scrollDownLabel = scrollToLaterYearsLabel - ) - } else { - emptyList() - } - }, - selected = selectedYear == displayedYear, - currentYear = selectedYear == currentYear, - onClick = { onYearSelected(selectedYear) }, - description = getString(Strings.DatePickerNavigateToYearDescription) - .format(localizedYear), - colors = colors - ) { - Text( - text = localizedYear, - // The semantics are set at the Year level. - modifier = Modifier.clearAndSetSemantics {}, - textAlign = TextAlign.Center - ) - } - } - } - } -} - -@Composable -private fun Year( - modifier: Modifier, - selected: Boolean, - currentYear: Boolean, - onClick: () -> Unit, - description: String, - colors: DatePickerColors, - content: @Composable () -> Unit -) { - val border = remember(currentYear, selected) { - if (currentYear && !selected) { - // Use the day's spec to draw a border around the current year. - BorderStroke( - DatePickerModalTokens.DateTodayContainerOutlineWidth, - colors.todayDateBorderColor - ) - } else { - null - } - } - Surface( - selected = selected, - onClick = onClick, - // Apply and merge semantics here. This will ensure that when scrolling the list the entire - // Year surface is treated as one unit and holds the date semantics even when it's not - // completely visible atm. - modifier = modifier.semantics(mergeDescendants = true) { - text = AnnotatedString(description) - role = Role.Button - }, - shape = DatePickerModalTokens.SelectionYearStateLayerShape.toShape(), - color = colors.yearContainerColor(selected = selected).value, - contentColor = colors.yearContentColor( - currentYear = currentYear, - selected = selected - ).value, - border = border, - ) { - Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - content() - } - } -} - -/** - * A composable that shows a year menu button and a couple of buttons that enable navigation between - * displayed months. - */ -@Composable -private fun MonthsNavigation( - modifier: Modifier, - nextAvailable: Boolean, - previousAvailable: Boolean, - yearPickerVisible: Boolean, - yearPickerText: String, - onNextClicked: () -> Unit, - onPreviousClicked: () -> Unit, - onYearPickerButtonClicked: () -> Unit, -) { - Row( - modifier = modifier - .fillMaxWidth() - .requiredHeight(MonthYearHeight), - horizontalArrangement = if (yearPickerVisible) { - Arrangement.Start - } else { - Arrangement.SpaceBetween - }, - verticalAlignment = Alignment.CenterVertically - ) { - // A menu button for selecting a year. - YearPickerMenuButton( - onClick = onYearPickerButtonClicked, - expanded = yearPickerVisible - ) { - Text(text = yearPickerText, - modifier = Modifier.semantics { - // Make the screen reader read out updates to the menu button text as the user - // navigates the arrows or scrolls to change the displayed month. - liveRegion = LiveRegionMode.Polite - contentDescription = yearPickerText - }) - } - // Show arrows for traversing months (only visible when the year selection is off) - if (!yearPickerVisible) { - Row { - val rtl = LocalLayoutDirection.current == LayoutDirection.Rtl - IconButton(onClick = onPreviousClicked, enabled = previousAvailable) { - Icon( - if (rtl) { - Icons.AutoMirrored.Filled.KeyboardArrowRight - } else { - Icons.AutoMirrored.Filled.KeyboardArrowLeft - }, - contentDescription = getString(Strings.DatePickerSwitchToPreviousMonth) - ) - } - IconButton(onClick = onNextClicked, enabled = nextAvailable) { - Icon( - if (rtl) { - Icons.AutoMirrored.Filled.KeyboardArrowLeft - } else { - Icons.AutoMirrored.Filled.KeyboardArrowRight - }, - contentDescription = getString(Strings.DatePickerSwitchToNextMonth) - ) - } - } - } - } -} - -// TODO: Replace with the official MenuButton when implemented. -@Composable -private fun YearPickerMenuButton( - onClick: () -> Unit, - expanded: Boolean, - modifier: Modifier = Modifier, - content: @Composable () -> Unit -) { - TextButton( - onClick = onClick, - modifier = modifier, - shape = CircleShape, - colors = - ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurfaceVariant), - elevation = null, - border = null, - ) { - content() - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Icon( - Icons.Filled.ArrowDropDown, - contentDescription = if (expanded) { - getString(Strings.DatePickerSwitchToDaySelection) - } else { - getString(Strings.DatePickerSwitchToYearSelection) - }, - Modifier.rotate(if (expanded) 180f else 0f) - ) - } -} - -private fun customScrollActions( - state: LazyGridState, - coroutineScope: CoroutineScope, - scrollUpLabel: String, - scrollDownLabel: String -): List { - val scrollUpAction = { - if (!state.canScrollBackward) { - false - } else { - coroutineScope.launch { - state.scrollToItem(state.firstVisibleItemIndex - YearsInRow) - } - true - } - } - val scrollDownAction = { - if (!state.canScrollForward) { - false - } else { - coroutineScope.launch { - state.scrollToItem(state.firstVisibleItemIndex + YearsInRow) - } - true - } - } - return listOf( - CustomAccessibilityAction( - label = scrollUpLabel, - action = scrollUpAction - ), - CustomAccessibilityAction( - label = scrollDownLabel, - action = scrollDownAction - ) - ) -} - -/** - * Returns a string representation of an integer at the current Locale. - */ -internal fun Int.toLocalString(): String { - val formatter = NumberFormat.getIntegerInstance() - // Eliminate any use of delimiters when formatting the integer. - formatter.isGroupingUsed = false - return formatter.format(this) -} - -internal val RecommendedSizeForAccessibility = 48.dp -internal val MonthYearHeight = 56.dp -internal val DatePickerHorizontalPadding = 12.dp -internal val DatePickerModeTogglePadding = PaddingValues(end = 12.dp, bottom = 12.dp) - -private val DatePickerTitlePadding = PaddingValues(start = 24.dp, end = 12.dp, top = 16.dp) -private val DatePickerHeadlinePadding = PaddingValues(start = 24.dp, end = 12.dp, bottom = 12.dp) - -private val YearsVerticalPadding = 16.dp - -private const val MaxCalendarRows = 6 -private const val YearsInRow: Int = 3 diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DatePickerTokens.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DatePickerTokens.kt deleted file mode 100644 index 813c970eb..000000000 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/DatePickerTokens.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * 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. - * - * - * Modifications copyright (C) 2024 Esri Inc - */ - -package com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date - -import androidx.compose.animation.core.CubicBezierEasing -import androidx.compose.ui.unit.dp -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.ColorSchemeKeyTokens -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.ElevationTokens -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.ShapeKeyTokens -import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.material3.TypographyKeyTokens - -internal object DatePickerModalTokens { - val ContainerColor = ColorSchemeKeyTokens.Surface - val ContainerElevation = ElevationTokens.Level3 - val ContainerHeight = 568.0.dp - val ContainerShape = ShapeKeyTokens.CornerExtraLarge - val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint - val ContainerWidth = 360.0.dp - val DateContainerHeight = 40.0.dp - val DateContainerShape = ShapeKeyTokens.CornerFull - val DateContainerWidth = 40.0.dp - val DateLabelTextFont = TypographyKeyTokens.BodyLarge - val DateSelectedContainerColor = ColorSchemeKeyTokens.Primary - val DateSelectedLabelTextColor = ColorSchemeKeyTokens.OnPrimary - val DateStateLayerHeight = 40.0.dp - val DateStateLayerShape = ShapeKeyTokens.CornerFull - val DateStateLayerWidth = 40.0.dp - val DateTodayContainerOutlineColor = ColorSchemeKeyTokens.Primary - val DateTodayContainerOutlineWidth = 1.0.dp - val DateTodayLabelTextColor = ColorSchemeKeyTokens.Primary - val DateUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurface - val HeaderContainerHeight = 120.0.dp - val HeaderContainerWidth = 360.0.dp - val HeaderHeadlineColor = ColorSchemeKeyTokens.OnSurfaceVariant - val HeaderHeadlineFont = TypographyKeyTokens.HeadlineLarge - val HeaderSupportingTextColor = ColorSchemeKeyTokens.OnSurfaceVariant - val HeaderSupportingTextFont = TypographyKeyTokens.LabelLarge - val RangeSelectionActiveIndicatorContainerColor = ColorSchemeKeyTokens.SecondaryContainer - val RangeSelectionActiveIndicatorContainerHeight = 40.0.dp - val RangeSelectionActiveIndicatorContainerShape = ShapeKeyTokens.CornerFull - val RangeSelectionContainerElevation = ElevationTokens.Level0 - val RangeSelectionContainerShape = ShapeKeyTokens.CornerNone - val SelectionDateInRangeLabelTextColor = ColorSchemeKeyTokens.OnSecondaryContainer - val RangeSelectionHeaderContainerHeight = 128.0.dp - val RangeSelectionHeaderHeadlineFont = TypographyKeyTokens.TitleLarge - val RangeSelectionMonthSubheadColor = ColorSchemeKeyTokens.OnSurfaceVariant - val RangeSelectionMonthSubheadFont = TypographyKeyTokens.TitleSmall - val WeekdaysLabelTextColor = ColorSchemeKeyTokens.OnSurface - val WeekdaysLabelTextFont = TypographyKeyTokens.BodyLarge - val SelectionYearContainerHeight = 36.0.dp - val SelectionYearContainerWidth = 72.0.dp - val SelectionYearLabelTextFont = TypographyKeyTokens.BodyLarge - val SelectionYearSelectedContainerColor = ColorSchemeKeyTokens.Primary - val SelectionYearSelectedLabelTextColor = ColorSchemeKeyTokens.OnPrimary - val SelectionYearStateLayerHeight = 36.0.dp - val SelectionYearStateLayerShape = ShapeKeyTokens.CornerFull - val SelectionYearStateLayerWidth = 72.0.dp - val SelectionYearUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant -} - -internal object MotionTokens { - const val DurationExtraLong1 = 700.0 - const val DurationExtraLong2 = 800.0 - const val DurationExtraLong3 = 900.0 - const val DurationExtraLong4 = 1000.0 - const val DurationLong1 = 450.0 - const val DurationLong2 = 500.0 - const val DurationLong3 = 550.0 - const val DurationLong4 = 600.0 - const val DurationMedium1 = 250.0 - const val DurationMedium2 = 300.0 - const val DurationMedium3 = 350.0 - const val DurationMedium4 = 400.0 - const val DurationShort1 = 50.0 - const val DurationShort2 = 100.0 - const val DurationShort3 = 150.0 - const val DurationShort4 = 200.0 - val EasingEmphasizedCubicBezier = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f) - val EasingEmphasizedAccelerateCubicBezier = CubicBezierEasing(0.3f, 0.0f, 0.8f, 0.15f) - val EasingEmphasizedDecelerateCubicBezier = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1.0f) - val EasingLegacyCubicBezier = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f) - val EasingLegacyAccelerateCubicBezier = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f) - val EasingLegacyDecelerateCubicBezier = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f) - val EasingLinearCubicBezier = CubicBezierEasing(0.0f, 0.0f, 1.0f, 1.0f) - val EasingStandardCubicBezier = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f) - val EasingStandardAccelerateCubicBezier = CubicBezierEasing(0.3f, 0.0f, 1.0f, 1.0f) - val EasingStandardDecelerateCubicBezier = CubicBezierEasing(0.0f, 0.0f, 0.0f, 1.0f) -} diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/SnapFlingBehavior.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/SnapFlingBehavior.kt deleted file mode 100644 index 02b3fbb80..000000000 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/date/SnapFlingBehavior.kt +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * 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. - * - * - * Modifications copyright (C) 2024 Esri Inc - */ - -package com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.date - -import androidx.compose.animation.core.AnimationScope -import androidx.compose.animation.core.AnimationSpec -import androidx.compose.animation.core.AnimationState -import androidx.compose.animation.core.AnimationVector -import androidx.compose.animation.core.AnimationVector1D -import androidx.compose.animation.core.DecayAnimationSpec -import androidx.compose.animation.core.animateDecay -import androidx.compose.animation.core.animateTo -import androidx.compose.animation.core.calculateTargetValue -import androidx.compose.animation.core.copy -import androidx.compose.foundation.gestures.FlingBehavior -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.ScrollScope -import androidx.compose.foundation.lazy.LazyListItemInfo -import androidx.compose.foundation.lazy.LazyListLayoutInfo -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.ui.MotionDurationScale -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.util.fastSumBy -import kotlinx.coroutines.withContext -import kotlin.math.abs -import kotlin.math.absoluteValue -import kotlin.math.sign - -/** - * A [FlingBehavior] that snaps to the mostly visible item in the list of items. This behavior is - * designed to be used when there is a single visible page. - * - * Note: This is a temporary fling behavior that will be removed once the framework's - * `rememberSnapFlingBehavior` function is stable. - * - * @param lazyListState a [LazyListState] - * @param decayAnimationSpec a [DecayAnimationSpec] that is used for the fling's decay animation - * @param snapAnimationSpec an [AnimationSpec] that is used for the snap animation - * @param density the current display [Density] - */ -// TODO(b/264687693): Replace with the framework's rememberSnapFlingBehavior once it's stable. -internal class SnapFlingBehavior( - private val lazyListState: LazyListState, - private val decayAnimationSpec: DecayAnimationSpec, - private val snapAnimationSpec: AnimationSpec, - private val density: Density -) : FlingBehavior { - private val MinFlingVelocityDp = 400.dp - private val visibleItemsInfo: List - get() = lazyListState.layoutInfo.visibleItemsInfo - - private val itemSize: Float - get() = if (visibleItemsInfo.isNotEmpty()) { - visibleItemsInfo.fastSumBy { it.size } / visibleItemsInfo.size.toFloat() - } else { - 0f - } - - private val velocityThreshold = with(density) { MinFlingVelocityDp.toPx() } - private var motionScaleDuration = object : MotionDurationScale { - override val scaleFactor: Float - get() = DefaultScrollMotionDurationScaleFactor - } - - override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { - val (remainingOffset, remainingState) = fling(initialVelocity) - - // No remaining offset means we've used everything, no need to propagate velocity. Otherwise - // we couldn't use everything (probably because we have hit the min/max bounds of the - // containing layout) we should propagate the offset. - return if (remainingOffset == 0f) 0f else remainingState.velocity - } - - private suspend fun ScrollScope.fling( - initialVelocity: Float - ): AnimationResult { - // If snapping from scroll (short snap) or fling (long snap) - val result = withContext(motionScaleDuration) { - if (abs(initialVelocity) <= abs(velocityThreshold)) { - shortSnap(initialVelocity) - } else { - longSnap(initialVelocity) - } - } - - return result - } - - private suspend fun ScrollScope.shortSnap( - velocity: Float - ): AnimationResult { - - val closestOffset = findClosestOffset(0f, lazyListState) - - val animationState = AnimationState(0f, velocity) - return animateSnap( - closestOffset, - closestOffset, - animationState, - snapAnimationSpec - ) - } - - private suspend fun ScrollScope.longSnap( - initialVelocity: Float - ): AnimationResult { - - val offset = - decayAnimationSpec.calculateTargetValue(0f, initialVelocity).absoluteValue - - val finalDecayOffset = (offset - itemSize).coerceAtLeast(0f) - val initialOffset = if (finalDecayOffset == 0f) { - finalDecayOffset - } else { - finalDecayOffset * initialVelocity.sign - } - - val (remainingOffset, animationState) = runApproach( - initialOffset, - initialVelocity - ) - - return animateSnap( - remainingOffset, - remainingOffset, - animationState.copy(value = 0f), - snapAnimationSpec - ) - } - - private suspend fun ScrollScope.runApproach( - initialTargetOffset: Float, - initialVelocity: Float - ): AnimationResult { - val animationState = AnimationState(initialValue = 0f, initialVelocity = initialVelocity) - val (_, currentAnimationState) = with(this) { - animateDecay(initialTargetOffset, animationState, decayAnimationSpec) - } - val remainingOffset = - findClosestOffset(currentAnimationState.velocity, lazyListState) - // will snap the remainder - return AnimationResult(remainingOffset, currentAnimationState) - } - - override fun equals(other: Any?): Boolean { - return if (other is SnapFlingBehavior) { - other.snapAnimationSpec == this.snapAnimationSpec && - other.decayAnimationSpec == this.decayAnimationSpec && - other.lazyListState == this.lazyListState && - other.density == this.density - } else { - false - } - } - - override fun hashCode(): Int = 0 - .let { 31 * it + snapAnimationSpec.hashCode() } - .let { 31 * it + decayAnimationSpec.hashCode() } - .let { 31 * it + lazyListState.hashCode() } - .let { 31 * it + density.hashCode() } - - private operator fun > ClosedFloatingPointRange.component1(): T = - this.start - - private operator fun > ClosedFloatingPointRange.component2(): T = - this.endInclusive - - private fun findClosestOffset( - velocity: Float, - lazyListState: LazyListState - ): Float { - - fun Float.isValidDistance(): Boolean { - return this != Float.POSITIVE_INFINITY && this != Float.NEGATIVE_INFINITY - } - - fun calculateSnappingOffsetBounds(): ClosedFloatingPointRange { - var lowerBoundOffset = Float.NEGATIVE_INFINITY - var upperBoundOffset = Float.POSITIVE_INFINITY - - with(lazyListState.layoutInfo) { - visibleItemsInfo.fastForEach { item -> - val offset = - calculateDistanceToDesiredSnapPosition(this, item) - - // Find item that is closest to the center - if (offset <= 0 && offset > lowerBoundOffset) { - lowerBoundOffset = offset - } - - // Find item that is closest to center, but after it - if (offset >= 0 && offset < upperBoundOffset) { - upperBoundOffset = offset - } - } - } - - return lowerBoundOffset.rangeTo(upperBoundOffset) - } - - val (lowerBound, upperBound) = calculateSnappingOffsetBounds() - - val finalDistance = when (sign(velocity)) { - 0f -> { - if (abs(upperBound) <= abs(lowerBound)) { - upperBound - } else { - lowerBound - } - } - - 1f -> upperBound - -1f -> lowerBound - else -> 0f - } - - return if (finalDistance.isValidDistance()) { - finalDistance - } else { - 0f - } - } - - /** - * Run a [DecayAnimationSpec] animation up to before [targetOffset] using [animationState] - * - * @param targetOffset The destination of this animation. Since this is a decay animation, we can - * use this value to prevent the animation to run until the end. - * @param animationState The previous [AnimationState] for continuation purposes. - * @param decayAnimationSpec The [DecayAnimationSpec] that will drive this animation - */ - private suspend fun ScrollScope.animateDecay( - targetOffset: Float, - animationState: AnimationState, - decayAnimationSpec: DecayAnimationSpec - ): AnimationResult { - var previousValue = 0f - - fun AnimationScope.consumeDelta(delta: Float) { - val consumed = scrollBy(delta) - if (abs(delta - consumed) > 0.5f) cancelAnimation() - } - - animationState.animateDecay( - decayAnimationSpec, - sequentialAnimation = animationState.velocity != 0f - ) { - if (abs(value) >= abs(targetOffset)) { - val finalValue = value.coerceToTarget(targetOffset) - val finalDelta = finalValue - previousValue - consumeDelta(finalDelta) - cancelAnimation() - } else { - val delta = value - previousValue - consumeDelta(delta) - previousValue = value - } - } - return AnimationResult( - targetOffset - previousValue, - animationState - ) - } - - /** - * Runs a [AnimationSpec] to snap the list into [targetOffset]. Uses [cancelOffset] to stop this - * animation before it reaches the target. - * - * @param targetOffset The final target of this animation - * @param cancelOffset If we'd like to finish the animation earlier we use this value - * @param animationState The current animation state for continuation purposes - * @param snapAnimationSpec The [AnimationSpec] that will drive this animation - */ - private suspend fun ScrollScope.animateSnap( - targetOffset: Float, - cancelOffset: Float, - animationState: AnimationState, - snapAnimationSpec: AnimationSpec - ): AnimationResult { - var consumedUpToNow = 0f - val initialVelocity = animationState.velocity - animationState.animateTo( - targetOffset, - animationSpec = snapAnimationSpec, - sequentialAnimation = (animationState.velocity != 0f) - ) { - val realValue = value.coerceToTarget(cancelOffset) - val delta = realValue - consumedUpToNow - val consumed = scrollBy(delta) - // stop when unconsumed or when we reach the desired value - if (abs(delta - consumed) > 0.5f || realValue != value) { - cancelAnimation() - } - consumedUpToNow += consumed - } - - // Always course correct velocity so they don't become too large. - val finalVelocity = animationState.velocity.coerceToTarget(initialVelocity) - return AnimationResult( - targetOffset - consumedUpToNow, - animationState.copy(velocity = finalVelocity) - ) - } - - private fun Float.coerceToTarget(target: Float): Float { - if (target == 0f) return 0f - return if (target > 0) coerceAtMost(target) else coerceAtLeast(target) - } - - private fun calculateDistanceToDesiredSnapPosition( - layoutInfo: LazyListLayoutInfo, - item: LazyListItemInfo - ): Float { - val containerSize = - with(layoutInfo) { singleAxisViewportSize - beforeContentPadding - afterContentPadding } - - val desiredDistance = - containerSize.toFloat() / 2 - item.size.toFloat() / 2 // snap to center - - val itemCurrentPosition = item.offset - return itemCurrentPosition - desiredDistance - } - - private val LazyListLayoutInfo.singleAxisViewportSize: Int - get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width - - private val DefaultScrollMotionDurationScaleFactor = 1f - -} - -private class AnimationResult( - val remainingOffset: T, - val currentAnimationState: AnimationState -) { - operator fun component1(): T = remainingOffset - operator fun component2(): AnimationState = currentAnimationState -} - diff --git a/toolkit/indoors/build.gradle.kts b/toolkit/indoors/build.gradle.kts index 304ce3010..533ee7518 100644 --- a/toolkit/indoors/build.gradle.kts +++ b/toolkit/indoors/build.gradle.kts @@ -78,7 +78,7 @@ dependencies { implementation(libs.bundles.core) implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) - implementation(libs.androidx.material.icons) + implementation(libs.bundles.icons) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) androidTestImplementation(project(":geoview-compose")) diff --git a/toolkit/offline/build.gradle.kts b/toolkit/offline/build.gradle.kts index aeebc6619..2d0db3245 100644 --- a/toolkit/offline/build.gradle.kts +++ b/toolkit/offline/build.gradle.kts @@ -103,7 +103,7 @@ dependencies { implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) implementation(libs.androidx.work.runtime.ktx) - implementation(libs.androidx.material.icons) + implementation(libs.bundles.icons) implementation(libs.kotlinx.serialization.json) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) diff --git a/toolkit/popup/build.gradle.kts b/toolkit/popup/build.gradle.kts index 834c70ff5..94b78dce9 100644 --- a/toolkit/popup/build.gradle.kts +++ b/toolkit/popup/build.gradle.kts @@ -104,7 +104,7 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.navigation) implementation(libs.kotlinx.serialization.json) - implementation(libs.androidx.material.icons) + implementation(libs.bundles.icons) implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.exoplayer.dash) implementation(libs.androidx.media3.ui) diff --git a/toolkit/utilitynetworks/build.gradle.kts b/toolkit/utilitynetworks/build.gradle.kts index d6e471abe..6930095bd 100644 --- a/toolkit/utilitynetworks/build.gradle.kts +++ b/toolkit/utilitynetworks/build.gradle.kts @@ -116,7 +116,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.navigation) - implementation(libs.androidx.material.icons) + implementation(libs.bundles.icons) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.truth) androidTestImplementation(platform(libs.androidx.compose.bom)) From ce7fc20fdd70125ef11bbcee50bff3855cce2126 Mon Sep 17 00:00:00 2001 From: Kaushik Meesala Date: Tue, 9 Dec 2025 12:57:05 -0800 Subject: [PATCH 2/8] update to m3 date picker --- .../toolkit/featureforms/FeatureFormState.kt | 2 +- .../datetime/picker/DateTimePicker.kt | 56 ++++---- .../datetime/picker/DateTimePickerState.kt | 47 +++--- .../datetime/picker/material3/Strings.kt | 136 ------------------ .../src/main/res/values-ar/strings.xml | 35 ----- .../src/main/res/values-bg/strings.xml | 35 ----- .../src/main/res/values-bs/strings.xml | 35 ----- .../src/main/res/values-ca/strings.xml | 35 ----- .../src/main/res/values-cs/strings.xml | 35 ----- .../src/main/res/values-da/strings.xml | 35 ----- .../src/main/res/values-de/strings.xml | 35 ----- .../src/main/res/values-el/strings.xml | 35 ----- .../src/main/res/values-es/strings.xml | 35 ----- .../src/main/res/values-et/strings.xml | 35 ----- .../src/main/res/values-fi/strings.xml | 35 ----- .../src/main/res/values-fr/strings.xml | 35 ----- .../src/main/res/values-hr/strings.xml | 35 ----- .../src/main/res/values-hu/strings.xml | 35 ----- .../src/main/res/values-in/strings.xml | 35 ----- .../src/main/res/values-it/strings.xml | 35 ----- .../src/main/res/values-iw/strings.xml | 35 ----- .../src/main/res/values-ja/strings.xml | 35 ----- .../src/main/res/values-ko/strings.xml | 35 ----- .../src/main/res/values-lt/strings.xml | 35 ----- .../src/main/res/values-lv/strings.xml | 35 ----- .../src/main/res/values-nl/strings.xml | 35 ----- .../src/main/res/values-no/strings.xml | 35 ----- .../src/main/res/values-pl/strings.xml | 35 ----- .../src/main/res/values-pt-rBR/strings.xml | 35 ----- .../src/main/res/values-pt-rPT/strings.xml | 35 ----- .../src/main/res/values-ro/strings.xml | 35 ----- .../src/main/res/values-ru/strings.xml | 35 ----- .../src/main/res/values-sk/strings.xml | 35 ----- .../src/main/res/values-sl/strings.xml | 35 ----- .../src/main/res/values-sr/strings.xml | 35 ----- .../src/main/res/values-sv/strings.xml | 35 ----- .../src/main/res/values-th/strings.xml | 35 ----- .../src/main/res/values-tr/strings.xml | 35 ----- .../src/main/res/values-uk/strings.xml | 35 ----- .../src/main/res/values-vi/strings.xml | 35 ----- .../src/main/res/values-zh-rHK/strings.xml | 35 ----- .../src/main/res/values-zh-rTW/strings.xml | 35 ----- .../src/main/res/values-zh/strings.xml | 35 ----- .../src/main/res/values/strings.xml | 35 ----- 44 files changed, 49 insertions(+), 1592 deletions(-) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/FeatureFormState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/FeatureFormState.kt index ee0a2d67c..70942b3dd 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/FeatureFormState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/FeatureFormState.kt @@ -546,7 +546,7 @@ internal fun createFieldState( required = element.isRequired, visible = element.isVisible, minEpochMillis = input.min, - maxEpochMillis = input.min, + maxEpochMillis = input.max, shouldShowTime = input.includeTime, fieldType = element.fieldType ), diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt index 37b99f4ca..99c31ea24 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt @@ -35,7 +35,9 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.AccessTime import androidx.compose.material.icons.rounded.CalendarMonth import androidx.compose.material3.AlertDialog +import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.DisplayMode import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -112,6 +114,7 @@ private fun calcYearRangeStart(min: Long?, selectedDateTime: Long?): Int { return year ?: DatePickerDefaults.YearRange.first } + private fun calcYearRangeEnd(max: Long?, selectedDateTime: Long?): Int { val year = if (max != null && selectedDateTime != null) { if (max > selectedDateTime) { @@ -127,6 +130,7 @@ private fun calcYearRangeEnd(max: Long?, selectedDateTime: Long?): Int { return year ?: DatePickerDefaults.YearRange.last } + /** * A material3 date and time picker presented as an [AlertDialog]. * @@ -155,29 +159,22 @@ internal fun DateTimePicker( // calculate the date ranges from the state val datePickerRange = IntRange( start = calcYearRangeStart(state.minDateTime?.toEpochMilli(), state.selectedDateTimeMillis), - endInclusive = calcYearRangeEnd(state.maxDateTime?.toEpochMilli(), state.selectedDateTimeMillis) + endInclusive = calcYearRangeEnd( + state.maxDateTime?.toEpochMilli(), + state.selectedDateTimeMillis + ) ) // The picker input type, date or time. val pickerInput by state.activePickerInput // DateTime from the state's value val dateTime by state.dateTime // create and remember a DatePickerState - -// val datePickerState = rememberSaveable(dateTime, saver = DatePickerState.Saver()) { -// DatePickerState( -// initialSelectedDateMillis = dateTime.dateForPicker, -// initialDisplayedMonthMillis = dateTime.dateForPicker -// ?: (state.minDateTime?.toEpochMilli() ?: state.maxDateTime?.toEpochMilli()), -// datePickerRange, -// DisplayMode.Picker -// ) -// } val datePickerState = rememberDatePickerState( initialSelectedDateMillis = dateTime.dateForPicker, initialDisplayedMonthMillis = dateTime.dateForPicker ?: (state.minDateTime?.toEpochMilli() ?: state.maxDateTime?.toEpochMilli()), yearRange = datePickerRange, - initialDisplayMode = androidx.compose.material3.DisplayMode.Picker, + initialDisplayMode = DisplayMode.Picker, selectableDates = state ) // create a DateTimePickerDialog @@ -264,13 +261,20 @@ private fun (ColumnScope).PickerContent( TimePicker(state = timePickerState, modifier = Modifier.padding(10.dp)) } else { key(state.dateTime.value) { - androidx.compose.material3.DatePicker( + DatePicker( state = datePickerState, -// dateValidator = { timeStamp -> -// state.dateValidator(timeStamp) -// }, - title = { title(if (style == DateTimePickerStyle.Date) null else Icons.Rounded.AccessTime) }, - + title = { + title( + if (style == DateTimePickerStyle.Date) { + null + } else { + Icons.Rounded.AccessTime + } + ) + }, + colors = DatePickerDefaults.colors( + containerColor = MaterialTheme.colorScheme.surface + ) ) } } @@ -338,7 +342,7 @@ private fun PickerFooter( // only enable Today button if today is within the range if provided // the date validator assumes the Long is from the picker, // i.e. offset from UTC. - enabled = state.dateValidator( + enabled = state.isSelectableDate( UtcDateTime.create(Instant.now().toEpochMilli()).dateForPicker!! ), modifier = Modifier.semantics { contentDescription = "current date or time button" } @@ -351,7 +355,9 @@ private fun PickerFooter( } } Spacer(modifier = Modifier.weight(1f)) - TextButton(onClick = onCancelled, modifier = Modifier.semantics { contentDescription = "cancel" }) { + TextButton( + onClick = onCancelled, + modifier = Modifier.semantics { contentDescription = "cancel" }) { Text(stringResource(R.string.cancel)) } TextButton(onClick = onConfirmed, enabled = confirmEnabled) { @@ -410,7 +416,7 @@ private object DateTimePickerDialogTokens { * [Modifier.widthIn] is used. This is useful when different layouts are needed in portrait * and landscape orientations. */ -internal fun Modifier.widthWithOrientation(width: Dp) : Modifier = composed { +internal fun Modifier.widthWithOrientation(width: Dp): Modifier = composed { val configuration = LocalConfiguration.current if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { this.widthIn(width) @@ -442,11 +448,3 @@ private fun DateTimePickerPreview() { ) DateTimePicker(state = state, {}, {}, {}) } - - -@Composable -@Preview -private fun DatePr() { - val state = rememberDatePickerState() - androidx.compose.material3.DatePicker(state) -} diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePickerState.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePickerState.kt index 44dd0abf1..37348d030 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePickerState.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePickerState.kt @@ -27,6 +27,7 @@ import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable import com.arcgismaps.toolkit.featureforms.internal.components.base.ValidationErrorState import com.arcgismaps.toolkit.featureforms.internal.components.datetime.formattedDateTime +import com.arcgismaps.toolkit.featureforms.internal.components.datetime.picker.UtcDateTime.Companion.createFromPickerValues import com.arcgismaps.toolkit.featureforms.internal.components.datetime.toDateMillis import com.arcgismaps.toolkit.featureforms.internal.components.datetime.toDateTimeInUtcZone import com.arcgismaps.toolkit.featureforms.internal.components.datetime.toZonedDateTime @@ -201,13 +202,6 @@ internal interface DateTimePickerState : SelectableDates { * is returned. Both the [minDateTime] and [maxDateTime] are included in the range. */ fun dateTimeValidator(timeStamp: Long): Boolean - - /** - * Validates if the UTC date of the [timeStamp] is between the dates of the given datetime ranges [minDateTime] - * and [maxDateTime] if they were provided. Returns true if the validation was successful, otherwise false - * is returned. Both the [minDateTime] and [maxDateTime] are included in the range. - */ - fun dateValidator(timeStamp: Long): Boolean /** * Sets the [dateTime]'s time value to the current time instant in local time. @@ -274,24 +268,6 @@ private class DateTimePickerStateImpl( utcDateTime <= it } ?: true } - - override fun dateValidator(timeStamp: Long): Boolean { - // the date validator is invoked by the date picker, - // which operates in milliseconds that are offset from UTC - // To compare it to min and max, the input must be converted - // to UTC. - val utcDate = UtcDateTime.create(timeStamp.minus(timeStamp.defaultTimeZoneOffset)).date!! - val minDate = UtcDateTime.create(minDateTime?.toEpochMilli()).date - val maxDate = UtcDateTime.create(maxDateTime?.toEpochMilli()).date - - return minDate?.let { min -> - maxDate?.let { max -> - utcDate in min..max - } ?: (utcDate >= min) - } ?: maxDate?.let { - utcDate <= it - } ?: true - } override fun now() { val now = Instant.now().toEpochMilli().toZonedDateTime() @@ -312,7 +288,26 @@ private class DateTimePickerStateImpl( } override fun isSelectableDate(utcTimeMillis: Long): Boolean { - return dateValidator(utcTimeMillis) + // The isSelectableDate is invoked by the date picker with the specific date in utc millis + // To compare it to min and max, they must be converted to utc millis as well. + val minDate = UtcDateTime.create(minDateTime?.toEpochMilli()).date + val maxDate = UtcDateTime.create(maxDateTime?.toEpochMilli()).date + + return when { + minDate != null && maxDate != null -> { + utcTimeMillis in minDate..maxDate + } + + minDate != null -> { + utcTimeMillis >= minDate + } + + maxDate != null -> { + utcTimeMillis <= maxDate + } + + else -> true + } } override fun isSelectableYear(year: Int): Boolean { diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/material3/Strings.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/material3/Strings.kt index 9daa1e5f8..698019be0 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/material3/Strings.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/material3/Strings.kt @@ -38,38 +38,6 @@ internal value class Strings private constructor( private var id = 0 private fun nextId() = id++ - val DatePickerTitle = Strings() - val DatePickerHeadline = Strings() - val DatePickerYearPickerPaneTitle = Strings() - val DatePickerSwitchToYearSelection = Strings() - val DatePickerSwitchToDaySelection = Strings() - val DatePickerSwitchToNextMonth = Strings() - val DatePickerSwitchToPreviousMonth = Strings() - val DatePickerNavigateToYearDescription = Strings() - val DatePickerHeadlineDescription = Strings() - val DatePickerNoSelectionDescription = Strings() - val DatePickerTodayDescription = Strings() - val DatePickerScrollToShowLaterYears = Strings() - val DatePickerScrollToShowEarlierYears = Strings() - val DateInputTitle = Strings() - val DateInputHeadline = Strings() - val DateInputLabel = Strings() - val DateInputHeadlineDescription = Strings() - val DateInputNoInputDescription = Strings() - val DateInputInvalidNotAllowed = Strings() - val DateInputInvalidForPattern = Strings() - val DateInputInvalidYearRange = Strings() - val DatePickerSwitchToCalendarMode = Strings() - val DatePickerSwitchToInputMode = Strings() - val DateRangePickerTitle = Strings() - val DateRangePickerStartHeadline = Strings() - val DateRangePickerEndHeadline = Strings() - val DateRangePickerScrollToShowNextMonth = Strings() - val DateRangePickerScrollToShowPreviousMonth = Strings() - val DateRangePickerDayInRange = Strings() - val DateRangeInputTitle = Strings() - val DateRangeInputInvalidRangeInput = Strings() - val TimePickerAM = Strings() val TimePickerPM = Strings() val TimePickerPeriodToggle = Strings() @@ -92,110 +60,6 @@ internal fun getString(string: Strings): String { LocalConfiguration.current val resources = LocalContext.current.resources return when (string) { - - Strings.DatePickerTitle -> resources.getString( - R.string.ff_date_picker_title - ) - - Strings.DatePickerHeadline -> resources.getString( - R.string.ff_date_picker_headline - ) - - Strings.DatePickerYearPickerPaneTitle -> resources.getString( - R.string.ff_date_picker_year_picker_pane_title - ) - - Strings.DatePickerSwitchToYearSelection -> resources.getString( - R.string.ff_date_picker_switch_to_year_selection - ) - - Strings.DatePickerSwitchToDaySelection -> resources.getString( - R.string.ff_date_picker_switch_to_day_selection - ) - - Strings.DatePickerSwitchToNextMonth -> resources.getString( - R.string.ff_date_picker_switch_to_next_month - ) - - Strings.DatePickerSwitchToPreviousMonth -> resources.getString( - R.string.ff_date_picker_switch_to_previous_month - ) - - Strings.DatePickerNavigateToYearDescription -> resources.getString( - R.string.ff_date_picker_navigate_to_year_description - ) - - Strings.DatePickerHeadlineDescription -> resources.getString( - R.string.ff_date_picker_headline_description - ) - - Strings.DatePickerNoSelectionDescription -> resources.getString( - R.string.ff_date_picker_no_selection_description - ) - Strings.DatePickerTodayDescription -> resources.getString( - R.string.ff_date_picker_today_description - ) - Strings.DatePickerScrollToShowLaterYears -> resources.getString( - R.string.ff_date_picker_scroll_to_later_years - ) - Strings.DatePickerScrollToShowEarlierYears -> resources.getString( - R.string.ff_date_picker_scroll_to_earlier_years - ) - Strings.DateInputTitle -> resources.getString( - R.string.ff_date_input_title - ) - Strings.DateInputHeadline -> resources.getString( - R.string.ff_date_input_headline - ) - Strings.DateInputLabel -> resources.getString( - R.string.ff_date_input_label - ) - Strings.DateInputHeadlineDescription -> resources.getString( - R.string.ff_date_input_headline_description - ) - Strings.DateInputNoInputDescription -> resources.getString( - R.string.ff_date_input_no_input_description - ) - Strings.DateInputInvalidNotAllowed -> resources.getString( - R.string.ff_date_input_invalid_not_allowed - ) - Strings.DateInputInvalidForPattern -> resources.getString( - R.string.ff_date_input_invalid_for_pattern - ) - Strings.DateInputInvalidYearRange -> resources.getString( - R.string.ff_date_input_invalid_year_range - ) - Strings.DatePickerSwitchToCalendarMode -> resources.getString( - R.string.ff_date_picker_switch_to_calendar_mode - ) - Strings.DatePickerSwitchToInputMode -> resources.getString( - R.string.ff_date_picker_switch_to_input_mode - ) - Strings.DateRangePickerTitle -> resources.getString( - R.string.ff_date_range_picker_title - ) - Strings.DateRangePickerStartHeadline -> resources.getString( - R.string.ff_date_range_picker_start_headline - ) - Strings.DateRangePickerEndHeadline -> resources.getString( - R.string.ff_date_range_picker_end_headline - ) - Strings.DateRangePickerScrollToShowNextMonth -> resources.getString( - R.string.ff_date_range_picker_scroll_to_next_month - ) - Strings.DateRangePickerScrollToShowPreviousMonth -> resources.getString( - R.string.ff_date_range_picker_scroll_to_previous_month - ) - Strings.DateRangePickerDayInRange -> resources.getString( - R.string.ff_date_range_picker_day_in_range - ) - Strings.DateRangeInputTitle -> resources.getString( - R.string.ff_date_range_input_title - ) - Strings.DateRangeInputInvalidRangeInput -> resources.getString( - R.string.ff_date_range_input_invalid_range_input - ) - Strings.TimePickerAM -> resources.getString(R.string.ff_time_picker_am) Strings.TimePickerPM -> resources.getString(R.string.ff_time_picker_pm) Strings.TimePickerPeriodToggle -> resources.getString(R.string.ff_time_picker_period_toggle_description) diff --git a/toolkit/featureforms/src/main/res/values-ar/strings.xml b/toolkit/featureforms/src/main/res/values-ar/strings.xml index 00f552a34..b4a051037 100644 --- a/toolkit/featureforms/src/main/res/values-ar/strings.xml +++ b/toolkit/featureforms/src/main/res/values-ar/strings.xml @@ -73,41 +73,6 @@ يجب أن يكون التاريخ في %1$s أو قبله - التاريخ الذي تم إدخاله - التاريخ الذي تم إدخاله: %1$s - التاريخ لا يتطابق مع النمط المتوقع: %1$s - التاريخ غير مسموح به: %1$s - - التاريخ خارج نطاق السنة المتوقعة %1$s -%2$s - - التاريخ - لا شيء - تحديد التاريخ - "التاريخ المحدد" - التحديد الحالي: %1$s - انتقال إلى السنة %1$s - لا شيء - التمرير لإظهار السنوات السابقة - التمرير لإظهار السنوات اللاحقة - التبديل إلى وضع إدخال التقويم - - "اسحب لتحديد سنة، أو اضغط للتبديل مرة أخرى إلى تحديد يوم" - - التبديل إلى وضع إدخال النص - "التغيير إلى الشهر القادم" - "التغيير إلى الشهر السابق" - "التبديل إلى اختيار سنة" - "تحديد تاريخ" - اليوم - منتقي السنة مرئي - إدخال النطاق الزمني غير صالح - إدخال تواريخ - ضمن النطاق - تاريخ الانتهاء - التمرير لإظهار الشهر التالي - التمرير لإظهار الشهر السابق - تاريخ البدء - تحديد التواريخ صباحًا diff --git a/toolkit/featureforms/src/main/res/values-bg/strings.xml b/toolkit/featureforms/src/main/res/values-bg/strings.xml index 6cb9adfb1..1d87b9f09 100644 --- a/toolkit/featureforms/src/main/res/values-bg/strings.xml +++ b/toolkit/featureforms/src/main/res/values-bg/strings.xml @@ -73,41 +73,6 @@ Датата трябва да е %1$s или преди - Въведена дата - Въведена дата: %1$s - Датата не отговаря на очаквания модел: %1$s - Датата не е разрешена: %1$s - - Датата е извън очаквания диапазон от години %1$s - %2$s - - Дата - Няма - Изберете дата - "Избрана дата" - Текущ избор: %1$s - Навигирайте до година %1$s - Няма - Превъртете, за да се покажат по-ранни години - Превъртете, за да се покажат по-късни години - Преминете към режим на въвеждане на календар - - "Плъзнете, за да изберете година, или докоснете, за да преминете обратно към избор на ден" - - Преминете към режим на въвеждане на текст - "Променете на следващия месец" - "Променете на предходния месец" - "Преминете към избор на година" - "Изберете дата" - Днес - Видим избор на година - Въвеждане на невалиден диапазон от дати - Въведете дати - В диапазона - Крайна дата - Превъртете, за да се покаже следващият месец - Превъртете, за да се покаже предходният месец - Начална дата - Изберете дати Преди обяд diff --git a/toolkit/featureforms/src/main/res/values-bs/strings.xml b/toolkit/featureforms/src/main/res/values-bs/strings.xml index e41b817b5..bfb59be6e 100644 --- a/toolkit/featureforms/src/main/res/values-bs/strings.xml +++ b/toolkit/featureforms/src/main/res/values-bs/strings.xml @@ -73,41 +73,6 @@ Datum početka mora biti na ili prije datuma %1$s. - Uneseni datum - Uneseni datum: %1$s - Datum ne odgovara očekivanom uzorku:: %1$s - Datum nije dopušten: %1$s - - Datum izvan očekivanog raspona godina %1$s - %2$s - - Datum - Nema - Odaberi datum - "Odabran datum" - Trenutačni odabir: %1$s - Navigiraj na godinu %1$s - Nema - Pomakni radi prikaza ranijih godina - Pomakni radi prikaza kasnijih godina - Prebaci na način unosa u kalendar - - "Prevucite prstom za odabir godine ili dodirnite za povratak na odabir dana" - - Prebaci na način unosa teksta - "Promijeni na sljedeći mjesec" - "Promijeni na prethodni mjesec" - "Prebaci na odabir godine" - "Odaberi datum" - Danas - Odabirač godine vidljiv - Neispravni unos raspona datuma - Unesite datume - U rasponu - Datum kraja - Pomaknite se za prikaz sljedećeg mjeseca - Pomaknite se za prikaz prethodnog mjeseca - Datum početka - Odaberi datume Ujutro diff --git a/toolkit/featureforms/src/main/res/values-ca/strings.xml b/toolkit/featureforms/src/main/res/values-ca/strings.xml index 8303d0663..116eb692e 100644 --- a/toolkit/featureforms/src/main/res/values-ca/strings.xml +++ b/toolkit/featureforms/src/main/res/values-ca/strings.xml @@ -73,41 +73,6 @@ La data ha de ser el dia %1$s o anterior - Data introduïda - Data introduïda: %1$s - La data no coincideix amb el patró esperat: %1$s - Data no permesa: %1$s - - La data està fora de l\'interval d\'anys esperat %1$s-%2$s - - Data - Cap - Seleccioneu la data - "Data seleccionada" - Selecció actual: %1$s - Ves a l\'any %1$s - Cap - Desplaceu-vos per veure els anys anteriors - Desplaceu-vos per veure els anys posteriors - Canvia al mode d\'entrada de calendari - - "Feu lliscar el dit per seleccionar un any o toqueu per canviar a la selecció d\'un dia" - - Canvia al mode d\'entrada de text - "Canvia al mes següent" - "Canvia al mes anterior" - "Canvia a la selecció d\'un any" - "Seleccioneu una data" - Avui - Selector d\'any visible - Entrada d\'interval de dates no vàlida - Introduïu les dates - A l\'interval - Data de finalització - Desplaceu-vos per mostrar el mes següent - Desplaceu-vos per mostrar el mes anterior - Data d\'inici - Seleccioneu les dates AM diff --git a/toolkit/featureforms/src/main/res/values-cs/strings.xml b/toolkit/featureforms/src/main/res/values-cs/strings.xml index 9fa56d62d..f40d84615 100644 --- a/toolkit/featureforms/src/main/res/values-cs/strings.xml +++ b/toolkit/featureforms/src/main/res/values-cs/strings.xml @@ -73,41 +73,6 @@ Datum musí být %1$s nebo dřívější - Zadané datum - Zadané datum: %1$s - Datum neodpovídá očekávanému vzorci: %1$s - Datum nepovoleno: %1$s - - Datum mimo očekávaný rozsah let %1$s–%2$s - - Datum - Žádný - Vyberte datum - "Vybrané datum" - Aktuální výběr: %1$s - Přejít na rok %1$s - Žádný - Posunout a zobrazit předešlé roky - Posunout a zobrazit následující roky - Přepnout do režimu zadávání kalendáře - - "Posunutím vyberte rok nebo klepnutím přepněte zpět na výběr dne" - - Přepnout do režimu zadávání textu - "Změnit na další měsíc" - "Změnit na předchozí měsíc" - "Přepnout na výběr roku" - "Vybrat datum" - Dnes - Výběr roku viditelný - Neplatné zadání rozsahu dat - Zadejte data - V rozsahu - Koncové datum - Posunout a zobrazit další měsíc - Posunout a zobrazit předchozí měsíc - Počáteční datum - Vyberte data dopoledne diff --git a/toolkit/featureforms/src/main/res/values-da/strings.xml b/toolkit/featureforms/src/main/res/values-da/strings.xml index 118076223..b1beabd8e 100644 --- a/toolkit/featureforms/src/main/res/values-da/strings.xml +++ b/toolkit/featureforms/src/main/res/values-da/strings.xml @@ -73,41 +73,6 @@ Dato skal være på eller før %1$s - Indtastet dato - Indtastet dato: %1$s - Date stemmer ikke overens med forventet mønster: %1$s - Dato ikke tilladt: %1$s - - Dato ligger uden for det forventede årsinterval %1$s - %2$s - - Dato - Ingen - Vælg dato - "Valg dato" - Aktuel markering: %1$s - Naviger til år %1$s - Ingen - Rul for at vise tidligere år - Rul for at vise senere år - Skift til kalenderinputtilstand - - "Stryg for at vælge et år, eller tryk for at skifte tilbage til at vælge en dato" - - Skift til tekstinputtilstand - "Skift til næste måned" - "Skift til forrige måned" - "Skift til at vælge et år" - "Vælg dato" - I dag - Årvælger er synlig - Ugyldigt input af datointerval - Angiv datoer - Inden for interval - Slutdato - Rul for at vise næste måned - Rul for at vise forrige måned - Startdato - Vælg datoer AM diff --git a/toolkit/featureforms/src/main/res/values-de/strings.xml b/toolkit/featureforms/src/main/res/values-de/strings.xml index 7efd8ab06..a51de162e 100644 --- a/toolkit/featureforms/src/main/res/values-de/strings.xml +++ b/toolkit/featureforms/src/main/res/values-de/strings.xml @@ -73,41 +73,6 @@ Das Datum muss auf oder vor folgendem Datum liegen: %1$s - Eingegebenes Datum - Eingegebenes Datum: %1$s - Datum stimmt nicht mit erwartetem Muster überein: %1$s - Datum nicht zulässig: %1$s - - Datum liegt außerhalb des erwarteten Jahresbereichs %1$s–%2$s. - - Datum - Keine - Datum auswählen - "Ausgewähltes Datum" - Aktuelle Auswahl: %1$s - Zum Jahr %1$s navigieren - Keine - Zum Anzeigen früherer Jahre scrollen - Zum Anzeigen späterer Jahre scrollen - Zum Eingabemodus für Kalender wechseln - - "Zum Auswählen eines Jahres streichen oder zum Zurückgehen zur Auswahl eines Tages tippen" - - Zum Eingabemodus für Text wechseln - "In nächsten Monat ändern" - "In vorherigen Monat ändern" - "Zur Auswahl eines Jahres wechseln" - "Datum auswählen" - Heute - Jahresauswahl sichtbar - Ungültige Datumsbereichseingabe - Daten eingeben - Im Bereich - Enddatum - Zum Anzeigen des nächsten Monats scrollen - Zum Anzeigen des vorherigen Monats scrollen - Startdatum - Daten auswählen AM diff --git a/toolkit/featureforms/src/main/res/values-el/strings.xml b/toolkit/featureforms/src/main/res/values-el/strings.xml index 40a3a7dff..137d5a112 100644 --- a/toolkit/featureforms/src/main/res/values-el/strings.xml +++ b/toolkit/featureforms/src/main/res/values-el/strings.xml @@ -73,41 +73,6 @@ Η ημερομηνία πρέπει να είναι στις ή πριν από τις %1$s - Καταχωρημένη ημερομηνία - Καταχωρημένη ημερομηνία: %1$s - Η ημερομηνία δεν συμφωνεί με το αναμενόμενο πρότυπο: %1$s - Η ημερομηνία δεν επιτρέπεται: %1$s - - Η ημερομηνία δεν εμπίπτει στο αναμενόμενο εύρος τιμών για το έτος %1$s - %2$s - - Ημερομηνία - Καθόλου - Επιλογή ημερομηνίας - "Επιλεγμένη ημερομηνία" - Τρέχουσα επιλογή: %1$s - Πλοήγηση στο έτος %1$s - Καθόλου - Κυλήστε για να εμφανιστούν τα προηγούμενα έτη - Κυλήστε για να εμφανιστούν τα μεταγενέστερα έτη - Αλλαγή σε λειτουργία εισαγωγής ημερολογίου - - "Αλλαγή για επιλογή ενός έτους ή πάτημα για επιστροφή για επιλογή μίας ημέρας" - - Αλλαγή σε λειτουργία εισαγωγής κειμένου - "Αλλαγή στον επόμενο μήνα" - "Αλλαγή στον προηγούμενο μήνα" - "Αλλαγή στην επιλογή ενός έτους" - "Επιλογή ημερομηνίας" - Σήμερα - Ορατός επιλογέας έτους - Μη έγκυρη εισαγωγή εύρους τιμών ημερομηνίας - Καταχώριση ημερομηνιών - Εντός του εύρους τιμών - Ημ/νία λήξης - Κυλήστε για να εμφανιστεί ο επόμενος μήνας - Κυλήστε για να εμφανιστεί ο προηγούμενος μήνας - Ημ/νία έναρξης - Επιλογή ημερομηνιών π.μ. diff --git a/toolkit/featureforms/src/main/res/values-es/strings.xml b/toolkit/featureforms/src/main/res/values-es/strings.xml index 9edf33b07..827029c5e 100644 --- a/toolkit/featureforms/src/main/res/values-es/strings.xml +++ b/toolkit/featureforms/src/main/res/values-es/strings.xml @@ -73,41 +73,6 @@ La fecha debe ser el %1$s o antes - Fecha introducida - Fecha introducida: %1$s - La fecha no tiene el patrón previsto: %1$s - Fecha no permitida: %1$s - - Fecha fuera del intervalo de años previsto %1$s - %2$s - - Fecha - Ninguno - Seleccionar fecha - "Fecha seleccionada" - Selección actual: %1$s - Ir al año %1$s - Ninguno - Desplazarse para mostrar años anteriores - Desplazarse para mostrar años posteriores - Cambiar al modo de entrada de calendario - - "Deslice para seleccionar un año o toque para volver y seleccionar un día" - - Cambiar al modo de entrada de texto - "Cambiar al mes siguiente" - "Cambiar para seleccionar un año" - "Cambiar para seleccionar un año" - "Seleccionar fecha" - Hoy - Selector de años visible - Entrada de rango de fechas no válido - Introducir fechas - En rango - Fecha de finalización - Desplazarse para mostrar el mes siguiente - Desplazarse para mostrar el mes anterior - Fecha de inicio - Seleccionar fechas a.m. diff --git a/toolkit/featureforms/src/main/res/values-et/strings.xml b/toolkit/featureforms/src/main/res/values-et/strings.xml index 5f1531b9e..2d09b0345 100644 --- a/toolkit/featureforms/src/main/res/values-et/strings.xml +++ b/toolkit/featureforms/src/main/res/values-et/strings.xml @@ -73,41 +73,6 @@ Kuupäev peab olema %1$s või varasem - Sisestatud kuupäev - Sisestatud kuupäev: %1$s - Kuupäev ei vasta eeldatud mustrile: %1$s - Kuupäev pole lubatud: %1$s - - Kuupäev on väljaspool eeldatud aastavahemikku: %1$s–%2$s - - Kuupäev - Puudub - Valige kuupäev - "Valitud kuupäev" - Praegune valik: %1$s - Liikuge aastasse %1$s - Puudub - Kerige varasemate aastate kuvamiseks - Kerige hilisemate aastate kuvamiseks - Aktiveerige kalendrisisestusrežiim - - "Nipsake aasta valimiseks või puudutage, et aktiveerida uuesti päeva valimine" - - Aktiveerige tekstisisestusrežiim - "Vali järgmine kuu" - "Vali eelmine kuu" - "Aktiveeri aasta valimine" - "Vali kuupäev" - Täna - Aastavalija on nähtav - Sobimatu kuupäevavahemiku sisend - Sisestage kuupäevad - Vahemikus - Lõppkuupäev - Kerige järgmise kuu kuvamiseks - Kerige eelmise kuu kuvamiseks - Alguskuupäev - Valige kuupäevad e.l. diff --git a/toolkit/featureforms/src/main/res/values-fi/strings.xml b/toolkit/featureforms/src/main/res/values-fi/strings.xml index 294ddc3dd..706d808e2 100644 --- a/toolkit/featureforms/src/main/res/values-fi/strings.xml +++ b/toolkit/featureforms/src/main/res/values-fi/strings.xml @@ -73,41 +73,6 @@ Päivämäärän on oltava %1$s tai sitä ennen - Annettu päivämäärä - Annettu päivämäärä: %1$s - Päivämäärä ei vastaa odotettua muotoa: %1$s - Päivämäärä ei sallittu: %1$s - - Päivämäärä odotetun vuosialueen ulkopuolella: %1$s–%2$s - - Päivämäärä - Ei mitään - Valitse päivämäärä - "Valittu päivämäärä" - Nykyinen valinta: %1$s - Siirry vuoteen %1$s - Ei mitään - Vieritä, jos haluat nähdä aiemmat vuodet - Vieritä, jos haluat nähdä myöhemmät vuodet - Vaihda kalenterin syöttötilaan - - "Valitse vuosi pyyhkäisemällä tai siirry takaisin päivän valintaan napauttamalla" - - Vaihda tekstinsyöttötilaan - "Muuta seuraavaksi kuukaudeksi" - "Muuta edelliseksi kuukaudeksi" - "Siirry vuoden valintaan" - "Valitse päivämäärä" - Tänään - Vuosivalitsin näkyvissä - Virheellinen päivämääräalueen syöttö - Anna päivämäärät - Alueella - Lopetuspäivämäärä - Vieritä, jos haluat nähdä seuraavan kuukauden - Vieritä, jos haluat nähdä edellisen kuukauden - Aloituspäivämäärä - Valitse päivämäärät AP diff --git a/toolkit/featureforms/src/main/res/values-fr/strings.xml b/toolkit/featureforms/src/main/res/values-fr/strings.xml index fc749d057..7ec801811 100644 --- a/toolkit/featureforms/src/main/res/values-fr/strings.xml +++ b/toolkit/featureforms/src/main/res/values-fr/strings.xml @@ -73,41 +73,6 @@ La date doit être le ou avant le %1$s - Date saisie - Date saisie : %1$s - La date ne correspond pas au modèle attendu : %1$s - Date non autorisée : %1$s - - Date hors de la plage d’années attendue %1$s – %2$s - - Date - Aucune - Sélectionner une date - "Date sélectionnée" - Sélection actuelle : %1$s - Accéder à l’année %1$s - Aucune - Faire défiler pour afficher les années précédentes - Faire défiler pour afficher les années suivantes - Basculer en mode de saisie Calendrier - - "Balayer pour sélectionner une année ou appuyer pour revenir à la sélection d’un jour" - - Basculer en mode de saisie Texte - "Passer au mois suivant" - "Passer au mois précédent" - "Passer à la sélection d’une année" - "Sélectionner une date" - Aujourd’hui - Sélecteur d’année visible - Plage de dates saisie non valide - Saisir des dates - Dans la plage - Date de fin - Faire défiler pour afficher le mois suivant - Faire défiler pour afficher le mois précédent - Date de début - Sélectionner des dates AM diff --git a/toolkit/featureforms/src/main/res/values-hr/strings.xml b/toolkit/featureforms/src/main/res/values-hr/strings.xml index e41b817b5..bfb59be6e 100644 --- a/toolkit/featureforms/src/main/res/values-hr/strings.xml +++ b/toolkit/featureforms/src/main/res/values-hr/strings.xml @@ -73,41 +73,6 @@ Datum početka mora biti na ili prije datuma %1$s. - Uneseni datum - Uneseni datum: %1$s - Datum ne odgovara očekivanom uzorku:: %1$s - Datum nije dopušten: %1$s - - Datum izvan očekivanog raspona godina %1$s - %2$s - - Datum - Nema - Odaberi datum - "Odabran datum" - Trenutačni odabir: %1$s - Navigiraj na godinu %1$s - Nema - Pomakni radi prikaza ranijih godina - Pomakni radi prikaza kasnijih godina - Prebaci na način unosa u kalendar - - "Prevucite prstom za odabir godine ili dodirnite za povratak na odabir dana" - - Prebaci na način unosa teksta - "Promijeni na sljedeći mjesec" - "Promijeni na prethodni mjesec" - "Prebaci na odabir godine" - "Odaberi datum" - Danas - Odabirač godine vidljiv - Neispravni unos raspona datuma - Unesite datume - U rasponu - Datum kraja - Pomaknite se za prikaz sljedećeg mjeseca - Pomaknite se za prikaz prethodnog mjeseca - Datum početka - Odaberi datume Ujutro diff --git a/toolkit/featureforms/src/main/res/values-hu/strings.xml b/toolkit/featureforms/src/main/res/values-hu/strings.xml index 088287287..1d8eb864d 100644 --- a/toolkit/featureforms/src/main/res/values-hu/strings.xml +++ b/toolkit/featureforms/src/main/res/values-hu/strings.xml @@ -73,41 +73,6 @@ A dátumnak a következő időpontra vagy azelőttre kell esnie: %1$s - Megadott dátum - Megadott dátum: %1$s - Nem egyezik az elvárt mintával: %1$s - Nem megengedett dátum: %1$s - - A dátum nincs az elvárt %1$s–%2$s évtartományban - - Dátum - Egyik sem - Dátum kiválasztása - "Kiválasztott dátum" - Aktuális választás: %1$s - Navigáljon a(z) %1$s évre - Egyik sem - Görgessen a korábbi évek megjelenítéséhez - Görgessen a későbbi évek megjelenítéséhez - Váltás naptárbeviteli módra - - "Legyintsen az év kiválasztásához, vagy koppintson, hogy visszaváltson a nap kiválasztására" - - Váltás szövegbeviteli módra - "Váltás a következő hónapra" - "Váltás az előző hónapra" - "Váltás évkiválasztásra" - "Dátum kiválasztása" - Ma - Látható évválasztó - A megadott dátumtartomány érvénytelen - Adjon meg dátumot - Tartományban - Befejező dátum - Görgessen a következő hónap megjelenítéséhez - Görgessen az előző hónap megjelenítéséhez - Kezdő dátum - Dátum kiválasztása DE diff --git a/toolkit/featureforms/src/main/res/values-in/strings.xml b/toolkit/featureforms/src/main/res/values-in/strings.xml index b54ed7da7..151ef4cef 100644 --- a/toolkit/featureforms/src/main/res/values-in/strings.xml +++ b/toolkit/featureforms/src/main/res/values-in/strings.xml @@ -73,41 +73,6 @@ Tanggal harus pada atau sebelum %1$s - Tanggal yang dimasukkan - Tanggal yang dimasukkan: %1$s - Tanggal tidak sesuai dengan pola yang diharapkan: %1$s - Tanggal tidak diizinkan: %1$s - - Tanggal di luar rentang tahun yang diharapkan %1$s - %2$s - - Tanggal - Tidak Ada - Pilih tanggal - "Tanggal yang dipillih" - Pilihan saat ini: %1$s - Navigasikan ke tahun %1$s - Tidak Ada - Gulir untuk menampilkan tahun-tahun sebelumnya - Gulir untuk menampilkan tahun-tahun berikutnya - Beralih ke mode input kalender - - "Geser untuk memilih tahun, atau ketuk untuk kembali memilih hari" - - Beralih ke mode input teks - "Ubah ke bulan berikutnya" - "Ubah ke bulan sebelumnya" - "Beralih ke memilih tahun" - "Pilih tanggal" - Hari ini - Pemilih tahun terlihat - Input rentang tanggal tidak valid - Masukkan tanggal - Dalam rentang - Tanggal akhir - Gulir untuk menampilkan bulan berikutnya - Gulir untuk menampilkan bulan sebelumnya - Tanggal mulai - Pilih tanggal AM diff --git a/toolkit/featureforms/src/main/res/values-it/strings.xml b/toolkit/featureforms/src/main/res/values-it/strings.xml index e5c7bf855..2f7631348 100644 --- a/toolkit/featureforms/src/main/res/values-it/strings.xml +++ b/toolkit/featureforms/src/main/res/values-it/strings.xml @@ -73,41 +73,6 @@ La data deve corrispondere o essere antecedente a %1$s - Data inserita - Data inserita: %1$s - La data non corrisponde al modello previsto: %1$s - Data non consentita: %1$s - - Data esterna all\'intervallo dell\'anno previsto %1$s - %2$s - - Data - Nessuno - Selezionare data - "Data selezionata" - Selezione corrente: %1$s - Sposta all\'anno %1$s - Nessuno - Scorrere per mostrare gli anni precedenti - Scorrere per mostrare gli anni successivi - Passa alla modalità di input calendario - - "Trascinare per selezionare un anno, oppure premere per tornare alla selezione del giorno" - - Passa alla modalità di input testo - "Cambia al mese successivo" - "Cambia al mese precedente" - "Passa alla selezione dell\'anno" - "Seleziona data" - Oggi - Selettore anno visibile - Input di intervallo di date non valido - Inserire le date - Nell\'intervallo - Data di fine - Scorrere per mostrare il mese successivo - Scorrere per mostrare il mese precedente - Data di inizio - Selezionare le date AM diff --git a/toolkit/featureforms/src/main/res/values-iw/strings.xml b/toolkit/featureforms/src/main/res/values-iw/strings.xml index 052836ce1..44ccdea1f 100644 --- a/toolkit/featureforms/src/main/res/values-iw/strings.xml +++ b/toolkit/featureforms/src/main/res/values-iw/strings.xml @@ -73,41 +73,6 @@ התאריך חייב להיות %1$s או לפני - תאריך שהוזן - תאריך שהוזן: %1$s - התאריך לא תואם לדפוס המצופה: %1$s - התאריך לא מותר: %1$s - - התאריך מחוץ לטווח השנים המצופה %1$s - %2$s - - תאריך - ללא - בחר תאריך - "תאריך שנבחר" - הבחירה הנוכחית: %1$s - נווט אל שנה %1$s - ללא - גלול להצגת שנים מוקדמות יותר - גלול להצגת שנים מאוחרות יותר - עבור למצב קלט של לוח שנה - - "החלק כדי לבחור שנה, או הקש כדי לחזור לבחירת יום" - - עבור למצב קלט טקסט - "החלף לחודש הבא" - "החלף לחודש הקודם" - "עבור לבחירת שנה" - "בחר תאריך" - היום - בורר תאריכים גלוי - קלט טווח תאריכים לא חוקי - הזן תאריכים - בטווח - תאריך סיום - גלול להצגת החודש הבא - גלול להצגת החודש הקודם - תאריך התחלה - בחר תאריכים AM diff --git a/toolkit/featureforms/src/main/res/values-ja/strings.xml b/toolkit/featureforms/src/main/res/values-ja/strings.xml index 737d3e469..39809aa3a 100644 --- a/toolkit/featureforms/src/main/res/values-ja/strings.xml +++ b/toolkit/featureforms/src/main/res/values-ja/strings.xml @@ -73,41 +73,6 @@ 日付は %1$s かそれ以前にする必要があります - 入力された日付 - 入力された日付: %1$s - 日付は予測されるパターンに一致しません: %1$s - 日付は許可されていません: %1$s - - 日付は予測される年の範囲 %1$s ~ %2$s を超えています - - 日付 - なし - 日付の選択 - "選択した日付" - 現在の選択: %1$s - %1$s 年に移動 - なし - スクロールして前の年を表示 - スクロールして次の年を表示 - カレンダー入力モードに切り替え - - "スワイプして年を選択するか、タップして日付の選択に戻る" - - テキスト入力モードに切り替え - "次月に変更" - "前月に変更" - "年の選択に切り替え" - "日付の選択" - 今日 - 年選択を表示 - 日付範囲の入力が無効です - 日付の入力 - 範囲内 - 終了日 - スクロールして次月を表示 - スクロールして前月を表示 - 開始日 - 日付の選択 AM diff --git a/toolkit/featureforms/src/main/res/values-ko/strings.xml b/toolkit/featureforms/src/main/res/values-ko/strings.xml index 6d59c6900..5224ac1c1 100644 --- a/toolkit/featureforms/src/main/res/values-ko/strings.xml +++ b/toolkit/featureforms/src/main/res/values-ko/strings.xml @@ -73,41 +73,6 @@ 날짜는 %1$s 또는 그 이전이어야 함 - 입력한 날짜 - 입력한 날짜: %1$s - 날짜가 예상 패턴과 일치하지 않음: %1$s - 날짜가 허용되지 않음: %1$s - - 날짜가 예상 연도 범위(%1$s - %2$s)를 벗어남 - - 날짜 - 없음 - 날짜 선택 - "선택한 날짜" - 현재 선택 항목: %1$s - 연도(%1$s)로 이동 - 없음 - 스크롤하여 이전 연도 표시 - 스크롤하여 이후 연도 표시 - 캘린더 입력 모드로 전환 - - "스와이프하여 연도를 선택하거나 눌러서 다시 날짜 선택으로 전환" - - 텍스트 입력 모드로 전환 - "다음 달로 변경" - "이전 달로 변경" - "연도 선택으로 전환" - "날짜 선택" - 오늘 - 연도 선택기 표시 - 잘못된 날짜 범위 입력 - 날짜 입력 - 범위 내 - 종료일 - 스크롤하여 다음 달 표시 - 스크롤하여 이전 달 표시 - 시작일 - 날짜 선택 오전 diff --git a/toolkit/featureforms/src/main/res/values-lt/strings.xml b/toolkit/featureforms/src/main/res/values-lt/strings.xml index 319311513..81c554dc3 100644 --- a/toolkit/featureforms/src/main/res/values-lt/strings.xml +++ b/toolkit/featureforms/src/main/res/values-lt/strings.xml @@ -73,41 +73,6 @@ Data turi būti %1$s arba ankstesnė - Įvesta data - Įvesta data: %1$s - Neatitinka numatytos struktūros: %1$s - Data neleidžiama: %1$s - - Data nepatenka į numatytą intervalą %1$s–%2$s - - Data - Nėra - Pasirinkti datą - "Pasirinkta data" - Dabartinis pasirinkimas: %1$s - Eiti į %1$s metus - Nėra - Paslinkite, jei norite peržiūrėti praėjusius metus - Paslinkite, jei norite peržiūrėti metus ateityje - Perjungti į kalendoriaus įvesties režimą - - "Braukite, jei norite pasirinkti metus, arba palieskite, jei norite grįžti prie dienos pasirinkimo" - - Perjungti į teksto įvesties režimą - "Pakeisti į kitą mėnesį" - "Pakeisti į ankstesnį mėnesį" - "Perjungti į metų pasirinkimą" - "Pasirinkti datą" - Šiandien - Matomas metų parinkiklis - Negalima datų intervalo įvestis - Įvesti datas - Patenka į intervalą - Pabaigos data - Paslinkite, jei norite peržiūrėti kitą mėnesį - Paslinkite, jei norite peržiūrėti ankstesnį mėnesį - Pradžios data - Pasirinkti datas priešpiet diff --git a/toolkit/featureforms/src/main/res/values-lv/strings.xml b/toolkit/featureforms/src/main/res/values-lv/strings.xml index d4de029bb..f2548433a 100644 --- a/toolkit/featureforms/src/main/res/values-lv/strings.xml +++ b/toolkit/featureforms/src/main/res/values-lv/strings.xml @@ -73,41 +73,6 @@ Datumam jāsakrīt vai jābūt pirms %1$s - Ievadīšanas datums - Ievadīšanas datums: %1$s - Datums neatbilst paredzētajam modelim: %1$s - Datums nav atļauts: %1$s - - Datums ārpus paredzētā gadu diapazona: %1$s–%2$s - - Datums - Nav - Atlasīt datumu - "Atlasītais datums" - Pašreizējā atlase: %1$s - Pāriet uz gadu %1$s - Nav - Ritiniet, lai parādītu iepriekšējos gadus - Ritiniet, lai parādītu vēlākos gadus - Pārslēgt uz kalendāra ievades režīmu - - "Pavelciet, lai izvēlētos gadu, vai pieskarieties, lai pārslēgtu atpakaļ uz dienas atlasi" - - Pārslēgt uz teksta ievades režīmu - "Mainīt uz nākamo mēnesi" - "Mainīt uz iepriekšējo mēnesi" - "Pārslēgt uz gada atlasi" - "Atlasīt datumu" - Šodien - Redzams gada atlasītājs - Nederīga datumu diapazona ievade - Ievadiet datumus - Diapazonā - Beigu datums - Ritiniet, lai parādītu nākamo mēnesi - Ritiniet, lai parādītu iepriekšējo mēnesi - Sākuma datums - Atlasīt datumus AM diff --git a/toolkit/featureforms/src/main/res/values-nl/strings.xml b/toolkit/featureforms/src/main/res/values-nl/strings.xml index f7f418b02..2c3fa70e0 100644 --- a/toolkit/featureforms/src/main/res/values-nl/strings.xml +++ b/toolkit/featureforms/src/main/res/values-nl/strings.xml @@ -73,41 +73,6 @@ Datum moet op of voor %1$s liggen - Ingevoerde datum - Ingevoerde datum: %1$s - Datum komt niet overeen met verwacht patroon: %1$s - Datum niet toegestaan: %1$s - - Datum ligt buiten verwacht jarenbereik %1$s - %2$s - - Datum - Geen - Datum selecteren - "Geselecteerd date" - Huidige selectie: %1$s - Ga naar jaar %1$s - Geen - Scrollen om eerdere jaren weer te geven - Scrollen om latere jaren weer te geven - Overschakelen naar kalenderinvoermodus - - "Veeg om een jaar te selecteren of tik om terug te gaan om een dag te selecteren" - - Overschakelen op tekstinvoermodus - "Veranderen in volgende maand" - "Veranderen in vorige maand" - "Overschakelen op selecteren van een jaar" - "Datum selecteren" - Vandaag - Jaarkiezer zichtbaar - Invoer heeft ongeldig datumbereik - Datums invoeren - Binnen bereik - Einddatum - Scrollen om de volgende maand weer te geven - Scrollen om de vorige maand weer te geven - Begindatum - Datums selecteren AM diff --git a/toolkit/featureforms/src/main/res/values-no/strings.xml b/toolkit/featureforms/src/main/res/values-no/strings.xml index f8dba92c4..29f83d8a2 100644 --- a/toolkit/featureforms/src/main/res/values-no/strings.xml +++ b/toolkit/featureforms/src/main/res/values-no/strings.xml @@ -73,41 +73,6 @@ Datoen må være den eller før %1$s - Angitt dato - Angitt dato: %1$s - Datoen matcher ikke det forventede mønsteret: %1$s - Dato ikke tillatt: %1$s - - Datoen er utenfor det forventede årsområdet %1$s – %2$s - - Dato - Ingen - Velg dato - "Valgt dato" - Gjeldende utvalg: %1$s - Naviger til år %1$s - Ingen - Rull for å vise tidligere år - Rull for å vise senere år - Bytt til kalenderinnsettingsmodus - - "Sveip for å velge et år eller trykk for å gå tilbake til å velge en dag" - - Bytt til tekstinnsettingsmodus - "Endre til neste måned" - "Endre til tidligere måned" - "Bytt til å velge et år" - "Velg dato" - I dag - Årsvelgeren er synlig - Ugyldige inndata for datoområde - Angi datoer - Innenfor området - Sluttdato - Rull ned for å vise neste måned - Bla ned for å vise forrige måned - Startdato - Velg datoer AM diff --git a/toolkit/featureforms/src/main/res/values-pl/strings.xml b/toolkit/featureforms/src/main/res/values-pl/strings.xml index ec9dad064..33e30e967 100644 --- a/toolkit/featureforms/src/main/res/values-pl/strings.xml +++ b/toolkit/featureforms/src/main/res/values-pl/strings.xml @@ -73,41 +73,6 @@ Data musi równa %1$s lub wcześniejsza - Wprowadzona data - Wprowadzona data: %1$s - Data nie pasuje do oczekiwanego wzorca: %1$s - Niedozwolona data: %1$s - - Data poza oczekiwanym zakresem lat %1$s - %2$s - - Data - Brak - Wybierz datę - "Wybrana data" - Bieżący wybór: %1$s - Przejdź do roku %1$s - Brak - Przewiń, aby wyświetlić wcześniejsze lata - Przewiń, aby wyświetlić późniejsze lata - Przełącz do trybu wprowadzania w kalendarzu - - "Przesuń palcem, aby wybrać rok, lub dotknij, aby powrócić do wyboru dnia" - - Przełącz do trybu wprowadzania tekstu - "Zmień na następny miesiąc" - "Zmień na poprzedni miesiąc" - "Przełącz na wybór roku" - "Wybierz datę" - Dzisiaj - Okno wyboru roku jest widoczne - Wprowadzono nieprawidłowy zakres dat - Wprowadź daty - W zakresie - Data końcowa - Przewiń, aby wyświetlić następny miesiąc - Przewiń, aby wyświetlić poprzedni miesiąc - Data początkowa - Wybierz daty przed południem diff --git a/toolkit/featureforms/src/main/res/values-pt-rBR/strings.xml b/toolkit/featureforms/src/main/res/values-pt-rBR/strings.xml index 169f69e5f..bc9afb857 100644 --- a/toolkit/featureforms/src/main/res/values-pt-rBR/strings.xml +++ b/toolkit/featureforms/src/main/res/values-pt-rBR/strings.xml @@ -73,41 +73,6 @@ A data deve ser igual ou anterior a %1$s - Data inserida - Data inserida: %1$s - A data não corresponde ao padrão esperado: %1$s - Data não permitida: %1$s - - Data fora do intervalo esperado do ano %1$s - %2$s - - Data - Nenhum - Selecionar data - "Data selecionada" - Seleção atual: %1$s - Ir para o ano %1$s - Nenhum - Rolar para mostrar os anos anteriores - Rolar para mostrar anos posteriores - Alterar para o modo de entrada de calendário - - "Deslize para selecionar um ano ou toque para voltar a selecionar um dia" - - Alterar para o modo de entrada de texto - "Alterar para o próximo mês" - "Alterar para o mês anterior" - "Alterar para selecionar um ano" - "Selecionar data" - Hoje - Seletor de ano visível - Entrada de período inválida - Inserir datas - No intervalo - Data final - Rolar para mostrar o próximo mês - Rolar para mostrar o mês anterior - Data inicial - Selecionar datas AM diff --git a/toolkit/featureforms/src/main/res/values-pt-rPT/strings.xml b/toolkit/featureforms/src/main/res/values-pt-rPT/strings.xml index 6a4dab0d8..9ad0de692 100644 --- a/toolkit/featureforms/src/main/res/values-pt-rPT/strings.xml +++ b/toolkit/featureforms/src/main/res/values-pt-rPT/strings.xml @@ -73,41 +73,6 @@ A data tem de ser igual ou anterior a %1$s - Data introduzida - Data introduzida: %1$s - A data não corresponde ao padrão esperado: %1$s - Data não permitida: %1$s - - Data fora do intervalo de anos previsto %1$s - %2$s - - Data - Nenhum - Selecionar data - "Data selecionada" - Seleção atual: %1$s - Navegar para o ano %1$s - Nenhum - Percorrer para ver os anos anteriores - Percorrer para ver os anos posteriores - Mudar para o modo de entrada do calendário - - "Deslize para selecionar um ano ou toque para voltar a selecionar um dia" - - Mudar para o modo de introdução de texto - "Mudar para o mês seguinte" - "Mudar para o mês anterior" - "Mudar para selecionar um ano" - "Selecionar data" - Hoje - Seletor de ano visível - Entrada de intervalo de datas inválida - Introduzir datas - Dentro do intervalo - Data de fim - Percorrer para mostrar o mês seguinte - Percorrer para mostrar o mês anterior - Data de início - Selecionar datas AM diff --git a/toolkit/featureforms/src/main/res/values-ro/strings.xml b/toolkit/featureforms/src/main/res/values-ro/strings.xml index f9ee3755c..7ed5cb6f4 100644 --- a/toolkit/featureforms/src/main/res/values-ro/strings.xml +++ b/toolkit/featureforms/src/main/res/values-ro/strings.xml @@ -73,41 +73,6 @@ Data trebuie să fie la sau înainte de %1$s - Data intrării - Data intrării: %1$s - Data nu corespunde cu modelul preconizat: %1$s - Data nu este permisă: %1$s - - Data este în afara intervalului anual preconizat %1$s - %2$s - - Dată - Niciunul - Selectare dată - „Data selectată” - Selecția curentă: %1$s - Navigați la anul %1$s - Niciunul - Derulați pentru afișarea anilor anteriori - Derulați pentru afișarea anilor ulteriori - Comutați la modul de introducere în calendar - - „Glisați pentru a selecta un an sau atingeți pentru a comuta înapoi la selectarea unei zile” - - Comutați la modul de introducere text - „Schimbați la luna următoare” - „Schimbați la luna anterioară” - „Comutați la selectarea unui an” - „Selectare dată” - Astăzi - Selector an vizibil - Introducere interval dată nevalid - Intrare date - În interval - Data terminării - Derulați pentru a afișa luna următoare - Derulați pentru a afișa luna anterioară - Data începerii - Selectare date AM diff --git a/toolkit/featureforms/src/main/res/values-ru/strings.xml b/toolkit/featureforms/src/main/res/values-ru/strings.xml index 5520facd7..f3e31505d 100644 --- a/toolkit/featureforms/src/main/res/values-ru/strings.xml +++ b/toolkit/featureforms/src/main/res/values-ru/strings.xml @@ -73,41 +73,6 @@ Дата должна быть %1$s или ранее - Введенная дата - Введенная дата: %1$s - Дата не соответствует ожидаемому шаблону: %1$s - Недопустимая дата: %1$s - - Дата не попадает в ожидаемый диапазон лет %1$s - %2$s - - Дата - Нет - Выбрать дату - "Выбранная дата" - Текущая выборка: %1$s - Перейти к году %1$s - Нет - Прокрутите, чтобы просмотреть предыдущие годы - Прокрутите, чтобы просмотреть следующие годы - Переключиться в режим ввода календаря - - "Проведите пальцем, чтобы выбрать год, или коснитесь, чтобы вернуться к выбору дня" - - Переключиться в режим ввода текста - "Перейти к следующему месяцу" - "Перейти к предыдущему месяцу" - "Переключиться на выбор года" - "Выбрать дату" - Сегодня - Видимый выбор года - Введен некорректный диапазон дат - Ввести даты - В диапазоне - Дата окончания - Прокрутить, чтобы отобразился следующий месяц - Прокрутить, чтобы отобразился предыдущий месяц - Дата начала - Выбрать даты AM diff --git a/toolkit/featureforms/src/main/res/values-sk/strings.xml b/toolkit/featureforms/src/main/res/values-sk/strings.xml index b2a1e6b90..7910082eb 100644 --- a/toolkit/featureforms/src/main/res/values-sk/strings.xml +++ b/toolkit/featureforms/src/main/res/values-sk/strings.xml @@ -73,41 +73,6 @@ Dátum musí byť v deň alebo pred %1$s - Zadaný dátum - Zadaný dátum: %1$s - Dátum nezodpovedá očakávanému vzoru: %1$s - Dátum nie je povolený: %1$s - - Dátum mimo očakávaného rozsahu rokov %1$s - %2$s - - Dátum - Žiadne - Vybrať dátum - "Vybraný dátum" - Aktuálny výber: %1$s - Prejsť na rok %1$s - Žiadne - Posúvaním zobrazíte predchádzajúce roky - Posúvaním zobrazíte neskoršie roky - Prepnúť do režimu zadávania kalendára - - "Potiahnutím vyberte rok alebo ťuknutím na prepnite späť na výber dňa" - - Prepnúť na režim zadávania textu - "Zmeniť na budúci mesiac" - "Zmeniť na predchádzajúci mesiac" - "Prepnúť na výber roka" - "Vybrať dátum" - Dnes - Výber roka viditeľný - Neplatný vstup rozsahu dátumu - Zadať dátumy - V rozsahu - Dátum ukončenia - Posúvaním zobrazíte nasledujúci mesiac - Posúvaním zobrazíte predchádzajúci mesiac - Dátum začiatku - Vybrať dátumy Dopoludnia diff --git a/toolkit/featureforms/src/main/res/values-sl/strings.xml b/toolkit/featureforms/src/main/res/values-sl/strings.xml index 4df0fa1fb..2c5c3033b 100644 --- a/toolkit/featureforms/src/main/res/values-sl/strings.xml +++ b/toolkit/featureforms/src/main/res/values-sl/strings.xml @@ -73,41 +73,6 @@ Datum mora biti na ali pred %1$s - Vnesen datum - Vnesen datum: %1$s - Se ne ujema s pričakovanim vzorcem: %1$s - Datum ni dovoljen: %1$s - - Datum je izven pričakovanega letnega razpona %1$s–%2$s - - Datum - Brez - Izberi datum - "Izbran datum" - Trenutna izbira: %1$s - Navigiraj na leto %1$s - Brez - Podrsaj za prikaz prejšnjih let - Podrsaj za prikaz kasnejših let - Preklopi na način vnosa v koledar - - "Podrsaj za izbiro leta ali se dotakni Preklopi nazaj za izbiro dneva" - - Preklopi na način vnosa besedila - "Spremeni na naslednji mesec" - "Spremeni na pretekli mesec" - "Preklopi na izbiro leta" - "Izberi datum" - Danes - Izbirnik leta je viden - Neveljaven vnos datumskega razpona - Vnesi datume - V razponu - Končni datum - Podrsaj za prikaz naslednjega meseca - Podrsaj za prikaz prejšnjega meseca - Začetni datum - Izberi datume dopoldne diff --git a/toolkit/featureforms/src/main/res/values-sr/strings.xml b/toolkit/featureforms/src/main/res/values-sr/strings.xml index 25abddc55..b3e9bf460 100644 --- a/toolkit/featureforms/src/main/res/values-sr/strings.xml +++ b/toolkit/featureforms/src/main/res/values-sr/strings.xml @@ -73,41 +73,6 @@ Datum mora biti na dan ili ranije %1$s - Uneseni datum - Uneseni datum: %1$s - Datum ne odgovara očekivanom obascu: %1$s - Datum nije dozvoljen: %1$s - - Datum izvan očekivanog opsega godina %1$s - %2$s - - Datum - Ništa - Izaberite datum - "Izabani datum" - Trenutni odabir: %1$s - Pređite na godinu %1$s - Ništa - Listajte za prikaz ranijih godina - Listajte za prikaz kasnijih godina - Prebacite se na režim unosa kalendara - - "Prevucite za odabir godine ili dodirnite da biste se vratili na izbor dana " - - Prebacite se na režim unosa teksta - "Prebaci na sledeći mesec" - "Prebaci na prethodni mesec" - "Prebaci se na izbor godine" - "Izaberi datum" - Danas - Vidljiv birač godine - Nevažeći unos opsega datuma - Unesi datume - U opsegu - Datum završetka - Listajte za prikaz narednog meseca - Listajte za prikaz prethodnog meseca - Datum početka - Izaberite datume Prepodne diff --git a/toolkit/featureforms/src/main/res/values-sv/strings.xml b/toolkit/featureforms/src/main/res/values-sv/strings.xml index d2c31d9ca..ca96cfa05 100644 --- a/toolkit/featureforms/src/main/res/values-sv/strings.xml +++ b/toolkit/featureforms/src/main/res/values-sv/strings.xml @@ -73,41 +73,6 @@ Datum måste vara på eller efter %1$s - Angivet datum - Angivet datum: %1$s - Datum matchar inte förväntat mönster: %1$s - Datum ej tillåtet: %1$s - - Datum ligger utanför förväntat årsintervall %1$s - %2$s - - Datum - Inga - Välj datum - "Valt datum" - Aktuellt urval: %1$s - Navigera till år %1$s - Inga - Bläddra för att visa tidigare år - Bläddra för att visa senare år - Växla till kalenderindataläge - - "Svep för att välja ett år, eller tryck för att gå tillbaka och välja en dag" - - Växla till textindataläge - "Ändra till nästa månad" - "Ändra till föregående månad" - "Växla för att välja ett år" - "Välj datum" - Idag - Årsväljare synlig - Ogiltig inmatning av datumintervall - Ange datum - I intervall - Slutdatum - Bläddra för att visa nästa månad - Bläddra för att visa föregående månad - Startdatum - Välj datum AM diff --git a/toolkit/featureforms/src/main/res/values-th/strings.xml b/toolkit/featureforms/src/main/res/values-th/strings.xml index 0f8e5653a..47e348d04 100644 --- a/toolkit/featureforms/src/main/res/values-th/strings.xml +++ b/toolkit/featureforms/src/main/res/values-th/strings.xml @@ -73,41 +73,6 @@ วันที่ต้องเท่ากับหรืออยู่ก่อนหน้า %1$s - วันที่ที่ป้อน - วันที่ที่ป้อน: %1$s - วันที่ไม่ตรงกับรูปแบบที่คาดไว้: %1$s - วันที่ที่ไม่ได้รับอนุญาต: %1$s - - วันที่อยู่นอกช่วงปีที่คาดไว้ %1$s - %2$s - - วันที่ - ไม่มีเลย - เลือกวันที่ - "วันที่ที่เลือก" - การเลือกปัจจุบัน: %1$s - นำทางไปยังปี %1$s - ไม่มีเลย - เลื่อนเพื่อแสดงปีช่วงก่อนหน้า - เลื่อนเพื่อแสดงปีช่วงหลัง - สลับเป็นโหมดอินพุทปฏิทิน - - "ปัดเพื่อเลือกปี หรือแตะเพื่อสลับกลับไปเลือกวัน" - - สลับเป็นโหมดอินพุทข้อความ - "เปลี่ยนเป็นเดือนถัดไป" - "เปลี่ยนเป็นเดือนก่อนหน้า" - "สลับเป็นการเลือกปี" - "เลือกวันที่" - วันนี้ - แสดงตัวเลือกปี - อินพุทช่วงวันที่ไม่ถูกต้อง - ป้อนวันที่ - อยู่ในช่วง - วันที่สิ้นสุด - เลื่อนเพื่อแสดงเดือนถัดไป - เลื่อนเพื่อแสดงเดือนก่อนหน้า - วันที่เริ่มต้น - เลือกวันที่ AM diff --git a/toolkit/featureforms/src/main/res/values-tr/strings.xml b/toolkit/featureforms/src/main/res/values-tr/strings.xml index 7986e7f7d..54d3a7318 100644 --- a/toolkit/featureforms/src/main/res/values-tr/strings.xml +++ b/toolkit/featureforms/src/main/res/values-tr/strings.xml @@ -73,41 +73,6 @@ Tarih %1$s veya daha önce olmalıdır - Girilen tarih - Girilen tarih: %1$s - Tarih beklenen kalıpla eşleşmiyor: %1$s - İzin verilmeyen tarih: %1$s - - Beklenen yıl aralığı %1$s - %2$s dışındaki tarih - - Tarih - Hiçbiri - Tarih seç - "Seçilen tarih" - Mevcut seçim: %1$s - %1$s yılına git - Hiçbiri - Önceki yılları göstermek için kaydır - Sonraki yılları göstermek için kaydır - Takvim giriş moduna geçin - - "Yıl seçmek için kaydırın veya gün seçmeye geri dönmek için dokunun" - - Metin giriş moduna geçin - "Sonraki aya değiştir" - "Önceki aya değiştir" - "Yıl seçimine değiştir" - "Tarih seç" - Bugün - Yıl seçici görünür - Geçersiz tarih aralığı girdisi - Tarihleri gir - Aralık içinde - Bitiş tarihi - Sonraki ayı göstermek için kaydır - Önceki ayı göstermek için kaydır - Başlangıç tarihi - Tarihleri seç AM diff --git a/toolkit/featureforms/src/main/res/values-uk/strings.xml b/toolkit/featureforms/src/main/res/values-uk/strings.xml index 35ba57f1a..03745d02e 100644 --- a/toolkit/featureforms/src/main/res/values-uk/strings.xml +++ b/toolkit/featureforms/src/main/res/values-uk/strings.xml @@ -73,41 +73,6 @@ Дата має бути не раніше %1$s - Введена дата - Введена дата: %1$s - Дата не відповідає очікуваному шаблону: %1$s - Дата не дозволена: %1$s - - Дата виходить за межі очікуваного діапазону років %1$s - %2$s - - Дата - Немає - Вибрати дату - "Вибрана дата" - Поточний вибір: %1$s - Перейти до року %1$s - Немає - Прокрутіть, щоб показати попередні роки - Прокрутіть, щоб показати наступні роки - Перейти в режим введення календаря - - "Проведіть пальцем, щоб вибрати рік, або торкніться, щоб повернутися до вибору дня" - - Перейти в режим введення тексту - "Змінити на наступний місяць" - "Змінити на попередній місяць" - "Перейти до вибору року" - "Вибрати дату" - Сьогодні - Елемент вибору року відображається - Неприпустимі вхідні дані діапазону дат - Ввести дати - В діапазоні - Кінцева дата - Прокрутіть, щоб показати наступний місяць - Прокрутіть, щоб показати попередній місяць - Початкова дата - Вибрати дати AM diff --git a/toolkit/featureforms/src/main/res/values-vi/strings.xml b/toolkit/featureforms/src/main/res/values-vi/strings.xml index 63793151e..b7b807dc4 100644 --- a/toolkit/featureforms/src/main/res/values-vi/strings.xml +++ b/toolkit/featureforms/src/main/res/values-vi/strings.xml @@ -73,41 +73,6 @@ Ngày phải vào hoặc trước %1$s - Ngày đã nhập - Ngày đã nhập: %1$s - Ngày không khớp với kiểu mẫu dự kiến: %1$s - Ngày không được cho phép: %1$s - - Ngày nằm ngoài khoảng năm dự kiến %1$s - %2$s - - Ngày - Không có - Chọn ngày - "Ngày đã chọn" - Lựa chọn hiện tại: %1$s - Điều hướng đến năm %1$s - Không có - Cuộn để hiển thị các năm trước - Cuộn để hiển thị các năm sau - Chuyển sang chế độ đầu vào lịch - - "Vuốt để chọn năm hoặc chạm để chuyển về chọn ngày" - - Chuyển sang chế độ đầu vào văn bản - "Thay đổi sang tháng sau" - "Thay đổi về tháng trước" - "Chuyển sang chọn năm" - "Chọn ngày" - Hôm nay - Trình chọn năm hiển thị - Đầu vào khoảng ngày tháng không hợp lệ - Nhập ngày - Trong khoảng - Ngày kết thúc - Cuộn để hiển thị tháng sau - Cuộn để hiển thị tháng trước - Ngày bắt đầu - Chọn ngày SA diff --git a/toolkit/featureforms/src/main/res/values-zh-rHK/strings.xml b/toolkit/featureforms/src/main/res/values-zh-rHK/strings.xml index c7a7d0f33..4613528c2 100644 --- a/toolkit/featureforms/src/main/res/values-zh-rHK/strings.xml +++ b/toolkit/featureforms/src/main/res/values-zh-rHK/strings.xml @@ -73,41 +73,6 @@ 日期必須等於或早於 %1$s - 輸入日期 - 輸入日期: %1$s - 日期與預期模式不符: %1$s - 不允許: %1$s - - 日期超出預期年份範圍 %1$s - %2$s - - 日期 - - 選擇日期 - "選擇的日期" - 目前選擇: %1$s - 導覽至 %1$s 年 - - 捲動以顯示較早年份 - 捲動以顯示較晚年份 - 切換到行事曆輸入模式 - - "切換以選擇年份,或點選以切換回選擇日期" - - 切換到文字輸入模式 - "變改為下個月" - "變改為上個月" - "切換以選擇某年" - "選擇日期" - 今天 - 年份選取器可見 - 日期範圍輸入無效 - 輸入日期 - 範圍中 - 結束日期 - 捲動以顯示下個月 - 捲動以顯示上個月 - 開始日期 - 選擇日期 上午 diff --git a/toolkit/featureforms/src/main/res/values-zh-rTW/strings.xml b/toolkit/featureforms/src/main/res/values-zh-rTW/strings.xml index c7a7d0f33..4613528c2 100644 --- a/toolkit/featureforms/src/main/res/values-zh-rTW/strings.xml +++ b/toolkit/featureforms/src/main/res/values-zh-rTW/strings.xml @@ -73,41 +73,6 @@ 日期必須等於或早於 %1$s - 輸入日期 - 輸入日期: %1$s - 日期與預期模式不符: %1$s - 不允許: %1$s - - 日期超出預期年份範圍 %1$s - %2$s - - 日期 - - 選擇日期 - "選擇的日期" - 目前選擇: %1$s - 導覽至 %1$s 年 - - 捲動以顯示較早年份 - 捲動以顯示較晚年份 - 切換到行事曆輸入模式 - - "切換以選擇年份,或點選以切換回選擇日期" - - 切換到文字輸入模式 - "變改為下個月" - "變改為上個月" - "切換以選擇某年" - "選擇日期" - 今天 - 年份選取器可見 - 日期範圍輸入無效 - 輸入日期 - 範圍中 - 結束日期 - 捲動以顯示下個月 - 捲動以顯示上個月 - 開始日期 - 選擇日期 上午 diff --git a/toolkit/featureforms/src/main/res/values-zh/strings.xml b/toolkit/featureforms/src/main/res/values-zh/strings.xml index 4b88787fb..1dc4a2ea6 100644 --- a/toolkit/featureforms/src/main/res/values-zh/strings.xml +++ b/toolkit/featureforms/src/main/res/values-zh/strings.xml @@ -73,41 +73,6 @@ 日期不得晚于 %1$s - 已输入日期 - 已输入日期:%1$s - 日期与预期模式不匹配:%1$s - 无效日期:%1$s - - 日期超出预期年份范围 %1$s - %2$s - - 日期 - - 选择日期 - “已选日期” - 当前选择:%1$s - 转至 %1$s 年 - - 滚动查看较早年份 - 滚动查看较晚年份 - 切换到日历输入模式 - - “滑动选择年份,或点击后切换到选择日期” - - 切换到文本输入模式 - “更改为下个月” - “更改为上个月” - “切换到选择年份” - “选择日期” - 今天 - 年份选择器可见 - 日期范围无效输入 - 输入日期 - 范围内 - 结束日期 - 滚动显示下个月 - 滚动显示上个月 - 开始日期 - 选择日期 AM diff --git a/toolkit/featureforms/src/main/res/values/strings.xml b/toolkit/featureforms/src/main/res/values/strings.xml index aabb0f872..1f6101711 100644 --- a/toolkit/featureforms/src/main/res/values/strings.xml +++ b/toolkit/featureforms/src/main/res/values/strings.xml @@ -75,41 +75,6 @@ Date must be on or before %1$s - Entered date - Entered date: %1$s - Date does not match expected pattern: %1$s - Date not allowed: %1$s - - Date out of expected year range %1$s - %2$s - - Date - None - Select date - "Selected date" - Current selection: %1$s - Navigate to year %1$s - None - Scroll to show earlier years - Scroll to show later years - Switch to calendar input mode - - "Swipe to select a year, or tap to switch back to selecting a day" - - Switch to text input mode - "Change to next month" - "Change to previous month" - "Switch to selecting a year" - "Select date" - Today - Year picker visible - Invalid date range input - Enter dates - In range - End date - Scroll to show the next month - Scroll to show the previous month - Start date - Select dates AM From 157c0a9eac1671a326f44512081c648d8be2cdf5 Mon Sep 17 00:00:00 2001 From: Kaushik Meesala Date: Tue, 9 Dec 2025 15:06:56 -0800 Subject: [PATCH 3/8] Update DateTimePicker.kt --- .../internal/components/datetime/picker/DateTimePicker.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt index 99c31ea24..f02db25b2 100644 --- a/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt +++ b/toolkit/featureforms/src/main/java/com/arcgismaps/toolkit/featureforms/internal/components/datetime/picker/DateTimePicker.kt @@ -37,6 +37,7 @@ import androidx.compose.material.icons.rounded.CalendarMonth import androidx.compose.material3.AlertDialog import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.DatePickerState import androidx.compose.material3.DisplayMode import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -232,7 +233,7 @@ private fun (ColumnScope).PickerContent( label: String, description: String, state: DateTimePickerState, - datePickerState: androidx.compose.material3.DatePickerState, + datePickerState: DatePickerState, timePickerState: TimePickerState, style: DateTimePickerStyle, picker: DateTimePickerInput, From dedc8ed326774d735302786064dfbaf65d32643e Mon Sep 17 00:00:00 2001 From: Kaushik Meesala Date: Tue, 9 Dec 2025 15:15:37 -0800 Subject: [PATCH 4/8] bump version --- gradle/libs.versions.toml | 2 +- microapps/CalloutApp/app/build.gradle.kts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5c7d00974..8b8b1463d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ androidGradlePlugin = "8.12.2" androidXBrowser = "1.9.0" coil3 = "3.3.0" coilBOM = "2.7.0" -composeBOM = "2025.11.01" +composeBOM = "2025.12.00" androidxCamera = "1.4.2" androidxCore = "1.17.0" androidxEspresso = "3.7.0" diff --git a/microapps/CalloutApp/app/build.gradle.kts b/microapps/CalloutApp/app/build.gradle.kts index cb3cbb58e..9c3373735 100644 --- a/microapps/CalloutApp/app/build.gradle.kts +++ b/microapps/CalloutApp/app/build.gradle.kts @@ -79,6 +79,7 @@ dependencies { implementation(arcgis.mapsSdk) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) + implementation(libs.bundles.icons) implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) From 3d23f16321e7cbfbe53afc46a464443dff25905c Mon Sep 17 00:00:00 2001 From: Kaushik Meesala Date: Tue, 9 Dec 2025 15:31:17 -0800 Subject: [PATCH 5/8] add icons dep --- microapps/CalloutApp/app/build.gradle.kts | 2 +- microapps/SceneViewSetViewpointApp/app/build.gradle.kts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/microapps/CalloutApp/app/build.gradle.kts b/microapps/CalloutApp/app/build.gradle.kts index 9c3373735..36b00ad5e 100644 --- a/microapps/CalloutApp/app/build.gradle.kts +++ b/microapps/CalloutApp/app/build.gradle.kts @@ -79,8 +79,8 @@ dependencies { implementation(arcgis.mapsSdk) implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) - implementation(libs.bundles.icons) implementation(libs.bundles.core) + implementation(libs.androidx.material.icons.core) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.compose.navigation) diff --git a/microapps/SceneViewSetViewpointApp/app/build.gradle.kts b/microapps/SceneViewSetViewpointApp/app/build.gradle.kts index d7bd09d30..b765f4976 100644 --- a/microapps/SceneViewSetViewpointApp/app/build.gradle.kts +++ b/microapps/SceneViewSetViewpointApp/app/build.gradle.kts @@ -81,6 +81,7 @@ dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) + implementation(libs.androidx.material.icons.core) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) testImplementation(platform(libs.androidx.compose.bom)) From 51a25ccbf6b6d77b31e17697b5d60a382ee8a3b9 Mon Sep 17 00:00:00 2001 From: Kaushik Meesala Date: Tue, 9 Dec 2025 15:35:55 -0800 Subject: [PATCH 6/8] change to icons core dependency --- toolkit/authentication/build.gradle.kts | 2 +- toolkit/indoors/build.gradle.kts | 2 +- toolkit/offline/build.gradle.kts | 2 +- toolkit/popup/build.gradle.kts | 2 +- toolkit/utilitynetworks/build.gradle.kts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/toolkit/authentication/build.gradle.kts b/toolkit/authentication/build.gradle.kts index 634e86e43..e910bc243 100644 --- a/toolkit/authentication/build.gradle.kts +++ b/toolkit/authentication/build.gradle.kts @@ -108,7 +108,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) implementation(libs.androidx.browser) - implementation(libs.bundles.icons) + implementation(libs.androidx.material.icons.core) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) debugImplementation(libs.bundles.debug) diff --git a/toolkit/indoors/build.gradle.kts b/toolkit/indoors/build.gradle.kts index 533ee7518..510a40f4e 100644 --- a/toolkit/indoors/build.gradle.kts +++ b/toolkit/indoors/build.gradle.kts @@ -78,7 +78,7 @@ dependencies { implementation(libs.bundles.core) implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) - implementation(libs.bundles.icons) + implementation(libs.androidx.material.icons.core) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) androidTestImplementation(project(":geoview-compose")) diff --git a/toolkit/offline/build.gradle.kts b/toolkit/offline/build.gradle.kts index 2d0db3245..00e608ee6 100644 --- a/toolkit/offline/build.gradle.kts +++ b/toolkit/offline/build.gradle.kts @@ -103,7 +103,7 @@ dependencies { implementation(libs.bundles.core) implementation(libs.androidx.activity.compose) implementation(libs.androidx.work.runtime.ktx) - implementation(libs.bundles.icons) + implementation(libs.androidx.material.icons.core) implementation(libs.kotlinx.serialization.json) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) diff --git a/toolkit/popup/build.gradle.kts b/toolkit/popup/build.gradle.kts index 94b78dce9..089035094 100644 --- a/toolkit/popup/build.gradle.kts +++ b/toolkit/popup/build.gradle.kts @@ -104,7 +104,7 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.navigation) implementation(libs.kotlinx.serialization.json) - implementation(libs.bundles.icons) + implementation(libs.androidx.material.icons.core) implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.exoplayer.dash) implementation(libs.androidx.media3.ui) diff --git a/toolkit/utilitynetworks/build.gradle.kts b/toolkit/utilitynetworks/build.gradle.kts index 6930095bd..bad34f271 100644 --- a/toolkit/utilitynetworks/build.gradle.kts +++ b/toolkit/utilitynetworks/build.gradle.kts @@ -116,7 +116,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.navigation) - implementation(libs.bundles.icons) + implementation(libs.androidx.material.icons.core) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.truth) androidTestImplementation(platform(libs.androidx.compose.bom)) From 93a3fdfafdbcf31822ada982cf874a90b871431e Mon Sep 17 00:00:00 2001 From: Kaushik Meesala Date: Tue, 9 Dec 2025 15:43:00 -0800 Subject: [PATCH 7/8] revert to a bundle call --- toolkit/authentication/build.gradle.kts | 2 +- toolkit/indoors/build.gradle.kts | 2 +- toolkit/offline/build.gradle.kts | 2 +- toolkit/popup/build.gradle.kts | 2 +- toolkit/utilitynetworks/build.gradle.kts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/toolkit/authentication/build.gradle.kts b/toolkit/authentication/build.gradle.kts index e910bc243..5304fd0bf 100644 --- a/toolkit/authentication/build.gradle.kts +++ b/toolkit/authentication/build.gradle.kts @@ -105,10 +105,10 @@ dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) + implementation(libs.bundles.icons) implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) implementation(libs.androidx.browser) - implementation(libs.androidx.material.icons.core) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) debugImplementation(libs.bundles.debug) diff --git a/toolkit/indoors/build.gradle.kts b/toolkit/indoors/build.gradle.kts index 510a40f4e..18129e128 100644 --- a/toolkit/indoors/build.gradle.kts +++ b/toolkit/indoors/build.gradle.kts @@ -76,9 +76,9 @@ dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) + implementation(libs.bundles.icons) implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) - implementation(libs.androidx.material.icons.core) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) androidTestImplementation(project(":geoview-compose")) diff --git a/toolkit/offline/build.gradle.kts b/toolkit/offline/build.gradle.kts index 00e608ee6..4dcf9aa47 100644 --- a/toolkit/offline/build.gradle.kts +++ b/toolkit/offline/build.gradle.kts @@ -101,9 +101,9 @@ dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) + implementation(libs.bundles.icons) implementation(libs.androidx.activity.compose) implementation(libs.androidx.work.runtime.ktx) - implementation(libs.androidx.material.icons.core) implementation(libs.kotlinx.serialization.json) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.bundles.composeTest) diff --git a/toolkit/popup/build.gradle.kts b/toolkit/popup/build.gradle.kts index 089035094..02ac244c5 100644 --- a/toolkit/popup/build.gradle.kts +++ b/toolkit/popup/build.gradle.kts @@ -101,10 +101,10 @@ dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) + implementation(libs.bundles.icons) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.navigation) implementation(libs.kotlinx.serialization.json) - implementation(libs.androidx.material.icons.core) implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.exoplayer.dash) implementation(libs.androidx.media3.ui) diff --git a/toolkit/utilitynetworks/build.gradle.kts b/toolkit/utilitynetworks/build.gradle.kts index bad34f271..0fd5dca25 100644 --- a/toolkit/utilitynetworks/build.gradle.kts +++ b/toolkit/utilitynetworks/build.gradle.kts @@ -113,10 +113,10 @@ dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.composeCore) implementation(libs.bundles.core) + implementation(libs.bundles.icons) implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.navigation) - implementation(libs.androidx.material.icons.core) testImplementation(libs.bundles.unitTest) androidTestImplementation(libs.truth) androidTestImplementation(platform(libs.androidx.compose.bom)) From d49d8628bcb38d62ccdf6212a78b27ec65c016ab Mon Sep 17 00:00:00 2001 From: Kaushik Meesala Date: Tue, 9 Dec 2025 16:03:19 -0800 Subject: [PATCH 8/8] Update libs.versions.toml --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8b8b1463d..431b954ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ androidGradlePlugin = "8.12.2" androidXBrowser = "1.9.0" coil3 = "3.3.0" coilBOM = "2.7.0" -composeBOM = "2025.12.00" +composeBOM = "2025.11.00" androidxCamera = "1.4.2" androidxCore = "1.17.0" androidxEspresso = "3.7.0"