1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-22 19:02:16 +00:00

feat: Too much things to say.

This commit is contained in:
Florian Bouillon 2023-01-29 19:10:46 +01:00
parent 357024770a
commit 2628bc1403
Signed by: Florian Bouillon
GPG Key ID: BEEAF3722D0EBF64
36 changed files with 550 additions and 678 deletions

View File

@ -50,7 +50,6 @@ android {
storeFile = file(keystoreProperties["storeFile"] as String)
}
} catch (_: Exception) {}
}
}
@ -90,14 +89,22 @@ android {
getByName("release") {
// Slimmer version
isMinifyEnabled = true
isMinifyEnabled = false
isShrinkResources = false
isDebuggable = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("release")
}
getByName("debug") {
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
isDebuggable = true
@ -130,7 +137,7 @@ android {
dependencies {
// Dzeio Charts
implementation("com.dzeio:charts:cbd5f57f8d")
implementation("com.dzeio:charts:fe20f90654")
// Dzeio Crash Handler
implementation("com.dzeio:crashhandler:1.0.1")

View File

@ -19,3 +19,68 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Keep annotation default values (e.g., retrofit2.http.Field.encoded).
-keepattributes AnnotationDefault
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
# Keep inherited services.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*

View File

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "2acd5897bbf15393886259605a1df934",
"identityHash": "414712cc283c7f1d14cde8e00da277fb",
"entities": [
{
"tableName": "Weight",
@ -34,10 +34,10 @@
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
],
"autoGenerate": true
]
},
"indices": [
{
@ -82,10 +82,10 @@
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
],
"autoGenerate": true
]
},
"indices": [
{
@ -130,10 +130,10 @@
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
],
"autoGenerate": true
]
},
"indices": [
{
@ -147,12 +147,86 @@
}
],
"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": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"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, '2acd5897bbf15393886259605a1df934')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '414712cc283c7f1d14cde8e00da277fb')"
]
}
}

View File

@ -1,226 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "0f92ae44f4503b964d4986959a15ef4e",
"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` 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",
"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": "INTEGER",
"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": "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, '0f92ae44f4503b964d4986959a15ef4e')"
]
}
}

View File

@ -1,232 +0,0 @@
{
"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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,9 +14,9 @@ buildscript {
plugins {
// android app plugin ? (tbh idk what thoses "plugins" does)
id("com.android.application") version "7.3.1" apply false
id("com.android.application") version "7.4.0" apply false
// is it a lib? no, do I need it? IDK
id("com.android.library") version "7.3.1" apply false
id("com.android.library") version "7.4.0" apply false
// add kotlin compatibility :>
id("org.jetbrains.kotlin.android") version "1.8.0" apply false