Skip to content

Commit

Permalink
Restore commonMain to AOSP state (material3 formatting)
Browse files Browse the repository at this point in the history
Partially fixes https://youtrack.jetbrains.com/issue/CMP-5740/Upstreaming.-compilation.-other-fixes

Restore to the last merged Jetpack Compose (1.8.0-alpha07) and Material3 (1.4.0-alpha04).

## Testing
- CI passes

## Release Notes
N/A
  • Loading branch information
igordmn committed Dec 20, 2024
1 parent 0c234a4 commit 7e070fd
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,18 @@ internal expect fun defaultLocale(): CalendarLocale
/**
* Returns a string representation of an integer for the current Locale.
*
* @param minDigits sets the minimum number of digits allowed in the integer portion of a number.
* If the minDigits value is greater than the [maxDigits] value, then [maxDigits] will also be set
* to this value.
* @param minDigits sets the minimum number of digits allowed in the integer portion of a number. If
* the minDigits value is greater than the [maxDigits] value, then [maxDigits] will also be set to
* this value.
* @param maxDigits sets the maximum number of digits allowed in the integer portion of a number. If
* this maxDigits value is less than the [minDigits] value, then [minDigits] will also be set to
* this value.
* @param isGroupingUsed set whether or not grouping will be used when formatting into a local
* string. By default, this value is false, which eliminates any use of delimiters when formatting
* the integer.
*/
internal expect fun Int.toLocalString(
minDigits: Int = 1,
// Removed other arguments comparing with upstream because they aren't used
maxDigits: Int = 40,
isGroupingUsed: Boolean = false
): String
Original file line number Diff line number Diff line change
Expand Up @@ -287,71 +287,41 @@ internal fun DateInputTextField(
*/
@OptIn(ExperimentalMaterial3Api::class)
@Stable
internal class DateInputValidator(
private val yearRange: IntRange,
private val selectableDates: SelectableDates,
private val dateInputFormat: DateInputFormat,
private val dateFormatter: DatePickerFormatter,
private val errorDatePattern: String,
private val errorDateOutOfYearRange: String,
private val errorInvalidNotAllowed: String,
private val errorInvalidRangeInput: String
internal expect class DateInputValidator(
yearRange: IntRange,
selectableDates: SelectableDates,
dateInputFormat: DateInputFormat,
dateFormatter: DatePickerFormatter,
errorDatePattern: String,
errorDateOutOfYearRange: String,
errorInvalidNotAllowed: String,
errorInvalidRangeInput: String,
) {
/**
* the currently selected start date in milliseconds. Only checked against when the
* [InputIdentifier] is [InputIdentifier.EndDateInput].
*/
var currentStartDateMillis: Long? = null

var currentStartDateMillis: Long?
/**
* the currently selected end date in milliseconds. Only checked against when the
* [InputIdentifier] is [InputIdentifier.StartDateInput].
*/
var currentEndDateMillis: Long? = null
var currentEndDateMillis: Long?

/**
* 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 dateToValidate a [CalendarDate] input to validate
* @param inputIdentifier an [InputIdentifier] that provides information about the input field
* that is supposed to hold the date.
* @param locale the current [CalendarLocale]
*/
fun validate(
dateToValidate: CalendarDate?,
inputIdentifier: InputIdentifier,
locale: CalendarLocale
): String {
if (dateToValidate == null) {
return errorDatePattern.format(dateInputFormat.patternWithDelimiters.uppercase())
}
// Check that the date is within the valid range of years.
if (!yearRange.contains(dateToValidate.year)) {
return errorDateOutOfYearRange.format(
yearRange.first.toLocalString(),
yearRange.last.toLocalString()
)
}
// Check that the provided SelectableDates allows this date to be selected.
with(selectableDates) {
if (
!isSelectableYear(dateToValidate.year) ||
!isSelectableDate(dateToValidate.utcTimeMillis)
) {
return errorInvalidNotAllowed.format(
dateFormatter.formatDate(
dateMillis = dateToValidate.utcTimeMillis,
locale = locale
)
)
}
}

// Additional validation when the InputIdentifier is for start of end dates in a range input
if (
(inputIdentifier == InputIdentifier.StartDateInput &&
dateToValidate.utcTimeMillis >= (currentEndDateMillis ?: Long.MAX_VALUE)) ||
(inputIdentifier == InputIdentifier.EndDateInput &&
dateToValidate.utcTimeMillis < (currentStartDateMillis ?: Long.MIN_VALUE))
) {
// The input start date is after the end date, or the end date is before the start date.
return errorInvalidRangeInput
}

return ""
}
): String
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.internal.Strings
import androidx.compose.material3.internal.format
import androidx.compose.material3.internal.getString
import androidx.compose.material3.internal.rememberAccessibilityServiceState
import androidx.compose.material3.tokens.MotionSchemeKeyTokens
Expand Down Expand Up @@ -587,7 +586,7 @@ fun rememberTimePickerState(

/** Represents the different configurations for the layout of the Time Picker */
@Immutable
@kotlin.jvm.JvmInline
@JvmInline
@ExperimentalMaterial3Api
value class TimePickerLayoutType internal constructor(internal val value: Int) {

Expand Down Expand Up @@ -1965,7 +1964,7 @@ internal fun numberContentDescription(
Strings.TimePickerHourSuffix
}

return getString(id).format(number)
return getString(id, number)
}

private fun dist(x1: Float, y1: Float, x2: Int, y2: Int): Float {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,22 +298,7 @@ internal data class DateInputFormat(val patternWithDelimiters: String, val delim
* - 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 delimiter = delimiterMatchResult!!.groups[0]!!.value
return DateInputFormat(patternWithDelimiters = patternWithDelimiters, delimiter = delimiter[0])
}
internal expect fun datePatternAsInputFormat(localeFormat: String): DateInputFormat

internal const val DaysInWeek: Int = 7
internal const val MillisecondsIn24Hours = 86400000L
internal const val MillisecondsIn24HoursDouble = 86400000.0
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,6 @@ internal expect value class Strings constructor(val value: Int) {

@Composable @ReadOnlyComposable internal expect fun getString(string: Strings): String

internal expect fun String.format(vararg formatArgs: Any?): String
@Composable
@ReadOnlyComposable
internal expect fun getString(string: Strings, vararg formatArgs: Any): String
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ package androidx.compose.material3
*/
internal actual fun Int.toLocalString(
minDigits: Int,
maxDigits: Int,
isGroupingUsed: Boolean
): String = toString().padStart(minDigits, '0')
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2024 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.
*/
package androidx.compose.material3

import androidx.compose.material3.internal.CalendarDate
import androidx.compose.material3.internal.DateInputFormat
import androidx.compose.material3.internal.format
import androidx.compose.runtime.Stable

@OptIn(ExperimentalMaterial3Api::class)
@Stable
internal actual class DateInputValidator
actual constructor(
private val yearRange: IntRange,
private val selectableDates: SelectableDates,
private val dateInputFormat: DateInputFormat,
private val dateFormatter: DatePickerFormatter,
private val errorDatePattern: String,
private val errorDateOutOfYearRange: String,
private val errorInvalidNotAllowed: String,
private val errorInvalidRangeInput: String
) {
/**
* the currently selected start date in milliseconds. Only checked against when the
* [InputIdentifier] is [InputIdentifier.EndDateInput].
*/
actual var currentStartDateMillis: Long? = null

/**
* the currently selected end date in milliseconds. Only checked against when the
* [InputIdentifier] is [InputIdentifier.StartDateInput].
*/
actual var currentEndDateMillis: Long? = null

actual fun validate(
dateToValidate: CalendarDate?,
inputIdentifier: InputIdentifier,
locale: CalendarLocale
): String {
if (dateToValidate == null) {
return errorDatePattern.format(dateInputFormat.patternWithDelimiters.uppercase())
}
// Check that the date is within the valid range of years.
if (!yearRange.contains(dateToValidate.year)) {
return errorDateOutOfYearRange.format(
yearRange.first.toLocalString(),
yearRange.last.toLocalString()
)
}
// Check that the provided SelectableDates allows this date to be selected.
with(selectableDates) {
if (
!isSelectableYear(dateToValidate.year) ||
!isSelectableDate(dateToValidate.utcTimeMillis)
) {
return errorInvalidNotAllowed.format(
dateFormatter.formatDate(
dateMillis = dateToValidate.utcTimeMillis,
locale = locale
)
)
}
}

// Additional validation when the InputIdentifier is for start of end dates in a range input
if (
(inputIdentifier == InputIdentifier.StartDateInput &&
dateToValidate.utcTimeMillis > (currentEndDateMillis ?: Long.MAX_VALUE)) ||
(inputIdentifier == InputIdentifier.EndDateInput &&
dateToValidate.utcTimeMillis < (currentStartDateMillis ?: Long.MIN_VALUE))
) {
// The input start date is after the end date, or the end date is before the start date.
return errorInvalidRangeInput
}

return ""
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,19 @@ internal actual fun formatWithSkeleton(
skeleton = skeleton,
)
}

internal actual 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 delimiter = delimiterMatchResult!!.groups[0]!!.value
return DateInputFormat(patternWithDelimiters = patternWithDelimiters, delimiter = delimiter[0])
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal actual value class Strings(val value: Int) {
// (without creating intermediate strings)
// TODO current implementation doesn't support sophisticated formatting like %.2f,
// but currently we use it only for integers and strings
internal actual fun String.format(vararg formatArgs: Any?): String {
internal fun String.format(vararg formatArgs: Any?): String {
var result = this
formatArgs.forEachIndexed { index, arg ->
result = result
Expand All @@ -125,6 +125,11 @@ internal actual fun getString(string: Strings): String {
return translation[string] ?: error("Missing translation for $string")
}

@Composable
@ReadOnlyComposable
internal actual fun getString(string: Strings, vararg formatArgs: Any): String =
getString(string).format(*formatArgs)

/**
* A single translation; should contain all the [Strings].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,6 @@ private inline fun dateLocaleOptions(init: Date.LocaleOptions.() -> Unit): Date.
val result = emptyLocaleOptions()
init(result)
return result
}
}

internal const val MillisecondsIn24HoursDouble = 86400000.0

0 comments on commit 7e070fd

Please sign in to comment.