mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 16:25:27 +02:00
Localized time zone names
This commit is contained in:
parent
6dc1e93c1f
commit
ce8bc5c738
@ -18,6 +18,8 @@
|
||||
|
||||
package com.sadellie.unitto.data.common
|
||||
|
||||
import android.icu.text.LocaleDisplayNames
|
||||
import android.icu.text.TimeZoneNames
|
||||
import android.icu.util.TimeZone
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
@ -31,7 +33,18 @@ fun TimeZone.offset(currentTime: ZonedDateTime): ZonedDateTime {
|
||||
return currentTimeWithoutOffset.plusSeconds(this.rawOffset / 1000L)
|
||||
}
|
||||
|
||||
val TimeZone.region: String
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun TimeZone.regionName(
|
||||
timeZoneNames: TimeZoneNames,
|
||||
localeDisplayNames: LocaleDisplayNames
|
||||
): String {
|
||||
val location = timeZoneNames.getExemplarLocationName(this.id) ?: return fallbackRegion
|
||||
val region = localeDisplayNames.regionDisplayName(TimeZone.getRegion(id)) ?: return fallbackRegion
|
||||
|
||||
return "$location, $region"
|
||||
}
|
||||
|
||||
private val TimeZone.fallbackRegion: String
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
get() = id
|
||||
.replace("_", " ")
|
||||
|
@ -18,11 +18,14 @@
|
||||
|
||||
package com.sadellie.unitto.data.timezone
|
||||
|
||||
import android.icu.text.LocaleDisplayNames
|
||||
import android.icu.text.TimeZoneNames
|
||||
import android.icu.util.TimeZone
|
||||
import android.icu.util.ULocale
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.sadellie.unitto.data.common.lev
|
||||
import com.sadellie.unitto.data.common.region
|
||||
import com.sadellie.unitto.data.common.regionName
|
||||
import com.sadellie.unitto.data.database.TimeZoneDao
|
||||
import com.sadellie.unitto.data.model.timezone.FavoriteZone
|
||||
import com.sadellie.unitto.data.model.timezone.SearchResultZone
|
||||
@ -90,10 +93,13 @@ class TimeZonesRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun filterAllTimeZones(
|
||||
searchQuery: String
|
||||
searchQuery: String,
|
||||
locale: ULocale,
|
||||
): List<SearchResultZone> =
|
||||
withContext(Dispatchers.IO) {
|
||||
val favorites = dao.getFavorites().first().map { it.id }
|
||||
val timeZoneNames = TimeZoneNames.getInstance(locale)
|
||||
val localeDisplayNames = LocaleDisplayNames.getInstance(locale)
|
||||
|
||||
val query = searchQuery.trim().lowercase()
|
||||
val threshold: Int = query.length / 2
|
||||
@ -103,8 +109,8 @@ class TimeZonesRepository @Inject constructor(
|
||||
if (timeZoneId in favorites) return@forEach
|
||||
|
||||
val timeZone = TimeZone.getTimeZone(timeZoneId)
|
||||
val name = timeZone.displayName
|
||||
val id = timeZone.region
|
||||
val displayName = timeZone.displayName
|
||||
val regionName = timeZone.regionName(timeZoneNames, localeDisplayNames)
|
||||
|
||||
// // CODE Match
|
||||
// if (codeToTimeZoneId[timeZone.id]?.lowercase() == query) {
|
||||
@ -115,42 +121,42 @@ class TimeZonesRepository @Inject constructor(
|
||||
// Display name match
|
||||
when {
|
||||
// not zero, so that lev can have that
|
||||
name.startsWith(query) -> {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, id) to 1)
|
||||
displayName.startsWith(query) -> {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, regionName) to 1)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
name.contains(query) -> {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, id) to 2)
|
||||
displayName.contains(query) -> {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, regionName) to 2)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
val nameLevDist = name
|
||||
.substring(0, minOf(query.length, name.length))
|
||||
val displayNameLevDist = displayName
|
||||
.substring(0, minOf(query.length, displayName.length))
|
||||
.lev(query)
|
||||
if (nameLevDist < threshold) {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, id) to nameLevDist)
|
||||
if (displayNameLevDist < threshold) {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, regionName) to displayNameLevDist)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
// ID Match
|
||||
when {
|
||||
// not zero, so that lev can have that
|
||||
id.startsWith(query) -> {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, id) to 1)
|
||||
regionName.startsWith(query) -> {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, regionName) to 1)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
id.contains(query) -> {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, id) to 2)
|
||||
regionName.contains(query) -> {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, regionName) to 2)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
val idLevDist = id
|
||||
.substring(0, minOf(query.length, id.length))
|
||||
val regionNameLevDist = regionName
|
||||
.substring(0, minOf(query.length, regionName.length))
|
||||
.lev(query)
|
||||
if (idLevDist < threshold) {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, id) to idLevDist)
|
||||
if (regionNameLevDist < threshold) {
|
||||
timeZonesWithDist.add(SearchResultZone(timeZone, regionName) to regionNameLevDist)
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ dependencies {
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
implementation(libs.com.github.sadellie.themmo)
|
||||
implementation(libs.org.burnoutcrew.composereorderable.reorderable)
|
||||
implementation(libs.androidx.appcompat.appcompat)
|
||||
|
||||
implementation(project(":data:common"))
|
||||
implementation(project(":data:userprefs"))
|
||||
|
@ -18,10 +18,14 @@
|
||||
|
||||
package com.sadellie.unitto.feature.timezone.components
|
||||
|
||||
import android.icu.text.LocaleDisplayNames
|
||||
import android.icu.text.TimeZoneNames
|
||||
import android.icu.util.TimeZone
|
||||
import android.icu.util.ULocale
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
@ -38,6 +42,10 @@ class FavoriteTimeZonesTest {
|
||||
@Test
|
||||
fun convertTime(): Unit = with(composeTestRule) {
|
||||
setContent {
|
||||
val locale = ULocale.getDefault()
|
||||
val timeZoneNames = remember(locale) { TimeZoneNames.getInstance(locale) }
|
||||
val localeDisplayNames = remember(locale) { LocaleDisplayNames.getInstance(locale) }
|
||||
|
||||
FavoriteTimeZoneItem(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer),
|
||||
@ -55,7 +63,9 @@ class FavoriteTimeZonesTest {
|
||||
onDelete = {},
|
||||
onPrimaryClick = {},
|
||||
onLabelClick = {},
|
||||
isDragging = false
|
||||
isDragging = false,
|
||||
timeZoneNames = timeZoneNames,
|
||||
localeDisplayNames = localeDisplayNames
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,13 @@
|
||||
|
||||
package com.sadellie.unitto.feature.timezone
|
||||
|
||||
import android.icu.text.LocaleDisplayNames
|
||||
import android.icu.text.TimeZoneNames
|
||||
import android.icu.util.TimeZone
|
||||
import android.icu.util.ULocale
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@ -45,7 +49,7 @@ import com.sadellie.unitto.core.ui.common.UnittoSearchBar
|
||||
import com.sadellie.unitto.core.ui.datetime.formatLocal
|
||||
import com.sadellie.unitto.core.ui.theme.numberHeadlineSmall
|
||||
import com.sadellie.unitto.data.common.offset
|
||||
import com.sadellie.unitto.data.common.region
|
||||
import com.sadellie.unitto.data.common.regionName
|
||||
import com.sadellie.unitto.data.model.timezone.SearchResultZone
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
@ -78,6 +82,7 @@ fun AddTimeZoneScreen(
|
||||
userTime: ZonedDateTime,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val locale = ULocale.forLanguageTag(AppCompatDelegate.getApplicationLocales().toLanguageTags())
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
@ -104,7 +109,7 @@ fun AddTimeZoneScreen(
|
||||
addToFavorites(it.timeZone)
|
||||
navigateUp()
|
||||
},
|
||||
headlineContent = { Text(it.timeZone.displayName) },
|
||||
headlineContent = { Text(it.timeZone.getDisplayName(locale)) },
|
||||
supportingContent = { Text(it.formattedLabel) },
|
||||
trailingContent = {
|
||||
Text(
|
||||
@ -124,6 +129,7 @@ fun AddTimeZoneScreen(
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewAddTimeZoneScreen() {
|
||||
val locale = ULocale.getDefault()
|
||||
AddTimeZoneScreen(
|
||||
uiState = AddTimeZoneUIState.Ready(
|
||||
query = TextFieldValue(),
|
||||
@ -135,7 +141,10 @@ fun PreviewAddTimeZoneScreen() {
|
||||
val zone = TimeZone.getTimeZone(it)
|
||||
SearchResultZone(
|
||||
timeZone = zone,
|
||||
formattedLabel = zone.region
|
||||
formattedLabel = zone.regionName(
|
||||
timeZoneNames = TimeZoneNames.getInstance(locale),
|
||||
localeDisplayNames = LocaleDisplayNames.getInstance(locale)
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
|
@ -19,8 +19,10 @@
|
||||
package com.sadellie.unitto.feature.timezone
|
||||
|
||||
import android.icu.util.TimeZone
|
||||
import android.icu.util.ULocale
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@ -55,7 +57,14 @@ class AddTimeZoneViewModel @Inject constructor(
|
||||
}
|
||||
.mapLatest { ui ->
|
||||
viewModelScope.launch {
|
||||
_result.update { timezonesRepository.filterAllTimeZones(ui.query.text) }
|
||||
_result.update {
|
||||
timezonesRepository.filterAllTimeZones(
|
||||
searchQuery = ui.query.text,
|
||||
locale = ULocale.forLanguageTag(
|
||||
AppCompatDelegate.getApplicationLocales().toLanguageTags()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
ui
|
||||
}
|
||||
|
@ -18,9 +18,13 @@
|
||||
|
||||
package com.sadellie.unitto.feature.timezone
|
||||
|
||||
import android.icu.text.LocaleDisplayNames
|
||||
import android.icu.text.TimeZoneNames
|
||||
import android.icu.util.TimeZone
|
||||
import android.icu.util.ULocale
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.animation.core.animateDp
|
||||
import androidx.compose.animation.core.animateInt
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
@ -137,6 +141,11 @@ private fun TimeZoneScreen(
|
||||
}
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
val locale = ULocale.forLanguageTag(AppCompatDelegate.getApplicationLocales().toLanguageTags())
|
||||
|
||||
val timeZoneNames = remember(locale) { TimeZoneNames.getInstance(locale) }
|
||||
val localeDisplayNames = remember(locale) { LocaleDisplayNames.getInstance(locale) }
|
||||
|
||||
LaunchedEffect(uiState.customUserTime) {
|
||||
while ((uiState.customUserTime == null) and isActive) {
|
||||
currentUserTime = uiState.userTimeZone.timeNow()
|
||||
@ -247,7 +256,9 @@ private fun TimeZoneScreen(
|
||||
onPrimaryClick = { offsetTime ->
|
||||
setDialogState(TimeZoneDialogState.FavoriteTimePicker(item, offsetTime))
|
||||
},
|
||||
isDragging = isDragging
|
||||
isDragging = isDragging,
|
||||
timeZoneNames = timeZoneNames,
|
||||
localeDisplayNames = localeDisplayNames,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,10 @@
|
||||
|
||||
package com.sadellie.unitto.feature.timezone.components
|
||||
|
||||
import android.icu.text.LocaleDisplayNames
|
||||
import android.icu.text.TimeZoneNames
|
||||
import android.icu.util.TimeZone
|
||||
import android.icu.util.ULocale
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
@ -65,6 +68,7 @@ import com.sadellie.unitto.core.ui.datetime.formatLocal
|
||||
import com.sadellie.unitto.core.ui.datetime.formatOffset
|
||||
import com.sadellie.unitto.core.ui.theme.numberHeadlineMedium
|
||||
import com.sadellie.unitto.data.common.offset
|
||||
import com.sadellie.unitto.data.common.regionName
|
||||
import com.sadellie.unitto.data.model.timezone.FavoriteZone
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
@ -81,6 +85,8 @@ internal fun FavoriteTimeZoneItem(
|
||||
onDelete: () -> Unit,
|
||||
onLabelClick: () -> Unit,
|
||||
onPrimaryClick: (ZonedDateTime) -> Unit,
|
||||
timeZoneNames: TimeZoneNames,
|
||||
localeDisplayNames: LocaleDisplayNames,
|
||||
) {
|
||||
var deleteAnimationRunning by remember { mutableStateOf(false) }
|
||||
val animatedAlpha by animateFloatAsState(
|
||||
@ -89,6 +95,10 @@ internal fun FavoriteTimeZoneItem(
|
||||
finishedListener = { if (it == 0f) onDelete() }
|
||||
)
|
||||
|
||||
val regionName = remember(timeZoneNames, localeDisplayNames) {
|
||||
item.timeZone.regionName(timeZoneNames, localeDisplayNames)
|
||||
}
|
||||
|
||||
val offsetTime by remember(fromTime) { mutableStateOf(item.timeZone.offset(fromTime)) }
|
||||
val offsetTimeFormatted = offsetTime.formatOffset(fromTime)
|
||||
|
||||
@ -118,7 +128,7 @@ internal fun FavoriteTimeZoneItem(
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||
) {
|
||||
Text(
|
||||
text = item.timeZone.displayName,
|
||||
text = regionName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
@ -298,6 +308,9 @@ private fun PreviewFavoriteTimeZones(
|
||||
@PreviewParameter(FavoriteTimeZoneItemParameterProvider::class) tz: FavoriteTimeZoneItemParameter,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(tz.expanded) }
|
||||
val locale = ULocale.getDefault()
|
||||
val timeZoneNames = remember(locale) { TimeZoneNames.getInstance(locale) }
|
||||
val localeDisplayNames = remember(locale) { LocaleDisplayNames.getInstance(locale) }
|
||||
|
||||
FavoriteTimeZoneItem(
|
||||
modifier = Modifier
|
||||
@ -312,6 +325,8 @@ private fun PreviewFavoriteTimeZones(
|
||||
onDelete = {},
|
||||
onPrimaryClick = {},
|
||||
onLabelClick = {},
|
||||
isDragging = false
|
||||
isDragging = false,
|
||||
timeZoneNames = timeZoneNames,
|
||||
localeDisplayNames = localeDisplayNames
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user