mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 16:25:27 +02:00
parent
37f25ed3ba
commit
02e9bfaf90
@ -138,6 +138,7 @@ dependencies {
|
|||||||
implementation(project(":feature:settings"))
|
implementation(project(":feature:settings"))
|
||||||
implementation(project(":feature:datecalculator"))
|
implementation(project(":feature:datecalculator"))
|
||||||
implementation(project(":feature:timezone"))
|
implementation(project(":feature:timezone"))
|
||||||
|
implementation(project(":feature:bodymass"))
|
||||||
implementation(project(":feature:glance"))
|
implementation(project(":feature:glance"))
|
||||||
implementation(project(":data:model"))
|
implementation(project(":data:model"))
|
||||||
implementation(project(":data:userprefs"))
|
implementation(project(":data:userprefs"))
|
||||||
|
@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
|
import com.sadellie.unitto.feature.bodymass.navigation.bodyMassGraph
|
||||||
import com.sadellie.unitto.feature.calculator.navigation.calculatorGraph
|
import com.sadellie.unitto.feature.calculator.navigation.calculatorGraph
|
||||||
import com.sadellie.unitto.feature.converter.navigation.converterGraph
|
import com.sadellie.unitto.feature.converter.navigation.converterGraph
|
||||||
import com.sadellie.unitto.feature.datecalculator.navigation.dateCalculatorGraph
|
import com.sadellie.unitto.feature.datecalculator.navigation.dateCalculatorGraph
|
||||||
@ -78,5 +79,10 @@ internal fun UnittoNavigation(
|
|||||||
navigateToSettings = navController::navigateToSettings,
|
navigateToSettings = navController::navigateToSettings,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bodyMassGraph(
|
||||||
|
openDrawer = openDrawer,
|
||||||
|
navigateToSettings = navController::navigateToSettings,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,9 @@ private const val DATE_CALCULATOR_START = "date_calculator_start"
|
|||||||
private const val TIME_ZONE_GRAPH = "time_zone_route"
|
private const val TIME_ZONE_GRAPH = "time_zone_route"
|
||||||
private const val TIME_ZONE_START = "time_zone_start"
|
private const val TIME_ZONE_START = "time_zone_start"
|
||||||
|
|
||||||
|
private const val BODY_MASS_GRAPH = "body_mass_route"
|
||||||
|
private const val BODY_MASS_START = "body_mass_start"
|
||||||
|
|
||||||
private const val SETTINGS_GRAPH = "settings_route"
|
private const val SETTINGS_GRAPH = "settings_route"
|
||||||
private const val SETTINGS_START = "settings_start"
|
private const val SETTINGS_START = "settings_start"
|
||||||
|
|
||||||
@ -94,6 +97,17 @@ sealed class TopLevelDestinations(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data object BodyMass : TopLevelDestinations(
|
||||||
|
graph = BODY_MASS_GRAPH,
|
||||||
|
start = BODY_MASS_START,
|
||||||
|
name = R.string.body_mass_title,
|
||||||
|
shortcut = Shortcut(
|
||||||
|
R.string.body_mass_title,
|
||||||
|
R.string.body_mass_title,
|
||||||
|
R.drawable.ic_shortcut_body_mass
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
data object Settings : TopLevelDestinations(
|
data object Settings : TopLevelDestinations(
|
||||||
graph = SETTINGS_GRAPH,
|
graph = SETTINGS_GRAPH,
|
||||||
start = SETTINGS_START,
|
start = SETTINGS_START,
|
||||||
@ -103,20 +117,19 @@ sealed class TopLevelDestinations(
|
|||||||
|
|
||||||
// Shown in settings
|
// Shown in settings
|
||||||
val TOP_LEVEL_DESTINATIONS by lazy {
|
val TOP_LEVEL_DESTINATIONS by lazy {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
var all = listOf(
|
||||||
listOf(
|
TopLevelDestinations.Calculator,
|
||||||
TopLevelDestinations.Calculator,
|
TopLevelDestinations.Converter,
|
||||||
TopLevelDestinations.Converter,
|
TopLevelDestinations.DateCalculator,
|
||||||
TopLevelDestinations.DateCalculator,
|
TopLevelDestinations.TimeZone,
|
||||||
TopLevelDestinations.TimeZone,
|
TopLevelDestinations.BodyMass,
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
listOf(
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
TopLevelDestinations.Calculator,
|
all = all - TopLevelDestinations.TimeZone
|
||||||
TopLevelDestinations.Converter,
|
|
||||||
TopLevelDestinations.DateCalculator,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
all
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only routes, not graphs!
|
// Only routes, not graphs!
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ 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 <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_shortcut_background" />
|
||||||
|
<foreground>
|
||||||
|
<inset
|
||||||
|
android:drawable="@drawable/ic_body_mass_foreground"
|
||||||
|
android:inset="29.999996%" />
|
||||||
|
</foreground>
|
||||||
|
</adaptive-icon>
|
31
core/base/src/main/res/drawable/ic_body_mass_foreground.xml
Normal file
31
core/base/src/main/res/drawable/ic_body_mass_foreground.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!--
|
||||||
|
~ Unitto is a unit converter for Android
|
||||||
|
~ Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M0,0h24v24h-24z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6,19H18L16.575,9H7.425L6,19ZM12,7C12.283,7 12.521,6.904 12.713,6.713C12.904,6.521 13,6.283 13,6C13,5.717 12.904,5.479 12.713,5.287C12.521,5.096 12.283,5 12,5C11.717,5 11.479,5.096 11.288,5.287C11.096,5.479 11,5.717 11,6C11,6.283 11.096,6.521 11.288,6.713C11.479,6.904 11.717,7 12,7ZM14.825,7H16.575C17.075,7 17.508,7.167 17.875,7.5C18.242,7.833 18.467,8.242 18.55,8.725L19.975,18.725C20.058,19.325 19.904,19.854 19.513,20.313C19.121,20.771 18.617,21 18,21H6C5.383,21 4.879,20.771 4.488,20.313C4.096,19.854 3.942,19.325 4.025,18.725L5.45,8.725C5.533,8.242 5.758,7.833 6.125,7.5C6.492,7.167 6.925,7 7.425,7H9.175C9.125,6.833 9.083,6.671 9.05,6.512C9.017,6.354 9,6.183 9,6C9,5.167 9.292,4.458 9.875,3.875C10.458,3.292 11.167,3 12,3C12.833,3 13.542,3.292 14.125,3.875C14.708,4.458 15,5.167 15,6C15,6.183 14.983,6.354 14.95,6.512C14.917,6.671 14.875,6.833 14.825,7Z"
|
||||||
|
android:fillColor="@color/unitto_primary"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
37
core/base/src/main/res/drawable/ic_shortcut_body_mass.xml
Normal file
37
core/base/src/main/res/drawable/ic_shortcut_body_mass.xml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ 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 <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:bottom="2.0dip"
|
||||||
|
android:left="2.0dip"
|
||||||
|
android:right="2.0dip"
|
||||||
|
android:top="2.0dip">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<size
|
||||||
|
android:width="44.0dip"
|
||||||
|
android:height="44.0dip" />
|
||||||
|
<solid android:color="@color/ic_shortcut_background" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item
|
||||||
|
android:bottom="12.0dip"
|
||||||
|
android:drawable="@drawable/ic_body_mass_foreground"
|
||||||
|
android:left="12.0dip"
|
||||||
|
android:right="12.0dip"
|
||||||
|
android:top="12.0dip" />
|
||||||
|
</layer-list>
|
@ -4,6 +4,23 @@
|
|||||||
<!-- https://s3.eu-west-1.amazonaws.com/po-pub/i/QNmUuI88wb1Fm5P8wRIZajZF.png -->
|
<!-- https://s3.eu-west-1.amazonaws.com/po-pub/i/QNmUuI88wb1Fm5P8wRIZajZF.png -->
|
||||||
<string name="add_label">Add</string>
|
<string name="add_label">Add</string>
|
||||||
<string name="app_name" translatable="false">Unitto</string>
|
<string name="app_name" translatable="false">Unitto</string>
|
||||||
|
|
||||||
|
<!-- Person's height -->
|
||||||
|
<string name="body_mass_height">Height</string>
|
||||||
|
<string name="body_mass_imperial">Imperial</string>
|
||||||
|
<string name="body_mass_metric">Metric</string>
|
||||||
|
<string name="body_mass_normal">Normal weight</string>
|
||||||
|
<string name="body_mass_obese_1">Obese (Class 1)</string>
|
||||||
|
<string name="body_mass_obese_2">Obese (Class 2)</string>
|
||||||
|
<string name="body_mass_obese_3">Obese (Class 3)</string>
|
||||||
|
<string name="body_mass_overweight">Overweight</string>
|
||||||
|
|
||||||
|
<!-- Also known as Body mass index (BMI) -->
|
||||||
|
<string name="body_mass_title">Body mass</string>
|
||||||
|
<string name="body_mass_underweight">Underweight</string>
|
||||||
|
|
||||||
|
<!-- Person's weight -->
|
||||||
|
<string name="body_mass_weight">Weight</string>
|
||||||
<string name="calculator_clear_history_support">All expressions from history will be deleted forever. This action can\'t be undone!</string>
|
<string name="calculator_clear_history_support">All expressions from history will be deleted forever. This action can\'t be undone!</string>
|
||||||
<string name="calculator_divide_by_zero_error">Can\'t divide by 0</string>
|
<string name="calculator_divide_by_zero_error">Can\'t divide by 0</string>
|
||||||
<string name="calculator_no_history">No history</string>
|
<string name="calculator_no_history">No history</string>
|
||||||
|
@ -20,11 +20,13 @@ package com.sadellie.unitto.core.ui.model
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Accessibility
|
||||||
import androidx.compose.material.icons.filled.Calculate
|
import androidx.compose.material.icons.filled.Calculate
|
||||||
import androidx.compose.material.icons.filled.Event
|
import androidx.compose.material.icons.filled.Event
|
||||||
import androidx.compose.material.icons.filled.Schedule
|
import androidx.compose.material.icons.filled.Schedule
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material.icons.filled.SwapHoriz
|
import androidx.compose.material.icons.filled.SwapHoriz
|
||||||
|
import androidx.compose.material.icons.outlined.Accessibility
|
||||||
import androidx.compose.material.icons.outlined.Calculate
|
import androidx.compose.material.icons.outlined.Calculate
|
||||||
import androidx.compose.material.icons.outlined.Event
|
import androidx.compose.material.icons.outlined.Event
|
||||||
import androidx.compose.material.icons.outlined.Schedule
|
import androidx.compose.material.icons.outlined.Schedule
|
||||||
@ -62,6 +64,12 @@ sealed class DrawerItems(
|
|||||||
defaultIcon = Icons.Outlined.Schedule
|
defaultIcon = Icons.Outlined.Schedule
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data object BodyMass : DrawerItems(
|
||||||
|
destination = TopLevelDestinations.BodyMass,
|
||||||
|
selectedIcon = Icons.Filled.Accessibility, // temporary
|
||||||
|
defaultIcon = Icons.Outlined.Accessibility // temporary
|
||||||
|
)
|
||||||
|
|
||||||
data object Settings : DrawerItems(
|
data object Settings : DrawerItems(
|
||||||
destination = TopLevelDestinations.Settings,
|
destination = TopLevelDestinations.Settings,
|
||||||
selectedIcon = Icons.Filled.Settings,
|
selectedIcon = Icons.Filled.Settings,
|
||||||
@ -73,20 +81,19 @@ sealed class DrawerItems(
|
|||||||
* Excluding Settings tab since it appears only for expanded layout
|
* Excluding Settings tab since it appears only for expanded layout
|
||||||
*/
|
*/
|
||||||
val MAIN by lazy {
|
val MAIN by lazy {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
var all = listOf(
|
||||||
listOf(
|
Calculator,
|
||||||
Calculator,
|
Converter,
|
||||||
Converter,
|
DateDifference,
|
||||||
DateDifference,
|
TimeZones,
|
||||||
TimeZones,
|
BodyMass
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
listOf(
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
Calculator,
|
all = all - TimeZones
|
||||||
Converter,
|
|
||||||
DateDifference,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import com.sadellie.unitto.data.model.unit.AbstractUnit
|
|||||||
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
|
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
|
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.AppPreferences
|
import com.sadellie.unitto.data.model.userprefs.AppPreferences
|
||||||
|
import com.sadellie.unitto.data.model.userprefs.BodyMassPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.CalculatorPreferences
|
import com.sadellie.unitto.data.model.userprefs.CalculatorPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.ConverterPreferences
|
import com.sadellie.unitto.data.model.userprefs.ConverterPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.DisplayPreferences
|
import com.sadellie.unitto.data.model.userprefs.DisplayPreferences
|
||||||
@ -42,6 +43,7 @@ interface UserPreferencesRepository {
|
|||||||
val formattingPrefs: Flow<FormattingPreferences>
|
val formattingPrefs: Flow<FormattingPreferences>
|
||||||
val unitGroupsPrefs: Flow<UnitGroupsPreferences>
|
val unitGroupsPrefs: Flow<UnitGroupsPreferences>
|
||||||
val addSubtractPrefs: Flow<AddSubtractPreferences>
|
val addSubtractPrefs: Flow<AddSubtractPreferences>
|
||||||
|
val bodyMassPrefs: Flow<BodyMassPreferences>
|
||||||
val aboutPrefs: Flow<AboutPreferences>
|
val aboutPrefs: Flow<AboutPreferences>
|
||||||
val startingScreenPrefs: Flow<StartingScreenPreferences>
|
val startingScreenPrefs: Flow<StartingScreenPreferences>
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sadellie.unitto.data.model.userprefs
|
||||||
|
|
||||||
|
interface BodyMassPreferences{
|
||||||
|
val separator: Int
|
||||||
|
val enableVibrations: Boolean
|
||||||
|
}
|
@ -24,6 +24,7 @@ import com.sadellie.unitto.data.model.UnitsListSorting
|
|||||||
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
|
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
|
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.AppPreferences
|
import com.sadellie.unitto.data.model.userprefs.AppPreferences
|
||||||
|
import com.sadellie.unitto.data.model.userprefs.BodyMassPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.CalculatorPreferences
|
import com.sadellie.unitto.data.model.userprefs.CalculatorPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.ConverterPreferences
|
import com.sadellie.unitto.data.model.userprefs.ConverterPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.DisplayPreferences
|
import com.sadellie.unitto.data.model.userprefs.DisplayPreferences
|
||||||
@ -96,6 +97,11 @@ data class AddSubtractPreferencesImpl(
|
|||||||
override val enableVibrations: Boolean,
|
override val enableVibrations: Boolean,
|
||||||
) : AddSubtractPreferences
|
) : AddSubtractPreferences
|
||||||
|
|
||||||
|
data class BodyMassPreferencesImpl(
|
||||||
|
override val separator: Int,
|
||||||
|
override val enableVibrations: Boolean,
|
||||||
|
) : BodyMassPreferences
|
||||||
|
|
||||||
data class AboutPreferencesImpl(
|
data class AboutPreferencesImpl(
|
||||||
override val enableToolsExperiment: Boolean,
|
override val enableToolsExperiment: Boolean,
|
||||||
) : AboutPreferences
|
) : AboutPreferences
|
||||||
|
@ -29,6 +29,7 @@ import com.sadellie.unitto.data.model.unit.AbstractUnit
|
|||||||
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
|
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
|
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.AppPreferences
|
import com.sadellie.unitto.data.model.userprefs.AppPreferences
|
||||||
|
import com.sadellie.unitto.data.model.userprefs.BodyMassPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.CalculatorPreferences
|
import com.sadellie.unitto.data.model.userprefs.CalculatorPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.ConverterPreferences
|
import com.sadellie.unitto.data.model.userprefs.ConverterPreferences
|
||||||
import com.sadellie.unitto.data.model.userprefs.DisplayPreferences
|
import com.sadellie.unitto.data.model.userprefs.DisplayPreferences
|
||||||
@ -136,6 +137,14 @@ class UserPreferencesRepositoryImpl @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val bodyMassPrefs: Flow<BodyMassPreferences> = data
|
||||||
|
.map { preferences ->
|
||||||
|
BodyMassPreferencesImpl(
|
||||||
|
separator = preferences.getSeparator(),
|
||||||
|
enableVibrations = preferences.getEnableVibrations(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override val aboutPrefs: Flow<AboutPreferences> = data
|
override val aboutPrefs: Flow<AboutPreferences> = data
|
||||||
.map { preferences ->
|
.map { preferences ->
|
||||||
AboutPreferencesImpl(
|
AboutPreferencesImpl(
|
||||||
|
1
feature/bodymass/.gitignore
vendored
Normal file
1
feature/bodymass/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
33
feature/bodymass/build.gradle.kts
Normal file
33
feature/bodymass/build.gradle.kts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Unitto is a unit converter for Android
|
||||||
|
* Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("unitto.library")
|
||||||
|
id("unitto.library.compose")
|
||||||
|
id("unitto.library.feature")
|
||||||
|
id("unitto.android.hilt")
|
||||||
|
id("unitto.android.library.jacoco")
|
||||||
|
}
|
||||||
|
|
||||||
|
android.namespace = "com.sadellie.unitto.feature.bodymass"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":data:common"))
|
||||||
|
implementation(project(":data:model"))
|
||||||
|
implementation(project(":data:userprefs"))
|
||||||
|
}
|
0
feature/bodymass/consumer-rules.pro
Normal file
0
feature/bodymass/consumer-rules.pro
Normal file
21
feature/bodymass/src/main/AndroidManifest.xml
Normal file
21
feature/bodymass/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Unitto is a unit converter for Android
|
||||||
|
~ Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest>
|
||||||
|
</manifest>
|
@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* Unitto is a unit converter for Android
|
||||||
|
* Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sadellie.unitto.feature.bodymass
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.animation.SizeTransform
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.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.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.sadellie.unitto.core.base.OutputFormat
|
||||||
|
import com.sadellie.unitto.core.base.R
|
||||||
|
import com.sadellie.unitto.core.base.Token
|
||||||
|
import com.sadellie.unitto.core.ui.common.MenuButton
|
||||||
|
import com.sadellie.unitto.core.ui.common.SegmentedButton
|
||||||
|
import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow
|
||||||
|
import com.sadellie.unitto.core.ui.common.SettingsButton
|
||||||
|
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
|
||||||
|
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTransformer
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.formatExpression
|
||||||
|
import com.sadellie.unitto.data.common.format
|
||||||
|
import com.sadellie.unitto.data.common.isEqualTo
|
||||||
|
import com.sadellie.unitto.data.common.isLessThan
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun BodyMassRoute(
|
||||||
|
openDrawer: () -> Unit,
|
||||||
|
navigateToSettings: () -> Unit,
|
||||||
|
viewModel: BodyMassViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
when (val uiState = viewModel.uiState.collectAsStateWithLifecycle().value) {
|
||||||
|
UIState.Loading -> UnittoEmptyScreen()
|
||||||
|
is UIState.Ready -> BodyMassScreen(
|
||||||
|
uiState = uiState,
|
||||||
|
updateHeight1 = viewModel::updateHeight1,
|
||||||
|
updateHeight2 = viewModel::updateHeight2,
|
||||||
|
updateWeight = viewModel::updateWeight,
|
||||||
|
updateIsMetric = viewModel::updateIsMetric,
|
||||||
|
openDrawer = openDrawer,
|
||||||
|
navigateToSettings = navigateToSettings
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BodyMassScreen(
|
||||||
|
uiState: UIState.Ready,
|
||||||
|
updateHeight1: (TextFieldValue) -> Unit,
|
||||||
|
updateHeight2: (TextFieldValue) -> Unit,
|
||||||
|
updateWeight: (TextFieldValue) -> Unit,
|
||||||
|
updateIsMetric: (Boolean) -> Unit,
|
||||||
|
openDrawer: () -> Unit,
|
||||||
|
navigateToSettings: () -> Unit,
|
||||||
|
) {
|
||||||
|
val mContext = LocalContext.current
|
||||||
|
val expressionTransformer = remember(uiState.formatterSymbols) {
|
||||||
|
ExpressionTransformer(uiState.formatterSymbols)
|
||||||
|
}
|
||||||
|
val weightLabel = remember(uiState.isMetric) {
|
||||||
|
val s1 = mContext.resources.getString(R.string.body_mass_weight)
|
||||||
|
val s2 = if (uiState.isMetric) {
|
||||||
|
mContext.resources.getString(R.string.unit_kilogram_short)
|
||||||
|
} else {
|
||||||
|
mContext.resources.getString(R.string.unit_pound_short)
|
||||||
|
}
|
||||||
|
|
||||||
|
"$s1, $s2"
|
||||||
|
}
|
||||||
|
|
||||||
|
UnittoScreenWithTopBar(
|
||||||
|
title = { Text(stringResource(R.string.body_mass_title)) },
|
||||||
|
navigationIcon = { MenuButton(openDrawer) },
|
||||||
|
actions = { SettingsButton(navigateToSettings) }
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
SegmentedButtonsRow(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
SegmentedButton(
|
||||||
|
label = stringResource(R.string.body_mass_metric),
|
||||||
|
onClick = { updateIsMetric(true) },
|
||||||
|
selected = uiState.isMetric,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
SegmentedButton(
|
||||||
|
label = stringResource(R.string.body_mass_imperial),
|
||||||
|
onClick = { updateIsMetric(false) },
|
||||||
|
selected = !uiState.isMetric,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
RoundedCornerShape(32.dp)
|
||||||
|
)
|
||||||
|
.padding(16.dp, 24.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Crossfade(targetState = uiState.isMetric) { isMetric ->
|
||||||
|
if (isMetric) {
|
||||||
|
BodyMassTextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = uiState.height1,
|
||||||
|
onValueChange = updateHeight1,
|
||||||
|
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_centimeter_short)}",
|
||||||
|
expressionFormatter = expressionTransformer,
|
||||||
|
imeAction = ImeAction.Next
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
BodyMassTextField(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
value = uiState.height1,
|
||||||
|
onValueChange = updateHeight1,
|
||||||
|
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_foot_short)}",
|
||||||
|
expressionFormatter = expressionTransformer,
|
||||||
|
imeAction = ImeAction.Next
|
||||||
|
)
|
||||||
|
BodyMassTextField(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
value = uiState.height2,
|
||||||
|
onValueChange = updateHeight2,
|
||||||
|
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_inch_short)}",
|
||||||
|
expressionFormatter = expressionTransformer,
|
||||||
|
imeAction = ImeAction.Next
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BodyMassTextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = uiState.weight,
|
||||||
|
onValueChange = updateWeight,
|
||||||
|
label = weightLabel,
|
||||||
|
expressionFormatter = expressionTransformer,
|
||||||
|
imeAction = ImeAction.Done
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
targetState = uiState.result,
|
||||||
|
transitionSpec = {
|
||||||
|
(fadeIn() togetherWith fadeOut()) using SizeTransform(false)
|
||||||
|
}
|
||||||
|
) { targetState ->
|
||||||
|
if (targetState.isEqualTo(BigDecimal.ZERO)) return@AnimatedContent
|
||||||
|
|
||||||
|
val value = remember(targetState) {
|
||||||
|
targetState
|
||||||
|
.format(3, OutputFormat.PLAIN)
|
||||||
|
.formatExpression(uiState.formatterSymbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
val classification = remember(targetState) {
|
||||||
|
getBodyMassData(targetState)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(32.dp))
|
||||||
|
.background(classification.color)
|
||||||
|
.padding(16.dp, 32.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
// TODO Link to web
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(classification.classification),
|
||||||
|
style = MaterialTheme.typography.displaySmall,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BodyMassTextField(
|
||||||
|
modifier: Modifier,
|
||||||
|
value: TextFieldValue,
|
||||||
|
onValueChange: (TextFieldValue) -> Unit,
|
||||||
|
label: String,
|
||||||
|
expressionFormatter: VisualTransformation,
|
||||||
|
imeAction: ImeAction
|
||||||
|
) {
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = modifier,
|
||||||
|
value = value,
|
||||||
|
onValueChange = {
|
||||||
|
val cleanText = it.text
|
||||||
|
.replace(",", ".")
|
||||||
|
.filter { char ->
|
||||||
|
Token.Digit.allWithDot.contains(char.toString())
|
||||||
|
}
|
||||||
|
onValueChange(it.copy(cleanText))
|
||||||
|
},
|
||||||
|
label = { AnimatedContent(label) { Text(it) } },
|
||||||
|
singleLine = true,
|
||||||
|
visualTransformation = expressionFormatter,
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal, imeAction = imeAction),
|
||||||
|
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
private data class BodyMassData(
|
||||||
|
val color: Color,
|
||||||
|
@StringRes val classification: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
private fun getBodyMassData(value: BigDecimal): BodyMassData = when {
|
||||||
|
value.isLessThan(BigDecimal("18.5")) -> {
|
||||||
|
BodyMassData(
|
||||||
|
color = Color(0x800EACDD),
|
||||||
|
classification = R.string.body_mass_underweight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value.isLessThan(BigDecimal("25")) -> {
|
||||||
|
BodyMassData(
|
||||||
|
color = Color(0x805BF724),
|
||||||
|
classification = R.string.body_mass_normal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value.isLessThan(BigDecimal("30")) -> {
|
||||||
|
BodyMassData(
|
||||||
|
color = Color(0x80DBEC18),
|
||||||
|
classification = R.string.body_mass_overweight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value.isLessThan(BigDecimal("35")) -> {
|
||||||
|
BodyMassData(
|
||||||
|
color = Color(0x80FF9634),
|
||||||
|
classification = R.string.body_mass_obese_1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value.isLessThan(BigDecimal("40")) -> {
|
||||||
|
BodyMassData(
|
||||||
|
color = Color(0x80F85F31),
|
||||||
|
classification = R.string.body_mass_obese_2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
BodyMassData(
|
||||||
|
color = Color(0x80FF2323),
|
||||||
|
classification = R.string.body_mass_obese_3
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Unitto is a unit converter for Android
|
||||||
|
* Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sadellie.unitto.feature.bodymass
|
||||||
|
|
||||||
|
import android.icu.util.LocaleData
|
||||||
|
import android.icu.util.ULocale
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.sadellie.unitto.core.base.MAX_PRECISION
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
|
||||||
|
import com.sadellie.unitto.data.common.combine
|
||||||
|
import com.sadellie.unitto.data.common.stateIn
|
||||||
|
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
internal class BodyMassViewModel @Inject constructor(
|
||||||
|
userPreferencesRepository: UserPreferencesRepository
|
||||||
|
): ViewModel() {
|
||||||
|
private val _isMetric = MutableStateFlow(getInitialIsMetric())
|
||||||
|
private val _height1 = MutableStateFlow(TextFieldValue())
|
||||||
|
private val _height2 = MutableStateFlow(TextFieldValue())
|
||||||
|
private val _weight = MutableStateFlow(TextFieldValue())
|
||||||
|
private val _result = MutableStateFlow<BigDecimal>(BigDecimal.ZERO)
|
||||||
|
private val cmToMFactor = BigDecimal("10000")
|
||||||
|
private val footToInchFactor = BigDecimal("12")
|
||||||
|
private val metricToImperialFactor = BigDecimal("703")
|
||||||
|
|
||||||
|
val uiState = combine(
|
||||||
|
userPreferencesRepository.bodyMassPrefs,
|
||||||
|
_isMetric,
|
||||||
|
_height1,
|
||||||
|
_height2,
|
||||||
|
_weight,
|
||||||
|
_result
|
||||||
|
) { userPrefs, isMetric, height1, height2, weight, result ->
|
||||||
|
UIState.Ready(
|
||||||
|
isMetric = isMetric,
|
||||||
|
height1 = height1,
|
||||||
|
height2 = height2,
|
||||||
|
weight = weight,
|
||||||
|
result = result,
|
||||||
|
allowVibration = userPrefs.enableVibrations,
|
||||||
|
formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.mapLatest { ui ->
|
||||||
|
val newResult: BigDecimal = try {
|
||||||
|
val height1 = BigDecimal(ui.height1.text.ifEmpty { "0" })
|
||||||
|
val weight = BigDecimal(ui.weight.text.ifEmpty { "0" })
|
||||||
|
|
||||||
|
if (ui.isMetric) {
|
||||||
|
calculateMetric(height1, weight)
|
||||||
|
} else {
|
||||||
|
val height2 = BigDecimal(ui.height2.text.ifEmpty { "0" })
|
||||||
|
calculateImperial(height1, height2, weight)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
BigDecimal.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
_result.update { newResult }
|
||||||
|
|
||||||
|
ui
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, UIState.Loading)
|
||||||
|
|
||||||
|
fun updateHeight1(textFieldValue: TextFieldValue) = _height1.update { textFieldValue }
|
||||||
|
fun updateHeight2(textFieldValue: TextFieldValue) = _height2.update { textFieldValue }
|
||||||
|
fun updateWeight(textFieldValue: TextFieldValue) = _weight.update { textFieldValue }
|
||||||
|
fun updateIsMetric(isMetric: Boolean) = _isMetric.update { isMetric }
|
||||||
|
|
||||||
|
private suspend fun calculateMetric(
|
||||||
|
height: BigDecimal,
|
||||||
|
weight: BigDecimal,
|
||||||
|
) = withContext(Dispatchers.Default) {
|
||||||
|
return@withContext weight
|
||||||
|
.divide(height.pow(2), MAX_PRECISION, RoundingMode.HALF_EVEN)
|
||||||
|
.multiply(cmToMFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun calculateImperial(
|
||||||
|
height1: BigDecimal,
|
||||||
|
height2: BigDecimal,
|
||||||
|
weight: BigDecimal
|
||||||
|
) = withContext(Dispatchers.Default) {
|
||||||
|
val height = height1
|
||||||
|
.multiply(footToInchFactor)
|
||||||
|
.plus(height2)
|
||||||
|
|
||||||
|
return@withContext weight
|
||||||
|
.divide(height.pow(2), MAX_PRECISION, RoundingMode.HALF_EVEN)
|
||||||
|
.multiply(metricToImperialFactor) // Approximate lbs/inch^2 to kg/m^2
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInitialIsMetric(): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return true
|
||||||
|
return LocaleData.getMeasurementSystem(ULocale.getDefault()) != LocaleData.MeasurementSystem.US
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Unitto is a unit converter for Android
|
||||||
|
* Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sadellie.unitto.feature.bodymass
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
internal sealed class UIState {
|
||||||
|
data object Loading : UIState()
|
||||||
|
|
||||||
|
data class Ready(
|
||||||
|
val isMetric: Boolean,
|
||||||
|
val height1: TextFieldValue,
|
||||||
|
val height2: TextFieldValue,
|
||||||
|
val weight: TextFieldValue,
|
||||||
|
val result: BigDecimal,
|
||||||
|
val allowVibration: Boolean,
|
||||||
|
val formatterSymbols: FormatterSymbols,
|
||||||
|
) : UIState()
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Unitto is a unit converter for Android
|
||||||
|
* Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sadellie.unitto.feature.bodymass.navigation
|
||||||
|
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.navDeepLink
|
||||||
|
import com.sadellie.unitto.core.base.TopLevelDestinations
|
||||||
|
import com.sadellie.unitto.core.ui.unittoComposable
|
||||||
|
import com.sadellie.unitto.core.ui.unittoNavigation
|
||||||
|
import com.sadellie.unitto.feature.bodymass.BodyMassRoute
|
||||||
|
|
||||||
|
private val graph = TopLevelDestinations.BodyMass.graph
|
||||||
|
private val start = TopLevelDestinations.BodyMass.start
|
||||||
|
|
||||||
|
fun NavGraphBuilder.bodyMassGraph(
|
||||||
|
openDrawer: () -> Unit,
|
||||||
|
navigateToSettings: () -> Unit
|
||||||
|
) {
|
||||||
|
unittoNavigation(
|
||||||
|
startDestination = start,
|
||||||
|
route = graph,
|
||||||
|
deepLinks = listOf(
|
||||||
|
navDeepLink { uriPattern = "app://com.sadellie.unitto/$graph" }
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
unittoComposable(start) {
|
||||||
|
BodyMassRoute(
|
||||||
|
openDrawer = openDrawer,
|
||||||
|
navigateToSettings = navigateToSettings
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ include(":feature:converter")
|
|||||||
include(":feature:calculator")
|
include(":feature:calculator")
|
||||||
include(":feature:datecalculator")
|
include(":feature:datecalculator")
|
||||||
include(":feature:timezone")
|
include(":feature:timezone")
|
||||||
|
include(":feature:bodymass")
|
||||||
include(":feature:settings")
|
include(":feature:settings")
|
||||||
include(":feature:glance")
|
include(":feature:glance")
|
||||||
include(":data:userprefs")
|
include(":data:userprefs")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user