1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-06-12 17:19:18 +00:00
Signed-off-by: Avior <github@avior.me>
This commit is contained in:
2022-06-28 17:43:29 +02:00
parent 6f33e2a1c3
commit 939dcd24d3
49 changed files with 1433 additions and 366 deletions

View File

@ -51,6 +51,12 @@ android {
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Languages
def locales = ["en", "fr"]
buildConfigField "String[]", "LOCALES", "new String[]{\""+locales.join("\",\"")+"\"}"
resConfigs locales
}
buildTypes {
@ -143,4 +149,4 @@ dependencies {
testImplementation "androidx.room:room-testing:$room_version"
}
}

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">OpenHealth - Debug</string>
</resources>
<string name="app_name" translatable="false">OpenHealth - Debug</string>
</resources>

View File

@ -1,8 +1,13 @@
package com.dzeio.openhealth
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources
import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
import java.util.Locale
@HiltAndroidApp
class Application : Application() {
@ -15,5 +20,16 @@ class Application : Application() {
// Android Dynamics Colors
DynamicColors.applyToActivitiesIfAvailable(this)
// Change application Language based on setting
val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val lang = preferences.getString("global_language", Locale.getDefault().language)
val locale = Locale(lang)
Locale.setDefault(locale)
val overrideConfiguration = baseContext.resources.configuration
overrideConfiguration.locale = locale
val context: Context = createConfigurationContext(overrideConfiguration)
val resources: Resources = context.getResources()
}
}
}

View File

@ -57,7 +57,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
WorkManager.getInstance(this)
.cancelAllWork()
WaterReminderService.setup(this)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -107,7 +106,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
Log.e("MainActivity", "Error Creating Notification Channel", e)
}
}
}
}
@ -125,9 +123,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
)
true
}
R.id.action_about -> {
navController.navigate(
HomeFragmentDirections.actionNavHomeToAboutFragment()
)
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
}

View File

@ -20,7 +20,7 @@ class WaterAdapter() : BaseAdapter<Water, LayoutItemListBinding>() {
position: Int
) {
holder.binding.value.text = "${item.value}ml"
holder.binding.datetime.text = "${item.formatTimestamp()} ${item.timestamp}"
holder.binding.datetime.text = "${item.formatTimestamp()}"
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)
}

View File

@ -6,8 +6,11 @@ import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.LayoutItemListBinding
import com.dzeio.openhealth.units.WeightUnit
class WeightAdapter() : BaseAdapter<Weight, LayoutItemListBinding>() {
class WeightAdapter : BaseAdapter<Weight, LayoutItemListBinding>() {
var unit: WeightUnit = WeightUnit.KG
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemListBinding
get() = LayoutItemListBinding::inflate
@ -19,10 +22,11 @@ class WeightAdapter() : BaseAdapter<Weight, LayoutItemListBinding>() {
item: Weight,
position: Int
) {
holder.binding.value.text = "${item.weight}kg"
val weightTxt = String.format("%.1f", item.weight * unit.fromKG)
holder.binding.value.text = "$weightTxt${unit.unit}"
holder.binding.datetime.text = item.formatTimestamp()
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)
}
}
}
}

View File

@ -1,48 +1,14 @@
package com.dzeio.openhealth.core
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
abstract class BaseFragment<VM : BaseViewModel, VB : ViewBinding>(private val viewModelClass: Class<VM>) : Fragment() {
abstract class BaseFragment<VM : BaseViewModel, VB : ViewBinding>(
private val viewModelClass: Class<VM>
) :
BaseStaticFragment<VB>() {
val viewModel by lazy {
ViewModelProvider(this)[viewModelClass]
}
private var _binding: VB? = null
val binding get() = _binding!!
/**
* Setup everything!
*/
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
_binding = bindingInflater(inflater, container, false)
return binding.root
}
/**
* Function to inflate the Fragment Bindings
*
* use like this: `ViewBinding::inflater`
*/
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
/**
* Destroy binding
*/
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
}

View File

@ -0,0 +1,44 @@
package com.dzeio.openhealth.core
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
abstract class BaseStaticFragment<VB : ViewBinding> : Fragment() {
private var _binding: VB? = null
val binding get() = _binding!!
/**
* Setup everything!
*/
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
_binding = bindingInflater(inflater, container, false)
return binding.root
}
/**
* Function to inflate the Fragment Bindings
*
* use like this: `ViewBinding::inflater`
*/
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
/**
* Destroy binding
*/
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,22 @@
package com.dzeio.openhealth.di
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
class SystemModule {
@Singleton
@Provides
fun provideSettings(@ApplicationContext context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
}

View File

@ -0,0 +1,143 @@
package com.dzeio.openhealth.graphs
import android.graphics.Color
import android.view.View
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.units.Units
import com.dzeio.openhealth.utils.GraphUtils
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.LimitLine
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.google.android.material.color.MaterialColors
import kotlin.math.max
import kotlin.math.min
object WeightChart {
fun setup(
chart: LineChart,
view: View,
data: List<Weight>,
modifier: Units.Mass,
goal: Float?,
limit: Boolean = true
) {
GraphUtils.lineChartSetup(
chart,
MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorPrimary
),
MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorOnBackground
)
)
if (data.isEmpty()) {
return
}
// Axis Max/Min
var axisMin = max(data.minOf { it.weight } - 10, 0f)
var axisMax = data.maxOf { it.weight } + 10
if (goal != null) {
axisMax = max(axisMax, goal)
axisMin = min(axisMin, goal)
}
// Average calculation
val averageCalculation = min(30, max(3, data.size / 2))
val isEven = averageCalculation % 2 == 1
val midValue = averageCalculation / 2
val averageYs = data.mapIndexed { index, entry ->
var minItem = index - midValue
var maxItem = index + if (!isEven) midValue + 1 else midValue
val lastEntry = data.size - 1
if (minItem < 0) {
maxItem += kotlin.math.abs(minItem)
minItem = 0
}
if (maxItem >= lastEntry) {
val diff = maxItem - lastEntry
minItem = max(0, minItem - diff)
maxItem -= diff
}
var average = 0f
for (i in minItem..maxItem) {
average += data[i].weight
}
return@mapIndexed Entry(
entry.timestamp.toFloat(),
(average / (maxItem - minItem + 1)) * modifier.modifier
)
}
val rawData = GraphUtils.lineDataSet(
LineDataSet(
data.mapIndexed { _, weight ->
return@mapIndexed Entry(
weight.timestamp.toFloat(),
weight.weight * modifier.modifier
)
},
"Weight"
)
).apply {
axisDependency = YAxis.AxisDependency.RIGHT
}
val averageData = GraphUtils.lineDataSet(LineDataSet(averageYs, "Average")).apply {
axisDependency = YAxis.AxisDependency.RIGHT
color = Color.GREEN
}
val entries = ArrayList<Entry>()
for (item in data) {
entries.add(
Entry(
item.timestamp.toFloat(),
item.weight * modifier.modifier
)
)
}
chart.apply {
this.data = LineData(rawData, averageData)
val twoWeeks = (data[data.size - 1].timestamp - data[0].timestamp) > 1290000000f
if (twoWeeks && limit) {
// idk what I did but it works lol
setVisibleXRange(
0f, data[data.size - 1].timestamp / 1000f
)
axisRight.axisMinimum = axisMin * modifier.modifier
axisRight.axisMaximum = axisMax * modifier.modifier
// BIS... :(
// Also it invalidate the view so I don't have to call invalidate
moveViewToX(data[data.size - 1].timestamp - 1290000000f)
}
if (goal != null) {
val limit = LimitLine(goal * modifier.modifier)
limit.lineColor = Color.RED
val dash = 30f
limit.enableDashedLine(dash, dash, 1f)
limit.lineWidth = 1f
limit.textColor = Color.BLACK
axisRight.addLimitLine(limit)
}
}
}
}

View File

@ -6,5 +6,5 @@ enum class NotificationChannels(
val importance: Int
) {
// 3 is IMPORTANCE_DEFAULT
DEFAULT("openhealth_default", "Default Channel", 3)
}
WATER("water", "Water Notifications", 3)
}

View File

@ -8,6 +8,7 @@ import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.navigation.NavDeepLinkBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkerParameters
import com.dzeio.openhealth.Application
@ -42,23 +43,24 @@ class WaterReminderService(
Log.d(TAG, "Ran! ${Date().toLocaleString()}")
with(NotificationManagerCompat.from(context)) {
val flag =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_IMMUTABLE else 0
val intent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.mobile_navigation)
.setDestination(R.id.nav_home)
// Will nav to water home when there will be a way to add it there
// .setDestination(R.id.nav_water_home)
.createTaskStackBuilder()
.getPendingIntent(0, flag)
val builder =
NotificationCompat.Builder(context, NotificationChannels.DEFAULT.channelName)
NotificationCompat.Builder(context, NotificationChannels.WATER.id)
.setContentTitle("Did you drink?")
.setContentText("Drink now! ${Date().toLocaleString()}")
.setSmallIcon(R.drawable.ic_logo_small)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(
PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
},
flag
)
).build()
intent
)
.build()
notify(
NotificationIds.WaterIntake.ordinal,
builder
@ -66,4 +68,4 @@ class WaterReminderService(
}
return Result.success()
}
}
}

View File

@ -0,0 +1,45 @@
package com.dzeio.openhealth.ui.about
import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.dzeio.openhealth.BuildConfig
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseStaticFragment
import com.dzeio.openhealth.databinding.FragmentAboutBinding
class AboutFragment : BaseStaticFragment<FragmentAboutBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentAboutBinding
get() = FragmentAboutBinding::inflate
@SuppressLint("StringFormatInvalid")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.version.text =
resources.getString(R.string.version_number, BuildConfig.VERSION_NAME)
binding.contactUs.setOnClickListener {
openLink("mailto:context.openhealth@dze.io")
}
binding.github.setOnClickListener {
openLink("https://github.com/dzeiocom/OpenHealth")
}
}
private fun openLink(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(requireContext(), "Could not handle link $url", Toast.LENGTH_LONG).show()
}
}
}

View File

@ -1,9 +1,9 @@
package com.dzeio.openhealth.ui.home
import android.animation.ValueAnimator
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.RectF
import android.os.Bundle
import android.util.Log
@ -13,74 +13,61 @@ import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.dzeio.openhealth.Application
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.graphs.WeightChart
import com.dzeio.openhealth.ui.weight.AddWeightDialog
import com.dzeio.openhealth.units.UnitFactory
import com.dzeio.openhealth.utils.DrawUtils
import com.dzeio.openhealth.utils.GraphUtils
import com.github.mikephil.charting.components.LimitLine
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlin.math.min
import kotlin.properties.Delegates
import kotlinx.coroutines.flow.collectLatest
@AndroidEntryPoint
class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewModel::class.java) {
companion object {
const val TAG = "${Application.TAG}/HomeFragment"
}
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHomeBinding
get() = FragmentHomeBinding::inflate
private var intake by Delegates.notNull<Float>()
private val settings: SharedPreferences by lazy {
PreferenceManager.getDefaultSharedPreferences(requireContext())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Bindings
binding.addWeight.setOnClickListener {
AddWeightDialog().show(requireActivity().supportFragmentManager, null)
}
binding.fragmentHomeWaterAdd.setOnClickListener {
val water = viewModel.water.value
if (water == null) {
if (water == null || !water.isToday()) {
val w = Water()
w.value = 200
w.value = viewModel.waterCupSize
viewModel.updateWater(w)
} else {
water.value += 200
water.value += viewModel.waterCupSize
viewModel.updateWater(water)
}
}
intake = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getString("water_intake", "1200")?.toFloat() ?: 1200f
binding.fragmentHomeWaterTotal.text = "${intake.toInt()}ml"
binding.fragmentHomeWaterRemove.setOnClickListener { _ ->
binding.fragmentHomeWaterTotal.text =
String.format(
resources.getString(viewModel.waterUnit.unit),
viewModel.dailyWaterIntake
)
binding.fragmentHomeWaterRemove.setOnClickListener {
val water = viewModel.water.value
if (water != null) {
water.value -= 200
if (water.value == 0) {
water.value -= viewModel.waterCupSize
if (water.value <= 0) {
viewModel.deleteWater(water)
} else {
viewModel.updateWater(water)
@ -88,22 +75,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
}
binding.fragmentHomeWaterRemove.setOnClickListener {
lifecycleScope.launch {
val item = viewModel.fetchTodayWater().first()
Log.d(TAG, "Collected latest $it")
if (item != null) {
item.value -= 200
if (item.value == 0) {
viewModel.deleteWater(item)
} else {
viewModel.updateWater(item)
}
}
}
}
binding.listWeight.setOnClickListener {
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight())
}
@ -126,32 +97,13 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
private fun updateGraph(list: List<Weight>) {
if (list.isNotEmpty()) {
val entries = ArrayList<Entry>()
for (item in list) {
entries.add(Entry(item.timestamp.toFloat(), item.weight))
}
val dataSet = LineDataSet(entries, "Label").apply {
axisDependency = YAxis.AxisDependency.RIGHT
setDrawCircles(false)
setDrawCircleHole(false)
mode = LineDataSet.Mode.HORIZONTAL_BEZIER
}
binding.weightGraph.apply {
// Apply new dataset
data = LineData(dataSet)
// idk what I did but it works lol
setVisibleXRange(
0f, entries[entries.size - 1].x / 1000f
)
val goal = PreferenceManager.getDefaultSharedPreferences(requireContext())
.getString("weight_goal", null)?.toFloatOrNull()
WeightChart.setup(
binding.weightGraph,
requireView(),
list,
viewModel.weightUnit,
viewModel.goalWeight?.toFloat()
)
// legend.apply {
// isEnabled = true
@ -165,29 +117,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
// setCustom(arrayOf(legendEntry))
// }
// }
setDrawBorders(false)
// BIS... :(
// Also it invalidate the view so I don't have to call invalidate
moveViewToX(entries[entries.size - 1].x - 1600000000f)
if (goal != null) {
axisRight.axisMinimum = goal
val limit = LimitLine(goal)
limit.lineColor = Color.RED
val dash = 30f
limit.enableDashedLine(dash, dash, 0f)
limit.lineWidth = 1f
limit.textColor = Color.BLACK
limit.textSize = 12f
axisRight.addLimitLine(limit)
} else {
isAutoScaleMinMaxEnabled = true
}
}
}
}
override fun onStart() {
@ -197,23 +126,28 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
viewModel.fetchWeights().collectLatest {
updateGraph(it)
}
updateWater(0)
updateWater(1234)
updateWater(0, 1)
}
viewModel.water.observe(viewLifecycleOwner) {
Log.d(TAG, "${it?.formatTimestamp()} $it")
if (it != null) {
updateWater(it.value)
updateWater(0, it.value)
} else {
updateWater(0)
updateWater(0, 1)
}
}
}
private fun updateWater(water: Int) {
val oldValue = binding.fragmentHomeWaterCurrent.text.toString().replace("ml", "").toInt()
binding.fragmentHomeWaterCurrent.text = "${water}ml"
private fun updateWater(oldValue: Int, newValue: Int) {
val waterUnit =
UnitFactory.volume(settings.getString("water_unit", "milliliter") ?: "Milliliter")
binding.fragmentHomeWaterCurrent.text =
String.format(
resources.getString(waterUnit.unit),
(newValue * waterUnit.modifier).toInt()
)
var width = 1500
var height = 750
@ -260,25 +194,27 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
3f
)
Log.d("Test", "${min(oldValue.toFloat(), intake)} ${min(water.toFloat(), intake)}")
ValueAnimator.ofFloat(min(oldValue.toFloat(), intake), min(water.toFloat(), intake))
.apply {
duration = 300
addUpdateListener {
DrawUtils.drawArc(
canvas,
100 * it.animatedValue as Float / intake,
rect,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
),
6f
)
canvas.save()
binding.background.setImageBitmap(graph)
}
start()
ValueAnimator.ofInt(
min(oldValue, viewModel.dailyWaterIntake),
min(newValue, viewModel.dailyWaterIntake)
).apply {
duration = 300
addUpdateListener {
DrawUtils.drawArc(
canvas,
100 * it.animatedValue as Int / viewModel.dailyWaterIntake.toFloat(),
rect,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
),
6f
)
canvas.save()
binding.background.setImageBitmap(graph)
}
start()
}
}
}

View File

@ -1,30 +1,62 @@
package com.dzeio.openhealth.ui.home
import android.content.SharedPreferences
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.Application.Companion.TAG
import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.water.WaterRepository
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.data.weight.WeightRepository
import com.dzeio.openhealth.units.UnitFactory
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject internal constructor(
private val weightRepository: WeightRepository,
private val waterRepository: WaterRepository
private val waterRepository: WaterRepository,
settings: SharedPreferences
) : BaseViewModel() {
private val _water = MutableLiveData<Water?>(null)
val water: LiveData<Water?> = _water
var waterCupSize = settings.getInt("water_cup_size", 200)
var waterUnit =
UnitFactory.volume(settings.getString("water_unit", "milliliter") ?: "Milliliter")
var weightUnit =
UnitFactory.mass(settings.getString("weight_unit", "kilogram") ?: "kilogram")
val goalWeight: Int? =
(settings.getString("weight_goal", null)?.toIntOrNull())
val dailyWaterIntake: Int =
((settings.getString("water_intake", "1200")?.toFloatOrNull() ?: 1200f) * waterUnit.modifier)
.toInt()
init {
viewModelScope.launch {
waterRepository.todayWater().collectLatest {
_water.value = it
}
}
// don't listen for prefs changes
settings.registerOnSharedPreferenceChangeListener { _, key ->
Log.d(TAG, "Pref changed: $key")
when (key) {
"water_cup_size" -> {
waterCupSize = settings.getInt("water_cup_size", 200)
}
}
}
}
/**
@ -32,7 +64,6 @@ class HomeViewModel @Inject internal constructor(
*/
fun fetchWeights() = weightRepository.getWeights()
/**
* @deprecated
*/
@ -44,11 +75,7 @@ class HomeViewModel @Inject internal constructor(
suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight)
suspend fun fetchTodayWater() = waterRepository.todayWater()
private val _water = MutableLiveData<Water?>(null)
val water: LiveData<Water?> = _water
fun fetchTodayWater() = waterRepository.todayWater()
fun updateWater(water: Water) {
viewModelScope.launch {
@ -62,4 +89,4 @@ class HomeViewModel @Inject internal constructor(
_water.postValue(null)
}
}
}
}

View File

@ -1,18 +1,99 @@
package com.dzeio.openhealth.ui.settings
import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Bundle
import android.text.InputType
import android.util.Log
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.dzeio.openhealth.BuildConfig
import com.dzeio.openhealth.R
import com.dzeio.openhealth.units.UnitFactory
import java.util.Locale
class SettingsFragment : PreferenceFragmentCompat() {
val settings: SharedPreferences by lazy {
PreferenceManager.getDefaultSharedPreferences(requireContext())
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
// Force only numbers on Goal
val weightGoal = findPreference<EditTextPreference>("weight_goal")
weightGoal?.setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_NUMBER
weightGoal?.apply {
setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_NUMBER
}
val value = settings.getString("weight_goal", null)
val modifier = UnitFactory.mass(settings.getString("weight_unit", null) ?: "kilogram")
if (value != null && value.isNotEmpty()) {
text = (value.toFloat() * modifier.modifier).toString()
}
setOnPreferenceChangeListener { _, newValue ->
val alue = ((newValue as String).toInt() / modifier.modifier).toInt().toString()
settings.edit()
.putString(
"weight_goal",
alue
)
.apply()
text = alue
return@setOnPreferenceChangeListener false
}
}
// 251 kg
// 553 lb
findPreference<ListPreference>("weight_unit")?.apply {
setOnPreferenceChangeListener { _, newValue ->
val unit = settings.getString("weight_unit", "kilogram")
?: return@setOnPreferenceChangeListener true
val goal = settings.getString("weight_goal", null)
?: return@setOnPreferenceChangeListener true
val modifier = UnitFactory.mass(newValue as String)
val oldModifier = UnitFactory.mass(unit)
val value =
(goal.toFloat() / oldModifier.modifier * modifier.modifier).toInt().toString()
settings.edit()
.putString(
"weight_goal",
value
)
.apply()
weightGoal?.text = value
return@setOnPreferenceChangeListener true
}
}
val languagesPreference = findPreference<ListPreference>("global_language")
Log.d("TAG", Locale.getDefault().language)
languagesPreference?.apply {
entries = BuildConfig.LOCALES
entryValues = BuildConfig.LOCALES
setDefaultValue(Locale.getDefault().language)
}
// Update App Locale
languagesPreference?.setOnPreferenceChangeListener { _, newValue ->
val locale = Locale(newValue as String)
Locale.setDefault(locale)
val config = Configuration()
config.locale = locale
requireActivity().baseContext.resources.updateConfiguration(
config,
requireActivity().baseContext.resources.displayMetrics
)
requireActivity().recreate()
return@setOnPreferenceChangeListener true
}
}
}

View File

@ -75,7 +75,6 @@ class EditWaterDialog :
water.timestamp = tsp
binding.date.setText(water.formatTimestamp())
}
datePicker.show(fragManager, "dialog")
Log.d("Tag", "${date.year + 1900}, ${date.month}, ${date.day}")
@ -88,8 +87,6 @@ class EditWaterDialog :
} else {
TODO("VERSION.SDK_INT < N")
}
}
viewModel.init(args.id)
@ -133,6 +130,5 @@ class EditWaterDialog :
}
else -> super.onOptionsItemSelected(item)
}
}
}
}

View File

@ -1,13 +1,11 @@
package com.dzeio.openhealth.ui.water
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.openhealth.Application.Companion.TAG
import com.dzeio.openhealth.adapters.WaterAdapter
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding
@ -17,9 +15,6 @@ import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import java.util.Calendar
import java.util.Date
import java.util.TimeZone
@AndroidEntryPoint
class WaterHomeFragment :
@ -62,6 +57,10 @@ class WaterHomeFragment :
)
)
binding.buttonEditDefaultIntake.setOnClickListener {
findNavController().navigate(WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterSizeDialog())
}
chart.xAxis.valueFormatter = GraphUtils.DateValueFormatter(1000 * 60 * 60 * 24)
viewModel.items.observe(viewLifecycleOwner) { list ->

View File

@ -1,13 +1,102 @@
package com.dzeio.openhealth.ui.water
import android.graphics.Color
import android.view.LayoutInflater
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseDialog
import com.dzeio.openhealth.databinding.DialogWaterSizeSelectorBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class WaterSizeSelectorDialog :
BaseDialog<WaterSizeSelectorViewModel, DialogWaterSizeSelectorBinding>(
WaterSizeSelectorViewModel::class.java
) {
override val bindingInflater: (LayoutInflater) -> DialogWaterSizeSelectorBinding
get() = DialogWaterSizeSelectorBinding::inflate
override fun onCreated() {
super.onCreated()
binding.cancel.setOnClickListener {
dismiss()
}
binding.validate.setOnClickListener {
dismiss()
}
viewModel.cupSize.observe(this) {
binding.customSizeText.text = String.format(
getString(R.string.custom_amount),
"${it}ml"
)
binding.size100ml.setBackgroundColor(Color.TRANSPARENT)
binding.size250ml.setBackgroundColor(Color.TRANSPARENT)
binding.size500ml.setBackgroundColor(Color.TRANSPARENT)
binding.size1000ml.setBackgroundColor(Color.TRANSPARENT)
binding.customSize.setBackgroundColor(Color.TRANSPARENT)
val back = resources.getColor(
com.google.android.material.R.color.material_dynamic_primary95,
)
when (it) {
100 -> {
binding.size100ml.setBackgroundColor(back)
}
250 -> {
binding.size250ml.setBackgroundColor(back)
}
500 -> {
binding.size500ml.setBackgroundColor(back)
}
1000 -> {
binding.size1000ml.setBackgroundColor(back)
}
else -> {
binding.customSize.setBackgroundColor(back)
}
}
}
binding.size100ml.setOnClickListener {
viewModel.setCupSize(100)
}
binding.size250ml.setOnClickListener {
viewModel.setCupSize(250)
}
binding.size500ml.setOnClickListener {
viewModel.setCupSize(500)
}
binding.size1000ml.setOnClickListener {
viewModel.setCupSize(1000)
}
binding.customSize.setOnClickListener {
val editTextLayout = TextInputLayout(requireContext())
val editText = TextInputEditText(requireContext())
editText.setText(viewModel.cupSize.value.toString())
editTextLayout.addView(editText)
MaterialAlertDialogBuilder(requireContext())
.setView(editTextLayout)
.setTitle("Custom Cup Size")
.setOnCancelListener {
it.dismiss()
}
.setPositiveButton(
R.string.validate
) { dialog, _ ->
viewModel.setCupSize(editText.text.toString().toInt())
dismiss()
dialog.dismiss()
}
.show()
}
}
}

View File

@ -1,6 +1,32 @@
package com.dzeio.openhealth.ui.water
import android.content.SharedPreferences
import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.core.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
class WaterSizeSelectorViewModel : BaseViewModel() {
@HiltViewModel
class WaterSizeSelectorViewModel @Inject constructor(
private val settings: SharedPreferences
) : BaseViewModel() {
private val _cupSize = MutableLiveData(0)
val cupSize = _cupSize
init {
val cup = settings.getInt("water_cup_size", -1)
if (cup != -1) {
_cupSize.value = cup
}
}
fun setCupSize(value: Int) {
settings.edit()
.putInt("water_cup_size", value)
.apply()
_cupSize.value = value
}
}

View File

@ -29,7 +29,6 @@ class AddWeightDialog : BaseDialog<HomeViewModel, DialogAddWeightBinding>(HomeVi
setNegativeButton("Cancel") { dialog, _ ->
dialog.cancel()
}
}
}
@ -40,7 +39,7 @@ class AddWeightDialog : BaseDialog<HomeViewModel, DialogAddWeightBinding>(HomeVi
viewModel.lastWeight().collect {
if (it != null) {
binding.kg.value = it.weight.toInt()
binding.gram.value = ((it.weight - it.weight.toInt()) * 10 ).toInt()
binding.gram.value = ((it.weight - it.weight.toInt()) * 10).toInt()
}
}
}
@ -56,12 +55,11 @@ class AddWeightDialog : BaseDialog<HomeViewModel, DialogAddWeightBinding>(HomeVi
val weight = Weight().apply {
weight = binding.kg.value + (binding.gram.value.toFloat() / 10)
source = "OpenHealth"
}
lifecycleScope.launchWhenCreated {
viewModel.addWeight(weight)
}
//callback?.invoke()
// callback?.invoke()
dialog?.dismiss()
}
}
}

View File

@ -2,15 +2,24 @@ package com.dzeio.openhealth.ui.weight
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.openhealth.R
import com.dzeio.openhealth.adapters.WeightAdapter
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.graphs.WeightChart
import com.dzeio.openhealth.ui.home.HomeViewModel
import com.dzeio.openhealth.units.WeightUnit
import com.dzeio.openhealth.utils.GraphUtils
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
@ -21,33 +30,82 @@ class ListWeightFragment :
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentListWeightBinding =
FragmentListWeightBinding::inflate
val settings by lazy {
PreferenceManager.getDefaultSharedPreferences(requireContext())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
val recycler = binding.list
val manager = LinearLayoutManager(requireContext())
recycler.layoutManager = manager
val adapter = WeightAdapter()
val unit = settings.getString("weight_unit", "Kilogram") ?: "Kilogram"
adapter.unit = WeightUnit.fromSettings(unit)
adapter.onItemClick = {
findNavController().navigate(
ListWeightFragmentDirections.actionNavListWeightToNavEditWeight(
it.id
)
)
//EditWeightDialog().show(requireActivity().supportFragmentManager, "dialog")
// EditWeightDialog().show(requireActivity().supportFragmentManager, "dialog")
}
recycler.adapter = adapter
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
viewModel.fetchWeights().collectLatest {
updateGraph(it)
val itt = it.toMutableList()
itt.sortWith { o1, o2 -> if (o1.timestamp > o2.timestamp) -1 else 1 }
adapter.set(itt)
}
}
GraphUtils.lineChartSetup(
binding.chart,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
),
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnBackground
)
)
}
}
private fun updateGraph(list: List<Weight>) {
WeightChart.setup(
binding.chart,
requireView(),
list,
viewModel.weightUnit,
viewModel.goalWeight?.toFloat(),
false
)
}
override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.action_add).isVisible = true
super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_add -> {
findNavController().navigate(ListWeightFragmentDirections.actionNavListWeightToNavAddWeightDialog())
true
}
else -> super.onOptionsItemSelected(item)
}
}
}

View File

@ -0,0 +1,20 @@
package com.dzeio.openhealth.units
object UnitFactory {
fun mass(unit: String): Units.Mass {
return when (unit.lowercase()) {
"kilogram", "kilograms", "kg" -> Units.Mass.KILOGRAM
"pound", "pounds", "lb" -> Units.Mass.POUND
else -> Units.Mass.KILOGRAM
}
}
fun volume(unit: String): Units.Volume {
return when (unit.lowercase()) {
"milliliter", "milliliters", "ml" -> Units.Volume.MILLILITER
"imperial ounce", "imperial ounces", "oz" -> Units.Volume.IMPERIAL_OUNCE
"us ounce", "us ounces" -> Units.Volume.US_OUNCE
else -> Units.Volume.MILLILITER
}
}
}

View File

@ -0,0 +1,57 @@
package com.dzeio.openhealth.units
import com.dzeio.openhealth.R
object Units {
enum class Mass(
/**
* Value based on the Kilogram
*/
val modifier: Float,
val singular: Int,
val plural: Int,
val unit: Int
) {
KILOGRAM(
1f,
R.string.unit_mass_kilogram_name_singular,
R.string.unit_mass_kilogram_name_plural,
R.string.unit_mass_kilogram_unit
),
POUND(
0.45359237f,
R.string.unit_mass_pound_name_singular,
R.string.unit_mass_pound_name_plural,
R.string.unit_mass_pound_unit
)
}
enum class Volume(
/**
* Value based on the Kilogram
*/
val modifier: Float,
val singular: Int,
val plural: Int,
val unit: Int
) {
MILLILITER(
1f,
R.string.unit_volume_milliliter_name_singular,
R.string.unit_volume_milliliter_name_plural,
R.string.unit_volume_milliliter_unit
),
IMPERIAL_OUNCE(
0.03519503f,
R.string.unit_volume_imperial_ounce_name_singular,
R.string.unit_volume_imperial_ounce_name_plural,
R.string.unit_volume_ounce_unit
),
US_OUNCE(
0.03381413f,
R.string.unit_volume_us_ounce_name_singular,
R.string.unit_volume_us_ounce_name_plural,
R.string.unit_volume_ounce_unit
)
}
}

View File

@ -0,0 +1,21 @@
package com.dzeio.openhealth.units
enum class WaterUnit(
val unit: String,
val fromML: Float
) {
ML("ml", 1f),
US_OZ("oz", 0.03381413f),
IMP_OZ("oz", 0.03519503f);
companion object {
fun fromSettings(value: String): WaterUnit {
return when (value.lowercase()) {
"milliliter" -> ML
"us ounce" -> US_OZ
"imperial ounce" -> IMP_OZ
else -> ML
}
}
}
}

View File

@ -0,0 +1,19 @@
package com.dzeio.openhealth.units
enum class WeightUnit(
val unit: String,
val fromKG: Float
) {
KG("kg", 1f),
LBS("lbs", 2.2046226218488f);
companion object {
fun fromSettings(value: String): WeightUnit {
return when (value.lowercase()) {
"kilogram" -> KG
"pounds" -> LBS
else -> KG
}
}
}
}

View File

@ -8,6 +8,7 @@ import com.github.mikephil.charting.components.Description
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.formatter.ValueFormatter
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet
import java.text.SimpleDateFormat
@ -21,6 +22,14 @@ object GraphUtils {
// chart.isAutoScaleMinMaxEnabled = true
}
fun lineDataSet(lineDataSet: LineDataSet): LineDataSet {
return lineDataSet.apply {
setDrawCircles(false)
setDrawCircleHole(false)
mode = LineDataSet.Mode.HORIZONTAL_BEZIER
}
}
fun barChartSetup(chart: BarChart, mainColor: Int, textColor: Int) {
barLineChartSetup(chart, mainColor, textColor)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,16h5v-2H3V16zM9.5,16h5v-2h-5V16zM16,16h5v-2h-5V16zM3,20h2v-2H3V20zM7,20h2v-2H7V20zM11,20h2v-2h-2V20zM15,20h2v-2h-2V20zM19,20h2v-2h-2V20zM3,12h8v-2H3V12zM13,12h8v-2h-8V12zM3,4v4h18V4H3z"/>
</vector>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:pathData="M10.9,2.1c-4.6,0.5 -8.3,4.2 -8.8,8.7c-0.5,4.7 2.2,8.9 6.3,10.5C8.7,21.4 9,21.2 9,20.8v-1.6c0,0 -0.4,0.1 -0.9,0.1c-1.4,0 -2,-1.2 -2.1,-1.9c-0.1,-0.4 -0.3,-0.7 -0.6,-1C5.1,16.3 5,16.3 5,16.2C5,16 5.3,16 5.4,16c0.6,0 1.1,0.7 1.3,1c0.5,0.8 1.1,1 1.4,1c0.4,0 0.7,-0.1 0.9,-0.2c0.1,-0.7 0.4,-1.4 1,-1.8c-2.3,-0.5 -4,-1.8 -4,-4c0,-1.1 0.5,-2.2 1.2,-3C7.1,8.8 7,8.3 7,7.6C7,7.2 7,6.6 7.3,6c0,0 1.4,0 2.8,1.3C10.6,7.1 11.3,7 12,7s1.4,0.1 2,0.3C15.3,6 16.8,6 16.8,6C17,6.6 17,7.2 17,7.6c0,0.8 -0.1,1.2 -0.2,1.4c0.7,0.8 1.2,1.8 1.2,3c0,2.2 -1.7,3.5 -4,4c0.6,0.5 1,1.4 1,2.3v2.6c0,0.3 0.3,0.6 0.7,0.5c3.7,-1.5 6.3,-5.1 6.3,-9.3C22,6.1 16.9,1.4 10.9,2.1z"
android:fillColor="@android:color/white"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M14.06,9.02l0.92,0.92L5.92,19L5,19v-0.92l9.06,-9.06M17.66,3c-0.25,0 -0.51,0.1 -0.7,0.29l-1.83,1.83 3.75,3.75 1.83,-1.83c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.2,-0.2 -0.45,-0.29 -0.71,-0.29zM14.06,6.19L3,17.25L3,21h3.75L17.81,9.94l-3.75,-3.75z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M4,19h16v2L4,21zM20,3L4,3v10c0,2.21 1.79,4 4,4h6c2.21,0 4,-1.79 4,-4v-3h2c1.11,0 2,-0.9 2,-2L22,5c0,-1.11 -0.89,-2 -2,-2zM16,13c0,1.1 -0.9,2 -2,2L8,15c-1.1,0 -2,-0.9 -2,-2L6,5h10v8zM20,8h-2L18,5h2v3z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,2l2.01,18.23C5.13,21.23 5.97,22 7,22h10c1.03,0 1.87,-0.77 1.99,-1.77L21,2L3,2zM17,20l-10,0.01L5.89,10L18.1,10L17,20zM18.33,8L5.67,8l-0.44,-4h13.53l-0.43,4zM12,19c1.66,0 3,-1.34 3,-3 0,-2 -3,-5.4 -3,-5.4S9,14 9,16c0,1.66 1.34,3 3,3zM12,13.91c0.59,0.91 1,1.73 1,2.09 0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1c0,-0.37 0.41,-1.19 1,-2.09z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M22,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6zM20,6l-8,4.99L4,6h16zM20,18L4,18L4,8l8,5 8,-5v10z"/>
</vector>

View File

@ -1,7 +1,182 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_outline_local_drink_24" />
<TextView
style="@style/TextAppearance.Material3.HeadlineSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:textAlignment="center"
android:text="@string/switch_cup_size" />
<TableLayout
android:layout_width="match_parent"
android:layout_marginBottom="8dp"
android:layout_height="wrap_content">
<TableRow
android:layout_width="match_parent"
android:layout_marginBottom="8dp"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:id="@+id/size_100ml"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_outline_local_drink_24" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_marginStart="8dp"
android:text="100ml" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:id="@+id/size_250ml"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_outline_local_drink_24" />
<TextView
android:layout_width="match_parent"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="250ml" />
</LinearLayout>
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/size_500ml"
android:gravity="center"
android:orientation="horizontal"
android:layout_marginEnd="8dp"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_outline_local_drink_24" />
<TextView
android:layout_width="match_parent"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="500ml" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/size_1000ml"
android:layout_marginStart="8dp"
android:gravity="center"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_outline_local_drink_24" />
<TextView
android:layout_width="match_parent"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="1000ml" />
</LinearLayout>
</TableRow>
</TableLayout>
<LinearLayout
android:layout_width="match_parent"
android:id="@+id/custom_size"
android:layout_height="wrap_content"
android:background="@color/material_dynamic_primary90"
android:gravity="center"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_outline_edit_24" />
<TextView
android:layout_width="match_parent"
android:id="@+id/custom_size_text"
android:layout_height="wrap_content"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_marginStart="8dp"
android:text="@string/custom_amount"
tools:text="Custom amount: 100ml" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_marginEnd="16dp"
android:text="@android:string/cancel" />
<Button
android:id="@+id/validate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/validate" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_margin="32dp"
app:srcCompat="@drawable/ic_logo_full" />
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tagline"
android:textAlignment="center" />
<TextView
android:id="@+id/version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:drawablePadding="16dp"
android:gravity="center_vertical"
android:text="@string/version_number"
app:drawableStartCompat="@drawable/ic_baseline_extension_24" />
<TextView
android:id="@+id/github"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:drawablePadding="16dp"
android:gravity="center_vertical"
android:text="@string/about_star_on_github"
app:drawableStartCompat="@drawable/ic_logo_github" />
<TextView
android:id="@+id/contact_us"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:drawablePadding="16dp"
android:gravity="center_vertical"
android:text="@string/contact_us"
app:drawableStartCompat="@drawable/ic_outline_mail_24" />
<TextView
android:id="@+id/licenses"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:drawablePadding="16dp"
android:gravity="center_vertical"
android:text="@string/licenses"
app:drawableStartCompat="@drawable/ic_baseline_line_style_24" />
</LinearLayout>

View File

@ -15,7 +15,7 @@
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
@ -40,7 +40,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Weight" />
android:text="@string/water_intake" />
<LinearLayout
android:layout_width="wrap_content"
@ -48,12 +48,6 @@
android:layout_weight="1"
android:gravity="end">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="16dp"
android:src="@drawable/ic_baseline_add_24" />
<ImageView
android:id="@+id/goto_water_home"
android:layout_width="24dp"
@ -75,7 +69,7 @@
android:id="@+id/fragment_home_water_remove"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="16dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:src="@drawable/ic_outline_hexagon_24"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
@ -98,7 +92,7 @@
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="900ml"
android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" />
@ -107,7 +101,7 @@
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="1200ml"
android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" />
</LinearLayout>
@ -117,7 +111,7 @@
android:id="@+id/fragment_home_water_add"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="16dp"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:src="@drawable/ic_baseline_add_24"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
@ -180,7 +174,7 @@
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Weight"
android:text="@string/weight"
android:layout_weight="1" />
<LinearLayout
@ -193,7 +187,7 @@
android:id="@+id/add_weight"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_baseline_add_24" />
<ImageView
@ -203,7 +197,6 @@
android:src="@drawable/ic_outline_hexagon_24" />
</LinearLayout>
</LinearLayout>
<com.github.mikephil.charting.charts.LineChart
@ -212,18 +205,8 @@
android:layout_height="200dp"
android:minHeight="200dp" />
<!-- <com.jjoe64.graphview.GraphView-->
<!-- android:id="@+id/weight_graph"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="200dp"-->
<!-- android:minHeight="200dp"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toBottomOf="@+id/constraintLayout2" />-->
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>

View File

@ -1,14 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:clipToPadding="false"
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="200dp"
android:minHeight="200dp" />
</com.google.android.material.card.MaterialCardView>
<androidx.recyclerview.widget.RecyclerView
android:clipToPadding="false"
android:id="@+id/list"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/layout_item_list"
tools:context=".ui.weight.ListWeightFragment" />
</LinearLayout>
tools:listitem="@layout/layout_item_list"
tools:context=".ui.weight.ListWeightFragment" />

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/side_nav_bar"
android:gravity="bottom"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/nav_header_desc"
android:paddingTop="@dimen/nav_header_vertical_spacing"
app:srcCompat="@mipmap/ic_launcher_round" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="@string/nav_header_title"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nav_header_subtitle" />
</LinearLayout>

View File

@ -1,16 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_add"
android:visible="false"
android:icon="@drawable/ic_baseline_add_24"
android:title="@string/add"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="ifRoom"
android:title="@string/page_settings"
android:icon="@drawable/ic_baseline_settings_24"/>
<item
android:id="@+id/action_extensions"
android:title="@string/menu_extensions"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_baseline_extension_24"
/>
</menu>
android:icon="@drawable/ic_baseline_extension_24" />
<item
android:id="@+id/action_about"
android:title="@string/about" />
</menu>

View File

@ -38,6 +38,12 @@
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_nav_home_to_nav_add_weight_dialog"
app:destination="@id/nav_add_weight_dialog" />
<action
android:id="@+id/action_nav_home_to_aboutFragment"
app:destination="@id/aboutFragment" />
</fragment>
<fragment
@ -53,7 +59,7 @@
<fragment
android:id="@+id/nav_list_weight"
android:name="com.dzeio.openhealth.ui.weight.ListWeightFragment"
android:label="@string/menu_list_weight"
android:label="@string/weight"
tools:layout="@layout/fragment_list_weight" >
<action
android:id="@+id/action_nav_list_weight_to_nav_edit_weight"
@ -62,6 +68,9 @@
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_nav_list_weight_to_nav_add_weight_dialog"
app:destination="@id/nav_add_weight_dialog" />
</fragment>
<fragment
@ -88,11 +97,45 @@
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_nav_water_home_to_nav_water_size_dialog"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right"
app:destination="@id/nav_water_size_dialog" />
</fragment>
<dialog
android:id="@+id/nav_water_size_dialog"
android:name="com.dzeio.openhealth.ui.water.WaterSizeSelectorDialog"
tools:layout="@layout/dialog_water_size_selector"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right">
</dialog>
<dialog
android:id="@+id/nav_add_weight_dialog"
android:name="com.dzeio.openhealth.ui.weight.AddWeightDialog"
tools:layout="@layout/dialog_water_size_selector"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right">
</dialog>
<fragment
android:id="@+id/nav_settings"
android:name="com.dzeio.openhealth.ui.settings.SettingsFragment">
android:label="@string/page_settings"
android:name="com.dzeio.openhealth.ui.settings.SettingsFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right">
</fragment>
@ -125,4 +168,9 @@
app:argType="string" />
</fragment>
</navigation>
<fragment
android:id="@+id/aboutFragment"
android:name="com.dzeio.openhealth.ui.about.AboutFragment"
android:label="AboutFragment"
tools:layout="@layout/fragment_about"/>
</navigation>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="unit_mass_kilogram_name_singular">Kilograme</string>
<string name="unit_mass_kilogram_name_plural">Kilogrames</string>
<string name="unit_mass_pound_name_singular">Livre</string>
<string name="unit_mass_pound_name_plural">Livres</string>
<string name="unit_volume_milliliter_name_singular">Millilitre</string>
<string name="unit_volume_milliliter_name_plural">Millilitres</string>
<string name="unit_volume_imperial_ounce_name_singular">Once Impérial</string>
<string name="unit_volume_imperial_ounce_name_plural">OncesImpérial</string>
<string name="unit_volume_us_ounce_name_singular">Once États Unis</string>
<string name="unit_volume_us_ounce_name_plural">Onces États Unis</string>
<string name="extension_informations">Les importations sont faites au démarrage de l\'application\nLes exports sont faits lorsque qu\'il y a des nouvelles valeurs dans l\'application</string>
<string name="menu_home">Accueil</string>
<string name="page_settings">Paramètres</string>
<string name="nav_water_home">Truc D\'eau</string>
<string name="languages">Langages</string>
<string name="settings_global">Paramètres Globaux</string>
<string name="weight">Poids</string>
<string name="water_intake">Consomation d\'eau</string>
<string name="switch_cup_size">Changer la taille du verre</string>
<string name="validate">Valider</string>
<string name="custom_amount">Montant personnalisée: %1$s</string>
<string name="menu_import">Importer</string>
<string name="menu_edit_weight">Changer le poid</string>
<string name="menu_extensions">Extensions</string>
<string name="tagline">Ton Application de santé libre, open source et respectueuse de la vie privée</string>
<string name="add">Ajouter</string>
<string name="about">A Propos</string>
<string name="version_number" formatted="false">Version %1$</string>
<string name="about_star_on_github">Mettez une étoile sur Github</string>
<string name="contact_us">Contactez-nous</string>
<string name="licenses">Licenses</string>
</resources>

View File

@ -4,8 +4,13 @@
<item>Male</item>
<item>Female</item>
</string-array>
<string-array name="water_units">
<item>Millimeters</item>
<item>Ounces</item>
<string-array name="weight_units">
<item name="kg">@string/unit_mass_kilogram_name_plural</item>
<item name="lb">@string/unit_mass_pound_name_singular</item>
</string-array>
</resources>
<string-array name="water_units">
<item name="ml">@string/unit_volume_milliliter_name_singular</item>
<item name="us_oz">@string/unit_volume_imperial_ounce_name_singular</item>
<item name="im_oz">@string/unit_volume_us_ounce_name_singular</item>
</string-array>
</resources>

View File

@ -1,20 +1,44 @@
<resources>
<string name="app_name">OpenHealth</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">Android Studio</string>
<string name="nav_header_subtitle">android.studio@android.com</string>
<string name="nav_header_desc">Navigation header</string>
<string name="action_settings">Settings</string>
<string name="app_name" translatable="false">OpenHealth</string>
<string name="tagline">Your privacy-friendly FOSS Health Application </string>
<string name="page_settings">Settings</string>
<string name="menu_home">Home</string>
<string name="menu_gallery">Gallery</string>
<string name="menu_slideshow">Slideshow</string>
<string name="menu_import">Import</string>
<string name="menu_list_weight">Weight List</string>
<string name="menu_edit_weight">Edit Weight</string>
<string name="nav_list_water">Water Intake</string>
<string name="nav_water_home">Water Intake</string>
<string name="menu_extensions">Extensions</string>
<string name="extension_informations">Imports are done at app startup\nExports are done when new inputs are give to the app</string>
</resources>
<!-- Units -->
<string name="unit_mass_kilogram_name_singular">Kilogram</string>
<string name="unit_mass_kilogram_name_plural">Kilograms</string>
<string name="unit_mass_kilogram_unit" translatable="false">%1$skg</string>
<string name="unit_mass_pound_name_singular">Pound</string>
<string name="unit_mass_pound_name_plural">Pounds</string>
<string name="unit_mass_pound_unit" translatable="false">%1$slb</string>
<string name="unit_volume_milliliter_name_singular">Milliliter</string>
<string name="unit_volume_milliliter_name_plural">Milliliters</string>
<string name="unit_volume_milliliter_unit" translatable="false">%1$sml</string>
<string name="unit_volume_imperial_ounce_name_singular">Imperial Ounce</string>
<string name="unit_volume_imperial_ounce_name_plural">Imperial Ounces</string>
<string name="unit_volume_us_ounce_name_singular">US Ounce</string>
<string name="unit_volume_us_ounce_name_plural">US Ounces</string>
<string name="unit_volume_ounce_unit" translatable="false">%1$soz</string>
<string name="languages">Languages</string>
<string name="settings_global">Global Settings</string>
<string name="weight">Weight</string>
<string name="water_intake">Water Intake</string>
<string name="switch_cup_size">Switch cup size</string>
<string name="validate">Validate</string>
<string name="custom_amount">Custom amount: %1$s</string>
<string name="add">Add</string>
<string name="about">About</string>
<string name="version_number" formatted="false">Version %1$s</string>
<string name="about_star_on_github">Star on Github</string>
<string name="contact_us">Contact us</string>
<string name="licenses">Licenses</string>
</resources>

View File

@ -1,13 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Global Settings">
<PreferenceCategory android:title="@string/settings_global">
<ListPreference
android:defaultValue="Male"
android:entries="@array/genders"
android:entryValues="@array/genders"
android:key="global_gender"
android:title="Gender" />
<ListPreference
android:key="global_language"
android:title="@string/languages" />
</PreferenceCategory>
<PreferenceCategory android:title="Weight Settings">
@ -24,6 +28,14 @@
android:digits="0123456789"
android:inputType="numberDecimal"
android:title="Goal Weight" />
<ListPreference
android:defaultValue="1"
android:entries="@array/weight_units"
android:entryValues="@array/weight_units"
android:key="weight_unit"
android:title="Weight Unit" />
</PreferenceCategory>
<PreferenceCategory android:title="Water Settings">