mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-22 19:02:16 +00:00
feat: Add support for Food input
This commit is contained in:
parent
4bcae95d52
commit
643861bf1e
@ -2,7 +2,7 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "794ed5ee15db239f9a2708b951f55552",
|
||||
"identityHash": "0f92ae44f4503b964d4986959a15ef4e",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Weight",
|
||||
@ -150,7 +150,7 @@
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
@ -164,6 +164,12 @@
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "serving",
|
||||
"columnName": "serving",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "quantity",
|
||||
"columnName": "quantity",
|
||||
@ -214,7 +220,7 @@
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '794ed5ee15db239f9a2708b951f55552')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0f92ae44f4503b964d4986959a15ef4e')"
|
||||
]
|
||||
}
|
||||
}
|
232
app/schemas/com.dzeio.openhealth.data.AppDatabase/3.json
Normal file
232
app/schemas/com.dzeio.openhealth.data.AppDatabase/3.json
Normal 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')"
|
||||
]
|
||||
}
|
||||
}
|
5
app/src/debug/res/drawable/baseline_chevron_left_24.xml
Normal file
5
app/src/debug/res/drawable/baseline_chevron_left_24.xml
Normal 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>
|
5
app/src/debug/res/drawable/baseline_chevron_right_24.xml
Normal file
5
app/src/debug/res/drawable/baseline_chevron_right_24.xml
Normal 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>
|
@ -6,8 +6,11 @@ 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 kotlin.math.roundToInt
|
||||
|
||||
class FoodAdapter() : BaseAdapter<Food, ItemFoodBinding>() {
|
||||
|
||||
class FoodAdapter : BaseAdapter<Food, ItemFoodBinding>() {
|
||||
|
||||
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ItemFoodBinding
|
||||
get() = ItemFoodBinding::inflate
|
||||
@ -19,8 +22,9 @@ class FoodAdapter() : BaseAdapter<Food, ItemFoodBinding>() {
|
||||
item: Food,
|
||||
position: Int
|
||||
) {
|
||||
holder.binding.foodName.text = item.name
|
||||
holder.binding.foodDescription.text = item.energy.toString()
|
||||
DownloadImageTask(holder.binding.productImage).execute(item.image)
|
||||
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 {
|
||||
onItemClick?.invoke(item)
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
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.FoodDao
|
||||
import com.dzeio.openhealth.data.step.Step
|
||||
@ -21,7 +23,7 @@ import com.dzeio.openhealth.data.weight.WeightDao
|
||||
Step::class,
|
||||
Food::class
|
||||
],
|
||||
version = 2,
|
||||
version = 3,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
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
|
||||
private fun buildDatabase(context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
|
||||
.addMigrations(MIGRATION_2_3)
|
||||
// .addCallback(object : Callback() {
|
||||
// override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
// super.onCreate(db)
|
||||
@ -66,5 +69,13 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
// })
|
||||
.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 ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.dzeio.openhealth.data.food
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.dzeio.openhealth.data.openfoodfact.OFFProduct
|
||||
import java.util.Calendar
|
||||
import java.util.TimeZone
|
||||
|
||||
@ -10,11 +11,40 @@ data class Food(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0,
|
||||
var name: String,
|
||||
var quantity: Int,
|
||||
var serving: String,
|
||||
var quantity: Float,
|
||||
var proteins: Float,
|
||||
var carbohydrates: Float,
|
||||
var fat: 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,4 +16,12 @@ class FoodRepository @Inject constructor(
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ data class OFFNutriments(
|
||||
@SerializedName("carbohydrates_100g")
|
||||
var carbohydrates: Float,
|
||||
@SerializedName("energy-kcal_100g")
|
||||
var energy: Float,
|
||||
var energy: Float?,
|
||||
@SerializedName("energy-kj_100g")
|
||||
var energyKJ: Float,
|
||||
@SerializedName("fat_100g")
|
||||
var fat: Float,
|
||||
@SerializedName("proteins_100g")
|
||||
|
@ -9,8 +9,20 @@ data class OFFProduct(
|
||||
var name: String,
|
||||
|
||||
@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")
|
||||
var nutriments: OFFNutriments
|
||||
var nutriments: OFFNutriments,
|
||||
|
||||
@SerializedName("image_url")
|
||||
var image: String?
|
||||
)
|
||||
|
@ -1,8 +1,6 @@
|
||||
package com.dzeio.openhealth.data.openfoodfact
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
@ -14,25 +12,30 @@ interface OpenFoodFactService {
|
||||
|
||||
companion object {
|
||||
fun getService(): OpenFoodFactService {
|
||||
val interceptor = HttpLoggingInterceptor()
|
||||
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
val client = OkHttpClient.Builder()
|
||||
.addInterceptor(interceptor)
|
||||
.build()
|
||||
// val interceptor = HttpLoggingInterceptor()
|
||||
// interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
// val client = OkHttpClient.Builder()
|
||||
// .addInterceptor(interceptor)
|
||||
// .build()
|
||||
val gson = GsonBuilder()
|
||||
.setLenient()
|
||||
.create()
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://world.openfoodfacts.org/")
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.client(client)
|
||||
// .client(client)
|
||||
.build()
|
||||
|
||||
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")
|
||||
@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>
|
||||
|
||||
}
|
||||
|
88
app/src/main/java/com/dzeio/openhealth/ui/food/FoodDialog.kt
Normal file
88
app/src/main/java/com/dzeio/openhealth/ui/food/FoodDialog.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,15 +2,20 @@ package com.dzeio.openhealth.ui.food
|
||||
|
||||
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.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.dzeio.openhealth.Application
|
||||
import com.dzeio.openhealth.R
|
||||
import com.dzeio.openhealth.adapters.FoodAdapter
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
import com.dzeio.openhealth.databinding.FragmentFoodHomeBinding
|
||||
import com.dzeio.openhealth.ui.steps.FoodHomeViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.Calendar
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FoodHomeFragment :
|
||||
@ -26,6 +31,9 @@ class FoodHomeFragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// FIXME: deprecated
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
viewModel.init()
|
||||
|
||||
val recycler = binding.list
|
||||
@ -35,13 +43,72 @@ class FoodHomeFragment :
|
||||
|
||||
val adapter = FoodAdapter()
|
||||
adapter.onItemClick = {
|
||||
// findNavController().navigate(
|
||||
// WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterEdit(
|
||||
// it.id
|
||||
// )
|
||||
// )
|
||||
findNavController().navigate(
|
||||
FoodHomeFragmentDirections.actionFoodHomeFragmentToNavDialogFoodProduct(
|
||||
it.id,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,22 +5,65 @@ 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.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Calendar
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class FoodHomeViewModel @Inject internal constructor(
|
||||
private val foodRepository: FoodRepository
|
||||
private val foodRepository: FoodRepository,
|
||||
private val foodFactService: OpenFoodFactService
|
||||
) : BaseViewModel() {
|
||||
val items: MutableLiveData<List<Food>> = MutableLiveData()
|
||||
private val list: MutableLiveData<List<Food>> = MutableLiveData(arrayListOf())
|
||||
val date: MutableLiveData<Long> = MutableLiveData(Calendar.getInstance().timeInMillis)
|
||||
|
||||
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 {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@ import com.dzeio.openhealth.utils.GraphUtils
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@AndroidEntryPoint
|
||||
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) {
|
||||
if (it != null) {
|
||||
updateGraph(it)
|
||||
@ -169,55 +176,42 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
|
||||
height = binding.background.height
|
||||
}
|
||||
|
||||
val graph = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
|
||||
val canvas = Canvas(graph)
|
||||
val rect = RectF(
|
||||
10f,
|
||||
15f,
|
||||
90f,
|
||||
85f
|
||||
)
|
||||
|
||||
// DrawUtils.drawRect(
|
||||
// canvas,
|
||||
// RectF(
|
||||
// 0f,
|
||||
// 0f,
|
||||
// 100f,
|
||||
// 100f
|
||||
// ),
|
||||
// MaterialColors.getColor(
|
||||
// requireView(),
|
||||
// com.google.android.material.R.attr.colorOnPrimary
|
||||
// ),
|
||||
// 3f
|
||||
// )
|
||||
|
||||
DrawUtils.drawArc(
|
||||
canvas,
|
||||
100f,
|
||||
rect,
|
||||
MaterialColors.getColor(
|
||||
requireView(),
|
||||
com.google.android.material.R.attr.colorOnPrimary
|
||||
),
|
||||
3f
|
||||
)
|
||||
|
||||
val animator = ValueAnimator.ofInt(
|
||||
min(this.oldValue, viewModel.dailyWaterIntake.toFloat()).toInt(),
|
||||
min(newValue, viewModel.dailyWaterIntake)
|
||||
this.oldValue.toInt(),
|
||||
newValue
|
||||
)
|
||||
animator.duration = 300 // ms
|
||||
animator.addUpdateListener {
|
||||
|
||||
this.oldValue = 100 * it.animatedValue as Int / viewModel.dailyWaterIntake.toFloat()
|
||||
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 rect = RectF(
|
||||
10f,
|
||||
15f,
|
||||
90f,
|
||||
85f
|
||||
)
|
||||
|
||||
// background Arc
|
||||
DrawUtils.drawArc(
|
||||
canvas,
|
||||
100f,
|
||||
rect,
|
||||
MaterialColors.getColor(
|
||||
requireView(),
|
||||
com.google.android.material.R.attr.colorOnPrimary
|
||||
),
|
||||
3f
|
||||
)
|
||||
|
||||
DrawUtils.drawArc(
|
||||
canvas,
|
||||
max(this.oldValue, 1f),
|
||||
max(value, 1f),
|
||||
rect,
|
||||
MaterialColors.getColor(
|
||||
requireView(),
|
||||
|
@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.dzeio.openhealth.Settings
|
||||
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.WaterRepository
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
@ -22,10 +23,17 @@ import javax.inject.Inject
|
||||
class HomeViewModel @Inject internal constructor(
|
||||
private val weightRepository: WeightRepository,
|
||||
private val waterRepository: WaterRepository,
|
||||
stepRepository: StepRepository,
|
||||
settings: SharedPreferences,
|
||||
config: Configuration
|
||||
) : 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)
|
||||
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 {
|
||||
weightRepository.getWeights().collectLatest {
|
||||
_weights.postValue(it)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
5
app/src/main/res/drawable/outline_fastfood_24.xml
Normal file
5
app/src/main/res/drawable/outline_fastfood_24.xml
Normal 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>
|
154
app/src/main/res/layout/dialog_food_product.xml
Normal file
154
app/src/main/res/layout/dialog_food_product.xml
Normal 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>
|
30
app/src/main/res/layout/dialog_food_search_product.xml
Normal file
30
app/src/main/res/layout/dialog_food_search_product.xml
Normal 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>
|
@ -16,13 +16,155 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
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>
|
||||
|
||||
</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
|
||||
android:clipToPadding="false"
|
||||
android:id="@+id/list"
|
||||
|
@ -96,11 +96,11 @@
|
||||
<TextView
|
||||
android:id="@+id/fragment_home_water_total"
|
||||
style="@style/TextAppearance.Material3.LabelMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:textColor="?attr/colorOnBackground"
|
||||
android:text="@string/unit_volume_milliliter_unit"
|
||||
android:textAlignment="center" />
|
||||
android:textAlignment="center"
|
||||
android:textColor="?attr/colorOnBackground" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -200,7 +200,7 @@
|
||||
<TextView
|
||||
android:id="@+id/steps_current"
|
||||
style="@style/TextAppearance.Material3.LabelMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/unit_volume_milliliter_unit"
|
||||
android:textAlignment="center"
|
||||
@ -208,18 +208,17 @@
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
/>
|
||||
android:layout_height="2dp" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/steps_total"
|
||||
style="@style/TextAppearance.Material3.LabelMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:textColor="?attr/colorOnBackground"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/unit_volume_milliliter_unit"
|
||||
android:textAlignment="center" />
|
||||
android:textAlignment="center"
|
||||
android:textColor="?attr/colorOnBackground" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -1,5 +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:tools="http://schemas.android.com/tools"
|
||||
style="?attr/materialCardViewFilledStyle"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clickable="true"
|
||||
@ -16,8 +17,18 @@
|
||||
android:orientation="horizontal"
|
||||
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
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
@ -152,5 +152,34 @@
|
||||
android:id="@+id/foodHomeFragment"
|
||||
tools:layout="@layout/fragment_food_home"
|
||||
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>
|
||||
|
@ -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="menu_steps">Pas</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 -->
|
||||
<string name="error_app_crash">Une Erreur est survenu lors de l\'utilisation de l\'application</string>
|
||||
|
@ -53,6 +53,8 @@
|
||||
<string name="edit_daily_goal">Modifiy daily goal</string>
|
||||
<string name="menu_steps">Steps</string>
|
||||
<string name="weight_current">Current weight: %1$s</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="close">Close</string>
|
||||
|
||||
|
||||
<!-- Error Activity Translations -->
|
||||
|
Loading…
x
Reference in New Issue
Block a user