1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-06-13 09:29:19 +00:00

feat: Too much things to say.

This commit is contained in:
2023-01-29 19:10:46 +01:00
parent 357024770a
commit 2628bc1403
36 changed files with 550 additions and 678 deletions

View File

@ -7,13 +7,9 @@ import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.food.Food
import com.dzeio.openhealth.databinding.ItemFoodBinding
import com.dzeio.openhealth.utils.DownloadImageTask
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.dzeio.openhealth.utils.NetworkUtils
import kotlin.math.roundToInt
class FoodAdapter : BaseAdapter<Food, ItemFoodBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ItemFoodBinding
@ -26,16 +22,13 @@ class FoodAdapter : BaseAdapter<Food, ItemFoodBinding>() {
item: Food,
position: Int
) {
CoroutineScope(Dispatchers.IO).launch {
// Download remote picture
if (item.image != null) {
NetworkUtils.getImageInBackground(holder.binding.productImage, item.image!!)
}
// Download remote picture
DownloadImageTask(holder.binding.productImage).execute(item.image)
// set the food name
holder.binding.foodName.text = item.name
holder.binding.foodName.text = item.name + " ${item.id}"
// set the food description
holder.binding.foodDescription.text = holder.itemView.context.getString(

View File

@ -1,6 +1,7 @@
package com.dzeio.openhealth.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseAdapter
@ -15,6 +16,8 @@ class StepsAdapter() : BaseAdapter<Step, LayoutItemListBinding>() {
var onItemClick: ((weight: Step) -> Unit)? = null
var isDay = false
override fun onBindData(
holder: BaseViewHolder<LayoutItemListBinding>,
item: Step,
@ -27,7 +30,13 @@ class StepsAdapter() : BaseAdapter<Step, LayoutItemListBinding>() {
)
// set the datetime
holder.binding.datetime.text = item.formatTimestamp()
holder.binding.datetime.text = item.formatTimestamp(!isDay)
if (isDay) {
holder.binding.iconRight.visibility = View.GONE
} else {
holder.binding.iconRight.setImageResource(R.drawable.ic_zoom_out_map)
}
// set the callback
holder.binding.edit.setOnClickListener {

View File

@ -10,7 +10,6 @@ import androidx.viewbinding.ViewBinding
*/
abstract class BaseActivity<VB : ViewBinding>() : AppCompatActivity() {
/**
* Function to inflate the Fragment Bindings
*

View File

@ -47,5 +47,4 @@ abstract class BaseAdapter<T, VB : ViewBinding> : RecyclerView.Adapter<BaseViewH
}
override fun getItemCount(): Int = items.size
}

View File

@ -8,7 +8,9 @@ import androidx.viewbinding.ViewBinding
*
* note: Dialog crash app with viewmodel error? add @AndroidEntryPoint
*/
abstract class BaseDialog<VM : BaseViewModel, VB : ViewBinding>(private val viewModelClass: Class<VM>) :
abstract class BaseDialog<VM : BaseViewModel, VB : ViewBinding>(
private val viewModelClass: Class<VM>
) :
BaseSimpleDialog<VB>() {
val viewModel by lazy {

View File

@ -16,7 +16,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
/**
* Base around the DialogFragment class to simplify usage
*/
abstract class BaseFullscreenDialog<VM : BaseViewModel, VB : ViewBinding>(private val viewModelClass: Class<VM>) : DialogFragment() {
abstract class BaseFullscreenDialog<VM : BaseViewModel, VB : ViewBinding>(
private val viewModelClass: Class<VM>
) : DialogFragment() {
/**
* Lazyload the viewModel
@ -33,7 +35,6 @@ abstract class BaseFullscreenDialog<VM : BaseViewModel, VB : ViewBinding>(privat
*/
abstract val bindingInflater: (LayoutInflater) -> VB
/**
* Function run when the dialog was created
*/
@ -44,7 +45,6 @@ abstract class BaseFullscreenDialog<VM : BaseViewModel, VB : ViewBinding>(privat
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bindingInflater(inflater)
setHasOptionsMenu(true)

View File

@ -39,7 +39,6 @@ abstract class BaseStaticFragment<VB : ViewBinding> : Fragment() {
return binding.root
}
/**
* Destroy binding
*/

View File

@ -7,6 +7,5 @@ import androidx.viewbinding.ViewBinding
* Simple implementation of RecyclerView.ViewHolder to limitate usage
*/
class BaseViewHolder<VB : ViewBinding>(
val binding : VB
) : RecyclerView.ViewHolder(binding.root) {
}
val binding: VB
) : RecyclerView.ViewHolder(binding.root)

View File

@ -10,7 +10,6 @@ open class Observable<T>(baseValue: T) {
private val functionObservers: ArrayList<(T) -> Unit> = ArrayList()
fun addObserver(fn: (T) -> Unit) {
if (!functionObservers.contains(fn)) {
functionObservers.add(fn)
@ -38,7 +37,6 @@ open class Observable<T>(baseValue: T) {
}
fun notifyObservers() {
// Notify Functions
for (fn in functionObservers) {
notifyObserver(fn)

View File

@ -1,7 +1,6 @@
package com.dzeio.openhealth.data
import android.content.Context
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@ -27,12 +26,8 @@ import com.dzeio.openhealth.data.weight.WeightDao
Food::class
],
// TODO: go back to version 1 when the app is published
version = 3,
exportSchema = true,
autoMigrations = [
AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3)
]
version = 1,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {

View File

@ -57,7 +57,7 @@ data class Food(
/**
* When the entry was added to our Database
*/
var timestamp: Long = Calendar.getInstance(TimeZone.getTimeZone("UTC")).timeInMillis,
var timestamp: Long = Calendar.getInstance(TimeZone.getTimeZone("UTC")).timeInMillis
) {
companion object {
@ -66,7 +66,10 @@ data class Food(
*/
fun fromOpenFoodFact(food: OFFProduct, quantity: Float? = null): Food? {
// filter out foods that we can't use in the app
if (food.name == null || ((food.servingSize == null || food.servingSize == "") && (food.quantity == null || food.quantity == "") && food.servingQuantity == null && food.productQuantity == null)) {
if (
food.nutriments == null ||
food.name == null ||
((food.servingSize == null || food.servingSize == "") && (food.quantity == null || food.quantity == "") && food.servingQuantity == null && food.productQuantity == null)) {
return null
}
@ -78,10 +81,13 @@ data class Food(
} else if (food.productQuantity != null && food.productQuantity != 0f) {
eaten = food.productQuantity!!
} else if (food.servingSize != null || food.quantity != null) {
Log.d("pouet", ".${food.servingSize ?: food.quantity}. .${(food.servingSize ?: food.quantity)!!.replace(Regex(" +\\w+$"), "")}. ${food}")
eaten = (food.servingSize ?: food.quantity)!!.trim().replace(Regex(" +\\w+$"), "").toInt().toFloat()
eaten = (food.servingSize ?: food.quantity)!!.trim().replace(
Regex(" +\\w+$"),
""
).toInt().toFloat()
}
}
Log.d("Food", "$food")
return Food(
name = food.name!!,
// do some slight edit on the serving to remove strange entries like `100 g`
@ -91,7 +97,8 @@ data class Food(
carbohydrates = food.nutriments.carbohydrates,
fat = food.nutriments.fat,
// handle case where the energy is not given in kcal but only in kj
energy = food.nutriments.energy ?: (food.nutriments.energyKJ * 0.2390057361).toFloat(),
energy = food.nutriments.energy
?: (food.nutriments.energyKJ * 0.2390057361).toFloat(),
image = food.image
)
}

View File

@ -1,17 +1,76 @@
package com.dzeio.openhealth.data.food
import com.dzeio.openhealth.data.openfoodfact.OFFResult
import com.dzeio.openhealth.data.openfoodfact.OpenFoodFactService
import retrofit2.Response
import com.dzeio.openhealth.utils.NetworkResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject
class FoodRepository @Inject constructor(
private val dao: FoodDao,
private val offSource: OpenFoodFactService
) {
suspend fun searchOpenFoodFact(name: String): Response<OFFResult> =
offSource.searchProducts(name)
suspend fun searchFood(name: String): Flow<NetworkResult<List<Food>>> = channelFlow {
val result = NetworkResult<List<Food>>()
val items = arrayListOf<Food>()
var otherFinished = false
launch { // Search OFF
try {
val request = offSource.searchProducts(name)
if (!request.isSuccessful) {
if (otherFinished) {
result.status = NetworkResult.NetworkStatus.ERRORED
} else {
otherFinished = true
}
send(result)
return@launch
}
val offProducts =
offSource.searchProducts(name)
.body()?.products?.map { Food.fromOpenFoodFact(it) }
if (offProducts != null) {
items.addAll(offProducts.filterNotNull())
result.data = items
if (otherFinished) {
result.status = NetworkResult.NetworkStatus.FINISHED
} else {
otherFinished = true
}
send(result)
}
} catch (e: IOException) {
if (otherFinished) {
result.status = NetworkResult.NetworkStatus.ERRORED
} else {
otherFinished = true
}
send(result)
}
}
launch { // search local DB
getAll().collectLatest { list ->
val filtered = list.filter { it.name.contains(name, true) }
items.removeAll { it.id > 0 }
items.addAll(0, filtered)
result.data = items
if (otherFinished) {
result.status = NetworkResult.NetworkStatus.FINISHED
} else {
otherFinished = true
}
send(result)
}
}
}
fun getAll() = dao.getAll()
@ -22,5 +81,4 @@ class FoodRepository @Inject constructor(
suspend fun delete(food: Food) = dao.delete(food)
suspend fun update(food: Food) = dao.update(food)
}

View File

@ -44,19 +44,24 @@ data class Step(
}
}
fun formatTimestamp(): String {
val formatter = DateFormat.getDateTimeInstance(
DateFormat.SHORT,
DateFormat.SHORT,
Locale.getDefault()
)
fun formatTimestamp(removeTime: Boolean = false): String {
val formatter = if (removeTime) {
DateFormat.getDateInstance(
DateFormat.SHORT,
Locale.getDefault()
)
} else {
DateFormat.getDateTimeInstance(
DateFormat.SHORT,
DateFormat.SHORT,
Locale.getDefault()
)
}
return formatter.format(Date(this.timestamp))
}
fun isToday(): Boolean {
val it = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
it.timeInMillis = timestamp
it.set(Calendar.HOUR, 0)
val it = getDay()
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
@ -64,7 +69,17 @@ data class Step(
cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0)
cal.set(Calendar.MILLISECOND, 0)
return it.timeInMillis == cal.timeInMillis
return it == cal.timeInMillis
}
fun getDay(): Long {
val it = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
timeInMillis = timestamp
set(Calendar.HOUR, 0)
set(Calendar.AM_PM, Calendar.AM)
}
return it.timeInMillis
}
fun isCurrent(): Boolean {

View File

@ -7,7 +7,7 @@ import androidx.navigation.fragment.navArgs
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseDialog
import com.dzeio.openhealth.databinding.DialogFoodProductBinding
import com.dzeio.openhealth.utils.DownloadImageTask
import com.dzeio.openhealth.utils.NetworkUtils
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@ -61,7 +61,12 @@ class FoodDialog :
updateGraphs(null)
binding.serving.text = "Serving: ${it.serving}"
DownloadImageTask(binding.image).execute(it.image)
if (it.image != null) {
NetworkUtils.getImageInBackground(
binding.image,
it.image!!
)
}
binding.quantity.setText(it.quantity.toString())
}

View File

@ -10,13 +10,16 @@ import com.dzeio.openhealth.R
import com.dzeio.openhealth.adapters.FoodAdapter
import com.dzeio.openhealth.core.BaseDialog
import com.dzeio.openhealth.databinding.DialogFoodSearchProductBinding
import com.dzeio.openhealth.utils.NetworkResult
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class SearchFoodDialog :
BaseDialog<SearchFoodDialogViewModel, DialogFoodSearchProductBinding>(SearchFoodDialogViewModel::class.java) {
BaseDialog<SearchFoodDialogViewModel, DialogFoodSearchProductBinding>(
SearchFoodDialogViewModel::class.java
) {
override val bindingInflater: (LayoutInflater) -> DialogFoodSearchProductBinding =
DialogFoodSearchProductBinding::inflate
@ -42,7 +45,6 @@ class SearchFoodDialog :
btn.setOnClickListener {
viewModel.search(binding.input.text.toString())
binding.loading.visibility = View.VISIBLE
}
}
}
@ -50,8 +52,6 @@ class SearchFoodDialog :
override fun onCreated() {
super.onCreated()
val recycler = binding.list
val manager = LinearLayoutManager(requireContext())
@ -59,7 +59,6 @@ class SearchFoodDialog :
val adapter = FoodAdapter()
adapter.onItemClick = {
lifecycleScope.launch {
val id = viewModel.addProduct(it)
findNavController().navigate(
@ -73,9 +72,13 @@ class SearchFoodDialog :
recycler.adapter = adapter
viewModel.items.observe(this) {
adapter.set(it)
binding.loading.visibility = View.GONE
adapter.set(it.data ?: arrayListOf())
if (it.status == NetworkResult.NetworkStatus.FINISHED) {
binding.loading.visibility = View.GONE
} else if (it.status == NetworkResult.NetworkStatus.ERRORED) {
binding.errorText.visibility = View.VISIBLE
binding.loading.visibility = View.GONE
}
}
}
}

View File

@ -5,35 +5,30 @@ import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.food.Food
import com.dzeio.openhealth.data.food.FoodRepository
import com.dzeio.openhealth.data.openfoodfact.OpenFoodFactService
import com.dzeio.openhealth.utils.NetworkResult
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.util.ArrayList
import javax.inject.Inject
@HiltViewModel
class SearchFoodDialogViewModel @Inject internal constructor(
private val foodRepository: FoodRepository,
private val foodFactService: OpenFoodFactService
private val foodRepository: FoodRepository
) : BaseViewModel() {
val items: MutableLiveData<List<Food>> = MutableLiveData()
val items: MutableLiveData<NetworkResult<List<Food>>> = MutableLiveData()
fun search(text: String) {
viewModelScope.launch {
val response = foodFactService.searchProducts(text)
val product = response.body()
if (product != null) {
items.postValue(
product.products
.map { Food.fromOpenFoodFact(it) }
.filter { it != null } as List<Food>
)
foodRepository.searchFood(text).collectLatest {
items.postValue(it)
}
}
}
suspend fun addProduct(product: Food): Long {
if (product.id > 0) {
product.id = 0
}
return foodRepository.add(product)
}
}

View File

@ -4,12 +4,14 @@ import android.animation.ValueAnimator
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.Line
import com.dzeio.charts.series.LineSerie
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.water.Water
@ -156,6 +158,13 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
serie.entries = entries
if (viewModel.goalWeight.value != null) {
chart.yAxis.addLine(
viewModel.goalWeight.value!!,
Line(true, Paint(chart.yAxis.linePaint).apply { strokeWidth = 4f })
)
}
if (list.isEmpty()) {
chart.xAxis.x = 0.0
} else {

View File

@ -1,9 +1,12 @@
package com.dzeio.openhealth.ui.steps
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.Line
@ -11,6 +14,7 @@ import com.dzeio.charts.series.BarSerie
import com.dzeio.openhealth.Application
import com.dzeio.openhealth.adapters.StepsAdapter
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.step.Step
import com.dzeio.openhealth.databinding.FragmentStepsHomeBinding
import com.dzeio.openhealth.utils.ChartUtils
import dagger.hilt.android.AndroidEntryPoint
@ -29,6 +33,8 @@ class StepsHomeFragment :
const val TAG = "${Application.TAG}/SHFragment"
}
private val args: StepsHomeFragmentArgs by navArgs()
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentStepsHomeBinding =
FragmentStepsHomeBinding::inflate
@ -37,18 +43,26 @@ class StepsHomeFragment :
viewModel.init()
val isDay = args.day > 0L
val recycler = binding.list
val manager = LinearLayoutManager(requireContext())
recycler.layoutManager = manager
val adapter = StepsAdapter()
adapter.onItemClick = {
// findNavController().navigate(
// WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterEdit(
// it.id
// )
// )
val adapter = StepsAdapter().apply {
this.isDay = isDay
}
if (!isDay) {
adapter.onItemClick = {
findNavController().navigate(
StepsHomeFragmentDirections.actionNavStepsHomeSelf().apply {
day = it.timestamp
title = "Steps from " + it.formatTimestamp(true)
}
)
}
}
recycler.adapter = adapter
@ -64,69 +78,103 @@ class StepsHomeFragment :
}
xAxis.apply {
dataWidth = 604800000.0
dataWidth = if (isDay) 8.64e+7 else 6.048e+8
scrollEnabled = !isDay
textPaint.textSize = 32f
onValueFormat = onValueFormat@{
val formatter = DateFormat.getDateInstance(
DateFormat.SHORT,
Locale.getDefault()
)
val formatter = if (isDay) {
DateFormat.getTimeInstance(
DateFormat.SHORT,
Locale.getDefault()
)
} else {
DateFormat.getDateInstance(
DateFormat.SHORT,
Locale.getDefault()
)
}
return@onValueFormat formatter.format(Date(it.toLong()))
}
}
annotator.annotationTitleFormat = { "${it.y.roundToInt()} steps" }
annotator.annotationSubTitleFormat = annotationSubTitleFormat@{
val formatter = DateFormat.getDateInstance(
DateFormat.SHORT,
Locale.getDefault()
)
val formatter = if (isDay) {
DateFormat.getTimeInstance(
DateFormat.SHORT,
Locale.getDefault()
)
} else {
DateFormat.getDateInstance(
DateFormat.SHORT,
Locale.getDefault()
)
}
return@annotationSubTitleFormat formatter.format(Date(it.x.toLong()))
}
}
viewModel.goal.observe(viewLifecycleOwner) {
if (it != null) {
chart.yAxis.addLine(it.toFloat(), Line(true))
if (it != null && !isDay) {
chart.yAxis.addLine(
it.toFloat(),
Line(true, Paint(chart.yAxis.linePaint).apply { strokeWidth = 4f })
)
chart.refresh()
}
}
viewModel.items.observe(viewLifecycleOwner) { list ->
adapter.set(list)
if (list.isEmpty()) {
adapter.set(arrayListOf())
return@observe
}
// chart.scroller.zoomEnabled = false
val filtered = if (!isDay) list else list.filter {
it.getDay() == args.day
}
if (isDay) {
adapter.set(filtered)
serie.entries = filtered.map {
Entry(
it.timestamp.toDouble(),
it.value.toFloat()
)
} as ArrayList<Entry>
} else {
val entries: HashMap<Long, Entry> = HashMap()
val entries: HashMap<Long, Entry> = HashMap()
list.forEach {
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
cal.timeInMillis = it.timestamp
list.forEach {
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
cal.timeInMillis = it.timestamp
cal.set(Calendar.HOUR, 0)
cal.set(Calendar.AM_PM, Calendar.AM)
val ts = cal.timeInMillis
if (!entries.containsKey(ts)) {
entries[ts] = Entry((ts).toDouble(), 0F, chart.yAxis.goalLinePaint.color)
}
cal.set(Calendar.HOUR, 0)
cal.set(Calendar.AM_PM, Calendar.AM)
val ts = cal.timeInMillis
if (!entries.containsKey(ts)) {
entries[ts] = Entry((ts).toDouble(), 0F, chart.yAxis.goalLinePaint.color)
}
entries[ts]!!.y += it.value.toFloat()
entries[ts]!!.y += it.value.toFloat()
if (viewModel.goal.value != null) {
if (entries[ts]!!.y > viewModel.goal.value!!) {
if (viewModel.goal.value != null) {
if (entries[ts]!!.y > viewModel.goal.value!!) {
entries[ts]!!.color = null
}
} else {
entries[ts]!!.color = null
}
} else {
entries[ts]!!.color = null
}
adapter.set(
entries.map { Step(value = it.value.y.toInt(), timestamp = it.key) }
.sortedByDescending { it.timestamp }
)
serie.entries = ArrayList(entries.values)
}
serie.entries = ArrayList(entries.values)
chart.xAxis.x = chart.xAxis.getXMin()
chart.xAxis.x =
chart.xAxis.getXMax() - chart.xAxis.dataWidth!! + chart.xAxis.dataWidth!! / (if (isDay) 24 else 7)
chart.refresh()
}

View File

@ -1,16 +1,20 @@
package com.dzeio.openhealth.ui.weight
import android.content.SharedPreferences
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.MenuProvider
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.Line
import com.dzeio.charts.series.LineSerie
import com.dzeio.openhealth.R
import com.dzeio.openhealth.adapters.WeightAdapter
@ -37,8 +41,27 @@ class ListWeightFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// FIXME: deprecated
setHasOptionsMenu(true)
// Menu
requireActivity().addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menu.findItem(R.id.action_add).isVisible = true
}
override fun onMenuItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_add -> {
findNavController().navigate(
ListWeightFragmentDirections.actionNavListWeightToNavWeightDialog(
WeightDialog.DialogTypes.ADD_WEIGHT.ordinal
)
)
true
}
else -> true
}
}
})
if (viewModel.goalWeight.value != null) {
binding.goalButton.setText(R.string.edit_goal)
@ -46,7 +69,9 @@ class ListWeightFragment :
binding.goalButton.setOnClickListener {
findNavController().navigate(
ListWeightFragmentDirections.actionNavListWeightToNavWeightDialog(WeightDialog.DialogTypes.EDIT_GOAL.ordinal)
ListWeightFragmentDirections.actionNavListWeightToNavWeightDialog(
WeightDialog.DialogTypes.EDIT_GOAL.ordinal
)
)
}
@ -110,7 +135,6 @@ class ListWeightFragment :
xAxis.apply {
// 7 day history
dataWidth = (7 * 24 * 60 * 60 * 1000).toDouble()
textPaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimaryContainer
@ -128,15 +152,15 @@ class ListWeightFragment :
}
// Debug button
if (binding.debugRandomValues != null) {
binding.debugRandomValues.setOnClickListener {
viewModel.generateRandomValues()
}
binding.debugRandomValues.setOnLongClickListener {
viewModel.delete(viewModel.weights.value!!)
return@setOnLongClickListener true
}
}
// if (binding.debugRandomValues != null) {
// binding.debugRandomValues.setOnClickListener {
// viewModel.generateRandomValues()
// }
// binding.debugRandomValues.setOnLongClickListener {
// viewModel.delete(viewModel.weights.value!!)
// return@setOnLongClickListener true
// }
// }
}
private fun updateGraph(list: List<Weight>) {
@ -146,13 +170,22 @@ class ListWeightFragment :
val entries: ArrayList<Entry> = arrayListOf()
list.forEach {
entries.add(Entry(
it.timestamp.toDouble(),
it.weight
))
entries.add(
Entry(
it.timestamp.toDouble(),
it.weight
)
)
}
serie.entries = entries
if (viewModel.goalWeight.value != null) {
chart.yAxis.addLine(
viewModel.goalWeight.value!!,
Line(true, Paint(chart.yAxis.linePaint).apply { strokeWidth = 4f })
)
}
if (list.isEmpty()) {
chart.xAxis.x = 0.0
} else {
@ -161,27 +194,4 @@ class ListWeightFragment :
chart.refresh()
}
@Deprecated("Deprecated in Java")
override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.action_add).isVisible = true
super.onPrepareOptionsMenu(menu)
}
@Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_add -> {
findNavController().navigate(
ListWeightFragmentDirections.actionNavListWeightToNavWeightDialog(
WeightDialog.DialogTypes.ADD_WEIGHT.ordinal
)
)
true
}
else -> super.onOptionsItemSelected(item)
}
}
}

View File

@ -21,7 +21,6 @@ class ListWeightViewModel @Inject internal constructor(
private val settings: Configuration
) : BaseViewModel() {
private val _massUnit = MutableLiveData(Units.Mass.KILOGRAM)
val massUnit: LiveData<Units.Mass> = _massUnit
@ -31,7 +30,6 @@ class ListWeightViewModel @Inject internal constructor(
private val _weights = MutableLiveData<List<Weight>?>(null)
val weights: LiveData<List<Weight>?> = _weights
init {
viewModelScope.launch {
weightRepository.getWeights().collectLatest {
@ -50,7 +48,7 @@ class ListWeightViewModel @Inject internal constructor(
}
}
fun generateRandomValues(): Unit {
fun generateRandomValues() {
viewModelScope.launch {
weightRepository.addWeight(
Weight(

View File

@ -1,37 +0,0 @@
package com.dzeio.openhealth.utils
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.AsyncTask
import android.util.Log
import android.widget.ImageView
import java.net.URL
/**
* Stolen from StackOverflow https://stackoverflow.com/a/10868126/7335674
*
* Allows to download an image asynchronously
*
* TODO: rework so it is not deprecated anymore
*/
class DownloadImageTask(var bmImage: ImageView) :
AsyncTask<String?, Void?, Bitmap?>() {
@Deprecated("Deprecated in Java")
override fun doInBackground(vararg urls: String?): Bitmap? {
val urldisplay = urls[0]
var mIcon11: Bitmap? = null
try {
val `in` = URL(urldisplay).openStream()
mIcon11 = BitmapFactory.decodeStream(`in`)
} catch (e: Exception) {
Log.e("Error", e.message!!)
e.printStackTrace()
}
return mIcon11
}
@Deprecated("Deprecated in Java", ReplaceWith("bmImage.setImageBitmap(result)"))
override fun onPostExecute(result: Bitmap?) {
bmImage.setImageBitmap(result)
}
}

View File

@ -0,0 +1,12 @@
package com.dzeio.openhealth.utils
data class NetworkResult<T>(
var status: NetworkStatus = NetworkStatus.RUNNING,
var data: T? = null
) {
enum class NetworkStatus {
RUNNING,
FINISHED,
ERRORED
}
}

View File

@ -0,0 +1,39 @@
package com.dzeio.openhealth.utils
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.ImageView
import java.net.URL
import java.util.concurrent.Executors
object NetworkUtils {
/**
* Fetch an image and apply it to an [image] [ImageView]
*
* Adapted from https://stackoverflow.com/a/10868126/7335674
* to not be deprecated
*
* @param image the ImageView
* @param url the url to fetch the image from
*/
fun getImageInBackground(image: ImageView, url: String) {
val executor = Executors.newSingleThreadExecutor()
val handler = Handler(Looper.getMainLooper())
executor.execute {
var bitmap: Bitmap? = null
try {
val `in` = URL(url).openStream()
bitmap = BitmapFactory.decodeStream(`in`)
handler.post {
image.setImageBitmap(bitmap)
}
} catch (e: Exception) {
Log.e("Error", e.message!!)
e.printStackTrace()
}
}
}
}

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="?attr/colorControlNormal" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15,3l2.3,2.3l-2.89,2.87l1.42,1.42L18.7,6.7L21,9V3H15zM3,9l2.3,-2.3l2.87,2.89l1.42,-1.42L6.7,5.3L9,3H3V9zM9,21l-2.3,-2.3l2.89,-2.87l-1.42,-1.42L5.3,17.3L3,15v6H9zM21,15l-2.3,2.3l-2.87,-2.89l-1.42,1.42l2.89,2.87L15,21h6V15z"/>
</vector>

View File

@ -19,12 +19,21 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/error_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:text="@string/connectivity_error" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:itemCount="5"
tools:listitem="@layout/item_food"/>
tools:listitem="@layout/item_food">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>

View File

@ -53,7 +53,7 @@
</LinearLayout>
<com.github.mikephil.charting.charts.LineChart
<com.dzeio.charts.ChartView
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="200dp"

View File

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="?attr/materialCardViewFilledStyle"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
@ -41,10 +39,11 @@
</LinearLayout>
<ImageView
android:id="@+id/icon_right"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_edit_24" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.card.MaterialCardView>

View File

@ -134,9 +134,23 @@
tools:layout="@layout/fragment_activity" />
<fragment
android:id="@+id/nav_steps_home"
android:label="{title}"
android:name="com.dzeio.openhealth.ui.steps.StepsHomeFragment"
android:label="@string/menu_steps"
tools:layout="@layout/fragment_steps_home" />
tools:layout="@layout/fragment_steps_home" >
<argument
android:name="day"
app:argType="long"
android:defaultValue="-1L"
/>
<argument
android:name="title"
app:argType="string"
android:defaultValue="Steps"
/>
<action
android:id="@+id/action_nav_steps_home_self"
app:destination="@id/nav_steps_home" />
</fragment>
<dialog
android:id="@+id/nav_weight_dialog"

View File

@ -54,5 +54,6 @@
<string name="steps_taken">Pas pris</string>
<string name="error_reporter_crash">Erreur lors de la géneration d\'un rapport d\'erreur</string>
<string name="steps_count">%1$d pas</string>
<string name="connectivity_error">Il semplerais que nous ne pouvons pas communiquer avec OpenFoodFact, Merci de re-essayer plus tard</string>
</resources>

View File

@ -67,4 +67,5 @@
<string name="error_reporter_crash">An error occurred while making the error report</string>
<string name="food_description" translatable="false">%1$s (%2$.0f kcal)</string>
<string name="steps_count">%1$d steps</string>
<string name="connectivity_error">It seems that we can\'t communicate with OpenFoodFact, please retry later</string>
</resources>