1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-23 11:22:10 +00:00

feat: Add support for Food input

This commit is contained in:
Florian Bouillon 2023-01-07 22:11:20 +01:00
parent 4bcae95d52
commit 643861bf1e
Signed by: Florian Bouillon
GPG Key ID: BEEAF3722D0EBF64
31 changed files with 1194 additions and 81 deletions

View File

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 2, "version": 2,
"identityHash": "794ed5ee15db239f9a2708b951f55552", "identityHash": "0f92ae44f4503b964d4986959a15ef4e",
"entities": [ "entities": [
{ {
"tableName": "Weight", "tableName": "Weight",
@ -150,7 +150,7 @@
}, },
{ {
"tableName": "Food", "tableName": "Food",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `quantity` INTEGER NOT NULL, `proteins` REAL NOT NULL, `carbohydrates` REAL NOT NULL, `fat` REAL NOT NULL, `energy` REAL NOT NULL, `timestamp` INTEGER NOT NULL)", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `serving` TEXT NOT NULL, `quantity` INTEGER NOT NULL, `proteins` REAL NOT NULL, `carbohydrates` REAL NOT NULL, `fat` REAL NOT NULL, `energy` REAL NOT NULL, `timestamp` INTEGER NOT NULL)",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -164,6 +164,12 @@
"affinity": "TEXT", "affinity": "TEXT",
"notNull": true "notNull": true
}, },
{
"fieldPath": "serving",
"columnName": "serving",
"affinity": "TEXT",
"notNull": true
},
{ {
"fieldPath": "quantity", "fieldPath": "quantity",
"columnName": "quantity", "columnName": "quantity",
@ -214,7 +220,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '794ed5ee15db239f9a2708b951f55552')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0f92ae44f4503b964d4986959a15ef4e')"
] ]
} }
} }

View File

@ -0,0 +1,232 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "414712cc283c7f1d14cde8e00da277fb",
"entities": [
{
"tableName": "Weight",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `weight` REAL NOT NULL, `timestamp` INTEGER NOT NULL, `source` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "weight",
"columnName": "weight",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_Weight_timestamp",
"unique": false,
"columnNames": [
"timestamp"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Weight_timestamp` ON `${TABLE_NAME}` (`timestamp`)"
}
],
"foreignKeys": []
},
{
"tableName": "Water",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `value` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `source` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_Water_timestamp",
"unique": false,
"columnNames": [
"timestamp"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Water_timestamp` ON `${TABLE_NAME}` (`timestamp`)"
}
],
"foreignKeys": []
},
{
"tableName": "Step",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `value` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `source` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_Step_timestamp",
"unique": false,
"columnNames": [
"timestamp"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Step_timestamp` ON `${TABLE_NAME}` (`timestamp`)"
}
],
"foreignKeys": []
},
{
"tableName": "Food",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `serving` TEXT NOT NULL, `quantity` REAL NOT NULL, `proteins` REAL NOT NULL, `carbohydrates` REAL NOT NULL, `fat` REAL NOT NULL, `energy` REAL NOT NULL, `image` TEXT, `timestamp` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serving",
"columnName": "serving",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "quantity",
"columnName": "quantity",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "proteins",
"columnName": "proteins",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "carbohydrates",
"columnName": "carbohydrates",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "fat",
"columnName": "fat",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "energy",
"columnName": "energy",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "image",
"columnName": "image",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '414712cc283c7f1d14cde8e00da277fb')"
]
}
}

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" 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.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" 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="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

View File

@ -6,8 +6,11 @@ import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.food.Food import com.dzeio.openhealth.data.food.Food
import com.dzeio.openhealth.databinding.ItemFoodBinding import com.dzeio.openhealth.databinding.ItemFoodBinding
import com.dzeio.openhealth.utils.DownloadImageTask
import kotlin.math.roundToInt
class FoodAdapter() : BaseAdapter<Food, ItemFoodBinding>() {
class FoodAdapter : BaseAdapter<Food, ItemFoodBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ItemFoodBinding override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ItemFoodBinding
get() = ItemFoodBinding::inflate get() = ItemFoodBinding::inflate
@ -19,8 +22,9 @@ class FoodAdapter() : BaseAdapter<Food, ItemFoodBinding>() {
item: Food, item: Food,
position: Int position: Int
) { ) {
holder.binding.foodName.text = item.name DownloadImageTask(holder.binding.productImage).execute(item.image)
holder.binding.foodDescription.text = item.energy.toString() holder.binding.foodName.text = "${item.name}"
holder.binding.foodDescription.text = "${item.quantity.roundToInt()}${item.serving.replace(Regex("\\d+"), "")} (${(item.energy / 100 * item.quantity).roundToInt()} kcal)"
holder.binding.edit.setOnClickListener { holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item) onItemClick?.invoke(item)
} }

View File

@ -5,6 +5,8 @@ import androidx.room.AutoMigration
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.dzeio.openhealth.data.food.Food import com.dzeio.openhealth.data.food.Food
import com.dzeio.openhealth.data.food.FoodDao import com.dzeio.openhealth.data.food.FoodDao
import com.dzeio.openhealth.data.step.Step import com.dzeio.openhealth.data.step.Step
@ -21,7 +23,7 @@ import com.dzeio.openhealth.data.weight.WeightDao
Step::class, Step::class,
Food::class Food::class
], ],
version = 2, version = 3,
exportSchema = true, exportSchema = true,
autoMigrations = [ autoMigrations = [
AutoMigration(from = 1, to = 2) AutoMigration(from = 1, to = 2)
@ -54,6 +56,7 @@ abstract class AppDatabase : RoomDatabase() {
// https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785 // https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785
private fun buildDatabase(context: Context): AppDatabase { private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.addMigrations(MIGRATION_2_3)
// .addCallback(object : Callback() { // .addCallback(object : Callback() {
// override fun onCreate(db: SupportSQLiteDatabase) { // override fun onCreate(db: SupportSQLiteDatabase) {
// super.onCreate(db) // super.onCreate(db)
@ -66,5 +69,13 @@ abstract class AppDatabase : RoomDatabase() {
// }) // })
.build() .build()
} }
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Food ADD COLUMN serving TEXT NOT NULL")
database.execSQL("ALTER TABLE Food ADD COLUMN image TEXT")
database.execSQL("ALTER TABLE Food ")
}
}
} }
} }

View File

@ -2,6 +2,7 @@ package com.dzeio.openhealth.data.food
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.dzeio.openhealth.data.openfoodfact.OFFProduct
import java.util.Calendar import java.util.Calendar
import java.util.TimeZone import java.util.TimeZone
@ -10,11 +11,40 @@ data class Food(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0, var id: Long = 0,
var name: String, var name: String,
var quantity: Int, var serving: String,
var quantity: Float,
var proteins: Float, var proteins: Float,
var carbohydrates: Float, var carbohydrates: Float,
var fat: Float, var fat: Float,
var energy: Float, var energy: Float,
var timestamp: Long = Calendar.getInstance(TimeZone.getTimeZone("UTC")).timeInMillis /**
) * the url of the image
*/
var image: String?,
var timestamp: Long = Calendar.getInstance(TimeZone.getTimeZone("UTC")).timeInMillis,
) {
companion object {
fun fromOpenFoodFact(food: OFFProduct, quantity: Float? = null): Food {
var eaten = quantity ?: food.servingQuantity ?: food.productQuantity ?: 0f
if (eaten == 0f) {
if (food.servingQuantity != null && food.servingQuantity != 0f) {
eaten = food.servingQuantity!!
} else if (food.productQuantity != null && food.productQuantity != 0f) {
eaten = food.productQuantity!!
}
}
return Food(
name = food.name,
serving = (food.servingSize ?: food.quantity ?: "unknown").replace(Regex(" +"), ""),
quantity = eaten,
proteins = food.nutriments.proteins,
carbohydrates = food.nutriments.carbohydrates,
fat = food.nutriments.fat,
energy = food.nutriments.energy ?: (food.nutriments.energyKJ * 0.2390057361).toFloat(),
image = food.image
)
}
}
}

View File

@ -16,4 +16,12 @@ class FoodRepository @Inject constructor(
fun getAll() = dao.getAll() fun getAll() = dao.getAll()
suspend fun add(food: Food) = dao.insert(food)
fun getById(id: Long) = dao.getOne(id)
suspend fun delete(food: Food) = dao.delete(food)
suspend fun update(food: Food) = dao.update(food)
} }

View File

@ -6,7 +6,9 @@ data class OFFNutriments(
@SerializedName("carbohydrates_100g") @SerializedName("carbohydrates_100g")
var carbohydrates: Float, var carbohydrates: Float,
@SerializedName("energy-kcal_100g") @SerializedName("energy-kcal_100g")
var energy: Float, var energy: Float?,
@SerializedName("energy-kj_100g")
var energyKJ: Float,
@SerializedName("fat_100g") @SerializedName("fat_100g")
var fat: Float, var fat: Float,
@SerializedName("proteins_100g") @SerializedName("proteins_100g")

View File

@ -9,8 +9,20 @@ data class OFFProduct(
var name: String, var name: String,
@SerializedName("serving_size") @SerializedName("serving_size")
var serving: String, var servingSize: String?,
@SerializedName("serving_quantity")
var servingQuantity: Float?,
@SerializedName("quantity")
var quantity: String?,
@SerializedName("product_quantity")
var productQuantity: Float?,
@SerializedName("nutriments") @SerializedName("nutriments")
var nutriments: OFFNutriments var nutriments: OFFNutriments,
@SerializedName("image_url")
var image: String?
) )

View File

@ -1,8 +1,6 @@
package com.dzeio.openhealth.data.openfoodfact package com.dzeio.openhealth.data.openfoodfact
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Response import retrofit2.Response
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
@ -14,25 +12,30 @@ interface OpenFoodFactService {
companion object { companion object {
fun getService(): OpenFoodFactService { fun getService(): OpenFoodFactService {
val interceptor = HttpLoggingInterceptor() // val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY) // interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder() // val client = OkHttpClient.Builder()
.addInterceptor(interceptor) // .addInterceptor(interceptor)
.build() // .build()
val gson = GsonBuilder() val gson = GsonBuilder()
.setLenient() .setLenient()
.create() .create()
val retrofit = Retrofit.Builder() val retrofit = Retrofit.Builder()
.baseUrl("https://world.openfoodfacts.org/") .baseUrl("https://world.openfoodfacts.org/")
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create(gson))
.client(client) // .client(client)
.build() .build()
return retrofit.create(OpenFoodFactService::class.java) return retrofit.create(OpenFoodFactService::class.java)
} }
} }
@Headers("User-Agent: OpenHealth - Android - Version 1.0 - https://github.com/dzeiocom/OpenHealth")
@GET("/cgi/search.pl?json=true&fields=_id,nutriments,product_name,serving_quantity,serving_size,quantity,product_quantity,image_url&action=process")
suspend fun searchProducts(@Query("search_terms2") name: String): Response<OFFResult>
@Headers("User-Agent: OpenHealth - Android - Version 1.0 - https://github.com/dzeiocom/OpenHealth") @Headers("User-Agent: OpenHealth - Android - Version 1.0 - https://github.com/dzeiocom/OpenHealth")
@GET("/api/v2/search?fields=_id,nutriments,product_name,serving_quantity") @GET("/api/v2/search?fields=_id,nutriments,product_name,serving_quantity")
suspend fun searchProducts(@Query("product_name") name: String): Response<OFFResult> suspend fun findByCode(@Query("code") code: String): Response<OFFResult>
} }

View File

@ -0,0 +1,88 @@
package com.dzeio.openhealth.ui.food
import android.util.Log
import android.view.LayoutInflater
import androidx.core.widget.addTextChangedListener
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.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class FoodDialog :
BaseDialog<FoodDialogViewModel, DialogFoodProductBinding>(FoodDialogViewModel::class.java) {
private val args: FoodDialogArgs by navArgs()
private var quantity: Float? = null
override val bindingInflater: (LayoutInflater) -> DialogFoodProductBinding =
DialogFoodProductBinding::inflate
override fun onBuilderInit(builder: MaterialAlertDialogBuilder) {
super.onBuilderInit(builder)
builder.apply {
setTitle("Product")
setIcon(R.drawable.ic_outline_fastfood_24)
setPositiveButton(R.string.validate) { dialog, _ ->
if (quantity != null) {
viewModel.saveNewQuantity(quantity!!)
}
dialog.dismiss()
}
setNegativeButton(R.string.cancel) { dialog, _ ->
if (args.deleteOnCancel) {
viewModel.delete()
}
dialog.cancel()
}
setNeutralButton(R.string.delete) { _, _ ->
viewModel.delete()
}
}
}
override fun onCreated() {
super.onCreated()
Log.d("FoodDialog", args.id.toString())
viewModel.init(args.id)
viewModel.items.observe(this) {
Log.d("FoodDialog", it.toString())
updateGraphs(null)
binding.serving.text = "Serving: ${it.serving}"
DownloadImageTask(binding.image).execute(it.image)
binding.quantity.setText(it.quantity.toString())
}
binding.quantity.addTextChangedListener {
updateGraphs(binding.quantity.text.toString().toFloatOrNull())
}
}
private fun updateGraphs(newQuantity: Float?) {
quantity = newQuantity
viewModel.items.value?.let {
val transformer = newQuantity ?: it.quantity
val energy = it.energy / 100 * transformer
binding.energyTxt.text = "${energy.toInt()} / 2594kcal"
binding.energyBar.progress = (100 * energy / 2594).toInt()
val proteins = it.proteins / 100 * transformer
binding.proteinsTxt.text = "${proteins.toInt()} / 130g"
binding.proteinsBar.progress = (100 * proteins / 130).toInt()
val carbohydrates = it.carbohydrates / 100 * transformer
binding.carbsTxt.text = "${carbohydrates.toInt()} / 324g"
binding.carbsBar.progress = (100 * carbohydrates / 324).toInt()
val fat = it.fat / 100 * transformer
binding.fatTxt.text = "${fat.toInt()} / 87g"
binding.fatBar.progress = (100 * fat / 87).toInt()
}
}
}

View File

@ -0,0 +1,55 @@
package com.dzeio.openhealth.ui.food
import androidx.lifecycle.MutableLiveData
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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class FoodDialogViewModel @Inject internal constructor(
private val foodRepository: FoodRepository
) : BaseViewModel() {
val items: MutableLiveData<Food> = MutableLiveData()
fun init(productId: Long) {
viewModelScope.launch {
val res = foodRepository.getById(productId)
val food = res.first()
if (food != null) {
items.postValue(food)
}
}
}
fun delete() {
viewModelScope.launch {
val item = items.value
if (item != null) {
foodRepository.delete(item)
}
}
}
fun saveNewQuantity(quantity: Float) {
val it = items.value
if (it == null) {
return
}
val transformer = (quantity ?: it.quantity) / it.quantity
it.energy = it.energy * transformer
it.proteins = it.proteins * transformer
it.carbohydrates = it.carbohydrates * transformer
it.fat = it.fat * transformer
it.quantity = quantity
viewModelScope.launch {
foodRepository.update(it)
}
}
}

View File

@ -2,15 +2,20 @@ package com.dzeio.openhealth.ui.food
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.openhealth.Application import com.dzeio.openhealth.Application
import com.dzeio.openhealth.R
import com.dzeio.openhealth.adapters.FoodAdapter import com.dzeio.openhealth.adapters.FoodAdapter
import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentFoodHomeBinding import com.dzeio.openhealth.databinding.FragmentFoodHomeBinding
import com.dzeio.openhealth.ui.steps.FoodHomeViewModel import com.dzeio.openhealth.ui.steps.FoodHomeViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.util.Calendar
@AndroidEntryPoint @AndroidEntryPoint
class FoodHomeFragment : class FoodHomeFragment :
@ -26,6 +31,9 @@ class FoodHomeFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// FIXME: deprecated
setHasOptionsMenu(true)
viewModel.init() viewModel.init()
val recycler = binding.list val recycler = binding.list
@ -35,13 +43,72 @@ class FoodHomeFragment :
val adapter = FoodAdapter() val adapter = FoodAdapter()
adapter.onItemClick = { adapter.onItemClick = {
// findNavController().navigate( findNavController().navigate(
// WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterEdit( FoodHomeFragmentDirections.actionFoodHomeFragmentToNavDialogFoodProduct(
// it.id it.id,
// ) false
// ) )
)
} }
recycler.adapter = adapter recycler.adapter = adapter
viewModel.items.observe(viewLifecycleOwner) {
adapter.set(it)
var energy = 0f
var proteins = 0f
var carbohydrates = 0f
var fat = 0f
for (food in it) {
energy += food.energy / 100 * food.quantity
proteins += food.proteins / 100 * food.quantity
carbohydrates += food.carbohydrates / 100 * food.quantity
fat += food.fat / 100 * food.quantity
}
binding.energyTxt.text = "${energy.toInt()} / 2594kcal"
binding.energyBar.progress = (100 * energy / 2594).toInt()
binding.proteinsTxt.text = "${proteins.toInt()} / 130g"
binding.proteinsBar.progress = (100 * proteins / 130).toInt()
binding.carbsTxt.text = "${carbohydrates.toInt()} / 324g"
binding.carbsBar.progress = (100 * carbohydrates / 324).toInt()
binding.fatTxt.text = "${fat.toInt()} / 87g"
binding.fatBar.progress = (100 * fat / 87).toInt()
}
binding.next.setOnClickListener {
viewModel.next()
}
binding.previous.setOnClickListener {
viewModel.previous()
}
viewModel.date.observe(viewLifecycleOwner) {
val date = Calendar.getInstance()
date.timeInMillis = it
binding.date.text = "${date.get(Calendar.YEAR)}-${date.get(Calendar.MONTH) + 1}-${date.get(Calendar.DAY_OF_MONTH)}"
}
}
@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(
FoodHomeFragmentDirections.actionFoodHomeFragmentToNavDialogFoodSearch()
)
true
}
else -> super.onOptionsItemSelected(item)
}
} }
} }

View File

@ -5,22 +5,65 @@ import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.core.BaseViewModel import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.food.Food import com.dzeio.openhealth.data.food.Food
import com.dzeio.openhealth.data.food.FoodRepository import com.dzeio.openhealth.data.food.FoodRepository
import com.dzeio.openhealth.data.openfoodfact.OpenFoodFactService
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Calendar
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class FoodHomeViewModel @Inject internal constructor( class FoodHomeViewModel @Inject internal constructor(
private val foodRepository: FoodRepository private val foodRepository: FoodRepository,
private val foodFactService: OpenFoodFactService
) : BaseViewModel() { ) : BaseViewModel() {
val items: MutableLiveData<List<Food>> = MutableLiveData() val items: MutableLiveData<List<Food>> = MutableLiveData()
private val list: MutableLiveData<List<Food>> = MutableLiveData(arrayListOf())
val date: MutableLiveData<Long> = MutableLiveData(Calendar.getInstance().timeInMillis)
fun init() { fun init() {
val now = Calendar.getInstance()
now.set(Calendar.HOUR, 0)
now.set(Calendar.MINUTE, 0)
now.set(Calendar.SECOND, 0)
date.postValue(now.timeInMillis)
viewModelScope.launch { viewModelScope.launch {
foodRepository.getAll().collectLatest { foodRepository.getAll().collectLatest {
items.postValue(it) list.postValue(it)
updateList(it)
} }
} }
} }
fun next() {
val now = Calendar.getInstance()
now.timeInMillis = date.value!!
now.add(Calendar.DAY_OF_YEAR, 1)
date.value = now.timeInMillis
updateList()
}
fun previous() {
val now = Calendar.getInstance()
now.timeInMillis = date.value!!
now.add(Calendar.DAY_OF_YEAR, -1)
date.value = now.timeInMillis
updateList()
}
private fun updateList(foods: List<Food>? = null) {
val day = Calendar.getInstance()
day.timeInMillis = date.value!!
val todayInMillis = day.timeInMillis
day.add(Calendar.DAY_OF_YEAR, 1)
val tomorrow = day.timeInMillis
items.postValue((foods ?: list.value!!).filter { food ->
food.timestamp in todayInMillis until tomorrow
})
}
} }

View File

@ -0,0 +1,81 @@
package com.dzeio.openhealth.ui.food
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
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.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class SearchFoodDialog :
BaseDialog<SearchFoodDialogViewModel, DialogFoodSearchProductBinding>(SearchFoodDialogViewModel::class.java) {
override val bindingInflater: (LayoutInflater) -> DialogFoodSearchProductBinding =
DialogFoodSearchProductBinding::inflate
override fun onBuilderInit(builder: MaterialAlertDialogBuilder) {
super.onBuilderInit(builder)
builder.apply {
setTitle("Add Product")
setIcon(R.drawable.ic_outline_fastfood_24)
setNegativeButton(R.string.close) { dialog, _ ->
dialog.cancel()
}
setNeutralButton("Search", null)
}
}
override fun onDialogInit(dialog: AlertDialog) {
super.onDialogInit(dialog)
dialog.setOnShowListener {
val btn = dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
btn.setOnClickListener {
viewModel.search(binding.input.text.toString())
binding.loading.visibility = View.VISIBLE
}
}
}
override fun onCreated() {
super.onCreated()
val recycler = binding.list
val manager = LinearLayoutManager(requireContext())
recycler.layoutManager = manager
val adapter = FoodAdapter()
adapter.onItemClick = {
lifecycleScope.launch {
val id = viewModel.addProduct(it)
findNavController().navigate(
SearchFoodDialogDirections.actionNavDialogFoodSearchToNavDialogFoodProduct(
id,
true
)
)
}
}
recycler.adapter = adapter
viewModel.items.observe(this) {
adapter.set(it)
binding.loading.visibility = View.GONE
}
}
}

View File

@ -0,0 +1,37 @@
package com.dzeio.openhealth.ui.food
import androidx.lifecycle.MutableLiveData
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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SearchFoodDialogViewModel @Inject internal constructor(
private val foodRepository: FoodRepository,
private val foodFactService: OpenFoodFactService
) : BaseViewModel() {
val items: MutableLiveData<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
.filter { it.name != null }
.map { Food.fromOpenFoodFact(it) }
)
}
}
}
suspend fun addProduct(product: Food): Long {
return foodRepository.add(product)
}
}

View File

@ -21,7 +21,6 @@ import com.dzeio.openhealth.utils.GraphUtils
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
@AndroidEntryPoint @AndroidEntryPoint
class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewModel::class.java) { class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewModel::class.java) {
@ -104,6 +103,14 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
} }
} }
viewModel.steps.observe(viewLifecycleOwner) {
binding.stepsCurrent.text = it.toString()
}
viewModel.stepsGoal.observe(viewLifecycleOwner) {
binding.stepsTotal.text = it.toString()
}
viewModel.weights.observe(viewLifecycleOwner) { viewModel.weights.observe(viewLifecycleOwner) {
if (it != null) { if (it != null) {
updateGraph(it) updateGraph(it)
@ -169,8 +176,19 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
height = binding.background.height height = binding.background.height
} }
val graph = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val animator = ValueAnimator.ofInt(
this.oldValue.toInt(),
newValue
)
animator.duration = 300 // ms
animator.addUpdateListener {
this.oldValue = (it.animatedValue as Int).toFloat()
val value = 100 * it.animatedValue as Int / viewModel.dailyWaterIntake.toFloat()
// Log.d("Test2", "${this.oldValue}")
val graph = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(graph) val canvas = Canvas(graph)
val rect = RectF( val rect = RectF(
10f, 10f,
@ -179,21 +197,7 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
85f 85f
) )
// DrawUtils.drawRect( // background Arc
// canvas,
// RectF(
// 0f,
// 0f,
// 100f,
// 100f
// ),
// MaterialColors.getColor(
// requireView(),
// com.google.android.material.R.attr.colorOnPrimary
// ),
// 3f
// )
DrawUtils.drawArc( DrawUtils.drawArc(
canvas, canvas,
100f, 100f,
@ -205,19 +209,9 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
3f 3f
) )
val animator = ValueAnimator.ofInt(
min(this.oldValue, viewModel.dailyWaterIntake.toFloat()).toInt(),
min(newValue, viewModel.dailyWaterIntake)
)
animator.duration = 300 // ms
animator.addUpdateListener {
this.oldValue = 100 * it.animatedValue as Int / viewModel.dailyWaterIntake.toFloat()
// Log.d("Test2", "${this.oldValue}")
DrawUtils.drawArc( DrawUtils.drawArc(
canvas, canvas,
max(this.oldValue, 1f), max(value, 1f),
rect, rect,
MaterialColors.getColor( MaterialColors.getColor(
requireView(), requireView(),

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.Settings import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.core.BaseViewModel import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.step.StepRepository
import com.dzeio.openhealth.data.water.Water import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.water.WaterRepository import com.dzeio.openhealth.data.water.WaterRepository
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
@ -22,10 +23,17 @@ import javax.inject.Inject
class HomeViewModel @Inject internal constructor( class HomeViewModel @Inject internal constructor(
private val weightRepository: WeightRepository, private val weightRepository: WeightRepository,
private val waterRepository: WaterRepository, private val waterRepository: WaterRepository,
stepRepository: StepRepository,
settings: SharedPreferences, settings: SharedPreferences,
config: Configuration config: Configuration
) : BaseViewModel() { ) : BaseViewModel() {
private val _steps = MutableLiveData(0)
val steps: LiveData<Int> = _steps
private val _stepsGoal: MutableLiveData<Int?> = MutableLiveData()
val stepsGoal: LiveData<Int?> = _stepsGoal
private val _water = MutableLiveData<Water?>(null) private val _water = MutableLiveData<Water?>(null)
val water: LiveData<Water?> = _water val water: LiveData<Water?> = _water
@ -53,6 +61,14 @@ class HomeViewModel @Inject internal constructor(
} }
} }
viewModelScope.launch {
_steps.postValue(stepRepository.todaySteps())
}
this._stepsGoal.postValue(
config.getInt(Settings.STEPS_GOAL).value
)
viewModelScope.launch { viewModelScope.launch {
weightRepository.getWeights().collectLatest { weightRepository.getWeights().collectLatest {
_weights.postValue(it) _weights.postValue(it)

View File

@ -0,0 +1,30 @@
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
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,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" 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.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" 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="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" 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="M1,21.98c0,0.56 0.45,1.01 1.01,1.01H15c0.56,0 1.01,-0.45 1.01,-1.01V21H1v0.98zM8.5,8.99C4.75,8.99 1,11 1,15h15c0,-4 -3.75,-6.01 -7.5,-6.01zM3.62,13c1.11,-1.55 3.47,-2.01 4.88,-2.01s3.77,0.46 4.88,2.01H3.62zM1,17h15v2H1zM18,5V1h-2v4h-5l0.23,2h9.56l-1.4,14H18v2h1.72c0.84,0 1.53,-0.65 1.63,-1.47L23,5h-5z"/>
</vector>

View File

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="16dp"
android:nestedScrollingEnabled="true"
android:orientation="vertical">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:srcCompat="@tools:sample/avatars" />
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_marginVertical="16dp"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Total" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Energy" />
<TextView
android:id="@+id/energy_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textEnd"
android:text="250 / 2594 kcal" />
</LinearLayout>
<ProgressBar
android:layout_width="match_parent"
android:id="@+id/energy_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_height="wrap_content"
android:progress="24"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Proteins" />
<TextView
android:id="@+id/proteins_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textEnd"
android:text="250 / 2594 kcal" />
</LinearLayout>
<ProgressBar
android:layout_width="match_parent"
android:id="@+id/proteins_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_height="wrap_content"
android:progress="24"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Carbs" />
<TextView
android:id="@+id/carbs_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textEnd"
android:text="250 / 2594 kcal" />
</LinearLayout>
<ProgressBar
android:layout_width="match_parent"
android:id="@+id/carbs_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_height="wrap_content"
android:progress="24"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Fat" />
<TextView
android:id="@+id/fat_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textEnd"
android:text="250 / 2594 kcal" />
</LinearLayout>
<ProgressBar
android:layout_width="match_parent"
android:id="@+id/fat_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_height="wrap_content"
android:progress="24"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/serving"
android:layout_width="match_parent"
android:textAlignment="textEnd"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Serving Size: 250g" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/quantity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</LinearLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="16dp"
android:nestedScrollingEnabled="true"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/loading"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<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"/>
</LinearLayout>

View File

@ -16,13 +16,155 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="horizontal"
> >
<ImageView
android:id="@+id/previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_chevron_left_24"/>
<TextView
android:id="@+id/date"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:textAlignment="center"
android:layout_gravity="center"
android:text="2022-12-24" />
<ImageView
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_chevron_right_24"/>
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Total" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Energy" />
<TextView
android:id="@+id/energy_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textEnd"
android:text="250 / 2594 kcal" />
</LinearLayout>
<ProgressBar
android:layout_width="match_parent"
android:id="@+id/energy_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_height="wrap_content"
android:progress="24"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Proteins" />
<TextView
android:id="@+id/proteins_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textEnd"
android:text="250 / 2594 kcal" />
</LinearLayout>
<ProgressBar
android:layout_width="match_parent"
android:id="@+id/proteins_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_height="wrap_content"
android:progress="24"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Carbs" />
<TextView
android:id="@+id/carbs_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textEnd"
android:text="250 / 2594 kcal" />
</LinearLayout>
<ProgressBar
android:layout_width="match_parent"
android:id="@+id/carbs_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_height="wrap_content"
android:progress="24"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Fat" />
<TextView
android:id="@+id/fat_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="textEnd"
android:text="250 / 2594 kcal" />
</LinearLayout>
<ProgressBar
android:layout_width="match_parent"
android:id="@+id/fat_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_height="wrap_content"
android:progress="24"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:clipToPadding="false" android:clipToPadding="false"
android:id="@+id/list" android:id="@+id/list"

View File

@ -96,11 +96,11 @@
<TextView <TextView
android:id="@+id/fragment_home_water_total" android:id="@+id/fragment_home_water_total"
style="@style/TextAppearance.Material3.LabelMedium" style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:textColor="?attr/colorOnBackground"
android:text="@string/unit_volume_milliliter_unit" android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" /> android:textAlignment="center"
android:textColor="?attr/colorOnBackground" />
</LinearLayout> </LinearLayout>
@ -200,7 +200,7 @@
<TextView <TextView
android:id="@+id/steps_current" android:id="@+id/steps_current"
style="@style/TextAppearance.Material3.LabelMedium" style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="@string/unit_volume_milliliter_unit" android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" android:textAlignment="center"
@ -208,18 +208,17 @@
<com.google.android.material.divider.MaterialDivider <com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="2dp" android:layout_height="2dp" />
/>
<TextView <TextView
android:id="@+id/steps_total" android:id="@+id/steps_total"
style="@style/TextAppearance.Material3.LabelMedium" style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:textColor="?attr/colorOnBackground"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="@string/unit_volume_milliliter_unit" android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" /> android:textAlignment="center"
android:textColor="?attr/colorOnBackground" />
</LinearLayout> </LinearLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewFilledStyle" style="?attr/materialCardViewFilledStyle"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:clickable="true" android:clickable="true"
@ -16,8 +17,18 @@
android:orientation="horizontal" android:orientation="horizontal"
android:weightSum="1"> android:weightSum="1">
<ImageView
android:id="@+id/product_image"
android:layout_width="43dp"
android:layout_height="match_parent"
android:layout_marginEnd="16dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
tools:srcCompat="@tools:sample/avatars" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical"> android:orientation="vertical">

View File

@ -152,5 +152,34 @@
android:id="@+id/foodHomeFragment" android:id="@+id/foodHomeFragment"
tools:layout="@layout/fragment_food_home" tools:layout="@layout/fragment_food_home"
android:name="com.dzeio.openhealth.ui.food.FoodHomeFragment" android:name="com.dzeio.openhealth.ui.food.FoodHomeFragment"
android:label="FoodHomeFragment" /> android:label="FoodHomeFragment" >
<action
android:id="@+id/action_foodHomeFragment_to_nav_dialog_food_search"
app:destination="@id/nav_dialog_food_search" />
<action
android:id="@+id/action_foodHomeFragment_to_nav_dialog_food_product"
app:destination="@id/nav_dialog_food_product" />
</fragment>
<dialog
android:id="@+id/nav_dialog_food_search"
android:name="com.dzeio.openhealth.ui.food.SearchFoodDialog"
tools:layout="@layout/dialog_food_search_product"
>
<action
android:id="@+id/action_nav_dialog_food_search_to_nav_dialog_food_product"
app:destination="@id/nav_dialog_food_product" />
</dialog>
<dialog
android:id="@+id/nav_dialog_food_product"
android:name="com.dzeio.openhealth.ui.food.FoodDialog"
tools:layout="@layout/dialog_food_product"
>
<argument
android:name="id"
app:argType="long" />
<argument
android:name="delete_on_cancel"
app:argType="boolean"
/>
</dialog>
</navigation> </navigation>

View File

@ -42,6 +42,8 @@
<string name="permission_declined">Vous avez décliné une permission, vous ne pouvez pas utiliser cette extension suaf si vous réactivez la permission manuellement</string> <string name="permission_declined">Vous avez décliné une permission, vous ne pouvez pas utiliser cette extension suaf si vous réactivez la permission manuellement</string>
<string name="menu_steps">Pas</string> <string name="menu_steps">Pas</string>
<string name="weight_current">Poid actuel: %1$s%2$s</string> <string name="weight_current">Poid actuel: %1$s%2$s</string>
<string name="delete">Supprimer</string>
<string name="close">Fermer</string>
<!-- Error Activity Translations --> <!-- Error Activity Translations -->
<string name="error_app_crash">Une Erreur est survenu lors de l\'utilisation de l\'application</string> <string name="error_app_crash">Une Erreur est survenu lors de l\'utilisation de l\'application</string>

View File

@ -53,6 +53,8 @@
<string name="edit_daily_goal">Modifiy daily goal</string> <string name="edit_daily_goal">Modifiy daily goal</string>
<string name="menu_steps">Steps</string> <string name="menu_steps">Steps</string>
<string name="weight_current">Current weight: %1$s</string> <string name="weight_current">Current weight: %1$s</string>
<string name="delete">Delete</string>
<string name="close">Close</string>
<!-- Error Activity Translations --> <!-- Error Activity Translations -->