From caf025778e30ef65c704ef195e714454f611d30a Mon Sep 17 00:00:00 2001 From: sadellie Date: Sat, 22 Jul 2023 18:30:09 +0300 Subject: [PATCH] Added Time zone converter Hidden as "tools experiment" Missing: - Clean data - Translations - UI/UX clean up - Relative time change functionality - Custom user time zone functionality closes #59 this is a squashed commit --- .../java/com/sadellie/unitto/UnittoApp.kt | 20 +- .../com/sadellie/unitto/UnittoNavigation.kt | 3 +- .../unitto/core/base/TopLevelDestinations.kt | 5 +- core/base/src/main/res/values-it/strings.xml | 2010 ++++++------- core/base/src/main/res/values/strings.xml | 2541 +++++++++-------- core/ui/build.gradle.kts | 1 + .../core/ui/common/DateTimePickerDialog.kt | 28 +- .../core/ui/common/UnittoScreenWithTopBar.kt | 12 +- .../core/ui/datetime/DateTimeFormatter.kt | 27 + .../core/ui/datetime/LocalDateTimeUtils.kt | 48 + .../core/ui/datetime/ZonedDateTimeUtils.kt | 120 + .../unitto/core/ui/model/DrawerItems.kt | 8 + .../com/sadellie/unitto/core/ui/theme/Type.kt | 4 +- .../unitto/core/ui/ZonedDateTimeUtilsTest.kt | 176 ++ .../2.json | 44 +- .../3.json | 122 + .../unitto/data/database/TimeZoneDao.kt | 41 + .../unitto/data/database/TimeZoneEntity.kt | 30 + .../unitto/data/database/UnittoDatabase.kt | 9 +- .../data/database/UnittoDatabaseModule.kt | 11 + .../unitto/data/model/UnittoTimeZone.kt | 37 + data/timezone/.gitignore | 1 + data/timezone/build.gradle.kts | 33 + data/timezone/consumer-rules.pro | 0 data/timezone/src/main/AndroidManifest.xml | 22 + .../data/timezone/TimeZonesRepository.kt | 490 ++++ .../unitto/data/userprefs/UserPreferences.kt | 7 +- .../datedifference/DateDifferenceScreen.kt | 59 +- .../datedifference/DateDifferenceViewModel.kt | 8 +- feature/timezone/build.gradle.kts | 2 + .../unitto/timezone/AddTimeZoneScreen.kt | 156 + .../unitto/timezone/AddTimeZoneUIState.kt | 28 + .../unitto/timezone/AddTimeZoneViewModel.kt | 82 + .../unitto/timezone/TimeZoneScreen.kt | 358 ++- .../unitto/timezone/TimeZoneUIState.kt | 28 + .../unitto/timezone/TimeZoneViewModel.kt | 78 + .../timezone/components/SelectableTimeZone.kt | 71 + .../timezone/components/TimeZoneListItem.kt | 186 ++ .../timezone/components/UnittoSearchBar.kt | 193 ++ .../timezone/navigation/TimeZoneNavigation.kt | 64 +- settings.gradle.kts | 1 + 41 files changed, 4813 insertions(+), 2351 deletions(-) create mode 100644 core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/DateTimeFormatter.kt create mode 100644 core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/LocalDateTimeUtils.kt create mode 100644 core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/ZonedDateTimeUtils.kt create mode 100644 core/ui/src/test/java/com/sadellie/unitto/core/ui/ZonedDateTimeUtilsTest.kt create mode 100644 data/database/schemas/com.sadellie.unitto.data.database.UnittoDatabase/3.json create mode 100644 data/database/src/main/java/com/sadellie/unitto/data/database/TimeZoneDao.kt create mode 100644 data/database/src/main/java/com/sadellie/unitto/data/database/TimeZoneEntity.kt create mode 100644 data/model/src/main/java/com/sadellie/unitto/data/model/UnittoTimeZone.kt create mode 100644 data/timezone/.gitignore create mode 100644 data/timezone/build.gradle.kts create mode 100644 data/timezone/consumer-rules.pro create mode 100644 data/timezone/src/main/AndroidManifest.xml create mode 100644 data/timezone/src/main/java/com/sadellie/unitto/data/timezone/TimeZonesRepository.kt create mode 100644 feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneScreen.kt create mode 100644 feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneUIState.kt create mode 100644 feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneViewModel.kt create mode 100644 feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneUIState.kt create mode 100644 feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneViewModel.kt create mode 100644 feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/SelectableTimeZone.kt create mode 100644 feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/TimeZoneListItem.kt create mode 100644 feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/UnittoSearchBar.kt diff --git a/app/src/main/java/com/sadellie/unitto/UnittoApp.kt b/app/src/main/java/com/sadellie/unitto/UnittoApp.kt index 224f872a..3c65f759 100644 --- a/app/src/main/java/com/sadellie/unitto/UnittoApp.kt +++ b/app/src/main/java/com/sadellie/unitto/UnittoApp.kt @@ -72,11 +72,19 @@ internal fun UnittoApp(uiPrefs: UIPreferences) { // Navigation drawer stuff val drawerState = rememberUnittoDrawerState() val drawerScope = rememberCoroutineScope() - val mainTabs = listOf( - DrawerItems.Calculator, - DrawerItems.Converter, - DrawerItems.DateDifference - ) + val mainTabs by remember(uiPrefs.enableToolsExperiment) { + derivedStateOf { + if (uiPrefs.enableToolsExperiment) { + listOf( + DrawerItems.Calculator, DrawerItems.Converter, DrawerItems.DateDifference, DrawerItems.TimeZones + ) + } else { + listOf( + DrawerItems.Calculator, DrawerItems.Converter, DrawerItems.DateDifference, + ) + } + } + } val additionalTabs = listOf(DrawerItems.Settings) val navBackStackEntry by navController.currentBackStackEntryAsState() @@ -129,7 +137,7 @@ internal fun UnittoApp(uiPrefs: UIPreferences) { UnittoNavigation( navController = navController, themmoController = it, - startDestination = uiPrefs.startingScreen, + startDestination = TopLevelDestinations.TimeZone.route, openDrawer = { drawerScope.launch { drawerState.open() } } ) } diff --git a/app/src/main/java/com/sadellie/unitto/UnittoNavigation.kt b/app/src/main/java/com/sadellie/unitto/UnittoNavigation.kt index f52e1fe2..3b742e43 100644 --- a/app/src/main/java/com/sadellie/unitto/UnittoNavigation.kt +++ b/app/src/main/java/com/sadellie/unitto/UnittoNavigation.kt @@ -95,7 +95,8 @@ internal fun UnittoNavigation( timeZoneScreen( navigateToMenu = openDrawer, - navigateToSettings = navController::navigateToSettings + navigateToSettings = navController::navigateToSettings, + navController = navController, ) } } diff --git a/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt b/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt index 6ee4bad5..c25222bc 100644 --- a/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt +++ b/core/base/src/main/java/com/sadellie/unitto/core/base/TopLevelDestinations.kt @@ -40,8 +40,8 @@ sealed class TopLevelDestinations( ) object TimeZone : TopLevelDestinations( - route = "time_zone_route", - name = R.string.time_zone + route = "time_zone_graph", + name = R.string.time_zone_screen ) object Settings : TopLevelDestinations( @@ -54,5 +54,6 @@ val TOP_LEVEL_DESTINATIONS: Map by lazy { mapOf( TopLevelDestinations.Converter to R.string.unit_converter, TopLevelDestinations.Calculator to R.string.calculator, + TopLevelDestinations.DateDifference to R.string.date_difference, ) } diff --git a/core/base/src/main/res/values-it/strings.xml b/core/base/src/main/res/values-it/strings.xml index cfe97c94..89b828a7 100644 --- a/core/base/src/main/res/values-it/strings.xml +++ b/core/base/src/main/res/values-it/strings.xml @@ -2,1100 +2,1100 @@ - "Attometro" - "am" - "Nanometro" - "nm" - "Micrometro" - "μm" - "Millimetro" - "mm" - "Centimetro" - "cm" - "Decimetro" - "dm" - "Metro" - "m" - "Chilometro" - "km" - "Miglio" - "Mi" - "Iarda" - "yd" - "Piede" - "ft" - "Pollice" - "in" - "Anno luce" - "al" - "Parsec" - "pc" - "Kiloparsec" - "kpc" - "Megaparsec" - "Mpc" - "Raggio equatoriale di Mercurio" - "R Mercurio" - "Raggio equatoriale di Venere" - "R Venere" - "Raggio equatoriale della Terra" - "R Terra" - "Raggio equatoriale di Marte" - "R Marte" - "Raggio equatoriale di Giove" - "R Giove" - "Raggio equatoriale di Saturno" - "R Saturno" - "Raggio equatoriale di Urano" - "R Urano" - "Raggio equatoriale di Nettuno" - "R Nettuno" - "Raggio equatoriale del Sole" - "R Sole" + Attometro + am + Nanometro + nm + Micrometro + μm + Millimetro + mm + Centimetro + cm + Decimetro + dm + Metro + m + Chilometro + km + Miglio + Mi + Iarda + yd + Piede + ft + Pollice + in + Anno luce + al + Parsec + pc + Kiloparsec + kpc + Megaparsec + Mpc + Raggio equatoriale di Mercurio + R Mercurio + Raggio equatoriale di Venere + R Venere + Raggio equatoriale della Terra + R Terra + Raggio equatoriale di Marte + R Marte + Raggio equatoriale di Giove + R Giove + Raggio equatoriale di Saturno + R Saturno + Raggio equatoriale di Urano + R Urano + Raggio equatoriale di Nettuno + R Nettuno + Raggio equatoriale del Sole + R Sole - "Massa elettrone" - "m e" - "Dalton" - "u" - "Milligrammo" - "mg" - "Grammo" - "g" - "Chilogrammo" - "hg" - "Tonnellata metrica" - "t" - "Tonnellata imperiale" - "t (UK)" - "Libbra" - "lbs" - "Oncia" - "oz" - "Carato" - "ct" - "Massa di Mercurio" - "M Mercurio" - "Massa di Venere" - "M Venere" - "Massa della Terra" - "M Terra" - "Massa di Marte" - "M Marte" - "Massa di Giove" - "M Giove" - "Massa di Saturno" - "M Saturno" - "Massa di Urano" - "M Urano" - "Massa di Nettuno" - "M Nettuno" - "Massa del Sole" - "M Sole" + Massa elettrone + m e + Dalton + u + Milligrammo + mg + Grammo + g + Chilogrammo + hg + Tonnellata metrica + t + Tonnellata imperiale + t (UK) + Libbra + lbs + Oncia + oz + Carato + ct + Massa di Mercurio + M Mercurio + Massa di Venere + M Venere + Massa della Terra + M Terra + Massa di Marte + M Marte + Massa di Giove + M Giove + Massa di Saturno + M Saturno + Massa di Urano + M Urano + Massa di Nettuno + M Nettuno + Massa del Sole + M Sole - "Celsius" - "Fahrenheit" - "Kelvin" + Celsius + Fahrenheit + Kelvin - "Millimetro all'ora" - "mm/h" - "Millimetro al minuto" - "mm/m" - "Millimetro al secondo" - "mm/s" - "Centimetro all'ora" - "cm/h" - "Centimetro al minuto" - "cm/m" - "Centimetro al secondo" - "cm/s" - "Metro all'ora" - "m/h" - "Metro al minuto" - "m/m" - "Metro al secondo" - "m/s" - "Chilometro all'ora" - "km/h" - "Chilometro al minuto" - "km/m" - "Chilometro al secondo" - "km/s" - "Piede all'ora" - "ft/h" - "Piede al minuto" - "ft/m" - "Piede al secondo" - "ft/s" - "Iarda all'ora" - "yd/h" - "Iarda al minuto" - "yd/m" - "Iarda al secondo" - "yd/s" - "Miglia all'ora" - "mi/h" - "Miglia al minuto" - "mi/m" - "Miglia al secondo" - "mi/s" - "Nodi" - "kt" - "Velocità della luce nel vuoto" - "Prima velocità cosmica" - "Seconda velocità cosmica" - "Terza velocità cosmica" - "Velocità orbitale della Terra" - "Mach" - "Mach (SI)" + Millimetro all\'ora + mm/h + Millimetro al minuto + mm/m + Millimetro al secondo + mm/s + Centimetro all\'ora + cm/h + Centimetro al minuto + cm/m + Centimetro al secondo + cm/s + Metro all\'ora + m/h + Metro al minuto + m/m + Metro al secondo + m/s + Chilometro all\'ora + km/h + Chilometro al minuto + km/m + Chilometro al secondo + km/s + Piede all\'ora + ft/h + Piede al minuto + ft/m + Piede al secondo + ft/s + Iarda all\'ora + yd/h + Iarda al minuto + yd/m + Iarda al secondo + yd/s + Miglia all\'ora + mi/h + Miglia al minuto + mi/m + Miglia al secondo + mi/s + Nodi + kt + Velocità della luce nel vuoto + Prima velocità cosmica + Seconda velocità cosmica + Terza velocità cosmica + Velocità orbitale della Terra + Mach + Mach (SI) - "Bit" - "b" - "Kibibit" - "Kib" - "Kilobit" - "Kb" - "Megabit" - "Mb" - "Mebibit" - "Mib" - "Gigabit" - "Gb" - "Terabit" - "Tb" - "Petabit" - "Pb" - "Exabit" - "Eb" - "Byte" - "B" - "Kibibyte" - "KiB" - "Kilobyte" - "KB" - "Megabyte" - "MB" - "Mebibyte" - "MiB" - "Gigabyte" - "GB" - "Terabyte" - "TB" - "Petabyte" - "PB" - "Exabyte" - "EB" + Bit + b + Kibibit + Kib + Kilobit + Kb + Megabit + Mb + Mebibit + Mib + Gigabit + Gb + Terabit + Tb + Petabit + Pb + Exabit + Eb + Byte + B + Kibibyte + KiB + Kilobyte + KB + Megabyte + MB + Mebibyte + MiB + Gigabyte + GB + Terabyte + TB + Petabyte + PB + Exabyte + EB - "Bit al secondo" - "b/s" - "Kibibit al secondo" - "Kib/s" - "Kilobit al secondo" - "Kb/s" - "Megabit al secondo" - "Mb/s" - "Mebibit al secondo" - "Mib/s" - "Gigabit al secondo" - "Gb/s" - "Terabit al secondo" - "Tb/s" - "Petabit al secondo" - "Pb/s" - "Exabit al secondo" - "Eb/s" - "Byte al secondo" - "B/s" - "Kibibyte al secondo" - "KiB/s" - "Kilobyte al secondo" - "KB/s" - "Megabyte al secondo" - "MB/s" - "Mebibyte al secondo" - "MiB/s" - "Gigabyte al secondo" - "GB/s" - "Terabyte al secondo" - "TB/s" - "Petabyte al secondo" - "PB/s" - "Exabyte al secondo" - "EB/s" + Bit al secondo + b/s + Kibibit al secondo + Kib/s + Kilobit al secondo + Kb/s + Megabit al secondo + Mb/s + Mebibit al secondo + Mib/s + Gigabit al secondo + Gb/s + Terabit al secondo + Tb/s + Petabit al secondo + Pb/s + Exabit al secondo + Eb/s + Byte al secondo + B/s + Kibibyte al secondo + KiB/s + Kilobyte al secondo + KB/s + Megabyte al secondo + MB/s + Mebibyte al secondo + MiB/s + Gigabyte al secondo + GB/s + Terabyte al secondo + TB/s + Petabyte al secondo + PB/s + Exabyte al secondo + EB/s - "Attolitro" - "aL" - "Millilitro" - "mL" - "Litro" - "L" - "Gallone USA" - "gal (US)" - "Quarto US" - "qt (US)" - "Pinta US" - "pt (US)" - "Coppa US" - "cup (US)" - "Oncia liquida US" - "fl oz (US)" - "Cucchiaio US" - "cucchiaio (US)" - "Cucchiaino US" - "cucchiaino (US)" - "Gallone imperiale" - "gal (UK)" - "Quarto imperiale" - "qt (UK)" - "Pinta imperiale" - "pt (UK)" - "Coppa imperiale" - "tazza (UK)" - "Oncia liquida imperiale" - "fl oz (UK)" - "Cucchiaio imperiale" - "Cucchiaino (UK)" - "Cucchiaino imperiale" - "Cucchiaino (UK)" - "Millimetro cubo" - "mm^3" - "Centimetro cubo" - "cm^3" - "Metro cubo" - "m^3" - "Chilometro cubo" - "km^3" + Attolitro + aL + Millilitro + mL + Litro + L + Gallone USA + gal (US) + Quarto US + qt (US) + Pinta US + pt (US) + Coppa US + cup (US) + Oncia liquida US + fl oz (US) + Cucchiaio US + cucchiaio (US) + Cucchiaino US + cucchiaino (US) + Gallone imperiale + gal (UK) + Quarto imperiale + qt (UK) + Pinta imperiale + pt (UK) + Coppa imperiale + tazza (UK) + Oncia liquida imperiale + fl oz (UK) + Cucchiaio imperiale + Cucchiaino (UK) + Cucchiaino imperiale + Cucchiaino (UK) + Millimetro cubo + mm^3 + Centimetro cubo + cm^3 + Metro cubo + m^3 + Chilometro cubo + km^3 - "Attosecondo" - "as" - "Nanosecondo" - "ns" - "Microsecondo" - "µs" - "Millisecondo" - "ms" - "Jiffy" - "j" - "Secondo" - "s" - "Minuto" - "m" - "Ora" - "h" - "Giorno" - "g" - "Settimana" - "s" + Attosecondo + as + Nanosecondo + ns + Microsecondo + µs + Millisecondo + ms + Jiffy + j + Secondo + s + Minuto + m + Ora + h + Giorno + g + Settimana + s - "Sezione d'urto dell'elettrone" - "ecs" - "Acro" - "ac" - "Ettaro" - "ha" - "Piede quadrato" - "ft^2" - "Miglio quadrato" - "mi^2" - "Iarda quadrata" - "yd^2" - "Pollice quadrato" - "in^2" - "Micrometro quadrato" - "µm^2" - "Millimetro quadrato" - "mm^2" - "Centimetro quadrato" - "cm^2" - "Decimetro quadrato" - "dm^2" - "Metro quadrato" - "m^2" - "Chilometro quadrato" - "km^2" + Sezione d\'urto dell\'elettrone + ecs + Acro + ac + Ettaro + ha + Piede quadrato + ft^2 + Miglio quadrato + mi^2 + Iarda quadrata + yd^2 + Pollice quadrato + in^2 + Micrometro quadrato + µm^2 + Millimetro quadrato + mm^2 + Centimetro quadrato + cm^2 + Decimetro quadrato + dm^2 + Metro quadrato + m^2 + Chilometro quadrato + km^2 - "Elettronvolt" - "eV" - "Attojoule" - "aJ" - "Cavallo potenza" - "hp" - "Joule" - "J" - "Kilojoule" - "kJ" - "Megajoule" - "MJ" - "Gigajoule" - "GJ" - "Tonnellata di TNT" - "t" - "Chilotone di TNT" - "kt" - "Megatone di TNT" - "Mt" - "Gigatone di TNT" - "Gt" - "Calorie (th)" - "cal" - "Kilocalorie (th)" - "kcal" + Elettronvolt + eV + Attojoule + aJ + Cavallo potenza + hp + Joule + J + Kilojoule + kJ + Megajoule + MJ + Gigajoule + GJ + Tonnellata di TNT + t + Chilotone di TNT + kt + Megatone di TNT + Mt + Gigatone di TNT + Gt + Calorie (th) + cal + Kilocalorie (th) + kcal - "Attowatt" - "aW" - "Watt" - "W" - "Kilowatt" - "kW" - "Megawatt" - "MW" - "Cavallo di potenza" - "hp" + Attowatt + aW + Watt + W + Kilowatt + kW + Megawatt + MW + Cavallo di potenza + hp - "Secondo d'angolo" - "Minuto" - "Grado" - "Radiante" - "rad" - "Sestante" - "sxt" - "Giro" - "tr" + Secondo d\'angolo + Minuto + Grado + Radiante + rad + Sestante + sxt + Giro + tr - "Attopascal" - "aPa" - "Femtopascal" - "fPa" - "Picopascal" - "pPa" - "Nanopascal" - "nPa" - "Micropascal" - "µPa" - "Millipascal" - "mPa" - "Centipascal" - "cPa" - "Decipascal" - "dPa" - "Pascal" - "Pa" - "Decapascal" - "daPa" - "Ettopascal" - "hPa" - "Bar" - "bar" - "Megapascal" - "MPa" - "Gigapascal" - "GPA" - "Terapascal" - "TPa" - "Petapascal" - "PPa" - "Esapascal" - "EPa" - "Libbra/pollice quadrato" - "Kilolibbra/pollice quadrato" - "Atmosfera standard" - "atm" - "Torr" - "torr" - "Millimetro di mercurio" - "mmHg" + Attopascal + aPa + Femtopascal + fPa + Picopascal + pPa + Nanopascal + nPa + Micropascal + µPa + Millipascal + mPa + Centipascal + cPa + Decipascal + dPa + Pascal + Pa + Decapascal + daPa + Ettopascal + hPa + Bar + bar + Megapascal + MPa + Gigapascal + GPA + Terapascal + TPa + Petapascal + PPa + Esapascal + EPa + Libbra/pollice quadrato + Kilolibbra/pollice quadrato + Atmosfera standard + atm + Torr + torr + Millimetro di mercurio + mmHg - "Attometro/secondo quadrato" - "am/s^2" - "Femtometro/secondo quadrato" - "fm/s^2" - "Picometro/secondo quadrato" - "pm/s^2" - "Nanometro/secondo quadrato" - "nm/s^2" - "Micrometro/secondo quadrato" - "µm/s^2" - "Millimetro/secondo quadrato" - "mm/s^2" - "Centimetro/secondo quadrato" - "cm/s^2" - "Decimetro/secondo quadrato" - "dm/s^2" - "Metro/secondo quadrato" - "m/s^2" - "Kilometro/secondo quadrato" - "km/s^2" - "Decametro/secondo quadrato" - "dam/s^2" - "Ettometro/secondo quadrato" - "hm/s^2" - "Gallone" - "Gal" - "Gravità superficie di Mercurio" - "Mercurio g" - "Gravità superficie di Venere" - "Venere g" - "Gravità superficie della Terra" - "Terra g" - "Gravità superficie di Marte" - "Marte g" - "Gravità superficie di Giove" - "Giove g" - "Gravità superficie di Saturno" - "Saturno g" - "Gravità superficie di Urano" - "Urano g" - "Gravità superficie di Nettuno" - "Nettuno g" - "Gravità superficie del Sole" - "Sole g" - "Cardano" - "Dirham Emirati Arabi" - "Afghani" - "Lek albanese" - "Dram armeno" - "Fiorino delle Antille Olandesi" - "Kwanza angolano" - "Peso argentino" - "Dollaro australiano" - "Fiorino di Aruba" - "Manat dell'Azerbaigian" - "Marco bosniaco-ungherese convertibile" - "Dollaro delle Barbados" - "Taka bengalese" - "Lev bulgaro" - "Dinaro del Bahrain" - "Franco del Burundi" - "Dollaro delle Bermuda" - "Dollaro del Brunei" - "Boliviano" - "Real brasiliano" - "Dollaro delle Bahamas" - "Ngultrum del Bhutan" - "Pula del Botswana" - "Rublo bielorusso" - "Rublo bielorusso" - "Dollaro belizeano" - "Dollaro canadese" - "Franco congolese" - "Franco svizzero" - "Chilean Unit of Account (UF)" - "Peso cileno" - "Yuan cinese" - "Peso colombiano" - "Colón costaricano" - "Peso cubano" - "Peso cubano" - "Escudo di Capo Verde" - "Corona ceca" - "Dai" - "Franco del Gibuti" - "Corona danese" - "Peso dominicano" - "Dinaro algerino" - "Lira egiziana" - "Nafka eritra" - "Birr etiope" - "Euro" - "Dollaro delle Fiji" - "Sterlina delle Falkland" - "Sterlina" - "Lari georgese" - "Cedi ghanese" - "Sterlina di Gibilterra" - "Dalasi gambiano" - "Franco guineano" - "Quetzal del Guatemala" - "Dollaro guyanese" - "Dollaro di Hong Kong" - "Lempira di Honduras" - "Kuna croata" - "Gourde di Haiti" - "Forint ungherese" - "Rupia indonesiana" - "Shekel" - "Rupia indiana" - "Dinaro iracheno" - "Rial iraniano" - "Corona islandese" - "Sterlina di Jersey" - "Dollaro giamaicano" - "Dinaro giordano" - "Yen giapponese" - "Scellino keniota" - "Som del Kirghizistan" - "Riel cambogiano" - "Franco delle Isole Comore" - "Won nord coreano" - "Won sud coreano" - "Dinaro del Kuwait" - "Dollaro Isole Cayman" - "Tenge kazako" - "Kip di Laos" - "Lira libanese" - "Rupia srilankese" - "Dollaro liberiano" - "Loti del Lesoto" - "Lita lituana" - "Lat lettone" - "Dinaro libico" - "Dirham marocchino" - "Leu moldavo" - "Ariary malgascio" - "Dinaro macedone" - "Kyat" - "Tugrik" - "Pataca" - "Ouguiya mauritana" - "Rupia mauritana" - "Rufiyaa maldiviana" - "Kwacha malawiano" - "Peso messicano" - "Ringgit malese" - "Metical mozambicano" - "Dollaro namibiano" - "Naira nigeriana" - "Córdoba nicaraguense" - "Corona norvegeese" - "Rupia nepalese" - "Dollaro neozelandese" - "Riyal dell'Oman" - "Balboa panamense" - "Sol" - "Kina papuana" - "Peso filippino" - "Rupia pakistana" - "Złoty polacco" - "Guaraní paraguaiano" - "Rial qatariota" - "Leu rumeno" - "Dinaro serbo" - "Rublo russo" - "Franco ruandese" - "Riyal saudita" - "Dollaro delle Salomone" - "Rupia delle Seychelles" - "Dinaro sudanese" - "Corona svedese" - "Dollaro di Singapore" - "Shiba Inu" - "Sterlina di Sant'Elena" - "Leone del Sierra Leone" - "Scellinio somalo" - "Dollaro del Suriname" - "Dobra di São Tomé e Príncipe (pre-2018)" - "Colon salvadoregno" - "Lira siriana" - "Lilangeni" - "Baht tailandese" - "Theta" - "Somoni del Tagikistan" - "Manat turkmeno" - "Dinaro tunisino" - "Paʻanga tongano" - "Lira turca" - "Dollaro di Trinidad & Tobago" - "Dollaro taiwanese" - "Scellino della Tanzania" - "Hryvnia ucraino" - "Scellino ugandese" - "Universe" - "Dollaro statunitense" - "USD Coin" - "Peso uruguaiano" - "Som uzbeco" - "Bolívar venezuelano" - "Dong vietnamita" - "Vatu di Vanuatu" - "Wrapped Bitcoin" - "Tala samoano" - "Franco CFA" - "Oncia d'argento" - "Dollaro dei Caraibi Orientali" - "Diritti speciali di prelievo" - "West African CFA franc" - "Franco CFP" - "Rial yemenita" - "Rand sudafricano" - "Kwacha" - "Kwacha zambiano" - "Dollaro dello Zimbabwe" + Attometro/secondo quadrato + am/s^2 + Femtometro/secondo quadrato + fm/s^2 + Picometro/secondo quadrato + pm/s^2 + Nanometro/secondo quadrato + nm/s^2 + Micrometro/secondo quadrato + µm/s^2 + Millimetro/secondo quadrato + mm/s^2 + Centimetro/secondo quadrato + cm/s^2 + Decimetro/secondo quadrato + dm/s^2 + Metro/secondo quadrato + m/s^2 + Kilometro/secondo quadrato + km/s^2 + Decametro/secondo quadrato + dam/s^2 + Ettometro/secondo quadrato + hm/s^2 + Gallone + Gal + Gravità superficie di Mercurio + Mercurio g + Gravità superficie di Venere + Venere g + Gravità superficie della Terra + Terra g + Gravità superficie di Marte + Marte g + Gravità superficie di Giove + Giove g + Gravità superficie di Saturno + Saturno g + Gravità superficie di Urano + Urano g + Gravità superficie di Nettuno + Nettuno g + Gravità superficie del Sole + Sole g + Cardano + Dirham Emirati Arabi + Afghani + Lek albanese + Dram armeno + Fiorino delle Antille Olandesi + Kwanza angolano + Peso argentino + Dollaro australiano + Fiorino di Aruba + Manat dell\'Azerbaigian + Marco bosniaco-ungherese convertibile + Dollaro delle Barbados + Taka bengalese + Lev bulgaro + Dinaro del Bahrain + Franco del Burundi + Dollaro delle Bermuda + Dollaro del Brunei + Boliviano + Real brasiliano + Dollaro delle Bahamas + Ngultrum del Bhutan + Pula del Botswana + Rublo bielorusso + Rublo bielorusso + Dollaro belizeano + Dollaro canadese + Franco congolese + Franco svizzero + Chilean Unit of Account (UF) + Peso cileno + Yuan cinese + Peso colombiano + Colón costaricano + Peso cubano + Peso cubano + Escudo di Capo Verde + Corona ceca + Dai + Franco del Gibuti + Corona danese + Peso dominicano + Dinaro algerino + Lira egiziana + Nafka eritra + Birr etiope + Euro + Dollaro delle Fiji + Sterlina delle Falkland + Sterlina + Lari georgese + Cedi ghanese + Sterlina di Gibilterra + Dalasi gambiano + Franco guineano + Quetzal del Guatemala + Dollaro guyanese + Dollaro di Hong Kong + Lempira di Honduras + Kuna croata + Gourde di Haiti + Forint ungherese + Rupia indonesiana + Shekel + Rupia indiana + Dinaro iracheno + Rial iraniano + Corona islandese + Sterlina di Jersey + Dollaro giamaicano + Dinaro giordano + Yen giapponese + Scellino keniota + Som del Kirghizistan + Riel cambogiano + Franco delle Isole Comore + Won nord coreano + Won sud coreano + Dinaro del Kuwait + Dollaro Isole Cayman + Tenge kazako + Kip di Laos + Lira libanese + Rupia srilankese + Dollaro liberiano + Loti del Lesoto + Lita lituana + Lat lettone + Dinaro libico + Dirham marocchino + Leu moldavo + Ariary malgascio + Dinaro macedone + Kyat + Tugrik + Pataca + Ouguiya mauritana + Rupia mauritana + Rufiyaa maldiviana + Kwacha malawiano + Peso messicano + Ringgit malese + Metical mozambicano + Dollaro namibiano + Naira nigeriana + Córdoba nicaraguense + Corona norvegeese + Rupia nepalese + Dollaro neozelandese + Riyal dell\'Oman + Balboa panamense + Sol + Kina papuana + Peso filippino + Rupia pakistana + Złoty polacco + Guaraní paraguaiano + Rial qatariota + Leu rumeno + Dinaro serbo + Rublo russo + Franco ruandese + Riyal saudita + Dollaro delle Salomone + Rupia delle Seychelles + Dinaro sudanese + Corona svedese + Dollaro di Singapore + Shiba Inu + Sterlina di Sant\'Elena + Leone del Sierra Leone + Scellinio somalo + Dollaro del Suriname + Dobra di São Tomé e Príncipe (pre-2018) + Colon salvadoregno + Lira siriana + Lilangeni + Baht tailandese + Theta + Somoni del Tagikistan + Manat turkmeno + Dinaro tunisino + Paʻanga tongano + Lira turca + Dollaro di Trinidad & Tobago + Dollaro taiwanese + Scellino della Tanzania + Hryvnia ucraino + Scellino ugandese + Universe + Dollaro statunitense + USD Coin + Peso uruguaiano + Som uzbeco + Bolívar venezuelano + Dong vietnamita + Vatu di Vanuatu + Wrapped Bitcoin + Tala samoano + Franco CFA + Oncia d\'argento + Dollaro dei Caraibi Orientali + Diritti speciali di prelievo + West African CFA franc + Franco CFP + Rial yemenita + Rand sudafricano + Kwacha + Kwacha zambiano + Dollaro dello Zimbabwe - "Lunghezza" - "Tempo" - "Volume" - "Area" - "Temperatura" - "Velocità" - "Massa" - "Data" - "Energia" - "Potenza" - "Angolo" - "Traferimenti dati" - "Pressione" - "Accelerazione" - "Valuta" + Lunghezza + Tempo + Volume + Area + Temperatura + Velocità + Massa + Data + Energia + Potenza + Angolo + Traferimenti dati + Pressione + Accelerazione + Valuta - "Converti da" - "Converti in" - "Impostazioni" + Converti da + Converti in + Impostazioni - "Temi" - "Precisione" - "Separatore" - "Notazione esponenziale" - "Gruppi di unità" - "Tassi di cambio sbagliati?" - "Note" - "I tassi di cambio sono aggiornati quotidianamente. Non c'è alcun monitoraggio in tempo reale del mercato nell'app" - "Termini e condizioni" - "Politica sulla riservatezza" - "Licenze terze parti" - "Valuta questa app" - "Formato" - "Aggiuntive" + Temi + Precisione + Separatore + Notazione esponenziale + Gruppi di unità + Tassi di cambio sbagliati? + Note + I tassi di cambio sono aggiornati quotidianamente. Non c\'è alcun monitoraggio in tempo reale del mercato nell\'app + Termini e condizioni + Politica sulla riservatezza + Licenze terze parti + Valuta questa app + Formato + Aggiuntive - "Numero di posti decimali" - "Valori convertiti potrebbero avere una precisione maggiore di quella preferita." - "%1$s (Max)" + Numero di posti decimali + Valori convertiti potrebbero avere una precisione maggiore di quella preferita. + %1$s (Max) - "Simbolo separatore gruppo" - "Punto" - "Virgola" - "Spazio" + Simbolo separatore gruppo + Punto + Virgola + Spazio - "Sostituisce parte del numero con E" + Sostituisce parte del numero con E - "Aspetto dell'app" - "Auto" - "Chiaro" - "Scuro" - "Colore tema" - "Scuro AMOLED" - "Usa sfondo nero per temi scuri" - "Colori dinamici" - "Usa colori dal tuo sfondo" + Aspetto dell\'app + Auto + Chiaro + Scuro + Colore tema + Scuro AMOLED + Usa sfondo nero per temi scuri + Colori dinamici + Usa colori dal tuo sfondo - "Caricamento…" - "Errore" - "Copiato %1$s!" - "Cancella" - "Cerca unità" - "Nessun risultato trovato" - "Apri impostazioni" - "Assicurati che non ci siano errori, prova filtri diversi o controlla i gruppi di unità disabilitati." - "Ciao!" - "Abilitato" - "Disabilitato" + Caricamento… + Errore + Copiato %1$s! + Cancella + Cerca unità + Nessun risultato trovato + Apri impostazioni + Assicurati che non ci siano errori, prova filtri diversi o controlla i gruppi di unità disabilitati. + Ciao! + Abilitato + Disabilitato - "Naviga verso l'alto" - "Filtri selezionati" - "Apri impostazioni" - "Inverti unità" - "Pulsante cerca" - "Svuota input" - "Aggiungi o rimuovi unità dai preferiti" - "Svuota risultato ricerca" - "Apri o chiudi menù a discesa" - "Abilita gruppo unità" - "Riordina gruppo unità" - "Disabilita gruppi di unità" - "Nome versione" - "Riguardo a Unitto" - "Impara riguardo all'app" - "Disabilita e riordina unità" - "Centesimo" - "cent" + Naviga verso l\'alto + Filtri selezionati + Apri impostazioni + Inverti unità + Pulsante cerca + Svuota input + Aggiungi o rimuovi unità dai preferiti + Svuota risultato ricerca + Apri o chiudi menù a discesa + Abilita gruppo unità + Riordina gruppo unità + Disabilita gruppi di unità + Nome versione + Riguardo a Unitto + Impara riguardo all\'app + Disabilita e riordina unità + Centesimo + cent - "Maxwell" - "Mx" - "Weber" - "Wb" - "Milliweber" - "mWb" - "Microweber" - "μWb" - "Kiloweber" - "kWb" - "Megaweber" - "MWb" - "Gigaweber" - "GWb" - "Flusso" - "Vedi codice sorgente" - "Traduci questa app" - "Unisciti al progetto POEditor per aiutare" + Maxwell + Mx + Weber + Wb + Milliweber + mWb + Microweber + μWb + Kiloweber + kWb + Megaweber + MWb + Gigaweber + GWb + Flusso + Vedi codice sorgente + Traduci questa app + Unisciti al progetto POEditor per aiutare - "Binario" - "base2" - "Ternario" - "base3" - "Quaternario" - "base4" - "Quinario" - "base5" - "Senario" - "base6" - "Settenario" - "base7" - "Ottale" - "base8" - "Nonario" - "base9" - "Decimale" - "base10" - "Undecimale" - "base11" - "Duodecimale" - "base12" - "Tridecimale" - "base13" - "Tetradecimale" - "base14" - "Pentadecimale" - "base15" - "Esadecimale" - "base16" - "Base" - "Vibrazioni" - "Feedback tattile quando si fa clic sui pulsanti della tastiera" - "Millibar" - "mbar" - "Kilopascal" - "kPa" - "Micron di mercurio" - "μmHg" + Binario + base2 + Ternario + base3 + Quaternario + base4 + Quinario + base5 + Senario + base6 + Settenario + base7 + Ottale + base8 + Nonario + base9 + Decimale + base10 + Undecimale + base11 + Duodecimale + base12 + Tridecimale + base13 + Tetradecimale + base14 + Pentadecimale + base15 + Esadecimale + base16 + Base + Vibrazioni + Feedback tattile quando si fa clic sui pulsanti della tastiera + Millibar + mbar + Kilopascal + kPa + Micron di mercurio + μmHg - "Convertitore Epoch" + Convertitore Epoch - "Calcolatrice" + Calcolatrice - "a" - "m" - "Miglio nautico" - "M" - "Schermata iniziale" - "Scegli quale schermata è mostrata quando avvi l'app" + a + m + Miglio nautico + M + Schermata iniziale + Scegli quale schermata è mostrata quando avvi l\'app - "Convertitore di unità" - "Pulisci" - "Pulisci cronologia" - "Tutti risultati dalla cronologia saranno eliminati per sempre. Questa azione non può essere annullata!" - "Nessuna cronologia" - "Apri menù" - "Microgram" - "µg" + Convertitore di unità + Pulisci + Pulisci cronologia + Tutti risultati dalla cronologia saranno eliminati per sempre. Questa azione non può essere annullata! + Nessuna cronologia + Apri menù + Microgram + µg - "Attofarad" - "aF" - "Statfarad" - "stF" - "Farad" - "F" - "Exafarad" - "EF" - "Picofarad" - "pF" - "Nanofarad" - "nF" - "Microfarad" - "µF" - "Millifarad" - "mF" - "Kilofarad" - "kF" - "Megafarad" - "MF" - "Gigafarad" - "GF" - "Petafarad" - "PF" + Attofarad + aF + Statfarad + stF + Farad + F + Exafarad + EF + Picofarad + pF + Nanofarad + nF + Microfarad + µF + Millifarad + mF + Kilofarad + kF + Megafarad + MF + Gigafarad + GF + Petafarad + PF - "Quetta" - "Q" - "Ronna" - "R" - "Yotta" - "Y" - "Zetta" - "Z" - "Esa" - "E" - "Peta" - "P" - "Tera" - "T" - "Giga" - "G" - "Mega" - "M" - "Kilo" - "k" - "Etto" - "h" - "Deci" - "da" - "Base" - "Base" - "Deci" - "d" - "Centi" - "c" - "Milli" - "m" - "Micro" - "μ" - "Nano" - "n" - "Pico" - "p" - "Femto" - "f" - "Atto" - "a" - "Zepto" - "z" - "Yocto" - "y" - "Ronto" - "r" - "Quecto" - "q" + Quetta + Q + Ronna + R + Yotta + Y + Zetta + Z + Esa + E + Peta + P + Tera + T + Giga + G + Mega + M + Kilo + k + Etto + h + Deci + da + Base + Base + Deci + d + Centi + c + Milli + m + Micro + μ + Nano + n + Pico + p + Femto + f + Atto + a + Zepto + z + Yocto + y + Ronto + r + Quecto + q - "Newton" - "N" - "Kilonewton" - "kN" - "Grammo-forza" - "gf" - "Kilogrammo-forza" - "kgf" - "Tonnellata-forza" - "tf" - "Millinewton" - "mN" - "Attonewton" - "aN" - "Dyne" - "dyn" - "Joule/metro" - "J/m" - "Joule/centimetro" - "J/cm" - "Kilolibbra-forza" - "kipf" - "Libbra-forza" - "lbf" - "Oncia-forza" - "ozf" - "Pond" - "p" - "Kilopond" - "kp" + Newton + N + Kilonewton + kN + Grammo-forza + gf + Kilogrammo-forza + kgf + Tonnellata-forza + tf + Millinewton + mN + Attonewton + aN + Dyne + dyn + Joule/metro + J/m + Joule/centimetro + J/cm + Kilolibbra-forza + kipf + Libbra-forza + lbf + Oncia-forza + ozf + Pond + p + Kilopond + kp - "Newton metro" - "N*m" - "Newton centimetro" - "N*cm" - "Newton millimetro" - "N*mm" - "Kilonewton metro" - "kN*m" - "Dyne metro" - "dyn*m" - "Dyne centimetro" - "dyn*cm" - "Dyne millimetro" - "dyn*mm" - "Kilogrammo-forza metro" - "kgf*m" - "Kilogrammo-forza centimetro" - "kgf*cm" - "Kilogrammo-forza millimetro" - "kgf*mm" - "Grammo-forza metro" - "gf*m" - "Grammo-forza centimetro" - "gf*cm" - "Grammo-forza millimetro" - "gf*mm" - "Oncia-forza piede" - "ozf*ft" - "Oncia-forza pollice" - "ozf*in" - "Libbra-forza piede" - "lbf*ft" - "Libbra-forza pollice" - "lbf*in" + Newton metro + N*m + Newton centimetro + N*cm + Newton millimetro + N*mm + Kilonewton metro + kN*m + Dyne metro + dyn*m + Dyne centimetro + dyn*cm + Dyne millimetro + dyn*mm + Kilogrammo-forza metro + kgf*m + Kilogrammo-forza centimetro + kgf*cm + Kilogrammo-forza millimetro + kgf*mm + Grammo-forza metro + gf*m + Grammo-forza centimetro + gf*cm + Grammo-forza millimetro + gf*mm + Oncia-forza piede + ozf*ft + Oncia-forza pollice + ozf*in + Libbra-forza piede + lbf*ft + Libbra-forza pollice + lbf*in - "Litro/ora" - "L/h" - "Litro/minuto" - "L/m" - "Litro/secondo" - "L/s" - "Millilitro/ora" - "mL/h" - "Millilitro/minuto" - "mL/m" - "Millilitro/secondo" - "mL/s" - "Metro cubo/ora" - "m3/h" - "Metro cubo/minuto" - "m3/m" - "Metro cubo/secondo" - "m3/s" - "Millimetro cubo/ora" - "mm3/h" - "Millimetro cubo/minuto" - "mm3/m" - "Millimetro cubo/secondo" - "mm3/s" - "Piede cubo/ora" - "ft3/h" - "Piede cubo/minuto" - "ft3/m" - "Piede cubo/secondo" - "ft3/s" - "Gallone/ora (U.S.)" - "gal/h" - "Gallone/minuto (U.S.)" - "gal/m" - "Gallone/secondo (U.S.)" - "gal/s" - "Gallone/ora (Imperiale)" - "gal/h" - "Gallone/minuto (Imperiale)" - "gal/m" - "Gallone/secondo (Imperiale)" - "gal/s" + Litro/ora + L/h + Litro/minuto + L/m + Litro/secondo + L/s + Millilitro/ora + mL/h + Millilitro/minuto + mL/m + Millilitro/secondo + mL/s + Metro cubo/ora + m3/h + Metro cubo/minuto + m3/m + Metro cubo/secondo + m3/s + Millimetro cubo/ora + mm3/h + Millimetro cubo/minuto + mm3/m + Millimetro cubo/secondo + mm3/s + Piede cubo/ora + ft3/h + Piede cubo/minuto + ft3/m + Piede cubo/secondo + ft3/s + Gallone/ora (U.S.) + gal/h + Gallone/minuto (U.S.) + gal/m + Gallone/secondo (U.S.) + gal/s + Gallone/ora (Imperiale) + gal/h + Gallone/minuto (Imperiale) + gal/m + Gallone/secondo (Imperiale) + gal/s - "Candela/metro quadrato" - "cd/m^2" - "Candela/centimetro quadrato" - "cd/cm^2" - "Candela/piede quadrato" - "cd/ft^2" - "Candela/pollice quadrato" - "cd/in^2" - "Kilocandela/metro quadrato" - "kcd" - "Stilb" - "sb" - "Lumen/metro quadrato/steradian" - "lm/m^2/sr" - "Lumen/centimetro quadrato/steradian" - "lm/cm^2/sr" - "Lumen/piede quadrato/steradian" - "lm/ft^2/sr" - "Watt/centimetro quadrato/steradian" - "W/cm^2/sr" - "Nit" - "nt" - "Millinit" - "mnt" - "Lambert" - "L" - "Millilambert" - "mL" - "Piede-lambert" - "fL" - "Apostilb" - "asb" - "Blondel" - "blondel" - "Bril" - "bril" - "Skot" - "sk" - "Capacità" - "Prefisso" - "Forza" - "Coppia" - "Flusso" - "Luminanza" - "Formato tempo" - "Esempio: Mostra 130 minuti come 2h 10m" - "Ordine lista unità" - "Cambia ordine unità" + Candela/metro quadrato + cd/m^2 + Candela/centimetro quadrato + cd/cm^2 + Candela/piede quadrato + cd/ft^2 + Candela/pollice quadrato + cd/in^2 + Kilocandela/metro quadrato + kcd + Stilb + sb + Lumen/metro quadrato/steradian + lm/m^2/sr + Lumen/centimetro quadrato/steradian + lm/cm^2/sr + Lumen/piede quadrato/steradian + lm/ft^2/sr + Watt/centimetro quadrato/steradian + W/cm^2/sr + Nit + nt + Millinit + mnt + Lambert + L + Millilambert + mL + Piede-lambert + fL + Apostilb + asb + Blondel + blondel + Bril + bril + Skot + sk + Capacità + Prefisso + Forza + Coppia + Flusso + Luminanza + Formato tempo + Esempio: Mostra 130 minuti come 2h 10m + Ordine lista unità + Cambia ordine unità - "Utilizzo" - "Alfabetico" - "Scala (Decr.)" - "Scala (Asc.)" - "Scegli una modalità tematica" - "Schema colore" - "Colore selezionato" - "Stile selezionato" + Utilizzo + Alfabetico + Scala (Decr.) + Scala (Asc.) + Scegli una modalità tematica + Schema colore + Colore selezionato + Stile selezionato - "Precisione e aspetto dei numeri" - "Non divisibile per 0" - "Differenza di data" + Precisione e aspetto dei numeri + Non divisibile per 0 + Differenza di data - "Seleziona orario" - "Inizio" - "Fine" - "Differenza" + Seleziona orario + Inizio + Fine + Differenza - "Anni" + Anni - "Mesi" + Mesi - "Giorni" + Giorni - "Ore" + Ore - "Minuti" + Minuti - "Avanti" - "Anteprima (tocca per cambiare)" + Avanti + Anteprima (tocca per cambiare) \ No newline at end of file diff --git a/core/base/src/main/res/values/strings.xml b/core/base/src/main/res/values/strings.xml index 2b454edd..d8408acd 100644 --- a/core/base/src/main/res/values/strings.xml +++ b/core/base/src/main/res/values/strings.xml @@ -1,1366 +1,1371 @@ - "Unitto" + Unitto - "Attometer" - "am" - "Nanometer" - "nm" - "Micrometer" - "μm" - "Millimeter" - "mm" - "Centimeter" - "cm" - "Decimeter" - "dm" - "Meter" - "m" - "Kilometer" - "km" - "Mile" - "Mi" - "Yard" - "yd" - "Foot" - "ft" - "Inch" - "in" - "Light year" - "ly" - "Parsec" - "pc" - "Kiloparsec" - "kpc" - "Megaparsec" - "Mpc" - "Mercury equatorial radius" - "Mercury R" - "Venus equatorial radius" - "Venus R" - "Earth equatorial radius" - "Earth R" - "Mars equatorial radius" - "Mars R" - "Jupiter equatorial radius" - "Jupiter R" - "Saturn equatorial radius" - "Saturn R" - "Uranus equatorial radius" - "Uranus R" - "Neptune equatorial radius" - "Neptune R" - "Sun equatorial radius" - "Sun R" + Attometer + am + Nanometer + nm + Micrometer + μm + Millimeter + mm + Centimeter + cm + Decimeter + dm + Meter + m + Kilometer + km + Mile + Mi + Yard + yd + Foot + ft + Inch + in + Light year + ly + Parsec + pc + Kiloparsec + kpc + Megaparsec + Mpc + Mercury equatorial radius + Mercury R + Venus equatorial radius + Venus R + Earth equatorial radius + Earth R + Mars equatorial radius + Mars R + Jupiter equatorial radius + Jupiter R + Saturn equatorial radius + Saturn R + Uranus equatorial radius + Uranus R + Neptune equatorial radius + Neptune R + Sun equatorial radius + Sun R - "Electron mass" - "me" - "Dalton" - "u" - "Milligram" - "mg" - "Gram" - "g" - "Kilogram" - "kg" - "Metric ton" - "t" - "Imperial ton" - "t (UK)" - "Pound" - "lbs" - "Ounce" - "oz" - "Carat" - "ct" - "Mercury mass" - "Mercury M" - "Venus mass" - "Venus M" - "Earth mass" - "Earth M" - "Mars mass" - "Mars M" - "Jupiter mass" - "Jupiter M" - "Saturn mass" - "Saturn M" - "Uranus mass" - "Uranus M" - "Neptune mass" - "Neptune M" - "Sun mass" - "Sun M" + Electron mass + me + Dalton + u + Milligram + mg + Gram + g + Kilogram + kg + Metric ton + t + Imperial ton + t (UK) + Pound + lbs + Ounce + oz + Carat + ct + Mercury mass + Mercury M + Venus mass + Venus M + Earth mass + Earth M + Mars mass + Mars M + Jupiter mass + Jupiter M + Saturn mass + Saturn M + Uranus mass + Uranus M + Neptune mass + Neptune M + Sun mass + Sun M - "Celsius" - "°C" - "Fahrenheit" - "°F" - "Kelvin" - "K" + Celsius + °C + Fahrenheit + °F + Kelvin + K - "Millimeter/hour" - "mm/h" - "Millimeter/minute" - "mm/m" - "Millimeter/second" - "mm/s" - "Centimeter/hour" - "cm/h" - "Centimeter/minute" - "cm/m" - "Centimeter/second" - "cm/s" - "Meter/hour" - "m/h" - "Meter/minute" - "m/m" - "Meter/second" - "m/s" - "Kilometer/hour" - "km/h" - "Kilometer/minute" - "km/m" - "Kilometer/second" - "km/s" - "Foot/hour" - "ft/h" - "Foot/minute" - "ft/m" - "Foot/second" - "ft/s" - "Yard/hour" - "yd/h" - "Yard/minute" - "yd/m" - "Yard/second" - "yd/s" - "Mile/hour" - "mi/h" - "Mile/minute" - "mi/m" - "Mile/second" - "mi/s" - "Knot" - "kt" - "Speed of light in vacuum" - "c" - "First Cosmic Velocity" - "v1" - "Second Cosmic Velocity" - "v2" - "Third Cosmic Velocity" - "v3" - "Earth's orbital speed" - "ve" - "Mach" - "M" - "Mach (SI)" - "M" + Millimeter/hour + mm/h + Millimeter/minute + mm/m + Millimeter/second + mm/s + Centimeter/hour + cm/h + Centimeter/minute + cm/m + Centimeter/second + cm/s + Meter/hour + m/h + Meter/minute + m/m + Meter/second + m/s + Kilometer/hour + km/h + Kilometer/minute + km/m + Kilometer/second + km/s + Foot/hour + ft/h + Foot/minute + ft/m + Foot/second + ft/s + Yard/hour + yd/h + Yard/minute + yd/m + Yard/second + yd/s + Mile/hour + mi/h + Mile/minute + mi/m + Mile/second + mi/s + Knot + kt + Speed of light in vacuum + c + First Cosmic Velocity + v1 + Second Cosmic Velocity + v2 + Third Cosmic Velocity + v3 + Earth\'s orbital speed + ve + Mach + M + Mach (SI) + M - "Bit" - "b" - "Kibibit" - "Kib" - "Kilobit" - "Kb" - "Megabit" - "Mb" - "Mebibit" - "Mib" - "Gigabit" - "Gb" - "Terabit" - "Tb" - "Petabit" - "Pb" - "Exabit" - "Eb" - "Byte" - "B" - "Kibibyte" - "KiB" - "Kilobyte" - "KB" - "Megabyte" - "MB" - "Mebibyte" - "MiB" - "Gigabyte" - "GB" - "Terabyte" - "TB" - "Petabyte" - "PB" - "Exabyte" - "EB" + Bit + b + Kibibit + Kib + Kilobit + Kb + Megabit + Mb + Mebibit + Mib + Gigabit + Gb + Terabit + Tb + Petabit + Pb + Exabit + Eb + Byte + B + Kibibyte + KiB + Kilobyte + KB + Megabyte + MB + Mebibyte + MiB + Gigabyte + GB + Terabyte + TB + Petabyte + PB + Exabyte + EB - "Bit/second" - "b/s" - "Kibibit/second" - "Kib/s" - "Kilobit/second" - "Kb/s" - "Megabit/second" - "Mb/s" - "Mebibit/second" - "Mib/s" - "Gigabit/second" - "Gb/s" - "Terabit/second" - "Tb/s" - "Petabit/second" - "Pb/s" - "Exabit/second" - "Eb/s" - "Byte/second" - "B/s" - "Kibibyte/second" - "KiB/s" - "Kilobyte/second" - "KB/s" - "Megabyte/second" - "MB/s" - "Mebibyte/second" - "MiB/s" - "Gigabyte/second" - "GB/s" - "Terabyte/second" - "TB/s" - "Petabyte/second" - "PB/s" - "Exabyte/second" - "EB/s" + Bit/second + b/s + Kibibit/second + Kib/s + Kilobit/second + Kb/s + Megabit/second + Mb/s + Mebibit/second + Mib/s + Gigabit/second + Gb/s + Terabit/second + Tb/s + Petabit/second + Pb/s + Exabit/second + Eb/s + Byte/second + B/s + Kibibyte/second + KiB/s + Kilobyte/second + KB/s + Megabyte/second + MB/s + Mebibyte/second + MiB/s + Gigabyte/second + GB/s + Terabyte/second + TB/s + Petabyte/second + PB/s + Exabyte/second + EB/s - "Attoliter" - "aL" - "Milliliter" - "mL" - "Liter" - "L" - "US liquid gallon" - "gal (US)" - "US liquid quart" - "qt (US)" - "US liquid pint" - "pt (US)" - "US legal cup" - "cup (US)" - "US fluid ounce" - "fl oz (US)" - "US tablespoon" - "tablespoon (US)" - "US teaspoon" - "teaspoon (US)" - "Imperial gallon" - "gal (UK)" - "Imperial quart" - "qt (UK)" - "Imperial pint" - "pt (UK)" - "Imperial cup" - "cup (UK)" - "Imperial fluid ounce" - "fl oz (UK)" - "Imperial tablespoon" - "tablespoon (UK)" - "Imperial teaspoon" - "teaspoon (UK)" - "Cubic millimeter" - "mm^3" - "Cubic centimeter" - "cm^3" - "Cubic meter" - "m^3" - "Cubic kilometer" - "km^3" + Attoliter + aL + Milliliter + mL + Liter + L + US liquid gallon + gal (US) + US liquid quart + qt (US) + US liquid pint + pt (US) + US legal cup + cup (US) + US fluid ounce + fl oz (US) + US tablespoon + tablespoon (US) + US teaspoon + teaspoon (US) + Imperial gallon + gal (UK) + Imperial quart + qt (UK) + Imperial pint + pt (UK) + Imperial cup + cup (UK) + Imperial fluid ounce + fl oz (UK) + Imperial tablespoon + tablespoon (UK) + Imperial teaspoon + teaspoon (UK) + Cubic millimeter + mm^3 + Cubic centimeter + cm^3 + Cubic meter + m^3 + Cubic kilometer + km^3 - "Attosecond" - "as" - "Nanosecond" - "ns" - "Microsecond" - "µs" - "Millisecond" - "ms" - "Jiffy" - "j" - "Second" - "s" - "Minute" - "m" - "Hour" - "h" - "Day" - "d" - "Week" - "w" + Attosecond + as + Nanosecond + ns + Microsecond + µs + Millisecond + ms + Jiffy + j + Second + s + Minute + m + Hour + h + Day + d + Week + w - "Electron cross section" - "ecs" - "Acre" - "ac" - "Hectare" - "ha" - "Square foot" - "ft^2" - "Square mile" - "mi^2" - "Square yard" - "yd^2" - "Square inch" - "in^2" - "Square micrometer" - "µm^2" - "Square millimeter" - "mm^2" - "Square centimeter" - "cm^2" - "Square decimeter" - "dm^2" - "Square meter" - "m^2" - "Square kilometer" - "km^2" + Electron cross section + ecs + Acre + ac + Hectare + ha + Square foot + ft^2 + Square mile + mi^2 + Square yard + yd^2 + Square inch + in^2 + Square micrometer + µm^2 + Square millimeter + mm^2 + Square centimeter + cm^2 + Square decimeter + dm^2 + Square meter + m^2 + Square kilometer + km^2 - "Electron volt" - "eV" - "Attojoule" - "aJ" - "Horse power" - "hp" - "Joule" - "J" - "Kilojoule" - "kJ" - "Megajoule" - "MJ" - "Gigajoule" - "GJ" - "Ton of TNT" - "t" - "Kiloton of TNT" - "kt" - "Megaton of TNT" - "Mt" - "Gigaton of TNT" - "Gt" - "Calorie (th)" - "cal" - "Kilocalorie (th)" - "kcal" + Electron volt + eV + Attojoule + aJ + Horse power + hp + Joule + J + Kilojoule + kJ + Megajoule + MJ + Gigajoule + GJ + Ton of TNT + t + Kiloton of TNT + kt + Megaton of TNT + Mt + Gigaton of TNT + Gt + Calorie (th) + cal + Kilocalorie (th) + kcal - "Attowatt" - "aW" - "Watt" - "W" - "Kilowatt" - "kW" - "Megawatt" - "MG" - "Horsepower" - "hp" + Attowatt + aW + Watt + W + Kilowatt + kW + Megawatt + MG + Horsepower + hp - "Second" - "\"" - "Minute" - "'" - "Degree" - "°" - "Radian" - "rad" - "Sextant" - "sxt" - "Turn" - "tr" + Second + \" + Minute + \' + Degree + ° + Radian + rad + Sextant + sxt + Turn + tr - "Attopascal" - "aPa" - "Femtopascal" - "fPa" - "Picopascal" - "pPa" - "Nanopascal" - "nPa" - "Micropascal" - "µPa" - "Millipascal" - "mPa" - "Centipascal" - "cPa" - "Decipascal" - "dPa" - "Pascal" - "Pa" - "Dekapascal" - "daPa" - "Hectopascal" - "hPa" - "Bar" - "bar" - "Megapascal" - "MPa" - "Gigapascal" - "GPA" - "Terapascal" - "TPa" - "Petapascal" - "PPa" - "Exapascal" - "EPa" - "Pound/square inch" - "psi" - "Kilopound/square inch" - "ksi" - "Standard atmosphere" - "atm" - "Torr" - "torr" - "Millimeter of mercury" - "mm Hg" + Attopascal + aPa + Femtopascal + fPa + Picopascal + pPa + Nanopascal + nPa + Micropascal + µPa + Millipascal + mPa + Centipascal + cPa + Decipascal + dPa + Pascal + Pa + Dekapascal + daPa + Hectopascal + hPa + Bar + bar + Megapascal + MPa + Gigapascal + GPA + Terapascal + TPa + Petapascal + PPa + Exapascal + EPa + Pound/square inch + psi + Kilopound/square inch + ksi + Standard atmosphere + atm + Torr + torr + Millimeter of mercury + mm Hg - "Attometer/square second" - "am/s^2" - "Femtometer/square second" - "fm/s^2" - "Picometer/square second" - "pm/s^2" - "Nanometer/square second" - "nm/s^2" - "Micrometer/square second" - "µm/s^2" - "Millimeter/square second" - "mm/s^2" - "Centimeter/square second" - "cm/s^2" - "Decimeter/square second" - "dm/s^2" - "Meter/square second" - "m/s^2" - "Kilometer/square second" - "km/s^2" - "Dekameter/square second" - "dam/s^2" - "Hectometer/square second" - "hm/s^2" - "Gal" - "Gal" - "Mercury surface gravity" - "Mercury g" - "Venus surface gravity" - "Venus g" - "Earth surface gravity" - "Earth g" - "Mars surface gravity" - "Mars g" - "Jupiter surface gravity" - "Jupiter g" - "Saturn surface gravity" - "Saturn g" - "Uranus surface gravity" - "Uranus g" - "Neptune surface gravity" - "Neptune g" - "Sun surface gravity" - "Sun g" + Attometer/square second + am/s^2 + Femtometer/square second + fm/s^2 + Picometer/square second + pm/s^2 + Nanometer/square second + nm/s^2 + Micrometer/square second + µm/s^2 + Millimeter/square second + mm/s^2 + Centimeter/square second + cm/s^2 + Decimeter/square second + dm/s^2 + Meter/square second + m/s^2 + Kilometer/square second + km/s^2 + Dekameter/square second + dam/s^2 + Hectometer/square second + hm/s^2 + Gal + Gal + Mercury surface gravity + Mercury g + Venus surface gravity + Venus g + Earth surface gravity + Earth g + Mars surface gravity + Mars g + Jupiter surface gravity + Jupiter g + Saturn surface gravity + Saturn g + Uranus surface gravity + Uranus g + Neptune surface gravity + Neptune g + Sun surface gravity + Sun g - "1inch Network" - "NCH" - "Cardano" - "ADA" - "United Arab Emirates Dirham" - "AED" - "Afghan afghani" - "AFN" - "Algorand" - "LGO" - "Albanian lek" - "ALL" - "Armenian dram" - "AMD" - "Netherlands Antillean Guilder" - "ANG" - "Angolan kwanza" - "AOA" - "Argentine peso" - "ARS" - "Atomic Coin" - "TOM" - "Australian dollar" - "AUD" - "Avalanche" - "VAX" - "Aruban florin" - "AWG" - "Azerbaijani manat" - "AZN" - "Bosnia-Herzegovina Convertible Mark" - "BAM" - "Bajan dollar" - "BBD" - "Bitcoin Cash" - "BCH" - "Bangladeshi taka" - "BDT" - "Bulgarian lev" - "BGN" - "Bahraini dinar" - "BHD" - "Burundian Franc" - "BIF" - "Bermudan dollar" - "BMD" - "Binance Coin" - "BNB" - "Brunei dollar" - "BND" - "Bolivian boliviano" - "BOB" - "Brazilian real" - "BRL" - "Bahamian dollar" - "BSD" - "Bitcoin" - "BTC" - "Bhutan currency" - "BTN" - "Binance USD" - "USD" - "Botswanan Pula" - "BWP" - "New Belarusian Ruble" - "BYN" - "Belarusian Ruble" - "BYR" - "Belize dollar" - "BZD" - "Canadian dollar" - "CAD" - "Congolese franc" - "CDF" - "Swiss franc" - "CHF" - "Chiliz" - "CHZ" - "Chilean Unit of Account (UF)" - "CLF" - "Chilean peso" - "CLP" - "Chinese Yuan" - "CNY" - "Colombian peso" - "COP" - "Costa Rican Colón" - "CRC" - "Crypto.com Chain Token" - "CRO" - "Cuban convertible peso" - "CUC" - "Cuban Peso" - "CUP" - "Cape Verdean escudo" - "CVE" - "Czech koruna" - "CZK" - "Dai" - "DAI" - "Djiboutian franc" - "DJF" - "Danish krone" - "DKK" - "Dogecoin" - "OGE" - "Dominican peso" - "DOP" - "Dotcoin" - "DOT" - "Algerian dinar" - "DZD" - "Elrond" - "GLD" - "Egyptian pound" - "EGP" - "Enjin Coin" - "ENJ" - "Eritrean nakfa" - "ERN" - "Ethiopian birr" - "ETB" - "Ethereum Classic" - "ETC" - "Ether" - "ETH" - "Euro" - "EUR" - "FileCoin" - "FIL" - "Fijian dollar" - "FJD" - "Falkland Islands pound" - "FKP" - "FarmaTrust" - "FTT" - "Pound sterling" - "GBP" - "Georgian lari" - "GEL" - "GGPro" - "GGP" - "Ghanaian cedi" - "GHS" - "Gibraltar pound" - "GIP" - "Gambian dalasi" - "GMD" - "Guinean franc" - "GNF" - "Golden Ratio Token" - "GRT" - "Guatemalan quetzal" - "GTQ" - "Guyanaese Dollar" - "GYD" - "Hong Kong dollar" - "HKD" - "Honduran lempira" - "HNL" - "Croatian kuna" - "HRK" - "Haitian gourde" - "HTG" - "Hungarian forint" - "HUF" - "Internet Computer" - "ICP" - "Indonesian rupiah" - "IDR" - "Israeli New Shekel" - "ILS" - "CoinIMP" - "IMP" - "Injective" - "INJ" - "Indian rupee" - "INR" - "Iraqi dinar" - "IQD" - "Iranian rial" - "IRR" - "Icelandic króna" - "ISK" - "Jersey Pound" - "JEP" - "Jamaican dollar" - "JMD" - "Jordanian dinar" - "JOD" - "Japanese yen" - "JPY" - "Kenyan shilling" - "KES" - "Kyrgystani Som" - "KGS" - "Cambodian riel" - "KHR" - "Comorian franc" - "KMF" - "North Korean won" - "KPW" - "South Korean won" - "KRW" - "Kusama" - "KSM" - "Kuwaiti dinar" - "KWD" - "Cayman Islands dollar" - "KYD" - "Kazakhstani tenge" - "KZT" - "Laotian Kip" - "LAK" - "Lebanese pound" - "LBP" - "ChainLink" - "INK" - "Sri Lankan rupee" - "LKR" - "Liberian dollar" - "LRD" - "Lesotho loti" - "LSL" - "Litecoin" - "LTC" - "Lithuanian litas" - "LTL" - "Luna Coin" - "UNA" - "Latvian lats" - "LVL" - "Libyan dinar" - "LYD" - "Moroccan dirham" - "MAD" - "Polygon" - "TIC" - "Moldovan leu" - "MDL" - "Malagasy ariary" - "MGA" - "Macedonian denar" - "MKD" - "Myanmar Kyat" - "MMK" - "Mongolian tugrik" - "MNT" - "Macanese pataca" - "MOP" - "Mauritanian ouguiya" - "MRO" - "Mauritian rupee" - "MUR" - "Maldivian rufiyaa" - "MVR" - "Malawian kwacha" - "MWK" - "Mexican peso" - "MXN" - "Malaysian ringgit" - "MYR" - "Mozambican Metical" - "MZN" - "Namibian dollar" - "NAD" - "Nigerian naira" - "NGN" - "Nicaraguan córdoba" - "NIO" - "Norwegian krone" - "NOK" - "Nepalese rupee" - "NPR" - "New Zealand dollar" - "NZD" - "Omani rial" - "OMR" - "Menlo One" - "ONE" - "Panamanian balboa" - "PAB" - "Sol" - "PEN" - "Papua New Guinean kina" - "PGK" - "Philippine peso" - "PHP" - "Pakistani rupee" - "PKR" - "Poland złoty" - "PLN" - "Paraguayan guarani" - "PYG" - "Qatari Rial" - "QAR" - "Romanian leu" - "RON" - "Serbian dinar" - "RSD" - "Russian ruble" - "RUB" - "Rwandan Franc" - "RWF" - "Saudi riyal" - "SAR" - "Solomon Islands dollar" - "SBD" - "Seychellois rupee" - "SCR" - "Sudanese pound" - "SDG" - "Swedish krona" - "SEK" - "Singapore dollar" - "SGD" - "Shiba Inu" - "HIB" - "Saint Helena pound" - "SHP" - "Sierra Leonean leone" - "SLL" - "Sola" - "SOL" - "Somali shilling" - "SOS" - "Surinamese dollar" - "SRD" - "São Tomé and Príncipe Dobra (pre-2018)" - "STD" - "Salvadoran Colón" - "SVC" - "Syrian pound" - "SYP" - "Swazi lilangeni" - "SZL" - "Thai baht" - "THB" - "Theta" - "ETA" - "Tajikistani somoni" - "TJS" - "Turkmenistani manat" - "TMT" - "Tunisian dinar" - "TND" - "Tongan Paʻanga" - "TOP" - "TRON" - "TRX" - "Turkish lira" - "TRY" - "Trinidad & Tobago Dollar" - "TTD" - "New Taiwan dollar" - "TWD" - "Tanzanian shilling" - "TZS" - "Ukrainian hryvnia" - "UAH" - "Ugandan shilling" - "UGX" - "Universe" - "UNI" - "United States dollar" - "USD" - "USD Coin" - "SDC" - "Tether" - "SDT" - "Uruguayan peso" - "UYU" - "Uzbekistani som" - "UZS" - "Sovereign Bolivar" - "VEF" - "Vechain" - "VET" - "Vietnamese dong" - "VND" - "Vanuatu vatu" - "VUV" - "Wrapped Bitcoin" - "BTC" - "Samoan tala" - "WST" - "Central African CFA franc" - "XAF" - "Silver Ounce" - "XAG" - "XauCoin" - "XAU" - "East Caribbean dollar" - "XCD" - "Special Drawing Rights" - "XDR" - "Stellar" - "XLM" - "Monero" - "XMR" - "West African CFA franc" - "XOF" - "CFP franc" - "XPF" - "XRP" - "XRP" - "Yemeni rial" - "YER" - "South African rand" - "ZAR" - "Zambian kwacha" - "ZMK" - "Zambian Kwacha" - "ZMW" - "Zimbabwean Dollar" - "ZWL" + 1inch Network + NCH + Cardano + ADA + United Arab Emirates Dirham + AED + Afghan afghani + AFN + Algorand + LGO + Albanian lek + ALL + Armenian dram + AMD + Netherlands Antillean Guilder + ANG + Angolan kwanza + AOA + Argentine peso + ARS + Atomic Coin + TOM + Australian dollar + AUD + Avalanche + VAX + Aruban florin + AWG + Azerbaijani manat + AZN + Bosnia-Herzegovina Convertible Mark + BAM + Bajan dollar + BBD + Bitcoin Cash + BCH + Bangladeshi taka + BDT + Bulgarian lev + BGN + Bahraini dinar + BHD + Burundian Franc + BIF + Bermudan dollar + BMD + Binance Coin + BNB + Brunei dollar + BND + Bolivian boliviano + BOB + Brazilian real + BRL + Bahamian dollar + BSD + Bitcoin + BTC + Bhutan currency + BTN + Binance USD + USD + Botswanan Pula + BWP + New Belarusian Ruble + BYN + Belarusian Ruble + BYR + Belize dollar + BZD + Canadian dollar + CAD + Congolese franc + CDF + Swiss franc + CHF + Chiliz + CHZ + Chilean Unit of Account (UF) + CLF + Chilean peso + CLP + Chinese Yuan + CNY + Colombian peso + COP + Costa Rican Colón + CRC + Crypto.com Chain Token + CRO + Cuban convertible peso + CUC + Cuban Peso + CUP + Cape Verdean escudo + CVE + Czech koruna + CZK + Dai + DAI + Djiboutian franc + DJF + Danish krone + DKK + Dogecoin + OGE + Dominican peso + DOP + Dotcoin + DOT + Algerian dinar + DZD + Elrond + GLD + Egyptian pound + EGP + Enjin Coin + ENJ + Eritrean nakfa + ERN + Ethiopian birr + ETB + Ethereum Classic + ETC + Ether + ETH + Euro + EUR + FileCoin + FIL + Fijian dollar + FJD + Falkland Islands pound + FKP + FarmaTrust + FTT + Pound sterling + GBP + Georgian lari + GEL + GGPro + GGP + Ghanaian cedi + GHS + Gibraltar pound + GIP + Gambian dalasi + GMD + Guinean franc + GNF + Golden Ratio Token + GRT + Guatemalan quetzal + GTQ + Guyanaese Dollar + GYD + Hong Kong dollar + HKD + Honduran lempira + HNL + Croatian kuna + HRK + Haitian gourde + HTG + Hungarian forint + HUF + Internet Computer + ICP + Indonesian rupiah + IDR + Israeli New Shekel + ILS + CoinIMP + IMP + Injective + INJ + Indian rupee + INR + Iraqi dinar + IQD + Iranian rial + IRR + Icelandic króna + ISK + Jersey Pound + JEP + Jamaican dollar + JMD + Jordanian dinar + JOD + Japanese yen + JPY + Kenyan shilling + KES + Kyrgystani Som + KGS + Cambodian riel + KHR + Comorian franc + KMF + North Korean won + KPW + South Korean won + KRW + Kusama + KSM + Kuwaiti dinar + KWD + Cayman Islands dollar + KYD + Kazakhstani tenge + KZT + Laotian Kip + LAK + Lebanese pound + LBP + ChainLink + INK + Sri Lankan rupee + LKR + Liberian dollar + LRD + Lesotho loti + LSL + Litecoin + LTC + Lithuanian litas + LTL + Luna Coin + UNA + Latvian lats + LVL + Libyan dinar + LYD + Moroccan dirham + MAD + Polygon + TIC + Moldovan leu + MDL + Malagasy ariary + MGA + Macedonian denar + MKD + Myanmar Kyat + MMK + Mongolian tugrik + MNT + Macanese pataca + MOP + Mauritanian ouguiya + MRO + Mauritian rupee + MUR + Maldivian rufiyaa + MVR + Malawian kwacha + MWK + Mexican peso + MXN + Malaysian ringgit + MYR + Mozambican Metical + MZN + Namibian dollar + NAD + Nigerian naira + NGN + Nicaraguan córdoba + NIO + Norwegian krone + NOK + Nepalese rupee + NPR + New Zealand dollar + NZD + Omani rial + OMR + Menlo One + ONE + Panamanian balboa + PAB + Sol + PEN + Papua New Guinean kina + PGK + Philippine peso + PHP + Pakistani rupee + PKR + Poland złoty + PLN + Paraguayan guarani + PYG + Qatari Rial + QAR + Romanian leu + RON + Serbian dinar + RSD + Russian ruble + RUB + Rwandan Franc + RWF + Saudi riyal + SAR + Solomon Islands dollar + SBD + Seychellois rupee + SCR + Sudanese pound + SDG + Swedish krona + SEK + Singapore dollar + SGD + Shiba Inu + HIB + Saint Helena pound + SHP + Sierra Leonean leone + SLL + Sola + SOL + Somali shilling + SOS + Surinamese dollar + SRD + São Tomé and Príncipe Dobra (pre-2018) + STD + Salvadoran Colón + SVC + Syrian pound + SYP + Swazi lilangeni + SZL + Thai baht + THB + Theta + ETA + Tajikistani somoni + TJS + Turkmenistani manat + TMT + Tunisian dinar + TND + Tongan Paʻanga + TOP + TRON + TRX + Turkish lira + TRY + Trinidad & Tobago Dollar + TTD + New Taiwan dollar + TWD + Tanzanian shilling + TZS + Ukrainian hryvnia + UAH + Ugandan shilling + UGX + Universe + UNI + United States dollar + USD + USD Coin + SDC + Tether + SDT + Uruguayan peso + UYU + Uzbekistani som + UZS + Sovereign Bolivar + VEF + Vechain + VET + Vietnamese dong + VND + Vanuatu vatu + VUV + Wrapped Bitcoin + BTC + Samoan tala + WST + Central African CFA franc + XAF + Silver Ounce + XAG + XauCoin + XAU + East Caribbean dollar + XCD + Special Drawing Rights + XDR + Stellar + XLM + Monero + XMR + West African CFA franc + XOF + CFP franc + XPF + XRP + XRP + Yemeni rial + YER + South African rand + ZAR + Zambian kwacha + ZMK + Zambian Kwacha + ZMW + Zimbabwean Dollar + ZWL - "Length" - "Time" - "Volume" - "Area" - "Temperature" - "Speed" - "Mass" - "Data" - "Energy" - "Power" - "Angle" - "Data transfer" - "Pressure" - "Acceleration" - "Currency" + Length + Time + Volume + Area + Temperature + Speed + Mass + Data + Energy + Power + Angle + Data transfer + Pressure + Acceleration + Currency - "Convert from" - "Convert to" - "Settings" + Convert from + Convert to + Settings + Time zones - "Themes" - "Precision" - "Separator" - "Exponential notation" - "Unit groups" - "Wrong currency rates?" - "Note" - "Currency rates are updated daily. There's no real-time market monitoring in the app" - "Terms and Conditions" - "Privacy Policy" - "Third party licenses" - "Rate this app" - "Formatting" - "Additional" + Themes + Precision + Separator + Exponential notation + Unit groups + Wrong currency rates? + Note + Currency rates are updated daily. There\'s no real-time market monitoring in the app + Terms and Conditions + Privacy Policy + Third party licenses + Rate this app + Formatting + Additional - "Number of decimal places" - "Converted values may have a precision higher than the preferred one." - "%1$s (Max)" + Number of decimal places + Converted values may have a precision higher than the preferred one. + %1$s (Max) - "Group separator symbol" - "Period" - "Comma" - "Space" + Group separator symbol + Period + Comma + Space - "Replace part of the number with E" + Replace part of the number with E - "App look and feel" - "Auto" - "Light" - "Dark" - "Color theme" - "AMOLED Dark" - "Use black background for dark themes" - "Dynamic colors" - "Use colors from your wallpaper" + App look and feel + Auto + Light + Dark + Color theme + AMOLED Dark + Use black background for dark themes + Dynamic colors + Use colors from your wallpaper - "Loading…" - "Error" - "Click to try again" - "Copied %1$s!" - "Cancel" - "OK" - "Search units" - "No results found" - "Open settings" - "Make sure there are no typos, try different filters or check for disabled unit groups." - "Hello!" - "Enabled" - "Disabled" + Loading… + Error + Click to try again + Copied %1$s! + Cancel + OK + Search units + No results found + Open settings + Make sure there are no typos, try different filters or check for disabled unit groups. + Hello! + Enabled + Disabled - "Navigate up" - "Checked filter" - "Open settings" - "Swap units" - "Search button" - "Clear input" - "Add or remove unit from favorites" - "Empty search result" - "Open or close drop down menu" - "Enable unit group" - "Reorder unit group" - "Disable unit group" - "Version name" - "About Unitto" - "Learn about the app" - "Disable and rearrange units" - "Cent" - "cent" + Navigate up + Checked filter + Open settings + Swap units + Search button + Clear input + Add or remove unit from favorites + Empty search result + Open or close drop down menu + Enable unit group + Reorder unit group + Disable unit group + Version name + About Unitto + Learn about the app + Disable and rearrange units + Cent + cent - "Maxwell" - "Mx" - "Weber" - "Wb" - "Milliweber" - "mWb" - "Microweber" - "μWb" - "Kiloweber" - "kWb" - "Megaweber" - "MWb" - "Gigaweber" - "GWb" - "Flux" - "View source code" - "Translate this app" - "Join POEditor project to help" + Maxwell + Mx + Weber + Wb + Milliweber + mWb + Microweber + μWb + Kiloweber + kWb + Megaweber + MWb + Gigaweber + GWb + Flux + View source code + Translate this app + Join POEditor project to help - "Binary" - "base2" - "Ternary" - "base3" - "Quaternary" - "base4" - "Quinary" - "base5" - "Senary" - "base6" - "Septenary" - "base7" - "Octal" - "base8" - "Nonary" - "base9" - "Decimal" - "base10" - "Undecimal" - "base11" - "Duodecimal" - "base12" - "Tridecimal" - "base13" - "Tetradecimal" - "base14" - "Pentadecimal" - "base15" - "Hexadecimal" - "base16" - "Base" - "Vibrations" - "Haptic feedback when clicking keyboard buttons" - "Millibar" - "mbar" - "Kilopascal" - "kPa" - "Micron of mercury" - "μmHg" + Binary + base2 + Ternary + base3 + Quaternary + base4 + Quinary + base5 + Senary + base6 + Septenary + base7 + Octal + base8 + Nonary + base9 + Decimal + base10 + Undecimal + base11 + Duodecimal + base12 + Tridecimal + base13 + Tetradecimal + base14 + Pentadecimal + base15 + Hexadecimal + base16 + Base + Vibrations + Haptic feedback when clicking keyboard buttons + Millibar + mbar + Kilopascal + kPa + Micron of mercury + μmHg - "Epoch converter" + Epoch converter - "Calculator" + Calculator - "y" - "m" - "Nautical mile" - "M" - "Starting screen" - "Choose which screen is shown when you launch the app" + y + m + Nautical mile + M + Starting screen + Choose which screen is shown when you launch the app - "Unit converter" - "Clear" - "Clear history" - "All expressions from history will be deleted forever. This action can't be undone!" - "No history" - "Open menu" - "Microgram" - "µg" + Unit converter + Clear + Clear history + All expressions from history will be deleted forever. This action can\'t be undone! + No history + Open menu + Microgram + µg - "Attofarad" - "aF" - "Statfarad" - "stF" - "Farad" - "F" - "Exafarad" - "EF" - "Picofarad" - "pF" - "Nanofarad" - "nF" - "Microfarad" - "µF" - "Millifarad" - "mF" - "Kilofarad" - "kF" - "Megafarad" - "MF" - "Gigafarad" - "GF" - "Petafarad" - "PF" + Attofarad + aF + Statfarad + stF + Farad + F + Exafarad + EF + Picofarad + pF + Nanofarad + nF + Microfarad + µF + Millifarad + mF + Kilofarad + kF + Megafarad + MF + Gigafarad + GF + Petafarad + PF - "Quetta" - "Q" - "Ronna" - "R" - "Yotta" - "Y" - "Zetta" - "Z" - "Exa" - "E" - "Peta" - "P" - "Tera" - "T" - "Giga" - "G" - "Mega" - "M" - "Kilo" - "k" - "Hecto" - "h" - "Deca" - "da" - "Base" - "Base" - "Deci" - "d" - "Centi" - "c" - "Milli" - "m" - "Micro" - "μ" - "Nano" - "n" - "Pico" - "p" - "Femto" - "f" - "Atto" - "a" - "Zepto" - "z" - "Yocto" - "y" - "Ronto" - "r" - "Quecto" - "q" + Quetta + Q + Ronna + R + Yotta + Y + Zetta + Z + Exa + E + Peta + P + Tera + T + Giga + G + Mega + M + Kilo + k + Hecto + h + Deca + da + Base + Base + Deci + d + Centi + c + Milli + m + Micro + μ + Nano + n + Pico + p + Femto + f + Atto + a + Zepto + z + Yocto + y + Ronto + r + Quecto + q - "Newton" - "N" - "Kilonewton" - "kN" - "Gram-force" - "gf" - "Kilogram-force" - "kgf" - "Ton-force" - "tf" - "Millinewton" - "mN" - "Attonewton" - "aN" - "Dyne" - "dyn" - "Joule/meter" - "J/m" - "Joule/centimeter" - "J/cm" - "Kilopound-force" - "kipf" - "Pound-force" - "lbf" - "Ounce-force" - "ozf" - "Pond" - "p" - "Kilopond" - "kp" + Newton + N + Kilonewton + kN + Gram-force + gf + Kilogram-force + kgf + Ton-force + tf + Millinewton + mN + Attonewton + aN + Dyne + dyn + Joule/meter + J/m + Joule/centimeter + J/cm + Kilopound-force + kipf + Pound-force + lbf + Ounce-force + ozf + Pond + p + Kilopond + kp - "Newton meter" - "N*m" - "Newton centimeter" - "N*cm" - "Newton millimeter" - "N*mm" - "Kilonewton meter" - "kN*m" - "Dyne meter" - "dyn*m" - "Dyne centimeter" - "dyn*cm" - "Dyne millimeter" - "dyn*mm" - "Kilogram-force meter" - "kgf*m" - "Kilogram-force centimeter" - "kgf*cm" - "Kilogram-force millimeter" - "kgf*mm" - "Gram-force meter" - "gf*m" - "Gram-force centimeter" - "gf*cm" - "Gram-force millimeter" - "gf*mm" - "Ounce-force foot" - "ozf*ft" - "Ounce-force inch" - "ozf*in" - "Pound-force foot" - "lbf*ft" - "Pound-force inch" - "lbf*in" + Newton meter + N*m + Newton centimeter + N*cm + Newton millimeter + N*mm + Kilonewton meter + kN*m + Dyne meter + dyn*m + Dyne centimeter + dyn*cm + Dyne millimeter + dyn*mm + Kilogram-force meter + kgf*m + Kilogram-force centimeter + kgf*cm + Kilogram-force millimeter + kgf*mm + Gram-force meter + gf*m + Gram-force centimeter + gf*cm + Gram-force millimeter + gf*mm + Ounce-force foot + ozf*ft + Ounce-force inch + ozf*in + Pound-force foot + lbf*ft + Pound-force inch + lbf*in - "Liter/hour" - "L/h" - "Liter/minute" - "L/m" - "Liter/second" - "L/s" - "Milliliter/hour" - "mL/h" - "Milliliter/minute" - "mL/m" - "Milliliter/second" - "mL/s" - "Cubic Meter/hour" - "m3/h" - "Cubic Meter/minute" - "m3/m" - "Cubic Meter/second" - "m3/s" - "Cubic Millimeter/hour" - "mm3/h" - "Cubic Millimeter/minute" - "mm3/m" - "Cubic Millimeter/second" - "mm3/s" - "Cubic Foot/hour" - "ft3/h" - "Cubic Foot/minute" - "ft3/m" - "Cubic Foot/second" - "ft3/s" - "Gallon/hour (U.S.)" - "gal/h" - "Gallon/minute (U.S.)" - "gal/m" - "Gallon/second (U.S.)" - "gal/s" - "Gallon/hour (Imperial)" - "gal/h" - "Gallon/minute (Imperial)" - "gal/m" - "Gallon/second (Imperial)" - "gal/s" + Liter/hour + L/h + Liter/minute + L/m + Liter/second + L/s + Milliliter/hour + mL/h + Milliliter/minute + mL/m + Milliliter/second + mL/s + Cubic Meter/hour + m3/h + Cubic Meter/minute + m3/m + Cubic Meter/second + m3/s + Cubic Millimeter/hour + mm3/h + Cubic Millimeter/minute + mm3/m + Cubic Millimeter/second + mm3/s + Cubic Foot/hour + ft3/h + Cubic Foot/minute + ft3/m + Cubic Foot/second + ft3/s + Gallon/hour (U.S.) + gal/h + Gallon/minute (U.S.) + gal/m + Gallon/second (U.S.) + gal/s + Gallon/hour (Imperial) + gal/h + Gallon/minute (Imperial) + gal/m + Gallon/second (Imperial) + gal/s - "Candela/square meter" - "cd/m^2" - "Candela/square centimeter" - "cd/cm^2" - "Candela/square foot" - "cd/ft^2" - "Candela/square inch" - "cd/in^2" - "Kilocandela/square meter" - "kcd" - "Stilb" - "sb" - "Lumen/square meter/steradian" - "lm/m^2/sr" - "Lumen/square centimeter/steradian" - "lm/cm^2/sr" - "Lumen/square foot/steradian" - "lm/ft^2/sr" - "Watt/square centimeter/steradian" - "W/cm^2/sr" - "Nit" - "nt" - "Millinit" - "mnt" - "Lambert" - "L" - "Millilambert" - "mL" - "Foot-lambert" - "fL" - "Apostilb" - "asb" - "Blondel" - "blondel" - "Bril" - "bril" - "Skot" - "sk" - "Capacitance" - "Prefix" - "Force" - "Torque" - "Flow" - "Luminance" - "Format time" - "Example: Show 130 minutes as 2h 10m" - "Units list sorting" - "Change units order" + Candela/square meter + cd/m^2 + Candela/square centimeter + cd/cm^2 + Candela/square foot + cd/ft^2 + Candela/square inch + cd/in^2 + Kilocandela/square meter + kcd + Stilb + sb + Lumen/square meter/steradian + lm/m^2/sr + Lumen/square centimeter/steradian + lm/cm^2/sr + Lumen/square foot/steradian + lm/ft^2/sr + Watt/square centimeter/steradian + W/cm^2/sr + Nit + nt + Millinit + mnt + Lambert + L + Millilambert + mL + Foot-lambert + fL + Apostilb + asb + Blondel + blondel + Bril + bril + Skot + sk + Capacitance + Prefix + Force + Torque + Flow + Luminance + Format time + Example: Show 130 minutes as 2h 10m + Units list sorting + Change units order - "Usage" - "Alphabetical" - "Scale (Desc.)" - "Scale (Asc.)" - "Pick a theming mode" - "Color scheme" - "Selected color" - "Selected style" + Usage + Alphabetical + Scale (Desc.) + Scale (Asc.) + Pick a theming mode + Color scheme + Selected color + Selected style - "Precision and numbers appearance" - "Can't divide by 0" - "Date difference" + Precision and numbers appearance + Can\'t divide by 0 + Date difference - "Select time" - "Start" - "End" - "Difference" + Select time + Start + End + Difference - "Years" + Years - "Months" + Months - "Days" + Days - "Hours" + Hours - "Minutes" + Minutes - "Next" - "Preview (click to switch)" + Next + Preview (click to switch) + Tomorrow + Yesterday + Search… + Add time zone \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 51d74dca..681bdca9 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -37,6 +37,7 @@ android { dependencies { testImplementation(libs.junit) + testImplementation(libs.org.robolectric) testImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.test.manifest) diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/DateTimePickerDialog.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/DateTimePickerDialog.kt index 3f76bd75..c87264aa 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/DateTimePickerDialog.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/DateTimePickerDialog.kt @@ -18,6 +18,7 @@ package com.sadellie.unitto.core.ui.common +import android.content.res.Configuration import android.text.format.DateFormat import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -44,6 +45,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp @@ -59,23 +61,25 @@ import kotlin.math.max @Composable fun TimePickerDialog( modifier: Modifier = Modifier, - localDateTime: LocalDateTime, + hour: Int, + minute: Int, confirmLabel: String = stringResource(R.string.ok_label), dismissLabel: String = stringResource(R.string.cancel_label), onDismiss: () -> Unit = {}, - onConfirm: (LocalDateTime) -> Unit, - vertical: Boolean + onConfirm: (hour: Int, minute: Int) -> Unit, ) { + val isVertical = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT + val pickerState = rememberTimePickerState( - localDateTime.hour, - localDateTime.minute, - DateFormat.is24HourFormat(LocalContext.current) + initialHour = hour, + initialMinute = minute, + is24Hour = DateFormat.is24HourFormat(LocalContext.current) ) AlertDialog( onDismissRequest = onDismiss, modifier = modifier.wrapContentHeight(), - properties = DialogProperties(usePlatformDefaultWidth = vertical) + properties = DialogProperties(usePlatformDefaultWidth = isVertical) ) { Surface( modifier = modifier, @@ -97,7 +101,7 @@ fun TimePickerDialog( TimePicker( state = pickerState, modifier = Modifier.padding(top = 20.dp), - layoutType = if (vertical) TimePickerLayoutType.Vertical else TimePickerLayoutType.Horizontal + layoutType = if (isVertical) TimePickerLayoutType.Vertical else TimePickerLayoutType.Horizontal ) Row( @@ -110,13 +114,7 @@ fun TimePickerDialog( Text(text = dismissLabel) } TextButton( - onClick = { - onConfirm( - localDateTime - .withHour(pickerState.hour) - .withMinute(pickerState.minute) - ) - } + onClick = { onConfirm(pickerState.hour, pickerState.minute) } ) { Text(text = confirmLabel) } diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoScreenWithTopBar.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoScreenWithTopBar.kt index 09ee24ee..e87eedc2 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoScreenWithTopBar.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoScreenWithTopBar.kt @@ -21,9 +21,11 @@ package com.sadellie.unitto.core.ui.common import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.FabPosition import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBarColors import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -35,6 +37,8 @@ import androidx.compose.ui.Modifier * @param navigationIcon See [CenterAlignedTopAppBar] * @param actions See [CenterAlignedTopAppBar] * @param colors See [CenterAlignedTopAppBar] + * @param floatingActionButton See [Scaffold] + * @param scrollBehavior See [CenterAlignedTopAppBar] * @param content See [Scaffold] */ @Composable @@ -44,6 +48,9 @@ fun UnittoScreenWithTopBar( navigationIcon: @Composable () -> Unit, actions: @Composable RowScope.() -> Unit = {}, colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), + floatingActionButton: @Composable () -> Unit = {}, + floatingActionButtonPosition: FabPosition = FabPosition.End, + scrollBehavior: TopAppBarScrollBehavior? = null, content: @Composable (PaddingValues) -> Unit ) { Scaffold( @@ -53,9 +60,12 @@ fun UnittoScreenWithTopBar( title = title, navigationIcon = navigationIcon, actions = actions, - colors = colors + colors = colors, + scrollBehavior = scrollBehavior, ) }, + floatingActionButton = floatingActionButton, + floatingActionButtonPosition = floatingActionButtonPosition, content = content ) } diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/DateTimeFormatter.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/DateTimeFormatter.kt new file mode 100644 index 00000000..d40da1c6 --- /dev/null +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/DateTimeFormatter.kt @@ -0,0 +1,27 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.core.ui.datetime + +import java.time.format.DateTimeFormatter + +// FIXME Duplicate from date difference +internal val time24Formatter by lazy { DateTimeFormatter.ofPattern("HH:mm") } +internal val time12Formatter by lazy { DateTimeFormatter.ofPattern("hh:mm a") } +internal val dayMonthYear by lazy { DateTimeFormatter.ofPattern("d MMM y") } +internal val zoneFormatPattern by lazy { DateTimeFormatter.ofPattern("O") } diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/LocalDateTimeUtils.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/LocalDateTimeUtils.kt new file mode 100644 index 00000000..b4d6ca5d --- /dev/null +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/LocalDateTimeUtils.kt @@ -0,0 +1,48 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.core.ui.datetime + +import android.text.format.DateFormat +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import java.time.LocalDateTime + +@Composable +fun LocalDateTime.formatLocal(): String { + return if (DateFormat.is24HourFormat(LocalContext.current)) format24() + else format12() +} + +/** + * Formats [LocalDateTime] into string that looks like + * + * 23:58 + * + * @return Formatted string. + */ +fun LocalDateTime.format24(): String = this.format(time24Formatter) + +/** + * Formats [LocalDateTime] into string that looks like + * + * 11:58 am + * + * @return Formatted string. + */ +fun LocalDateTime.format12(): String = this.format(time12Formatter) diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/ZonedDateTimeUtils.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/ZonedDateTimeUtils.kt new file mode 100644 index 00000000..ad680fe4 --- /dev/null +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/datetime/ZonedDateTimeUtils.kt @@ -0,0 +1,120 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.core.ui.datetime + +import android.text.format.DateFormat +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import com.sadellie.unitto.core.base.R +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit +import kotlin.math.absoluteValue + +@Composable +fun ZonedDateTime.formatLocal(): String { + return if (DateFormat.is24HourFormat(LocalContext.current)) format24() + else format12() +} + +/** + * Formats [ZonedDateTime] into string that looks like + * + * 23:58 + * + * @return Formatted string. + */ +fun ZonedDateTime.format24(): String = this.format(time24Formatter) + +/** + * Formats [ZonedDateTime] into string that looks like + * + * 11:58 am + * + * @return Formatted string. + */ +fun ZonedDateTime.format12(): String = this.format(time12Formatter) + +/** + * Formats [ZonedDateTime] into string that looks like + * + * 21 Jul 2023 + * + * @return Formatted string. + */ +fun ZonedDateTime.formatDayMonthYear(): String = this.format(dayMonthYear) + +fun ZonedDateTime.formatTimeZoneOffset(): String = this.format(zoneFormatPattern) + +/** + * Format offset string. Examples: + * + * 0 + * + * +8 + * + * +8, tomorrow + * + * -8, yesterday + * + * @receiver [ZonedDateTime] Time with offset. + * @param currentTime Time without offset. + * @return Formatted string. + */ +@Composable +fun ZonedDateTime.formatOffset( + currentTime: ZonedDateTime +): String? { + + val offsetFixed = ChronoUnit.SECONDS.between(currentTime, this) + + if (offsetFixed == 0L) return null + + var resultBuffer = "" + val absoluteOffset = offsetFixed.absoluteValue + + // Add a positive/negative prefix symbol + when { + offsetFixed > 0 -> resultBuffer += "+" + offsetFixed < 0 -> resultBuffer += "-" + } + + // Formatted hours and minutes + val hour = absoluteOffset / 3600 + val minute = absoluteOffset % 3600 / 60 + + if (hour != 0L) { + resultBuffer += "${hour}${stringResource(R.string.hour_short)}" + } + + // TODO Very ugly + if (minute != 0L) { + if (hour != 0L) resultBuffer += " " + resultBuffer += "${minute}${stringResource(R.string.minute_short)}" + } + + // Day after time string + val diff = this.dayOfYear - currentTime.dayOfYear + when { + diff > 0 -> resultBuffer += ", ${stringResource(R.string.tomorrow).lowercase()}" + diff < 0 -> resultBuffer += ", ${stringResource(R.string.yesterday).lowercase()}" + } + + return resultBuffer +} diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/model/DrawerItems.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/model/DrawerItems.kt index d3169397..fc444dda 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/model/DrawerItems.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/model/DrawerItems.kt @@ -21,10 +21,12 @@ package com.sadellie.unitto.core.ui.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Calculate import androidx.compose.material.icons.filled.Event +import androidx.compose.material.icons.filled.Schedule import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.material.icons.outlined.Calculate import androidx.compose.material.icons.outlined.Event +import androidx.compose.material.icons.outlined.Schedule import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.SwapHoriz import androidx.compose.ui.graphics.vector.ImageVector @@ -53,6 +55,12 @@ sealed class DrawerItems( defaultIcon = Icons.Outlined.Event ) + object TimeZones : DrawerItems( + destination = TopLevelDestinations.TimeZone, + selectedIcon = Icons.Filled.Schedule, + defaultIcon = Icons.Outlined.Schedule + ) + object Settings : DrawerItems( destination = TopLevelDestinations.Settings, selectedIcon = Icons.Filled.Settings, diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/theme/Type.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/theme/Type.kt index 5740782f..9f81b829 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/theme/Type.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/theme/Type.kt @@ -27,14 +27,14 @@ import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp import com.sadellie.unitto.core.base.R -private val Montserrat = FontFamily( +val Montserrat = FontFamily( Font(R.font.montserrat_light, weight = FontWeight.Light), Font(R.font.montserrat_regular, weight = FontWeight.Normal), Font(R.font.montserrat_medium, weight = FontWeight.Medium), Font(R.font.montserrat_semibold, weight = FontWeight.SemiBold), ) -private val Lato = FontFamily( +val Lato = FontFamily( Font(R.font.lato_regular) ) diff --git a/core/ui/src/test/java/com/sadellie/unitto/core/ui/ZonedDateTimeUtilsTest.kt b/core/ui/src/test/java/com/sadellie/unitto/core/ui/ZonedDateTimeUtilsTest.kt new file mode 100644 index 00000000..284ab64f --- /dev/null +++ b/core/ui/src/test/java/com/sadellie/unitto/core/ui/ZonedDateTimeUtilsTest.kt @@ -0,0 +1,176 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.core.ui + +import androidx.compose.ui.test.junit4.createComposeRule +import com.sadellie.unitto.core.ui.datetime.formatOffset +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.Assert.assertEquals +import org.robolectric.RobolectricTestRunner +import java.time.ZonedDateTime + +@RunWith(RobolectricTestRunner::class) +class ZonedDateTimeUtilsTest { + + @get: Rule + val composeTestRule = createComposeRule() + + @Test + fun `no difference`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val formatted = currentTime.formatOffset(currentTime) + + assertEquals(null, formatted) + } + + @Test + fun `show positive hour`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val offset = currentTime + .plusSeconds(7200) // + 2h = 14:00 + .formatOffset(currentTime) + + assertEquals("+2h", offset) + } + + @Test + fun `show positive hour minute`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val offset = currentTime + .plusSeconds(9000) // + 2h 30m = 14:30 + .formatOffset(currentTime) + + assertEquals("+2h 30m", offset) + } + + @Test + fun `show positive hour minute day`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val offset = currentTime + .plusSeconds(50400) // + 14h = 02:00 tomorrow + .formatOffset(currentTime) + + assertEquals("+14h, tomorrow", offset) + } + + @Test + fun `show positive minute`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val offset = currentTime + .plusSeconds(1800) // + 30m = 12:30 + .formatOffset(currentTime) + + assertEquals("+30m", offset) + } + + @Test + fun `show positive minute day`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(23) + .withMinute(45) + + val offset = currentTime + .plusSeconds(1800) // + 30m = 00:15 tomorrow + .formatOffset(currentTime) + + assertEquals("+30m, tomorrow", offset) + } + + @Test + fun `show negative hour`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val offset = currentTime + .minusSeconds(7200) // - 2h = 10:00 + .formatOffset(currentTime) + + assertEquals("-2h", offset) + } + + @Test + fun `show negative hour minute`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val offset = currentTime + .minusSeconds(9000) // - 2h 30m = 09:30 tomorrow + .formatOffset(currentTime) + + assertEquals("-2h 30m", offset) + } + + @Test + fun `show negative hour minute day`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val offset = currentTime + .minusSeconds(50400) // - 14h = 22:00 yesterday + .formatOffset(currentTime) + + assertEquals("-14h, yesterday", offset) + } + + @Test + fun `show negative minute`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(12) + .withMinute(0) + + val offset = currentTime + .minusSeconds(1800) // - 30m = 11:30 + .formatOffset(currentTime) + + assertEquals("-30m", offset) + } + + @Test + fun `show negative minute day`() = composeTestRule.setContent { + val currentTime = ZonedDateTime.now() + .withHour(0) + .withMinute(15) + + val offset = currentTime + .minusSeconds(1800) // - 30m = 23:45 yesterday + .formatOffset(currentTime) + + assertEquals("-30m, yesterday", offset) + } +} diff --git a/data/database/schemas/com.sadellie.unitto.data.database.UnittoDatabase/2.json b/data/database/schemas/com.sadellie.unitto.data.database.UnittoDatabase/2.json index 531c500c..a5ee0753 100644 --- a/data/database/schemas/com.sadellie.unitto.data.database.UnittoDatabase/2.json +++ b/data/database/schemas/com.sadellie.unitto.data.database.UnittoDatabase/2.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "d5dca9e0346c3400b7ff5b31e85c7827", + "identityHash": "ab71572ff5556256d1f042af36243f6f", "entities": [ { "tableName": "units", @@ -34,10 +34,10 @@ } ], "primaryKey": { + "autoGenerate": false, "columnNames": [ "unitId" - ], - "autoGenerate": false + ] }, "indices": [], "foreignKeys": [] @@ -72,10 +72,42 @@ } ], "primaryKey": { + "autoGenerate": true, "columnNames": [ "entityId" - ], - "autoGenerate": true + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "time_zones", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `position` INTEGER NOT NULL, `label` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] }, "indices": [], "foreignKeys": [] @@ -84,7 +116,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd5dca9e0346c3400b7ff5b31e85c7827')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab71572ff5556256d1f042af36243f6f')" ] } } \ No newline at end of file diff --git a/data/database/schemas/com.sadellie.unitto.data.database.UnittoDatabase/3.json b/data/database/schemas/com.sadellie.unitto.data.database.UnittoDatabase/3.json new file mode 100644 index 00000000..079aadb8 --- /dev/null +++ b/data/database/schemas/com.sadellie.unitto.data.database.UnittoDatabase/3.json @@ -0,0 +1,122 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "ab71572ff5556256d1f042af36243f6f", + "entities": [ + { + "tableName": "units", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`unitId` TEXT NOT NULL, `is_favorite` INTEGER, `paired_unit_id` TEXT, `frequency` INTEGER, PRIMARY KEY(`unitId`))", + "fields": [ + { + "fieldPath": "unitId", + "columnName": "unitId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isFavorite", + "columnName": "is_favorite", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "pairedUnitId", + "columnName": "paired_unit_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "frequency", + "columnName": "frequency", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "unitId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "calculator_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entityId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `expression` TEXT NOT NULL, `result` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "entityId", + "columnName": "entityId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expression", + "columnName": "expression", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "result", + "columnName": "result", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "entityId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "time_zones", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `position` INTEGER NOT NULL, `label` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab71572ff5556256d1f042af36243f6f')" + ] + } +} \ No newline at end of file diff --git a/data/database/src/main/java/com/sadellie/unitto/data/database/TimeZoneDao.kt b/data/database/src/main/java/com/sadellie/unitto/data/database/TimeZoneDao.kt new file mode 100644 index 00000000..3de4f9d4 --- /dev/null +++ b/data/database/src/main/java/com/sadellie/unitto/data/database/TimeZoneDao.kt @@ -0,0 +1,41 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.data.database + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow + +@Dao +interface TimeZoneDao { + @Query("SELECT * FROM time_zones ORDER BY position ASC") + fun getAll(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(vararg timeZoneEntity: TimeZoneEntity) + + @Query("UPDATE time_zones SET position = ( SELECT SUM(position) FROM time_zones WHERE id IN (:fromId, :toId) ) - position WHERE id IN (:fromId, :toId)") + suspend fun swap(fromId: String, toId: String) + + @Delete + suspend fun remove(timeZoneEntity: TimeZoneEntity) +} diff --git a/data/database/src/main/java/com/sadellie/unitto/data/database/TimeZoneEntity.kt b/data/database/src/main/java/com/sadellie/unitto/data/database/TimeZoneEntity.kt new file mode 100644 index 00000000..91383b85 --- /dev/null +++ b/data/database/src/main/java/com/sadellie/unitto/data/database/TimeZoneEntity.kt @@ -0,0 +1,30 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.data.database + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "time_zones") +class TimeZoneEntity( + @PrimaryKey val id: String, + @ColumnInfo(name = "position") val position: Int, + @ColumnInfo(name = "label") val label: String = "", +) diff --git a/data/database/src/main/java/com/sadellie/unitto/data/database/UnittoDatabase.kt b/data/database/src/main/java/com/sadellie/unitto/data/database/UnittoDatabase.kt index 8162acc1..1c25b0d8 100644 --- a/data/database/src/main/java/com/sadellie/unitto/data/database/UnittoDatabase.kt +++ b/data/database/src/main/java/com/sadellie/unitto/data/database/UnittoDatabase.kt @@ -23,17 +23,20 @@ import androidx.room.Database import androidx.room.RoomDatabase @Database( - version = 2, + version = 3, exportSchema = true, entities = [ UnitsEntity::class, - CalculatorHistoryEntity::class + CalculatorHistoryEntity::class, + TimeZoneEntity::class ], autoMigrations = [ - AutoMigration (from = 1, to = 2) + AutoMigration (from = 1, to = 2), + AutoMigration (from = 2, to = 3), ] ) abstract class UnittoDatabase : RoomDatabase() { abstract fun unitsDao(): UnitsDao abstract fun calculatorHistoryDao(): CalculatorHistoryDao + abstract fun timeZoneDao(): TimeZoneDao } diff --git a/data/database/src/main/java/com/sadellie/unitto/data/database/UnittoDatabaseModule.kt b/data/database/src/main/java/com/sadellie/unitto/data/database/UnittoDatabaseModule.kt index bc2902ae..3e1760b8 100644 --- a/data/database/src/main/java/com/sadellie/unitto/data/database/UnittoDatabaseModule.kt +++ b/data/database/src/main/java/com/sadellie/unitto/data/database/UnittoDatabaseModule.kt @@ -56,6 +56,17 @@ class UnittoDatabaseModule { return unittoDatabase.calculatorHistoryDao() } + /** + * Tells Hilt to use this method to get [TimeZoneDao] + * + * @param unittoDatabase Database for which we need DAO + * @return Singleton of [TimeZoneDao] + */ + @Provides + fun provideTimeZoneDao(unittoDatabase: UnittoDatabase): TimeZoneDao { + return unittoDatabase.timeZoneDao() + } + /** * Tells Hilt to use this method to get [UnittoDatabase] * diff --git a/data/model/src/main/java/com/sadellie/unitto/data/model/UnittoTimeZone.kt b/data/model/src/main/java/com/sadellie/unitto/data/model/UnittoTimeZone.kt new file mode 100644 index 00000000..7ba757e7 --- /dev/null +++ b/data/model/src/main/java/com/sadellie/unitto/data/model/UnittoTimeZone.kt @@ -0,0 +1,37 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.data.model + +import java.time.ZonedDateTime + +data class UnittoTimeZone( + val id: String, + // For beta only, will change to StringRes later + val nameRes: String, + val position: Int = 0, + val offsetSeconds: Long = 0, + val code: String = "CODE", +) { + fun offsetFrom(currentTime: ZonedDateTime): ZonedDateTime { + val offsetSeconds = currentTime.offset.totalSeconds.toLong() + val currentTimeWithoutOffset = currentTime.minusSeconds(offsetSeconds) + + return currentTimeWithoutOffset.plusSeconds(this.offsetSeconds) + } +} diff --git a/data/timezone/.gitignore b/data/timezone/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/data/timezone/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/timezone/build.gradle.kts b/data/timezone/build.gradle.kts new file mode 100644 index 00000000..8a98d783 --- /dev/null +++ b/data/timezone/build.gradle.kts @@ -0,0 +1,33 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +plugins { + id("unitto.library") + id("unitto.android.hilt") +} + +android { + namespace = "com.sadellie.unitto.data.timezone" +} + +dependencies { + implementation(project(mapOf("path" to ":core:base"))) + implementation(project(mapOf("path" to ":data:common"))) + implementation(project(mapOf("path" to ":data:model"))) + implementation(project(mapOf("path" to ":data:database"))) +} diff --git a/data/timezone/consumer-rules.pro b/data/timezone/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/data/timezone/src/main/AndroidManifest.xml b/data/timezone/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7bdbce91 --- /dev/null +++ b/data/timezone/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/data/timezone/src/main/java/com/sadellie/unitto/data/timezone/TimeZonesRepository.kt b/data/timezone/src/main/java/com/sadellie/unitto/data/timezone/TimeZonesRepository.kt new file mode 100644 index 00000000..af977045 --- /dev/null +++ b/data/timezone/src/main/java/com/sadellie/unitto/data/timezone/TimeZonesRepository.kt @@ -0,0 +1,490 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.data.timezone + +import com.sadellie.unitto.data.common.lev +import com.sadellie.unitto.data.model.UnittoTimeZone +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class TimeZonesRepository @Inject constructor() { + private val allTimeZones: HashMap = hashMapOf( + "alfa_time_zone" to UnittoTimeZone(id = "alfa_time_zone", nameRes = "Alfa Time Zone", offsetSeconds = 3600), + "australian_central_daylight_time" to UnittoTimeZone(id = "australian_central_daylight_time", nameRes = "Australian Central Daylight Time", offsetSeconds = 37800), + "australian_central_standard_time" to UnittoTimeZone(id = "australian_central_standard_time", nameRes = "Australian Central Standard Time", offsetSeconds = 34200), + "acre_time" to UnittoTimeZone(id = "acre_time", nameRes = "Acre Time", offsetSeconds = -18000), + "australian_central_western_standard_time" to UnittoTimeZone(id = "australian_central_western_standard_time", nameRes = "Australian Central Western Standard Time", offsetSeconds = 31500), + "arabia_daylight_time" to UnittoTimeZone(id = "arabia_daylight_time", nameRes = "Arabia Daylight Time", offsetSeconds = 14400), + "atlantic_daylight_time" to UnittoTimeZone(id = "atlantic_daylight_time", nameRes = "Atlantic Daylight Time", offsetSeconds = -10800), + "heure_avanc_e_de_latlantique_french" to UnittoTimeZone(id = "heure_avanc_e_de_latlantique_french", nameRes = "Heure Avanc-e de l'Atlantique (French)", offsetSeconds = -10800), + "australian_eastern_daylight_time" to UnittoTimeZone(id = "australian_eastern_daylight_time", nameRes = "Australian Eastern Daylight Time", offsetSeconds = 39600), + "eastern_daylight_time" to UnittoTimeZone(id = "eastern_daylight_time", nameRes = "Eastern Daylight Time", offsetSeconds = 39600), + "eastern_daylight_saving_time" to UnittoTimeZone(id = "eastern_daylight_saving_time", nameRes = "Eastern Daylight Saving Time", offsetSeconds = 39600), + "australian_eastern_standard_time" to UnittoTimeZone(id = "australian_eastern_standard_time", nameRes = "Australian Eastern Standard Time", offsetSeconds = 36000), + "eastern_standard_time" to UnittoTimeZone(id = "eastern_standard_time", nameRes = "Eastern Standard Time", offsetSeconds = 36000), + "australian_eastern_time" to UnittoTimeZone(id = "australian_eastern_time", nameRes = "Australian Eastern Time", offsetSeconds = 36000), + "afghanistan_time" to UnittoTimeZone(id = "afghanistan_time", nameRes = "Afghanistan Time", offsetSeconds = 16200), + "alaska_daylight_time" to UnittoTimeZone(id = "alaska_daylight_time", nameRes = "Alaska Daylight Time", offsetSeconds = -28800), + "alaska_standard_time" to UnittoTimeZone(id = "alaska_standard_time", nameRes = "Alaska Standard Time", offsetSeconds = -32400), + "alma_ata_time" to UnittoTimeZone(id = "alma_ata_time", nameRes = "Alma-Ata Time", offsetSeconds = 21600), + "amazon_summer_time" to UnittoTimeZone(id = "amazon_summer_time", nameRes = "Amazon Summer Time", offsetSeconds = -10800), + "armenia_daylight_time" to UnittoTimeZone(id = "armenia_daylight_time", nameRes = "Armenia Daylight Time", offsetSeconds = 18000), + "amazon_time" to UnittoTimeZone(id = "amazon_time", nameRes = "Amazon Time", offsetSeconds = -14400), + "armenia_time" to UnittoTimeZone(id = "armenia_time", nameRes = "Armenia Time", offsetSeconds = 14400), + "anadyr_summer_time" to UnittoTimeZone(id = "anadyr_summer_time", nameRes = "Anadyr Summer Time", offsetSeconds = 43200), + "anadyr_time" to UnittoTimeZone(id = "anadyr_time", nameRes = "Anadyr Time", offsetSeconds = 43200), + "aqtobe_time" to UnittoTimeZone(id = "aqtobe_time", nameRes = "Aqtobe Time", offsetSeconds = 18000), + "argentina_time" to UnittoTimeZone(id = "argentina_time", nameRes = "Argentina Time", offsetSeconds = -10800), + "arabic_standard_time" to UnittoTimeZone(id = "arabic_standard_time", nameRes = "Arabic Standard Time", offsetSeconds = 10800), + "atlantic_standard_time" to UnittoTimeZone(id = "atlantic_standard_time", nameRes = "Atlantic Standard Time", offsetSeconds = -14400), + "tiempo_est_ndar_del_atl_ntico_spanish" to UnittoTimeZone(id = "tiempo_est_ndar_del_atl_ntico_spanish", nameRes = "Tiempo Est-ndar del Atl-ntico (Spanish)", offsetSeconds = -14400), + "heure_normale_de_latlantique_french" to UnittoTimeZone(id = "heure_normale_de_latlantique_french", nameRes = "Heure Normale de l'Atlantique (French)", offsetSeconds = -14400), + "australian_western_daylight_time" to UnittoTimeZone(id = "australian_western_daylight_time", nameRes = "Australian Western Daylight Time", offsetSeconds = 32400), + "western_daylight_time" to UnittoTimeZone(id = "western_daylight_time", nameRes = "Western Daylight Time", offsetSeconds = 32400), + "western_summer_time" to UnittoTimeZone(id = "western_summer_time", nameRes = "Western Summer Time", offsetSeconds = 32400), + "australian_western_standard_time" to UnittoTimeZone(id = "australian_western_standard_time", nameRes = "Australian Western Standard Time", offsetSeconds = 28800), + "western_standard_time" to UnittoTimeZone(id = "western_standard_time", nameRes = "Western Standard Time", offsetSeconds = 28800), + "western_australia_time" to UnittoTimeZone(id = "western_australia_time", nameRes = "Western Australia Time", offsetSeconds = 28800), + "azores_summer_time" to UnittoTimeZone(id = "azores_summer_time", nameRes = "Azores Summer Time", offsetSeconds = 0), + "azores_daylight_time" to UnittoTimeZone(id = "azores_daylight_time", nameRes = "Azores Daylight Time", offsetSeconds = 0), + "azores_time" to UnittoTimeZone(id = "azores_time", nameRes = "Azores Time", offsetSeconds = -3600), + "azores_standard_time" to UnittoTimeZone(id = "azores_standard_time", nameRes = "Azores Standard Time", offsetSeconds = -3600), + "azerbaijan_summer_time" to UnittoTimeZone(id = "azerbaijan_summer_time", nameRes = "Azerbaijan Summer Time", offsetSeconds = 18000), + "azerbaijan_time" to UnittoTimeZone(id = "azerbaijan_time", nameRes = "Azerbaijan Time", offsetSeconds = 14400), + "anywhere_on_earth" to UnittoTimeZone(id = "anywhere_on_earth", nameRes = "Anywhere on Earth", offsetSeconds = -43200), + "bravo_time_zone" to UnittoTimeZone(id = "bravo_time_zone", nameRes = "Bravo Time Zone", offsetSeconds = 7200), + "brunei_darussalam_time" to UnittoTimeZone(id = "brunei_darussalam_time", nameRes = "Brunei Darussalam Time", offsetSeconds = 28800), + "brunei_time" to UnittoTimeZone(id = "brunei_time", nameRes = "Brunei Time", offsetSeconds = 28800), + "bolivia_time" to UnittoTimeZone(id = "bolivia_time", nameRes = "Bolivia Time", offsetSeconds = -14400), + "brasilia_summer_time" to UnittoTimeZone(id = "brasilia_summer_time", nameRes = "Brasilia Summer Time", offsetSeconds = -7200), + "brazil_summer_time" to UnittoTimeZone(id = "brazil_summer_time", nameRes = "Brazil Summer Time", offsetSeconds = -7200), + "brazilian_summer_time" to UnittoTimeZone(id = "brazilian_summer_time", nameRes = "Brazilian Summer Time", offsetSeconds = -7200), + "bras_lia_time" to UnittoTimeZone(id = "bras_lia_time", nameRes = "Bras-lia Time", offsetSeconds = -10800), + "brazil_time" to UnittoTimeZone(id = "brazil_time", nameRes = "Brazil Time", offsetSeconds = -10800), + "brazilian_time" to UnittoTimeZone(id = "brazilian_time", nameRes = "Brazilian Time", offsetSeconds = -10800), + "bangladesh_standard_time" to UnittoTimeZone(id = "bangladesh_standard_time", nameRes = "Bangladesh Standard Time", offsetSeconds = 21600), + "bougainville_standard_time" to UnittoTimeZone(id = "bougainville_standard_time", nameRes = "Bougainville Standard Time", offsetSeconds = 39600), + "british_summer_time" to UnittoTimeZone(id = "british_summer_time", nameRes = "British Summer Time", offsetSeconds = 3600), + "british_daylight_time" to UnittoTimeZone(id = "british_daylight_time", nameRes = "British Daylight Time", offsetSeconds = 3600), + "british_daylight_saving_time" to UnittoTimeZone(id = "british_daylight_saving_time", nameRes = "British Daylight Saving Time", offsetSeconds = 3600), + "bhutan_time" to UnittoTimeZone(id = "bhutan_time", nameRes = "Bhutan Time", offsetSeconds = 21600), + "charlie_time_zone" to UnittoTimeZone(id = "charlie_time_zone", nameRes = "Charlie Time Zone", offsetSeconds = 10800), + "casey_time" to UnittoTimeZone(id = "casey_time", nameRes = "Casey Time", offsetSeconds = 28800), + "central_africa_time" to UnittoTimeZone(id = "central_africa_time", nameRes = "Central Africa Time", offsetSeconds = 7200), + "cocos_islands_time" to UnittoTimeZone(id = "cocos_islands_time", nameRes = "Cocos Islands Time", offsetSeconds = 23400), + "central_daylight_time" to UnittoTimeZone(id = "central_daylight_time", nameRes = "Central Daylight Time", offsetSeconds = -18000), + "central_daylight_saving_time" to UnittoTimeZone(id = "central_daylight_saving_time", nameRes = "Central Daylight Saving Time", offsetSeconds = -18000), + "north_american_central_daylight_time" to UnittoTimeZone(id = "north_american_central_daylight_time", nameRes = "North American Central Daylight Time", offsetSeconds = -18000), + "heure_avanc_e_du_centre_french" to UnittoTimeZone(id = "heure_avanc_e_du_centre_french", nameRes = "Heure Avanc-e du Centre (French)", offsetSeconds = -18000), + "cuba_daylight_time" to UnittoTimeZone(id = "cuba_daylight_time", nameRes = "Cuba Daylight Time", offsetSeconds = -14400), + "central_european_summer_time" to UnittoTimeZone(id = "central_european_summer_time", nameRes = "Central European Summer Time", offsetSeconds = 7200), + "central_european_daylight_time" to UnittoTimeZone(id = "central_european_daylight_time", nameRes = "Central European Daylight Time", offsetSeconds = 7200), + "european_central_summer_time" to UnittoTimeZone(id = "european_central_summer_time", nameRes = "European Central Summer Time", offsetSeconds = 7200), + "mitteleurop_ische_sommerzeit_german" to UnittoTimeZone(id = "mitteleurop_ische_sommerzeit_german", nameRes = "Mitteleurop-ische Sommerzeit (German)", offsetSeconds = 7200), + "central_european_time" to UnittoTimeZone(id = "central_european_time", nameRes = "Central European Time", offsetSeconds = 3600), + "european_central_time" to UnittoTimeZone(id = "european_central_time", nameRes = "European Central Time", offsetSeconds = 3600), + "central_europe_time" to UnittoTimeZone(id = "central_europe_time", nameRes = "Central Europe Time", offsetSeconds = 3600), + "mitteleurop_ische_zeit_german" to UnittoTimeZone(id = "mitteleurop_ische_zeit_german", nameRes = "Mitteleurop-ische Zeit (German)", offsetSeconds = 3600), + "chatham_island_daylight_time" to UnittoTimeZone(id = "chatham_island_daylight_time", nameRes = "Chatham Island Daylight Time", offsetSeconds = 49500), + "chatham_daylight_time" to UnittoTimeZone(id = "chatham_daylight_time", nameRes = "Chatham Daylight Time", offsetSeconds = 49500), + "chatham_island_standard_time" to UnittoTimeZone(id = "chatham_island_standard_time", nameRes = "Chatham Island Standard Time", offsetSeconds = 45900), + "choibalsan_summer_time" to UnittoTimeZone(id = "choibalsan_summer_time", nameRes = "Choibalsan Summer Time", offsetSeconds = 32400), + "choibalsan_daylight_time" to UnittoTimeZone(id = "choibalsan_daylight_time", nameRes = "Choibalsan Daylight Time", offsetSeconds = 32400), + "choibalsan_daylight_saving_time" to UnittoTimeZone(id = "choibalsan_daylight_saving_time", nameRes = "Choibalsan Daylight Saving Time", offsetSeconds = 32400), + "choibalsan_time" to UnittoTimeZone(id = "choibalsan_time", nameRes = "Choibalsan Time", offsetSeconds = 28800), + "chuuk_time" to UnittoTimeZone(id = "chuuk_time", nameRes = "Chuuk Time", offsetSeconds = 36000), + "cayman_islands_daylight_saving_time" to UnittoTimeZone(id = "cayman_islands_daylight_saving_time", nameRes = "Cayman Islands Daylight Saving Time", offsetSeconds = -14400), + "cayman_islands_standard_time" to UnittoTimeZone(id = "cayman_islands_standard_time", nameRes = "Cayman Islands Standard Time", offsetSeconds = -18000), + "cayman_islands_time" to UnittoTimeZone(id = "cayman_islands_time", nameRes = "Cayman Islands Time", offsetSeconds = -18000), + "cook_island_time" to UnittoTimeZone(id = "cook_island_time", nameRes = "Cook Island Time", offsetSeconds = -36000), + "chile_summer_time" to UnittoTimeZone(id = "chile_summer_time", nameRes = "Chile Summer Time", offsetSeconds = -10800), + "chile_daylight_time" to UnittoTimeZone(id = "chile_daylight_time", nameRes = "Chile Daylight Time", offsetSeconds = -10800), + "chile_standard_time" to UnittoTimeZone(id = "chile_standard_time", nameRes = "Chile Standard Time", offsetSeconds = -14400), + "chile_time" to UnittoTimeZone(id = "chile_time", nameRes = "Chile Time", offsetSeconds = -14400), + "chile_standard_time" to UnittoTimeZone(id = "chile_standard_time", nameRes = "Chile Standard Time", offsetSeconds = -14400), + "colombia_time" to UnittoTimeZone(id = "colombia_time", nameRes = "Colombia Time", offsetSeconds = -18000), + "central_standard_time" to UnittoTimeZone(id = "central_standard_time", nameRes = "Central Standard Time", offsetSeconds = -21600), + "central_time" to UnittoTimeZone(id = "central_time", nameRes = "Central Time", offsetSeconds = -21600), + "north_american_central_standard_time" to UnittoTimeZone(id = "north_american_central_standard_time", nameRes = "North American Central Standard Time", offsetSeconds = -21600), + "tiempo_central_est_ndar_spanish" to UnittoTimeZone(id = "tiempo_central_est_ndar_spanish", nameRes = "Tiempo Central Est-ndar (Spanish)", offsetSeconds = -21600), + "heure_normale_du_centre_french" to UnittoTimeZone(id = "heure_normale_du_centre_french", nameRes = "Heure Normale du Centre (French)", offsetSeconds = -21600), + "china_standard_time" to UnittoTimeZone(id = "china_standard_time", nameRes = "China Standard Time", offsetSeconds = 28800), + "cuba_standard_time" to UnittoTimeZone(id = "cuba_standard_time", nameRes = "Cuba Standard Time", offsetSeconds = -18000), + "cape_verde_time" to UnittoTimeZone(id = "cape_verde_time", nameRes = "Cape Verde Time", offsetSeconds = -3600), + "christmas_island_time" to UnittoTimeZone(id = "christmas_island_time", nameRes = "Christmas Island Time", offsetSeconds = 25200), + "chamorro_standard_time" to UnittoTimeZone(id = "chamorro_standard_time", nameRes = "Chamorro Standard Time", offsetSeconds = 36000), + "guam_standard_time" to UnittoTimeZone(id = "guam_standard_time", nameRes = "Guam Standard Time", offsetSeconds = 36000), + "delta_time_zone" to UnittoTimeZone(id = "delta_time_zone", nameRes = "Delta Time Zone", offsetSeconds = 14400), + "davis_time" to UnittoTimeZone(id = "davis_time", nameRes = "Davis Time", offsetSeconds = 25200), + "dumont_durville_time" to UnittoTimeZone(id = "dumont_durville_time", nameRes = "Dumont-d'Urville Time", offsetSeconds = 36000), + "echo_time_zone" to UnittoTimeZone(id = "echo_time_zone", nameRes = "Echo Time Zone", offsetSeconds = 18000), + "easter_island_summer_time" to UnittoTimeZone(id = "easter_island_summer_time", nameRes = "Easter Island Summer Time", offsetSeconds = -18000), + "easter_island_daylight_time" to UnittoTimeZone(id = "easter_island_daylight_time", nameRes = "Easter Island Daylight Time", offsetSeconds = -18000), + "easter_island_standard_time" to UnittoTimeZone(id = "easter_island_standard_time", nameRes = "Easter Island Standard Time", offsetSeconds = -21600), + "eastern_africa_time" to UnittoTimeZone(id = "eastern_africa_time", nameRes = "Eastern Africa Time", offsetSeconds = 10800), + "east_africa_time" to UnittoTimeZone(id = "east_africa_time", nameRes = "East Africa Time", offsetSeconds = 10800), + "ecuador_time" to UnittoTimeZone(id = "ecuador_time", nameRes = "Ecuador Time", offsetSeconds = -18000), + "eastern_daylight_time" to UnittoTimeZone(id = "eastern_daylight_time", nameRes = "Eastern Daylight Time", offsetSeconds = -14400), + "eastern_daylight_savings_time" to UnittoTimeZone(id = "eastern_daylight_savings_time", nameRes = "Eastern Daylight Savings Time", offsetSeconds = -14400), + "north_american_eastern_daylight_time" to UnittoTimeZone(id = "north_american_eastern_daylight_time", nameRes = "North American Eastern Daylight Time", offsetSeconds = -14400), + "heure_avanc_e_de_lest_french" to UnittoTimeZone(id = "heure_avanc_e_de_lest_french", nameRes = "Heure Avanc-e de l'Est (French)", offsetSeconds = -14400), + "tiempo_de_verano_del_este_spanish" to UnittoTimeZone(id = "tiempo_de_verano_del_este_spanish", nameRes = "Tiempo de verano del Este (Spanish)", offsetSeconds = -14400), + "eastern_european_summer_time" to UnittoTimeZone(id = "eastern_european_summer_time", nameRes = "Eastern European Summer Time", offsetSeconds = 10800), + "eastern_european_daylight_time" to UnittoTimeZone(id = "eastern_european_daylight_time", nameRes = "Eastern European Daylight Time", offsetSeconds = 10800), + "osteurop_ische_sommerzeit_german" to UnittoTimeZone(id = "osteurop_ische_sommerzeit_german", nameRes = "Osteurop-ische Sommerzeit (German)", offsetSeconds = 10800), + "eastern_european_time" to UnittoTimeZone(id = "eastern_european_time", nameRes = "Eastern European Time", offsetSeconds = 7200), + "osteurop_ische_zeit_german" to UnittoTimeZone(id = "osteurop_ische_zeit_german", nameRes = "Osteurop-ische Zeit (German)", offsetSeconds = 7200), + "eastern_greenland_summer_time" to UnittoTimeZone(id = "eastern_greenland_summer_time", nameRes = "Eastern Greenland Summer Time", offsetSeconds = 0), + "east_greenland_summer_time" to UnittoTimeZone(id = "east_greenland_summer_time", nameRes = "East Greenland Summer Time", offsetSeconds = 0), + "east_greenland_time" to UnittoTimeZone(id = "east_greenland_time", nameRes = "East Greenland Time", offsetSeconds = -3600), + "eastern_greenland_time" to UnittoTimeZone(id = "eastern_greenland_time", nameRes = "Eastern Greenland Time", offsetSeconds = -3600), + "eastern_standard_time" to UnittoTimeZone(id = "eastern_standard_time", nameRes = "Eastern Standard Time", offsetSeconds = -18000), + "eastern_time_" to UnittoTimeZone(id = "eastern_time_", nameRes = "Eastern Time ", offsetSeconds = -18000), + "north_american_eastern_standard_time" to UnittoTimeZone(id = "north_american_eastern_standard_time", nameRes = "North American Eastern Standard Time", offsetSeconds = -18000), + "tiempo_del_este_spanish" to UnittoTimeZone(id = "tiempo_del_este_spanish", nameRes = "Tiempo del Este (Spanish)", offsetSeconds = -18000), + "heure_normale_de_lest_french" to UnittoTimeZone(id = "heure_normale_de_lest_french", nameRes = "Heure Normale de l'Est (French)", offsetSeconds = -18000), + "foxtrot_time_zone" to UnittoTimeZone(id = "foxtrot_time_zone", nameRes = "Foxtrot Time Zone", offsetSeconds = 21600), + "further_eastern_european_time" to UnittoTimeZone(id = "further_eastern_european_time", nameRes = "Further-Eastern European Time", offsetSeconds = 10800), + "fiji_summer_time" to UnittoTimeZone(id = "fiji_summer_time", nameRes = "Fiji Summer Time", offsetSeconds = 46800), + "fiji_daylight_time" to UnittoTimeZone(id = "fiji_daylight_time", nameRes = "Fiji Daylight Time", offsetSeconds = 46800), + "fiji_time" to UnittoTimeZone(id = "fiji_time", nameRes = "Fiji Time", offsetSeconds = 43200), + "falkland_islands_summer_time" to UnittoTimeZone(id = "falkland_islands_summer_time", nameRes = "Falkland Islands Summer Time", offsetSeconds = -10800), + "falkland_island_daylight_time" to UnittoTimeZone(id = "falkland_island_daylight_time", nameRes = "Falkland Island Daylight Time", offsetSeconds = -10800), + "falkland_island_time" to UnittoTimeZone(id = "falkland_island_time", nameRes = "Falkland Island Time", offsetSeconds = -14400), + "falkland_island_standard_time" to UnittoTimeZone(id = "falkland_island_standard_time", nameRes = "Falkland Island Standard Time", offsetSeconds = -14400), + "fernando_de_noronha_time" to UnittoTimeZone(id = "fernando_de_noronha_time", nameRes = "Fernando de Noronha Time", offsetSeconds = -7200), + "golf_time_zone" to UnittoTimeZone(id = "golf_time_zone", nameRes = "Golf Time Zone", offsetSeconds = 25200), + "galapagos_time" to UnittoTimeZone(id = "galapagos_time", nameRes = "Galapagos Time", offsetSeconds = -21600), + "gambier_time" to UnittoTimeZone(id = "gambier_time", nameRes = "Gambier Time", offsetSeconds = -32400), + "gambier_islands_time" to UnittoTimeZone(id = "gambier_islands_time", nameRes = "Gambier Islands Time", offsetSeconds = -32400), + "georgia_standard_time" to UnittoTimeZone(id = "georgia_standard_time", nameRes = "Georgia Standard Time", offsetSeconds = 14400), + "french_guiana_time" to UnittoTimeZone(id = "french_guiana_time", nameRes = "French Guiana Time", offsetSeconds = -10800), + "gilbert_island_time" to UnittoTimeZone(id = "gilbert_island_time", nameRes = "Gilbert Island Time", offsetSeconds = 43200), + "greenwich_mean_time" to UnittoTimeZone(id = "greenwich_mean_time", nameRes = "Greenwich Mean Time", offsetSeconds = 0), + "coordinated_universal_time" to UnittoTimeZone(id = "coordinated_universal_time", nameRes = "Coordinated Universal Time", offsetSeconds = 0), + "greenwich_time" to UnittoTimeZone(id = "greenwich_time", nameRes = "Greenwich Time", offsetSeconds = 0), + "gulf_standard_time" to UnittoTimeZone(id = "gulf_standard_time", nameRes = "Gulf Standard Time", offsetSeconds = 14400), + "south_georgia_time" to UnittoTimeZone(id = "south_georgia_time", nameRes = "South Georgia Time", offsetSeconds = -7200), + "guyana_time" to UnittoTimeZone(id = "guyana_time", nameRes = "Guyana Time", offsetSeconds = -14400), + "hotel_time_zone" to UnittoTimeZone(id = "hotel_time_zone", nameRes = "Hotel Time Zone", offsetSeconds = 28800), + "hawaii_aleutian_daylight_time" to UnittoTimeZone(id = "hawaii_aleutian_daylight_time", nameRes = "Hawaii-Aleutian Daylight Time", offsetSeconds = -32400), + "hawaii_daylight_time" to UnittoTimeZone(id = "hawaii_daylight_time", nameRes = "Hawaii Daylight Time", offsetSeconds = -32400), + "hong_kong_time" to UnittoTimeZone(id = "hong_kong_time", nameRes = "Hong Kong Time", offsetSeconds = 28800), + "hovd_summer_time" to UnittoTimeZone(id = "hovd_summer_time", nameRes = "Hovd Summer Time", offsetSeconds = 28800), + "hovd_daylight_time" to UnittoTimeZone(id = "hovd_daylight_time", nameRes = "Hovd Daylight Time", offsetSeconds = 28800), + "hovd_daylight_saving_time" to UnittoTimeZone(id = "hovd_daylight_saving_time", nameRes = "Hovd Daylight Saving Time", offsetSeconds = 28800), + "hovd_time" to UnittoTimeZone(id = "hovd_time", nameRes = "Hovd Time", offsetSeconds = 25200), + "hawaii_standard_time" to UnittoTimeZone(id = "hawaii_standard_time", nameRes = "Hawaii Standard Time", offsetSeconds = -36000), + "hawaii_aleutian_standard_time" to UnittoTimeZone(id = "hawaii_aleutian_standard_time", nameRes = "Hawaii-Aleutian Standard Time", offsetSeconds = -36000), + "india_time_zone" to UnittoTimeZone(id = "india_time_zone", nameRes = "India Time Zone", offsetSeconds = 32400), + "indochina_time" to UnittoTimeZone(id = "indochina_time", nameRes = "Indochina Time", offsetSeconds = 25200), + "israel_daylight_time" to UnittoTimeZone(id = "israel_daylight_time", nameRes = "Israel Daylight Time", offsetSeconds = 10800), + "indian_chagos_time" to UnittoTimeZone(id = "indian_chagos_time", nameRes = "Indian Chagos Time", offsetSeconds = 21600), + "iran_daylight_time" to UnittoTimeZone(id = "iran_daylight_time", nameRes = "Iran Daylight Time", offsetSeconds = 16200), + "iran_summer_time" to UnittoTimeZone(id = "iran_summer_time", nameRes = "Iran Summer Time", offsetSeconds = 16200), + "iran_daylight_time" to UnittoTimeZone(id = "iran_daylight_time", nameRes = "Iran Daylight Time", offsetSeconds = 16200), + "irkutsk_summer_time" to UnittoTimeZone(id = "irkutsk_summer_time", nameRes = "Irkutsk Summer Time", offsetSeconds = 32400), + "irkutsk_time" to UnittoTimeZone(id = "irkutsk_time", nameRes = "Irkutsk Time", offsetSeconds = 28800), + "iran_standard_time" to UnittoTimeZone(id = "iran_standard_time", nameRes = "Iran Standard Time", offsetSeconds = 12600), + "iran_time" to UnittoTimeZone(id = "iran_time", nameRes = "Iran Time", offsetSeconds = 12600), + "india_standard_time" to UnittoTimeZone(id = "india_standard_time", nameRes = "India Standard Time", offsetSeconds = 19800), + "india_time" to UnittoTimeZone(id = "india_time", nameRes = "India Time", offsetSeconds = 19800), + "indian_standard_time" to UnittoTimeZone(id = "indian_standard_time", nameRes = "Indian Standard Time", offsetSeconds = 19800), + "irish_standard_time" to UnittoTimeZone(id = "irish_standard_time", nameRes = "Irish Standard Time", offsetSeconds = 3600), + "irish_summer_time" to UnittoTimeZone(id = "irish_summer_time", nameRes = "Irish Summer Time", offsetSeconds = 3600), + "israel_standard_time" to UnittoTimeZone(id = "israel_standard_time", nameRes = "Israel Standard Time", offsetSeconds = 7200), + "japan_standard_time" to UnittoTimeZone(id = "japan_standard_time", nameRes = "Japan Standard Time", offsetSeconds = 32400), + "kilo_time_zone" to UnittoTimeZone(id = "kilo_time_zone", nameRes = "Kilo Time Zone", offsetSeconds = 36000), + "kyrgyzstan_time" to UnittoTimeZone(id = "kyrgyzstan_time", nameRes = "Kyrgyzstan Time", offsetSeconds = 21600), + "kosrae_time" to UnittoTimeZone(id = "kosrae_time", nameRes = "Kosrae Time", offsetSeconds = 39600), + "krasnoyarsk_summer_time" to UnittoTimeZone(id = "krasnoyarsk_summer_time", nameRes = "Krasnoyarsk Summer Time", offsetSeconds = 28800), + "krasnoyarsk_time" to UnittoTimeZone(id = "krasnoyarsk_time", nameRes = "Krasnoyarsk Time", offsetSeconds = 25200), + "korea_standard_time" to UnittoTimeZone(id = "korea_standard_time", nameRes = "Korea Standard Time", offsetSeconds = 32400), + "korean_standard_time" to UnittoTimeZone(id = "korean_standard_time", nameRes = "Korean Standard Time", offsetSeconds = 32400), + "korea_time" to UnittoTimeZone(id = "korea_time", nameRes = "Korea Time", offsetSeconds = 32400), + "kuybyshev_time" to UnittoTimeZone(id = "kuybyshev_time", nameRes = "Kuybyshev Time", offsetSeconds = 14400), + "samara_summer_time" to UnittoTimeZone(id = "samara_summer_time", nameRes = "Samara Summer Time", offsetSeconds = 14400), + "lima_time_zone" to UnittoTimeZone(id = "lima_time_zone", nameRes = "Lima Time Zone", offsetSeconds = 39600), + "lord_howe_daylight_time" to UnittoTimeZone(id = "lord_howe_daylight_time", nameRes = "Lord Howe Daylight Time", offsetSeconds = 39600), + "lord_howe_standard_time" to UnittoTimeZone(id = "lord_howe_standard_time", nameRes = "Lord Howe Standard Time", offsetSeconds = 37800), + "line_islands_time" to UnittoTimeZone(id = "line_islands_time", nameRes = "Line Islands Time", offsetSeconds = 50400), + "mike_time_zone" to UnittoTimeZone(id = "mike_time_zone", nameRes = "Mike Time Zone", offsetSeconds = 43200), + "magadan_summer_time" to UnittoTimeZone(id = "magadan_summer_time", nameRes = "Magadan Summer Time", offsetSeconds = 43200), + "magadan_island_summer_time" to UnittoTimeZone(id = "magadan_island_summer_time", nameRes = "Magadan Island Summer Time", offsetSeconds = 43200), + "magadan_time" to UnittoTimeZone(id = "magadan_time", nameRes = "Magadan Time", offsetSeconds = 39600), + "magadan_island_time" to UnittoTimeZone(id = "magadan_island_time", nameRes = "Magadan Island Time", offsetSeconds = 39600), + "marquesas_time" to UnittoTimeZone(id = "marquesas_time", nameRes = "Marquesas Time", offsetSeconds = -34200), + "mawson_time" to UnittoTimeZone(id = "mawson_time", nameRes = "Mawson Time", offsetSeconds = 18000), + "mountain_daylight_time" to UnittoTimeZone(id = "mountain_daylight_time", nameRes = "Mountain Daylight Time", offsetSeconds = -21600), + "mountain_daylight_saving_time" to UnittoTimeZone(id = "mountain_daylight_saving_time", nameRes = "Mountain Daylight Saving Time", offsetSeconds = -21600), + "north_american_mountain_daylight_time" to UnittoTimeZone(id = "north_american_mountain_daylight_time", nameRes = "North American Mountain Daylight Time", offsetSeconds = -21600), + "heure_avanc_e_des_rocheuses_french" to UnittoTimeZone(id = "heure_avanc_e_des_rocheuses_french", nameRes = "Heure Avanc-e des Rocheuses (French)", offsetSeconds = -21600), + "marshall_islands_time" to UnittoTimeZone(id = "marshall_islands_time", nameRes = "Marshall Islands Time", offsetSeconds = 43200), + "myanmar_time" to UnittoTimeZone(id = "myanmar_time", nameRes = "Myanmar Time", offsetSeconds = 23400), + "moscow_daylight_time" to UnittoTimeZone(id = "moscow_daylight_time", nameRes = "Moscow Daylight Time", offsetSeconds = 14400), + "moscow_summer_time" to UnittoTimeZone(id = "moscow_summer_time", nameRes = "Moscow Summer Time", offsetSeconds = 14400), + "moscow_standard_time" to UnittoTimeZone(id = "moscow_standard_time", nameRes = "Moscow Standard Time", offsetSeconds = 10800), + "moscow_time" to UnittoTimeZone(id = "moscow_time", nameRes = "Moscow Time", offsetSeconds = 10800), + "mountain_standard_time" to UnittoTimeZone(id = "mountain_standard_time", nameRes = "Mountain Standard Time", offsetSeconds = -25200), + "mountain_time" to UnittoTimeZone(id = "mountain_time", nameRes = "Mountain Time", offsetSeconds = -25200), + "north_american_mountain_standard_time" to UnittoTimeZone(id = "north_american_mountain_standard_time", nameRes = "North American Mountain Standard Time", offsetSeconds = -25200), + "heure_normale_des_rocheuses_french" to UnittoTimeZone(id = "heure_normale_des_rocheuses_french", nameRes = "Heure Normale des Rocheuses (French)", offsetSeconds = -25200), + "mauritius_time" to UnittoTimeZone(id = "mauritius_time", nameRes = "Mauritius Time", offsetSeconds = 14400), + "maldives_time" to UnittoTimeZone(id = "maldives_time", nameRes = "Maldives Time", offsetSeconds = 18000), + "malaysia_time" to UnittoTimeZone(id = "malaysia_time", nameRes = "Malaysia Time", offsetSeconds = 28800), + "malaysian_standard_time" to UnittoTimeZone(id = "malaysian_standard_time", nameRes = "Malaysian Standard Time", offsetSeconds = 28800), + "november_time_zone" to UnittoTimeZone(id = "november_time_zone", nameRes = "November Time Zone", offsetSeconds = -3600), + "new_caledonia_time" to UnittoTimeZone(id = "new_caledonia_time", nameRes = "New Caledonia Time", offsetSeconds = 39600), + "newfoundland_daylight_time" to UnittoTimeZone(id = "newfoundland_daylight_time", nameRes = "Newfoundland Daylight Time", offsetSeconds = -9000), + "heure_avanc_e_de_terre_neuve_french" to UnittoTimeZone(id = "heure_avanc_e_de_terre_neuve_french", nameRes = "Heure Avanc-e de Terre-Neuve (French)", offsetSeconds = -9000), + "norfolk_daylight_time" to UnittoTimeZone(id = "norfolk_daylight_time", nameRes = "Norfolk Daylight Time", offsetSeconds = 43200), + "norfolk_island_daylight_time" to UnittoTimeZone(id = "norfolk_island_daylight_time", nameRes = "Norfolk Island Daylight Time", offsetSeconds = 43200), + "norfolk_time" to UnittoTimeZone(id = "norfolk_time", nameRes = "Norfolk Time", offsetSeconds = 39600), + "norfolk_island_time" to UnittoTimeZone(id = "norfolk_island_time", nameRes = "Norfolk Island Time", offsetSeconds = 39600), + "novosibirsk_summer_time" to UnittoTimeZone(id = "novosibirsk_summer_time", nameRes = "Novosibirsk Summer Time", offsetSeconds = 25200), + "omsk_summer_time" to UnittoTimeZone(id = "omsk_summer_time", nameRes = "Omsk Summer Time", offsetSeconds = 25200), + "novosibirsk_time" to UnittoTimeZone(id = "novosibirsk_time", nameRes = "Novosibirsk Time", offsetSeconds = 25200), + "omsk_standard_time" to UnittoTimeZone(id = "omsk_standard_time", nameRes = "Omsk Standard Time", offsetSeconds = 25200), + "nepal_time" to UnittoTimeZone(id = "nepal_time", nameRes = "Nepal Time", offsetSeconds = 20700), + "nauru_time" to UnittoTimeZone(id = "nauru_time", nameRes = "Nauru Time", offsetSeconds = 43200), + "newfoundland_standard_time" to UnittoTimeZone(id = "newfoundland_standard_time", nameRes = "Newfoundland Standard Time", offsetSeconds = -12600), + "heure_normale_de_terre_neuve_french" to UnittoTimeZone(id = "heure_normale_de_terre_neuve_french", nameRes = "Heure Normale de Terre-Neuve (French)", offsetSeconds = -12600), + "niue_time" to UnittoTimeZone(id = "niue_time", nameRes = "Niue Time", offsetSeconds = -39600), + "new_zealand_daylight_time" to UnittoTimeZone(id = "new_zealand_daylight_time", nameRes = "New Zealand Daylight Time", offsetSeconds = 46800), + "new_zealand_standard_time" to UnittoTimeZone(id = "new_zealand_standard_time", nameRes = "New Zealand Standard Time", offsetSeconds = 43200), + "oscar_time_zone" to UnittoTimeZone(id = "oscar_time_zone", nameRes = "Oscar Time Zone", offsetSeconds = -7200), + "omsk_summer_time" to UnittoTimeZone(id = "omsk_summer_time", nameRes = "Omsk Summer Time", offsetSeconds = 25200), + "novosibirsk_summer_time" to UnittoTimeZone(id = "novosibirsk_summer_time", nameRes = "Novosibirsk Summer Time", offsetSeconds = 25200), + "omsk_standard_time" to UnittoTimeZone(id = "omsk_standard_time", nameRes = "Omsk Standard Time", offsetSeconds = 21600), + "omsk_time" to UnittoTimeZone(id = "omsk_time", nameRes = "Omsk Time", offsetSeconds = 21600), + "novosibirsk_time" to UnittoTimeZone(id = "novosibirsk_time", nameRes = "Novosibirsk Time", offsetSeconds = 21600), + "oral_time" to UnittoTimeZone(id = "oral_time", nameRes = "Oral Time", offsetSeconds = 18000), + "papa_time_zone" to UnittoTimeZone(id = "papa_time_zone", nameRes = "Papa Time Zone", offsetSeconds = -10800), + "pacific_daylight_time" to UnittoTimeZone(id = "pacific_daylight_time", nameRes = "Pacific Daylight Time", offsetSeconds = -25200), + "pacific_daylight_saving_time" to UnittoTimeZone(id = "pacific_daylight_saving_time", nameRes = "Pacific Daylight Saving Time", offsetSeconds = -25200), + "north_american_pacific_daylight_time" to UnittoTimeZone(id = "north_american_pacific_daylight_time", nameRes = "North American Pacific Daylight Time", offsetSeconds = -25200), + "heure_avanc_e_du_pacifique_french" to UnittoTimeZone(id = "heure_avanc_e_du_pacifique_french", nameRes = "Heure Avanc-e du Pacifique (French)", offsetSeconds = -25200), + "peru_time" to UnittoTimeZone(id = "peru_time", nameRes = "Peru Time", offsetSeconds = -18000), + "kamchatka_summer_time" to UnittoTimeZone(id = "kamchatka_summer_time", nameRes = "Kamchatka Summer Time", offsetSeconds = 43200), + "kamchatka_time" to UnittoTimeZone(id = "kamchatka_time", nameRes = "Kamchatka Time", offsetSeconds = 43200), + "petropavlovsk_kamchatski_time" to UnittoTimeZone(id = "petropavlovsk_kamchatski_time", nameRes = "Petropavlovsk-Kamchatski Time", offsetSeconds = 43200), + "papua_new_guinea_time" to UnittoTimeZone(id = "papua_new_guinea_time", nameRes = "Papua New Guinea Time", offsetSeconds = 36000), + "phoenix_island_time" to UnittoTimeZone(id = "phoenix_island_time", nameRes = "Phoenix Island Time", offsetSeconds = 46800), + "philippine_time" to UnittoTimeZone(id = "philippine_time", nameRes = "Philippine Time", offsetSeconds = 28800), + "philippine_standard_time" to UnittoTimeZone(id = "philippine_standard_time", nameRes = "Philippine Standard Time", offsetSeconds = 28800), + "pakistan_standard_time" to UnittoTimeZone(id = "pakistan_standard_time", nameRes = "Pakistan Standard Time", offsetSeconds = 18000), + "pakistan_time" to UnittoTimeZone(id = "pakistan_time", nameRes = "Pakistan Time", offsetSeconds = 18000), + "pierre_&_miquelon_daylight_time" to UnittoTimeZone(id = "pierre_&_miquelon_daylight_time", nameRes = "Pierre & Miquelon Daylight Time", offsetSeconds = -7200), + "pierre_&_miquelon_standard_time" to UnittoTimeZone(id = "pierre_&_miquelon_standard_time", nameRes = "Pierre & Miquelon Standard Time", offsetSeconds = -10800), + "pohnpei_standard_time" to UnittoTimeZone(id = "pohnpei_standard_time", nameRes = "Pohnpei Standard Time", offsetSeconds = 39600), + "pacific_standard_time" to UnittoTimeZone(id = "pacific_standard_time", nameRes = "Pacific Standard Time", offsetSeconds = -28800), + "pacific_time" to UnittoTimeZone(id = "pacific_time", nameRes = "Pacific Time", offsetSeconds = -28800), + "north_american_pacific_standard_time" to UnittoTimeZone(id = "north_american_pacific_standard_time", nameRes = "North American Pacific Standard Time", offsetSeconds = -28800), + "tiempo_del_pac_fico_spanish" to UnittoTimeZone(id = "tiempo_del_pac_fico_spanish", nameRes = "Tiempo del Pac-fico (Spanish)", offsetSeconds = -28800), + "heure_normale_du_pacifique_french" to UnittoTimeZone(id = "heure_normale_du_pacifique_french", nameRes = "Heure Normale du Pacifique (French)", offsetSeconds = -28800), + "pitcairn_standard_time" to UnittoTimeZone(id = "pitcairn_standard_time", nameRes = "Pitcairn Standard Time", offsetSeconds = -28800), + "palau_time" to UnittoTimeZone(id = "palau_time", nameRes = "Palau Time", offsetSeconds = 32400), + "paraguay_summer_time" to UnittoTimeZone(id = "paraguay_summer_time", nameRes = "Paraguay Summer Time", offsetSeconds = -10800), + "paraguay_time" to UnittoTimeZone(id = "paraguay_time", nameRes = "Paraguay Time", offsetSeconds = -14400), + "pyongyang_time" to UnittoTimeZone(id = "pyongyang_time", nameRes = "Pyongyang Time", offsetSeconds = 30600), + "pyongyang_standard_time" to UnittoTimeZone(id = "pyongyang_standard_time", nameRes = "Pyongyang Standard Time", offsetSeconds = 30600), + "quebec_time_zone" to UnittoTimeZone(id = "quebec_time_zone", nameRes = "Quebec Time Zone", offsetSeconds = -14400), + "qyzylorda_time" to UnittoTimeZone(id = "qyzylorda_time", nameRes = "Qyzylorda Time", offsetSeconds = 21600), + "romeo_time_zone" to UnittoTimeZone(id = "romeo_time_zone", nameRes = "Romeo Time Zone", offsetSeconds = -18000), + "reunion_time" to UnittoTimeZone(id = "reunion_time", nameRes = "Reunion Time", offsetSeconds = 14400), + "rothera_time" to UnittoTimeZone(id = "rothera_time", nameRes = "Rothera Time", offsetSeconds = -10800), + "sierra_time_zone" to UnittoTimeZone(id = "sierra_time_zone", nameRes = "Sierra Time Zone", offsetSeconds = -21600), + "sakhalin_time" to UnittoTimeZone(id = "sakhalin_time", nameRes = "Sakhalin Time", offsetSeconds = 39600), + "samara_time" to UnittoTimeZone(id = "samara_time", nameRes = "Samara Time", offsetSeconds = 14400), + "samara_standard_time" to UnittoTimeZone(id = "samara_standard_time", nameRes = "Samara Standard Time", offsetSeconds = 14400), + "south_africa_standard_time" to UnittoTimeZone(id = "south_africa_standard_time", nameRes = "South Africa Standard Time", offsetSeconds = 7200), + "south_african_standard_time" to UnittoTimeZone(id = "south_african_standard_time", nameRes = "South African Standard Time", offsetSeconds = 7200), + "solomon_islands_time" to UnittoTimeZone(id = "solomon_islands_time", nameRes = "Solomon Islands Time", offsetSeconds = 39600), + "solomon_island_time" to UnittoTimeZone(id = "solomon_island_time", nameRes = "Solomon Island Time", offsetSeconds = 39600), + "seychelles_time" to UnittoTimeZone(id = "seychelles_time", nameRes = "Seychelles Time", offsetSeconds = 14400), + "singapore_time" to UnittoTimeZone(id = "singapore_time", nameRes = "Singapore Time", offsetSeconds = 28800), + "singapore_standard_time" to UnittoTimeZone(id = "singapore_standard_time", nameRes = "Singapore Standard Time", offsetSeconds = 28800), + "srednekolymsk_time" to UnittoTimeZone(id = "srednekolymsk_time", nameRes = "Srednekolymsk Time", offsetSeconds = 39600), + "suriname_time" to UnittoTimeZone(id = "suriname_time", nameRes = "Suriname Time", offsetSeconds = -10800), + "samoa_standard_time" to UnittoTimeZone(id = "samoa_standard_time", nameRes = "Samoa Standard Time", offsetSeconds = -39600), + "syowa_time" to UnittoTimeZone(id = "syowa_time", nameRes = "Syowa Time", offsetSeconds = 10800), + "tango_time_zone" to UnittoTimeZone(id = "tango_time_zone", nameRes = "Tango Time Zone", offsetSeconds = -25200), + "tahiti_time" to UnittoTimeZone(id = "tahiti_time", nameRes = "Tahiti Time", offsetSeconds = -36000), + "french_southern_and_antarctic_time" to UnittoTimeZone(id = "french_southern_and_antarctic_time", nameRes = "French Southern and Antarctic Time", offsetSeconds = 18000), + "kerguelen_islands_time" to UnittoTimeZone(id = "kerguelen_islands_time", nameRes = "Kerguelen (Islands) Time", offsetSeconds = 18000), + "tajikistan_time" to UnittoTimeZone(id = "tajikistan_time", nameRes = "Tajikistan Time", offsetSeconds = 18000), + "tokelau_time" to UnittoTimeZone(id = "tokelau_time", nameRes = "Tokelau Time", offsetSeconds = 46800), + "east_timor_time" to UnittoTimeZone(id = "east_timor_time", nameRes = "East Timor Time", offsetSeconds = 32400), + "turkmenistan_time" to UnittoTimeZone(id = "turkmenistan_time", nameRes = "Turkmenistan Time", offsetSeconds = 18000), + "tonga_summer_time" to UnittoTimeZone(id = "tonga_summer_time", nameRes = "Tonga Summer Time", offsetSeconds = 50400), + "tonga_time" to UnittoTimeZone(id = "tonga_time", nameRes = "Tonga Time", offsetSeconds = 46800), + "turkey_time" to UnittoTimeZone(id = "turkey_time", nameRes = "Turkey Time", offsetSeconds = 10800), + "tuvalu_time" to UnittoTimeZone(id = "tuvalu_time", nameRes = "Tuvalu Time", offsetSeconds = 43200), + "uniform_time_zone" to UnittoTimeZone(id = "uniform_time_zone", nameRes = "Uniform Time Zone", offsetSeconds = -28800), + "ulaanbaatar_summer_time" to UnittoTimeZone(id = "ulaanbaatar_summer_time", nameRes = "Ulaanbaatar Summer Time", offsetSeconds = 32400), + "ulan_bator_summer_time" to UnittoTimeZone(id = "ulan_bator_summer_time", nameRes = "Ulan Bator Summer Time", offsetSeconds = 32400), + "ulaanbaatar_time" to UnittoTimeZone(id = "ulaanbaatar_time", nameRes = "Ulaanbaatar Time", offsetSeconds = 28800), + "ulan_bator_time" to UnittoTimeZone(id = "ulan_bator_time", nameRes = "Ulan Bator Time", offsetSeconds = 28800), + "coordinated_universal_time" to UnittoTimeZone(id = "coordinated_universal_time", nameRes = "Coordinated Universal Time", offsetSeconds = 0), + "uruguay_summer_time" to UnittoTimeZone(id = "uruguay_summer_time", nameRes = "Uruguay Summer Time", offsetSeconds = -7200), + "uruguay_time" to UnittoTimeZone(id = "uruguay_time", nameRes = "Uruguay Time", offsetSeconds = -10800), + "uzbekistan_time" to UnittoTimeZone(id = "uzbekistan_time", nameRes = "Uzbekistan Time", offsetSeconds = 18000), + "victor_time_zone" to UnittoTimeZone(id = "victor_time_zone", nameRes = "Victor Time Zone", offsetSeconds = -32400), + "venezuelan_standard_time" to UnittoTimeZone(id = "venezuelan_standard_time", nameRes = "Venezuelan Standard Time", offsetSeconds = -14400), + "hora_legal_de_venezuela_spanish" to UnittoTimeZone(id = "hora_legal_de_venezuela_spanish", nameRes = "Hora Legal de Venezuela (Spanish)", offsetSeconds = -14400), + "vladivostok_summer_time" to UnittoTimeZone(id = "vladivostok_summer_time", nameRes = "Vladivostok Summer Time", offsetSeconds = 39600), + "vladivostok_time" to UnittoTimeZone(id = "vladivostok_time", nameRes = "Vladivostok Time", offsetSeconds = 36000), + "vostok_time" to UnittoTimeZone(id = "vostok_time", nameRes = "Vostok Time", offsetSeconds = 21600), + "vanuatu_time" to UnittoTimeZone(id = "vanuatu_time", nameRes = "Vanuatu Time", offsetSeconds = 39600), + "efate_time" to UnittoTimeZone(id = "efate_time", nameRes = "Efate Time", offsetSeconds = 39600), + "whiskey_time_zone" to UnittoTimeZone(id = "whiskey_time_zone", nameRes = "Whiskey Time Zone", offsetSeconds = -36000), + "wake_time" to UnittoTimeZone(id = "wake_time", nameRes = "Wake Time", offsetSeconds = 43200), + "western_argentine_summer_time" to UnittoTimeZone(id = "western_argentine_summer_time", nameRes = "Western Argentine Summer Time", offsetSeconds = -10800), + "west_africa_summer_time" to UnittoTimeZone(id = "west_africa_summer_time", nameRes = "West Africa Summer Time", offsetSeconds = 7200), + "west_africa_time" to UnittoTimeZone(id = "west_africa_time", nameRes = "West Africa Time", offsetSeconds = 3600), + "western_european_summer_time" to UnittoTimeZone(id = "western_european_summer_time", nameRes = "Western European Summer Time", offsetSeconds = 3600), + "western_european_daylight_time" to UnittoTimeZone(id = "western_european_daylight_time", nameRes = "Western European Daylight Time", offsetSeconds = 3600), + "westeurop_ische_sommerzeit_german" to UnittoTimeZone(id = "westeurop_ische_sommerzeit_german", nameRes = "Westeurop-ische Sommerzeit (German)", offsetSeconds = 3600), + "western_european_time" to UnittoTimeZone(id = "western_european_time", nameRes = "Western European Time", offsetSeconds = 0), + "greenwich_mean_time" to UnittoTimeZone(id = "greenwich_mean_time", nameRes = "Greenwich Mean Time", offsetSeconds = 0), + "westeurop_ische_zeit_german" to UnittoTimeZone(id = "westeurop_ische_zeit_german", nameRes = "Westeurop-ische Zeit (German)", offsetSeconds = 0), + "wallis_and_futuna_time" to UnittoTimeZone(id = "wallis_and_futuna_time", nameRes = "Wallis and Futuna Time", offsetSeconds = 43200), + "western_greenland_summer_time" to UnittoTimeZone(id = "western_greenland_summer_time", nameRes = "Western Greenland Summer Time", offsetSeconds = -7200), + "west_greenland_summer_time" to UnittoTimeZone(id = "west_greenland_summer_time", nameRes = "West Greenland Summer Time", offsetSeconds = -7200), + "west_greenland_time" to UnittoTimeZone(id = "west_greenland_time", nameRes = "West Greenland Time", offsetSeconds = -10800), + "western_greenland_time" to UnittoTimeZone(id = "western_greenland_time", nameRes = "Western Greenland Time", offsetSeconds = -10800), + "western_indonesian_time" to UnittoTimeZone(id = "western_indonesian_time", nameRes = "Western Indonesian Time", offsetSeconds = 25200), + "waktu_indonesia_barat" to UnittoTimeZone(id = "waktu_indonesia_barat", nameRes = "Waktu Indonesia Barat", offsetSeconds = 25200), + "eastern_indonesian_time" to UnittoTimeZone(id = "eastern_indonesian_time", nameRes = "Eastern Indonesian Time", offsetSeconds = 32400), + "waktu_indonesia_timur" to UnittoTimeZone(id = "waktu_indonesia_timur", nameRes = "Waktu Indonesia Timur", offsetSeconds = 32400), + "central_indonesian_time" to UnittoTimeZone(id = "central_indonesian_time", nameRes = "Central Indonesian Time", offsetSeconds = 28800), + "waktu_indonesia_tengah" to UnittoTimeZone(id = "waktu_indonesia_tengah", nameRes = "Waktu Indonesia Tengah", offsetSeconds = 28800), + "west_samoa_time" to UnittoTimeZone(id = "west_samoa_time", nameRes = "West Samoa Time", offsetSeconds = 46800), + "samoa_time" to UnittoTimeZone(id = "samoa_time", nameRes = "Samoa Time", offsetSeconds = 46800), + "western_sahara_summer_time" to UnittoTimeZone(id = "western_sahara_summer_time", nameRes = "Western Sahara Summer Time", offsetSeconds = 3600), + "western_sahara_standard_time" to UnittoTimeZone(id = "western_sahara_standard_time", nameRes = "Western Sahara Standard Time", offsetSeconds = 0), + "x_ray_time_zone" to UnittoTimeZone(id = "x_ray_time_zone", nameRes = "X-ray Time Zone", offsetSeconds = -39600), + "yankee_time_zone" to UnittoTimeZone(id = "yankee_time_zone", nameRes = "Yankee Time Zone", offsetSeconds = -43200), + "yakutsk_summer_time" to UnittoTimeZone(id = "yakutsk_summer_time", nameRes = "Yakutsk Summer Time", offsetSeconds = 36000), + "yakutsk_time" to UnittoTimeZone(id = "yakutsk_time", nameRes = "Yakutsk Time", offsetSeconds = 32400), + "yap_time" to UnittoTimeZone(id = "yap_time", nameRes = "Yap Time", offsetSeconds = 36000), + "yekaterinburg_summer_time" to UnittoTimeZone(id = "yekaterinburg_summer_time", nameRes = "Yekaterinburg Summer Time", offsetSeconds = 21600), + "yekaterinburg_time" to UnittoTimeZone(id = "yekaterinburg_time", nameRes = "Yekaterinburg Time", offsetSeconds = 18000), + "zulu_time_zone" to UnittoTimeZone(id = "zulu_time_zone", nameRes = "Zulu Time Zone", offsetSeconds = 0) + ) + + val favoriteTimeZones: MutableStateFlow> = MutableStateFlow(emptyList()) + +// UNCOMMENT FOR RELEASE +// val favoriteTimeZones: Flow> = dao +// .getAll() +// .map { list -> +// val favorites = mutableListOf() +// list.forEach { entity -> +// val foundTimeZone = allTimeZones[entity.id] ?: return@forEach +// val mapped = foundTimeZone.copy( +// position = entity.position +// ) +// favorites.add(mapped) +// } +// +// favorites +// } + + suspend fun swapTimeZones(from: String, to: String) = withContext(Dispatchers.IO) { +// UNCOMMENT FOR RELEASE +// dao.swap(from, to) + + favoriteTimeZones.update { + val fromIndex = it.indexOfFirst { it.id == from } + val toIndex = it.indexOfFirst { it.id == to } + + it + .toMutableList() + .apply { + add(toIndex, removeAt(fromIndex)) + } + } + + return@withContext + } + + suspend fun delete(timeZone: UnittoTimeZone) = withContext(Dispatchers.IO) { +// UNCOMMENT FOR RELEASE +// // Only PrimaryKey is needed +// dao.remove(TimeZoneEntity(id = timeZone.id, position = 0)) + + favoriteTimeZones.update { it.minus(timeZone) } + } + + suspend fun filterAllTimeZones(searchQuery: String): List = + withContext(Dispatchers.IO) { + val query = searchQuery.trim().lowercase() + val threshold: Int = query.length / 2 + val timeZonesWithDist = mutableListOf>() + + allTimeZones.values.forEach { timeZone -> + val timeZoneName = timeZone.nameRes + + if (timeZone.code.lowercase() == query) { + timeZonesWithDist.add(timeZone to 1) + return@forEach + } + + when { + // not zero, so that lev can have that + timeZoneName.startsWith(query) -> { + timeZonesWithDist.add(timeZone to 1) + return@forEach + } + + timeZoneName.contains(query) -> { + timeZonesWithDist.add(timeZone to 2) + return@forEach + } + } + + val levDist = timeZoneName + .substring(0, minOf(query.length, timeZoneName.length)) + .lev(query) + + if (levDist < threshold) { + timeZonesWithDist.add(timeZone to levDist) + } + } + + return@withContext timeZonesWithDist.sortedBy { it.second }.map { it.first } + } + + suspend fun addToFavorites(timeZone: UnittoTimeZone) { +// UNCOMMENT FOR RELEASE +// dao.insert( +// TimeZoneEntity( +// id = timeZone.id, +// position = System.currentTimeMillis().toInt() +// ) +// ) + favoriteTimeZones.update { it.plus(timeZone) } + } +} diff --git a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt index cee3d379..6f2b797e 100644 --- a/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt +++ b/data/userprefs/src/main/java/com/sadellie/unitto/data/userprefs/UserPreferences.kt @@ -92,6 +92,7 @@ data class UIPreferences( val customColor: Color = Color.Unspecified, val monetMode: MonetMode = MonetMode.TONAL_SPOT, val startingScreen: String = TopLevelDestinations.Converter.route, + val enableToolsExperiment: Boolean = false, ) data class MainPreferences( @@ -153,6 +154,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS val monetMode: MonetMode = preferences[PrefsKeys.MONET_MODE]?.let { MonetMode.valueOf(it) } ?: MonetMode.TONAL_SPOT val startingScreen: String = preferences[PrefsKeys.STARTING_SCREEN] ?: TopLevelDestinations.Converter.route + val enableToolsExperiment: Boolean = preferences[PrefsKeys.ENABLE_TOOLS_EXPERIMENT] ?: false UIPreferences( themingMode = themingMode, @@ -160,7 +162,8 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS enableAmoledTheme = enableAmoledTheme, customColor = customColor, monetMode = monetMode, - startingScreen = startingScreen + startingScreen = startingScreen, + enableToolsExperiment = enableToolsExperiment ) } @@ -228,7 +231,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS latestRightSideUnit = main.latestRightSideUnit, shownUnitGroups = main.shownUnitGroups, enableVibrations = main.enableVibrations, - enableToolsExperiment = false, + enableToolsExperiment = ui.enableToolsExperiment, startingScreen = ui.startingScreen, radianMode = main.radianMode, unitConverterFavoritesOnly = main.unitConverterFavoritesOnly, diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceScreen.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceScreen.kt index f4cd5377..be811fd9 100644 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceScreen.kt +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceScreen.kt @@ -18,7 +18,6 @@ package com.sadellie.unitto.feature.datedifference -import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically @@ -34,7 +33,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -62,8 +60,10 @@ internal fun DateDifferenceRoute( navigateToMenu = navigateToMenu, navigateToSettings = navigateToSettings, uiState = uiState.value, - updateStart = viewModel::setStartTime, - updateEnd = viewModel::setEndTime + setStartTime = viewModel::setStartTime, + setEndTime = viewModel::setEndTime, + setStartDate = viewModel::setStartDate, + setEndDate = viewModel::setEndDate, ) } @@ -72,12 +72,13 @@ internal fun DateDifferenceRoute( internal fun DateDifferenceScreen( navigateToMenu: () -> Unit, navigateToSettings: () -> Unit, - updateStart: (LocalDateTime) -> Unit, - updateEnd: (LocalDateTime) -> Unit, + setStartTime: (Int, Int) -> Unit, + setEndTime: (Int, Int) -> Unit, + setStartDate: (LocalDateTime) -> Unit, + setEndDate: (LocalDateTime) -> Unit, uiState: UIState, ) { var dialogState by remember { mutableStateOf(DialogState.NONE) } - val isVertical = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT UnittoScreenWithTopBar( title = { Text(stringResource(R.string.date_difference)) }, @@ -138,26 +139,26 @@ internal fun DateDifferenceScreen( when (dialogState) { DialogState.FROM -> { TimePickerDialog( - localDateTime = uiState.start, + hour = uiState.start.hour, + minute = uiState.start.minute, onDismiss = ::resetDialog, - onConfirm = { - updateStart(it) + onConfirm = { hour, minute -> + setStartTime(hour, minute) dialogState = DialogState.FROM_DATE }, confirmLabel = stringResource(R.string.next_label), - vertical = isVertical, ) } DialogState.FROM_TIME -> { TimePickerDialog( - localDateTime = uiState.start, + hour = uiState.start.hour, + minute = uiState.start.minute, onDismiss = ::resetDialog, - onConfirm = { - updateStart(it) + onConfirm = { hour, minute -> + setStartTime(hour, minute) resetDialog() }, - vertical = isVertical, ) } @@ -166,7 +167,7 @@ internal fun DateDifferenceScreen( localDateTime = uiState.start, onDismiss = ::resetDialog, onConfirm = { - updateStart(it) + setStartDate(it) resetDialog() } ) @@ -174,26 +175,26 @@ internal fun DateDifferenceScreen( DialogState.TO -> { TimePickerDialog( - localDateTime = uiState.end, + hour = uiState.end.hour, + minute = uiState.end.minute, onDismiss = ::resetDialog, - onConfirm = { - updateEnd(it) + onConfirm = { hour, minute -> + setEndTime(hour, minute) dialogState = DialogState.TO_DATE }, confirmLabel = stringResource(R.string.next_label), - vertical = isVertical, ) } DialogState.TO_TIME -> { TimePickerDialog( - localDateTime = uiState.end, + hour = uiState.end.hour, + minute = uiState.end.minute, onDismiss = ::resetDialog, - onConfirm = { - updateEnd(it) + onConfirm = { hour, minute -> + setEndTime(hour, minute) resetDialog() }, - vertical = isVertical, ) } @@ -202,7 +203,7 @@ internal fun DateDifferenceScreen( localDateTime = uiState.end, onDismiss = ::resetDialog, onConfirm = { - updateEnd(it) + setEndDate(it) resetDialog() } ) @@ -222,10 +223,12 @@ private fun DateDifferenceScreenPreview() { DateDifferenceScreen( navigateToMenu = {}, navigateToSettings = {}, - updateStart = {}, - updateEnd = {}, + setStartTime = { _, _ -> }, + setEndTime = { _, _ -> }, uiState = UIState( result = DateDifference.Default(4, 1, 2, 3, 4) - ) + ), + setStartDate = {}, + setEndDate = {}, ) } diff --git a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceViewModel.kt b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceViewModel.kt index 1d151afe..f80a4e4b 100644 --- a/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceViewModel.kt +++ b/feature/datedifference/src/main/java/com/sadellie/unitto/feature/datedifference/DateDifferenceViewModel.kt @@ -46,9 +46,13 @@ internal class DateDifferenceViewModel @Inject constructor() : ViewModel() { viewModelScope, SharingStarted.WhileSubscribed(5000L), UIState() ) - fun setStartTime(newTime: LocalDateTime) = _start.update { newTime } + fun setStartTime(hour: Int, minute: Int) = _start.update { it.withHour(hour).withMinute(minute) } - fun setEndTime(newTime: LocalDateTime) = _end.update { newTime } + fun setEndTime(hour: Int, minute: Int) = _end.update { it.withHour(hour).withMinute(minute) } + + fun setStartDate(dateTime: LocalDateTime) = _start.update { dateTime } + + fun setEndDate(dateTime: LocalDateTime) = _end.update { dateTime } init { viewModelScope.launch(Dispatchers.Default) { diff --git a/feature/timezone/build.gradle.kts b/feature/timezone/build.gradle.kts index d8eeb87d..b0bcd246 100644 --- a/feature/timezone/build.gradle.kts +++ b/feature/timezone/build.gradle.kts @@ -30,9 +30,11 @@ android { dependencies { testImplementation(libs.junit) implementation(libs.com.github.sadellie.themmo) + implementation(libs.org.burnoutcrew.composereorderable) implementation(project(mapOf("path" to ":data:common"))) implementation(project(mapOf("path" to ":data:userprefs"))) implementation(project(mapOf("path" to ":data:database"))) + implementation(project(mapOf("path" to ":data:timezone"))) implementation(project(mapOf("path" to ":data:model"))) } diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneScreen.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneScreen.kt new file mode 100644 index 00000000..a9f08764 --- /dev/null +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneScreen.kt @@ -0,0 +1,156 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.timezone + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.sadellie.unitto.core.base.R +import com.sadellie.unitto.data.model.UnittoTimeZone +import com.sadellie.unitto.timezone.components.SelectableTimeZone +import com.sadellie.unitto.timezone.components.UnittoSearchBar +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import java.time.ZonedDateTime + +@Composable +internal fun AddTimeZoneRoute( + viewModel: AddTimeZoneViewModel = hiltViewModel(), + navigateUp: () -> Unit, + userTime: ZonedDateTime? = null +) { + val uiState = viewModel.addTimeZoneUIState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + if (userTime == null) { + while (isActive) { + viewModel.setTime(ZonedDateTime.now()) + delay(1000) + } + } else { + viewModel.setTime(userTime) + } + } + + AddTimeZoneScreen( + uiState = uiState.value, + navigateUp = navigateUp, + onQueryChange = viewModel::onQueryChange, + addToFavorites = viewModel::addToFavorites, + ) +} + +@Composable +fun AddTimeZoneScreen( + uiState: AddTimeZoneUIState, + navigateUp: () -> Unit, + onQueryChange: (String) -> Unit, + addToFavorites: (UnittoTimeZone) -> Unit, +) { + val listState = rememberLazyListState() + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior( + rememberTopAppBarState() + ) + val elevatedColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp) + val needToTint by remember { + derivedStateOf { scrollBehavior.state.overlappedFraction > 0.01f } + } + + val searchBarBackground = animateColorAsState( + targetValue = if (needToTint) elevatedColor else MaterialTheme.colorScheme.surface, + animationSpec = tween(durationMillis = 500, easing = LinearOutSlowInEasing), + label = "Search bar background" + ) + + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + UnittoSearchBar( + modifier = Modifier, + query = uiState.query, + onQueryChange = onQueryChange, + navigateUp = navigateUp, + title = stringResource(R.string.add_time_zone_title), + placeholder = stringResource(R.string.search_text_field_placeholder), + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = searchBarBackground.value + ) + ) + }, + ) { paddingValues -> + LazyColumn( + modifier = Modifier.padding(paddingValues), + state = listState + ) { + items(uiState.list) { + SelectableTimeZone( + timeZone = it, + modifier = Modifier + .clickable { addToFavorites(it); navigateUp() } + .fillMaxWidth(), + currentTime = uiState.userTime + ) + Divider() + } + } + } +} + +@Preview +@Composable +fun PreviewAddTimeZoneScreen() { + AddTimeZoneScreen( + navigateUp = {}, + uiState = AddTimeZoneUIState( + list = List(50) { + UnittoTimeZone( + id = "timezone $it", + nameRes = "Time zone $it", + ) + } + ), + onQueryChange = {}, + addToFavorites = {}, + ) +} diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneUIState.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneUIState.kt new file mode 100644 index 00000000..affe3222 --- /dev/null +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneUIState.kt @@ -0,0 +1,28 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.timezone + +import com.sadellie.unitto.data.model.UnittoTimeZone +import java.time.ZonedDateTime + +data class AddTimeZoneUIState( + val query: String = "", + val list: List = emptyList(), + val userTime: ZonedDateTime? = null, +) diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneViewModel.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneViewModel.kt new file mode 100644 index 00000000..c37032a2 --- /dev/null +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneViewModel.kt @@ -0,0 +1,82 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.timezone + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sadellie.unitto.data.model.UnittoTimeZone +import com.sadellie.unitto.data.timezone.TimeZonesRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import java.time.ZonedDateTime +import javax.inject.Inject + +@HiltViewModel +class AddTimeZoneViewModel @Inject constructor( + private val timezonesRepository: TimeZonesRepository, +) : ViewModel() { + + private val _userTime = MutableStateFlow(ZonedDateTime.now()) + private val _query = MutableStateFlow("") + + private val _filteredTimeZones = MutableStateFlow(emptyList()) + val addTimeZoneUIState = combine( + _query, + _filteredTimeZones, + _userTime, + ) { query, filteredTimeZone, userTime -> + return@combine AddTimeZoneUIState( + query = query, + list = filteredTimeZone, + userTime = userTime, + ) + }.stateIn( + viewModelScope, SharingStarted.WhileSubscribed(5000), AddTimeZoneUIState() + ) + + fun onQueryChange(query: String) { + _query.update { query } + filterTimeZones(query) + } + + private fun filterTimeZones(query: String = "") = viewModelScope.launch { + _filteredTimeZones.update { + timezonesRepository.filterAllTimeZones(query) + } + } + + fun addToFavorites(timeZone: UnittoTimeZone) = viewModelScope.launch(Dispatchers.IO) { + timezonesRepository.addToFavorites(timeZone) + } + + fun setTime(time: ZonedDateTime) = viewModelScope.launch(Dispatchers.Default) { + _userTime.update { time } + } + + init { + // TODO Maybe only when actually needed? + filterTimeZones() + } +} diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneScreen.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneScreen.kt index 89333315..aba669f6 100644 --- a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneScreen.kt +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneScreen.kt @@ -18,10 +18,364 @@ package com.sadellie.unitto.timezone +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.animateColor +import androidx.compose.animation.core.animateDp +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.AnchoredDraggableState +import androidx.compose.foundation.gestures.DraggableAnchors +import androidx.compose.foundation.gestures.animateTo +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.outlined.History +import androidx.compose.material3.FabPosition +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeFloatingActionButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +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.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.sadellie.unitto.core.base.R +import com.sadellie.unitto.core.ui.common.MenuButton +import com.sadellie.unitto.core.ui.common.SettingsButton +import com.sadellie.unitto.core.ui.common.TimePickerDialog +import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar +import com.sadellie.unitto.core.ui.common.squashable +import com.sadellie.unitto.core.ui.datetime.formatDayMonthYear +import com.sadellie.unitto.core.ui.datetime.formatLocal +import com.sadellie.unitto.core.ui.datetime.formatTimeZoneOffset +import com.sadellie.unitto.core.ui.theme.AppTypography +import com.sadellie.unitto.core.ui.theme.DarkThemeColors +import com.sadellie.unitto.core.ui.theme.LightThemeColors +import com.sadellie.unitto.data.model.UnittoTimeZone +import com.sadellie.unitto.timezone.components.TimeZoneListItem +import io.github.sadellie.themmo.Themmo +import io.github.sadellie.themmo.rememberThemmoController +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import org.burnoutcrew.reorderable.ReorderableItem +import org.burnoutcrew.reorderable.detectReorderAfterLongPress +import org.burnoutcrew.reorderable.rememberReorderableLazyListState +import org.burnoutcrew.reorderable.reorderable +import java.time.ZonedDateTime @Composable -internal fun TimeZoneRoute() {} +internal fun TimeZoneRoute( + viewModel: TimeZoneViewModel = hiltViewModel(), + navigateToMenu: () -> Unit, + navigateToSettings: () -> Unit, + navigateToAddTimeZone: (ZonedDateTime?) -> Unit, +) { + val uiState = viewModel.timeZoneUIState.collectAsStateWithLifecycle() + + TimeZoneScreen( + uiState = uiState.value, + navigateToMenu = navigateToMenu, + navigateToSettings = navigateToSettings, + navigateToAddTimeZone = { + navigateToAddTimeZone( + if (uiState.value.updateTime) null + else uiState.value.userTime + ) + }, + onDragEnd = viewModel::onDragEnd, + onDelete = viewModel::onDelete, + setSelectedTime = viewModel::setCustomTime, + setCurrentTime = viewModel::setCurrentTime, + resetTime = viewModel::resetTime, + ) +} @Composable -private fun TimeZoneScreen() {} +private fun TimeZoneScreen( + uiState: TimeZoneUIState, + navigateToMenu: () -> Unit, + navigateToSettings: () -> Unit, + navigateToAddTimeZone: () -> Unit, + onDragEnd: (String, String) -> Unit, + onDelete: (UnittoTimeZone) -> Unit, + setSelectedTime: (ZonedDateTime) -> Unit, + setCurrentTime: () -> Unit, + resetTime: () -> Unit, +) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + + LaunchedEffect(uiState.updateTime) { + while (uiState.updateTime and isActive) { + setCurrentTime() + delay(1000) + } + } + + val copiedList = rememberUpdatedState(newValue = uiState.list) as MutableState + val state = rememberReorderableLazyListState( + onMove = onMove@{ from, to -> + // -1 because we use fake item. It fixes animation for the first item in list + copiedList.value = copiedList.value + .toMutableList() + .apply { + add(to.index - 1, removeAt(from.index - 1)) + } + onDragEnd(from.key as String, to.key as String) + }, + canDragOver = { draggedOver, _ -> + // Don't allow dragging over fake item + draggedOver.index > 0 + } + ) + // TODO Unswipe on dragging + var swiped by remember> { mutableStateOf(null) } + var showTimeSelector by rememberSaveable { mutableStateOf(false) } + val maxDrag = -with(LocalDensity.current) { 80.dp.toPx() } + + UnittoScreenWithTopBar( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + title = { Text(stringResource(R.string.time_zone_screen)) }, + navigationIcon = { MenuButton(navigateToMenu) }, + actions = { SettingsButton(navigateToSettings) }, + floatingActionButton = { + LargeFloatingActionButton(navigateToAddTimeZone) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = null, + modifier = Modifier.size(FloatingActionButtonDefaults.LargeIconSize), + ) + } + }, + floatingActionButtonPosition = FabPosition.Center, + scrollBehavior = scrollBehavior, + + ) { paddingValues -> + + LazyColumn( + state = state.listState, + modifier = Modifier + .padding(paddingValues) + .fillMaxHeight() + .reorderable(state) + .detectReorderAfterLongPress(state), + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(bottom = 124.dp) + ) { + // This is a fake item. First item in list can not animated, so we do this magic fuckery + item { + UserTimeZone( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp), + userTime = uiState.userTime, + onClick = { showTimeSelector = true }, + onResetClick = resetTime, + showReset = !uiState.updateTime, + ) + } + items(copiedList.value, { it.id }) { item -> + ReorderableItem(state, key = item.id) { isDragging -> + val transition = updateTransition(isDragging, label = "draggedTransition") + val background by transition.animateColor(label = "background") { + if (it) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.secondaryContainer + } + val itemPadding by transition.animateDp(label = "itemPadding") { + if (it) 32.dp else 16.dp + } + + val scope = rememberCoroutineScope() + val draggableState = rememberDraggableTimeZone(maxDrag) { swiped = item } + + LaunchedEffect(swiped) { + if (swiped != item) scope.launch { + draggableState.animateTo(false) + } + } + + TimeZoneListItem( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = itemPadding), + timeZone = item, + currentTime = uiState.userTime, + onDelete = onDelete, + color = background, + onSwipe = {}, + draggableState = draggableState, + ) + } + } + } + } + + if (showTimeSelector) { + TimePickerDialog( + hour = uiState.userTime.hour, + minute = uiState.userTime.minute, + onConfirm = { hour, minute -> + setSelectedTime( + uiState.userTime + .withHour(hour) + .withMinute(minute) + ) + showTimeSelector = false + }, + onDismiss = { showTimeSelector = false } + ) + } +} + +@Composable +private fun UserTimeZone( + modifier: Modifier, + userTime: ZonedDateTime, + onClick: () -> Unit, + onResetClick: () -> Unit, + showReset: Boolean, +) { + + Row( + modifier = modifier + .squashable( + onClick = onClick, + onLongClick = onResetClick, + cornerRadiusRange = 8.dp..32.dp, + interactionSource = remember { MutableInteractionSource() } + ) + .background(MaterialTheme.colorScheme.tertiaryContainer) + .padding(horizontal = 16.dp, vertical = 12.dp), + ) { + Column(Modifier.weight(1f)) { + Text( + text = userTime.formatTimeZoneOffset(), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onTertiaryContainer + ) + // TODO Swipe to increase, touch to set + AnimatedContent( + targetState = userTime.formatLocal(), + label = "user time change", + transitionSpec = { + slideInVertically { height -> height } + fadeIn() togetherWith + slideOutVertically { height -> -height } + fadeOut() using + SizeTransform() + } + ) { time -> + Text( + text = time, + style = MaterialTheme.typography.displayLarge, + color = MaterialTheme.colorScheme.onTertiaryContainer + ) + } + Text( + text = userTime.formatDayMonthYear(), + style = MaterialTheme.typography.headlineMedium, + color = MaterialTheme.colorScheme.onTertiaryContainer + ) + } + AnimatedVisibility( + visible = showReset, + enter = scaleIn() + fadeIn(), + exit = scaleOut() + fadeOut(), + ) { + IconButton(onResetClick) { + Icon( + imageVector = Icons.Outlined.History, + contentDescription = null, + tint = MaterialTheme.colorScheme.onTertiaryContainer, + ) + } + } + } +} + +@Composable +private fun rememberDraggableTimeZone( + maxDrag: Float, + onSwipe: () -> Unit, +) = remember { + AnchoredDraggableState( + initialValue = false, + anchors = DraggableAnchors { + false at 0f + true at maxDrag + }, + positionalThreshold = { it * 0.5f }, + velocityThreshold = { maxDrag }, + animationSpec = tween(), + confirmValueChange = { + onSwipe() + true + } + ) +} + +@Preview +@Composable +fun PreviewTimeZoneScreen() { + Themmo( + themmoController = rememberThemmoController( + lightColorScheme = LightThemeColors, + darkColorScheme = DarkThemeColors, + ), + typography = AppTypography, + ) { + TimeZoneScreen( + uiState = TimeZoneUIState( + list = List(50) { + UnittoTimeZone( + id = "timezone $it", + nameRes = "Time zone $it", + ) + } + ), + navigateToMenu = {}, + navigateToSettings = {}, + navigateToAddTimeZone = {}, + onDragEnd = { _, _ -> }, + onDelete = {}, + setSelectedTime = {}, + setCurrentTime = {}, + resetTime = {}, + ) + } +} diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneUIState.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneUIState.kt new file mode 100644 index 00000000..2584b6c7 --- /dev/null +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneUIState.kt @@ -0,0 +1,28 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.timezone + +import com.sadellie.unitto.data.model.UnittoTimeZone +import java.time.ZonedDateTime + +data class TimeZoneUIState( + val list: List = emptyList(), + val userTime: ZonedDateTime = ZonedDateTime.now(), + val updateTime: Boolean = true, +) diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneViewModel.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneViewModel.kt new file mode 100644 index 00000000..09e4ff2e --- /dev/null +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/TimeZoneViewModel.kt @@ -0,0 +1,78 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.timezone + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sadellie.unitto.data.model.UnittoTimeZone +import com.sadellie.unitto.data.timezone.TimeZonesRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import java.time.ZonedDateTime +import javax.inject.Inject + +@HiltViewModel +class TimeZoneViewModel @Inject constructor( + private val timezonesRepository: TimeZonesRepository +): ViewModel() { + + private val _userTime = MutableStateFlow(ZonedDateTime.now()) + private val _updateTime = MutableStateFlow(true) + + val timeZoneUIState = combine( + _userTime, + _updateTime, + timezonesRepository.favoriteTimeZones + ) { userTime, updateTime, favorites -> + return@combine TimeZoneUIState( + list = favorites, + userTime = userTime, + updateTime = updateTime + ) + }.stateIn( + viewModelScope, SharingStarted.WhileSubscribed(5000), TimeZoneUIState() + ) + + fun onDragEnd(from: String, to: String) = viewModelScope.launch { + timezonesRepository.swapTimeZones(from, to) + } + + fun onDelete(timeZone: UnittoTimeZone) = viewModelScope.launch { + timezonesRepository.delete(timeZone) + } + + fun setCustomTime(time: ZonedDateTime) = viewModelScope.launch(Dispatchers.Default) { + _updateTime.update { false } + _userTime.update { time } + } + + fun resetTime() = viewModelScope.launch(Dispatchers.Default) { + _updateTime.update { true } + } + + fun setCurrentTime() = viewModelScope.launch(Dispatchers.Default) { + _userTime.update { ZonedDateTime.now() } + } +} diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/SelectableTimeZone.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/SelectableTimeZone.kt new file mode 100644 index 00000000..7c0b50b6 --- /dev/null +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/SelectableTimeZone.kt @@ -0,0 +1,71 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.timezone.components + +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.sadellie.unitto.core.ui.datetime.formatLocal +import com.sadellie.unitto.data.model.UnittoTimeZone +import java.time.ZonedDateTime + +@Composable +fun SelectableTimeZone( + modifier: Modifier = Modifier, + timeZone: UnittoTimeZone, + currentTime: ZonedDateTime?, +) { + val offsetTime: ZonedDateTime? by remember(currentTime) { + mutableStateOf(currentTime?.let { timeZone.offsetFrom(it) }) + } + + ListItem( + modifier = modifier, + headlineContent = { + Text(text = timeZone.nameRes) + }, + supportingContent = { + Text(text = timeZone.id) + }, + trailingContent = { + Text(text = offsetTime?.formatLocal() ?: "", style = MaterialTheme.typography.headlineSmall) + } + ) +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +fun PreviewSelectableTimeZone() { + SelectableTimeZone( + timeZone = UnittoTimeZone( + id = "text", + nameRes = "Time zone" + ), + modifier = Modifier.width(440.dp), + currentTime = ZonedDateTime.now(), + ) +} diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/TimeZoneListItem.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/TimeZoneListItem.kt new file mode 100644 index 00000000..edd3670d --- /dev/null +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/TimeZoneListItem.kt @@ -0,0 +1,186 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.timezone.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.AnchoredDraggableState +import androidx.compose.foundation.gestures.DraggableAnchors +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.anchoredDraggable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import com.sadellie.unitto.core.ui.datetime.formatLocal +import com.sadellie.unitto.core.ui.datetime.formatOffset +import com.sadellie.unitto.data.model.UnittoTimeZone +import java.time.ZonedDateTime +import kotlin.math.roundToInt + +@Composable +fun TimeZoneListItem( + modifier: Modifier, + timeZone: UnittoTimeZone, + currentTime: ZonedDateTime, + onClick: () -> Unit = {}, + onDelete: (UnittoTimeZone) -> Unit = {}, + color: Color = MaterialTheme.colorScheme.surfaceVariant, + onSwipe: (UnittoTimeZone) -> Unit = {}, + draggableState: AnchoredDraggableState, +) { + val offsetTime by remember(currentTime) { mutableStateOf(timeZone.offsetFrom(currentTime)) } + val offsetTimeFormatted = offsetTime.formatOffset(currentTime) + + // TODO Animate deleting + Box( + modifier = modifier + .heightIn(72.dp) + .clip(RoundedCornerShape(25)) + .anchoredDraggable( + state = draggableState, + orientation = Orientation.Horizontal, + ), + contentAlignment = Alignment.Center, + ) { + // TODO Reveal animation + Box( + modifier = Modifier + .clickable { onDelete(timeZone) } + .background(MaterialTheme.colorScheme.errorContainer) + .matchParentSize() + ) { + Icon( + modifier = Modifier + .align(Alignment.CenterEnd) + .padding(horizontal = 24.dp) + .size(32.dp), + imageVector = Icons.Outlined.Delete, + contentDescription = null, + tint = MaterialTheme.colorScheme.onErrorContainer + ) + } + + Row( + modifier = Modifier + .offset { + IntOffset( + y = 0, + x = draggableState + .requireOffset() + .roundToInt() + ) + } + .clickable {} + .background(color) + .matchParentSize() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column(Modifier.weight(1f)) { + // TODO Name + Text( + text = timeZone.nameRes, + style = MaterialTheme.typography.bodyLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + AnimatedVisibility( + visible = offsetTimeFormatted != null, + label = "Nullable offset" + ) { + Text(offsetTimeFormatted ?: "", style = MaterialTheme.typography.bodyMedium) + } + } + Column { + // Time + AnimatedContent( + targetState = offsetTime.formatLocal(), + label = "Time change", + transitionSpec = { + fadeIn() togetherWith fadeOut() using (SizeTransform(clip = false)) + } + ) { time -> + Text(time, style = MaterialTheme.typography.headlineMedium) + } + } + } + } +} + +@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) +@Composable +fun PreviewTimeZoneListItem() { + + val maxDrag = -with(LocalDensity.current) { 80.dp.toPx() } + val draggableState = remember { + AnchoredDraggableState( + initialValue = false, + anchors = DraggableAnchors { + false at 0f + true at maxDrag + }, + positionalThreshold = { 0f }, + velocityThreshold = { 0f }, + animationSpec = tween(), + confirmValueChange = { true } + ) + } + + TimeZoneListItem( + modifier = Modifier, + timeZone = UnittoTimeZone( + id = "timezone", + offsetSeconds = -10800, + nameRes = "Time zone" + ), + currentTime = ZonedDateTime.now(), + draggableState = draggableState + ) +} diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/UnittoSearchBar.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/UnittoSearchBar.kt new file mode 100644 index 00000000..9b4a44b3 --- /dev/null +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/UnittoSearchBar.kt @@ -0,0 +1,193 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.timezone.components + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActionScope +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.outlined.Clear +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import com.sadellie.unitto.core.base.R +import com.sadellie.unitto.core.ui.common.NavigateUpButton + +@Composable +fun UnittoSearchBar( + modifier: Modifier = Modifier, + query: String, + onQueryChange: (String) -> Unit, + navigateUp: () -> Unit, + title: String, + searchActions: @Composable (RowScope.() -> Unit) = {}, + noSearchActions: @Composable (RowScope.() -> Unit) = {}, + placeholder: String, + scrollBehavior: TopAppBarScrollBehavior? = null, + colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors() +) { + var showSearch by remember { mutableStateOf(false) } + val focusRequester = remember { FocusRequester() } + + fun stagedNavigateUp() { + if (showSearch) { + // Search text field is open, need to close it and clear search query + showSearch = false + // focusManager.clearFocus() + onQueryChange("") + } else { + // No search text field is shown, can go back as usual + navigateUp() + } + } + + TopAppBar( + modifier = modifier, + title = { + Crossfade(showSearch) { _showSearch -> + if (_showSearch) { + LaunchedEffect(Unit) { focusRequester.requestFocus() } + + SearchTextField( + modifier = Modifier + .focusRequester(focusRequester), + value = query, + placeholder = placeholder, + onValueChange = onQueryChange, + onSearch = {} + ) + } else { + Text( + text = title, + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.titleLarge + ) + } + } + }, + navigationIcon = { + NavigateUpButton { stagedNavigateUp() } + }, + actions = { + Crossfade(showSearch) { _showSearch -> + if (_showSearch) { + ClearButton(visible = query.isNotEmpty()) { onQueryChange("") } + searchActions() + } else { + SearchButton { showSearch = true } + noSearchActions() + } + } + }, + scrollBehavior = scrollBehavior, + colors = colors, + ) + + BackHandler { stagedNavigateUp() } +} + +@Composable +private fun SearchTextField( + modifier: Modifier, + value: String, + placeholder: String, + onValueChange: (String) -> Unit, + onSearch: KeyboardActionScope.() -> Unit +) { + BasicTextField( + modifier = modifier, + value = value, + onValueChange = onValueChange, + singleLine = true, + textStyle = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onSurface), + cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions(onSearch = onSearch), + decorationBox = { innerTextField -> + innerTextField() + // Showing placeholder only when there is query is empty + value.ifEmpty { + Text( + modifier = Modifier.alpha(0.7f), + text = placeholder, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + ) +} + +@Composable +private fun SearchButton( + onClick: () -> Unit +) { + IconButton(onClick) { + Icon( + Icons.Default.Search, + contentDescription = stringResource(R.string.search_button_description) + ) + } +} + +@Composable +private fun ClearButton( + visible: Boolean, + onClick: () -> Unit +) { + IconButton(onClick = onClick) { + AnimatedVisibility( + visible = visible, + enter = fadeIn(), + exit = fadeOut() + ) { + Icon( + imageVector = Icons.Outlined.Clear, + contentDescription = stringResource(R.string.clear_input_description) + ) + } + } +} diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/navigation/TimeZoneNavigation.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/navigation/TimeZoneNavigation.kt index d53efbb6..6c5b734f 100644 --- a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/navigation/TimeZoneNavigation.kt +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/navigation/TimeZoneNavigation.kt @@ -18,24 +18,72 @@ package com.sadellie.unitto.timezone.navigation +import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.NavType import androidx.navigation.compose.composable +import androidx.navigation.compose.navigation +import androidx.navigation.navArgument import androidx.navigation.navDeepLink import com.sadellie.unitto.core.base.TopLevelDestinations +import com.sadellie.unitto.timezone.AddTimeZoneRoute import com.sadellie.unitto.timezone.TimeZoneRoute +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter -private val timeZoneRoute: String by lazy { TopLevelDestinations.TimeZone.route } +private val timeZoneGraph: String by lazy { TopLevelDestinations.TimeZone.route } +internal const val timeZoneRoute = "time_zone_route" +internal const val addTimeZoneRoute = "add_time_zone_route" +internal const val userTimeArg = "userTime" + +fun NavController.navigateToAddTimeZone( + userTime: ZonedDateTime? +) { + val formattedTime = userTime + ?.format(DateTimeFormatter.ISO_ZONED_DATE_TIME) + ?.replace("/", "|") // this is so wrong + + navigate("$addTimeZoneRoute/$formattedTime") +} fun NavGraphBuilder.timeZoneScreen( navigateToMenu: () -> Unit, - navigateToSettings: () -> Unit + navigateToSettings: () -> Unit, + navController: NavHostController, ) { - composable( - route = timeZoneRoute, - deepLinks = listOf( - navDeepLink { uriPattern = "app://com.sadellie.unitto/$timeZoneRoute" } - ) + navigation( + startDestination = timeZoneRoute, + route = timeZoneGraph, + deepLinks = listOf(navDeepLink { uriPattern = "app://com.sadellie.unitto/$timeZoneRoute" }) ) { - TimeZoneRoute() + composable(timeZoneRoute) { + TimeZoneRoute( + navigateToMenu = navigateToMenu, + navigateToSettings = navigateToSettings, + navigateToAddTimeZone = navController::navigateToAddTimeZone + ) + } + + composable( + route = "$addTimeZoneRoute/{$userTimeArg}", + arguments = listOf( + navArgument(userTimeArg) { + defaultValue = null + nullable = true + type = NavType.StringType + } + ) + ) { stackEntry -> + val userTime = stackEntry.arguments + ?.getString(userTimeArg) + ?.replace("|", "/") // war crime, don't look + ?.let { ZonedDateTime.parse(it, DateTimeFormatter.ISO_ZONED_DATE_TIME) } + + AddTimeZoneRoute( + navigateUp = navController::navigateUp, + userTime = userTime + ) + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 761b20b9..b6b9ee4b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,3 +35,4 @@ include(":data:database") include(":data:model") include(":data:common") include(":data:evaluatto") +include(":data:timezone")