From 2628bc14030227cf2bbcc2d5278fc8271a61fc9a Mon Sep 17 00:00:00 2001 From: Avior Date: Sun, 29 Jan 2023 19:10:46 +0100 Subject: [PATCH] feat: Too much things to say. --- app/build.gradle.kts | 17 +- app/proguard-rules.pro | 67 ++++- .../1.json | 90 ++++++- .../2.json | 226 ----------------- .../3.json | 232 ------------------ .../dzeio/openhealth/adapters/FoodAdapter.kt | 17 +- .../dzeio/openhealth/adapters/StepsAdapter.kt | 11 +- .../com/dzeio/openhealth/core/BaseActivity.kt | 1 - .../com/dzeio/openhealth/core/BaseAdapter.kt | 1 - .../com/dzeio/openhealth/core/BaseDialog.kt | 4 +- .../openhealth/core/BaseFullscreenDialog.kt | 6 +- .../openhealth/core/BaseStaticFragment.kt | 1 - .../dzeio/openhealth/core/BaseViewHolder.kt | 5 +- .../com/dzeio/openhealth/core/Observable.kt | 2 - .../com/dzeio/openhealth/data/AppDatabase.kt | 9 +- .../com/dzeio/openhealth/data/food/Food.kt | 17 +- .../openhealth/data/food/FoodRepository.kt | 70 +++++- .../com/dzeio/openhealth/data/step/Step.kt | 35 ++- .../dzeio/openhealth/ui/food/FoodDialog.kt | 9 +- .../openhealth/ui/food/SearchFoodDialog.kt | 19 +- .../ui/food/SearchFoodDialogViewModel.kt | 23 +- .../dzeio/openhealth/ui/home/HomeFragment.kt | 9 + .../openhealth/ui/steps/StepsHomeFragment.kt | 128 +++++++--- .../ui/weight/ListWeightFragment.kt | 90 ++++--- .../ui/weight/ListWeightViewModel.kt | 4 +- .../openhealth/utils/DownloadImageTask.kt | 37 --- .../dzeio/openhealth/utils/NetworkResult.kt | 12 + .../dzeio/openhealth/utils/NetworkUtils.kt | 39 +++ app/src/main/res/drawable/ic_zoom_out_map.xml | 5 + .../res/layout/dialog_food_search_product.xml | 11 +- .../main/res/layout/fragment_list_weight.xml | 2 +- app/src/main/res/layout/layout_item_list.xml | 5 +- .../main/res/navigation/mobile_navigation.xml | 18 +- app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + build.gradle.kts | 4 +- 36 files changed, 550 insertions(+), 678 deletions(-) delete mode 100644 app/schemas/com.dzeio.openhealth.data.AppDatabase/2.json delete mode 100644 app/schemas/com.dzeio.openhealth.data.AppDatabase/3.json delete mode 100644 app/src/main/java/com/dzeio/openhealth/utils/DownloadImageTask.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/utils/NetworkResult.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/utils/NetworkUtils.kt create mode 100644 app/src/main/res/drawable/ic_zoom_out_map.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2326bd1..ca256ff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ff59496..f3dade6 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,69 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-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.* ; +} + +# 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.* ; } +-keep,allowobfuscation interface <1> + +# Keep inherited services. +-if interface * { @retrofit2.http.* ; } +-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.* diff --git a/app/schemas/com.dzeio.openhealth.data.AppDatabase/1.json b/app/schemas/com.dzeio.openhealth.data.AppDatabase/1.json index ef5c56a..628f44a 100644 --- a/app/schemas/com.dzeio.openhealth.data.AppDatabase/1.json +++ b/app/schemas/com.dzeio.openhealth.data.AppDatabase/1.json @@ -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')" ] } } \ No newline at end of file diff --git a/app/schemas/com.dzeio.openhealth.data.AppDatabase/2.json b/app/schemas/com.dzeio.openhealth.data.AppDatabase/2.json deleted file mode 100644 index 06863c9..0000000 --- a/app/schemas/com.dzeio.openhealth.data.AppDatabase/2.json +++ /dev/null @@ -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')" - ] - } -} \ No newline at end of file diff --git a/app/schemas/com.dzeio.openhealth.data.AppDatabase/3.json b/app/schemas/com.dzeio.openhealth.data.AppDatabase/3.json deleted file mode 100644 index 73f0509..0000000 --- a/app/schemas/com.dzeio.openhealth.data.AppDatabase/3.json +++ /dev/null @@ -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')" - ] - } -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/FoodAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/FoodAdapter.kt index 1ae4b3c..f2689d2 100644 --- a/app/src/main/java/com/dzeio/openhealth/adapters/FoodAdapter.kt +++ b/app/src/main/java/com/dzeio/openhealth/adapters/FoodAdapter.kt @@ -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() { override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ItemFoodBinding @@ -26,16 +22,13 @@ class FoodAdapter : BaseAdapter() { 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( diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/StepsAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/StepsAdapter.kt index fea4570..71625fc 100644 --- a/app/src/main/java/com/dzeio/openhealth/adapters/StepsAdapter.kt +++ b/app/src/main/java/com/dzeio/openhealth/adapters/StepsAdapter.kt @@ -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() { var onItemClick: ((weight: Step) -> Unit)? = null + var isDay = false + override fun onBindData( holder: BaseViewHolder, item: Step, @@ -27,7 +30,13 @@ class StepsAdapter() : BaseAdapter() { ) // 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 { diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt index 8705c2b..00cacdc 100644 --- a/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt @@ -10,7 +10,6 @@ import androidx.viewbinding.ViewBinding */ abstract class BaseActivity() : AppCompatActivity() { - /** * Function to inflate the Fragment Bindings * diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt index 643869c..2e66c2f 100644 --- a/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt @@ -47,5 +47,4 @@ abstract class BaseAdapter : RecyclerView.Adapter(private val viewModelClass: Class) : +abstract class BaseDialog( + private val viewModelClass: Class +) : BaseSimpleDialog() { val viewModel by lazy { diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt index 87c3d15..223292c 100644 --- a/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt @@ -16,7 +16,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder /** * Base around the DialogFragment class to simplify usage */ -abstract class BaseFullscreenDialog(private val viewModelClass: Class) : DialogFragment() { +abstract class BaseFullscreenDialog( + private val viewModelClass: Class +) : DialogFragment() { /** * Lazyload the viewModel @@ -33,7 +35,6 @@ abstract class BaseFullscreenDialog(privat */ abstract val bindingInflater: (LayoutInflater) -> VB - /** * Function run when the dialog was created */ @@ -44,7 +45,6 @@ abstract class BaseFullscreenDialog(privat container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = bindingInflater(inflater) setHasOptionsMenu(true) diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseStaticFragment.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseStaticFragment.kt index 4057ca9..b94b918 100644 --- a/app/src/main/java/com/dzeio/openhealth/core/BaseStaticFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseStaticFragment.kt @@ -39,7 +39,6 @@ abstract class BaseStaticFragment : Fragment() { return binding.root } - /** * Destroy binding */ diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt index 3c8113c..d3d54f9 100644 --- a/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt @@ -7,6 +7,5 @@ import androidx.viewbinding.ViewBinding * Simple implementation of RecyclerView.ViewHolder to limitate usage */ class BaseViewHolder( - val binding : VB -) : RecyclerView.ViewHolder(binding.root) { -} + val binding: VB +) : RecyclerView.ViewHolder(binding.root) diff --git a/app/src/main/java/com/dzeio/openhealth/core/Observable.kt b/app/src/main/java/com/dzeio/openhealth/core/Observable.kt index e7df48f..bee4b72 100644 --- a/app/src/main/java/com/dzeio/openhealth/core/Observable.kt +++ b/app/src/main/java/com/dzeio/openhealth/core/Observable.kt @@ -10,7 +10,6 @@ open class Observable(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(baseValue: T) { } fun notifyObservers() { - // Notify Functions for (fn in functionObservers) { notifyObserver(fn) diff --git a/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt b/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt index de7efe7..e49f036 100644 --- a/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt +++ b/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt @@ -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() { diff --git a/app/src/main/java/com/dzeio/openhealth/data/food/Food.kt b/app/src/main/java/com/dzeio/openhealth/data/food/Food.kt index 8a7b615..e770c12 100644 --- a/app/src/main/java/com/dzeio/openhealth/data/food/Food.kt +++ b/app/src/main/java/com/dzeio/openhealth/data/food/Food.kt @@ -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 ) } diff --git a/app/src/main/java/com/dzeio/openhealth/data/food/FoodRepository.kt b/app/src/main/java/com/dzeio/openhealth/data/food/FoodRepository.kt index 767b214..6577e52 100644 --- a/app/src/main/java/com/dzeio/openhealth/data/food/FoodRepository.kt +++ b/app/src/main/java/com/dzeio/openhealth/data/food/FoodRepository.kt @@ -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 = - offSource.searchProducts(name) + + suspend fun searchFood(name: String): Flow>> = channelFlow { + val result = NetworkResult>() + val items = arrayListOf() + 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) - } diff --git a/app/src/main/java/com/dzeio/openhealth/data/step/Step.kt b/app/src/main/java/com/dzeio/openhealth/data/step/Step.kt index 835287c..115fc4d 100644 --- a/app/src/main/java/com/dzeio/openhealth/data/step/Step.kt +++ b/app/src/main/java/com/dzeio/openhealth/data/step/Step.kt @@ -44,19 +44,24 @@ data class Step( } } - fun formatTimestamp(): String { - val formatter = DateFormat.getDateTimeInstance( - DateFormat.SHORT, - DateFormat.SHORT, - Locale.getDefault() - ) + fun formatTimestamp(removeTime: Boolean = false): String { + val formatter = if (removeTime) { + DateFormat.getDateInstance( + DateFormat.SHORT, + Locale.getDefault() + ) + } else { + DateFormat.getDateTimeInstance( + DateFormat.SHORT, + DateFormat.SHORT, + Locale.getDefault() + ) + } return formatter.format(Date(this.timestamp)) } fun isToday(): Boolean { - val it = Calendar.getInstance(TimeZone.getTimeZone("UTC")) - it.timeInMillis = timestamp - it.set(Calendar.HOUR, 0) + val it = getDay() val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) @@ -64,7 +69,17 @@ data class Step( cal.set(Calendar.MINUTE, 0) cal.set(Calendar.SECOND, 0) cal.set(Calendar.MILLISECOND, 0) - return it.timeInMillis == cal.timeInMillis + return it == cal.timeInMillis + } + + fun getDay(): Long { + val it = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { + timeInMillis = timestamp + set(Calendar.HOUR, 0) + + set(Calendar.AM_PM, Calendar.AM) + } + return it.timeInMillis } fun isCurrent(): Boolean { diff --git a/app/src/main/java/com/dzeio/openhealth/ui/food/FoodDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/food/FoodDialog.kt index 2a4dfa7..a4fec23 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/food/FoodDialog.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/food/FoodDialog.kt @@ -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()) } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/food/SearchFoodDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/food/SearchFoodDialog.kt index 551ec00..0b6d0fe 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/food/SearchFoodDialog.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/food/SearchFoodDialog.kt @@ -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::class.java) { + BaseDialog( + SearchFoodDialogViewModel::class.java + ) { override val bindingInflater: (LayoutInflater) -> DialogFoodSearchProductBinding = DialogFoodSearchProductBinding::inflate @@ -42,7 +45,6 @@ class SearchFoodDialog : btn.setOnClickListener { viewModel.search(binding.input.text.toString()) binding.loading.visibility = View.VISIBLE - } } } @@ -50,8 +52,6 @@ class SearchFoodDialog : override fun onCreated() { super.onCreated() - - val recycler = binding.list val manager = LinearLayoutManager(requireContext()) @@ -59,7 +59,6 @@ class SearchFoodDialog : val adapter = FoodAdapter() adapter.onItemClick = { - lifecycleScope.launch { val id = viewModel.addProduct(it) findNavController().navigate( @@ -73,9 +72,13 @@ class SearchFoodDialog : recycler.adapter = adapter viewModel.items.observe(this) { - adapter.set(it) - binding.loading.visibility = View.GONE + adapter.set(it.data ?: arrayListOf()) + if (it.status == NetworkResult.NetworkStatus.FINISHED) { + binding.loading.visibility = View.GONE + } else if (it.status == NetworkResult.NetworkStatus.ERRORED) { + binding.errorText.visibility = View.VISIBLE + binding.loading.visibility = View.GONE + } } - } } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/food/SearchFoodDialogViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/food/SearchFoodDialogViewModel.kt index f2179d7..430d0af 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/food/SearchFoodDialogViewModel.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/food/SearchFoodDialogViewModel.kt @@ -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> = MutableLiveData() + val items: MutableLiveData>> = 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 - ) + foodRepository.searchFood(text).collectLatest { + items.postValue(it) } } } suspend fun addProduct(product: Food): Long { + if (product.id > 0) { + product.id = 0 + } return foodRepository.add(product) } } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt index 3701129..dee8064 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt @@ -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(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 { diff --git a/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeFragment.kt index 7d4b359..ba227d7 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeFragment.kt @@ -1,9 +1,12 @@ package com.dzeio.openhealth.ui.steps +import android.graphics.Paint import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.dzeio.charts.Entry import com.dzeio.charts.axis.Line @@ -11,6 +14,7 @@ import com.dzeio.charts.series.BarSerie import com.dzeio.openhealth.Application import com.dzeio.openhealth.adapters.StepsAdapter import com.dzeio.openhealth.core.BaseFragment +import com.dzeio.openhealth.data.step.Step import com.dzeio.openhealth.databinding.FragmentStepsHomeBinding import com.dzeio.openhealth.utils.ChartUtils import dagger.hilt.android.AndroidEntryPoint @@ -29,6 +33,8 @@ class StepsHomeFragment : const val TAG = "${Application.TAG}/SHFragment" } + private val args: StepsHomeFragmentArgs by navArgs() + override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentStepsHomeBinding = FragmentStepsHomeBinding::inflate @@ -37,18 +43,26 @@ class StepsHomeFragment : viewModel.init() + val isDay = args.day > 0L + val recycler = binding.list val manager = LinearLayoutManager(requireContext()) recycler.layoutManager = manager - val adapter = StepsAdapter() - adapter.onItemClick = { -// findNavController().navigate( -// WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterEdit( -// it.id -// ) -// ) + val adapter = StepsAdapter().apply { + this.isDay = isDay + } + + if (!isDay) { + adapter.onItemClick = { + findNavController().navigate( + StepsHomeFragmentDirections.actionNavStepsHomeSelf().apply { + day = it.timestamp + title = "Steps from " + it.formatTimestamp(true) + } + ) + } } recycler.adapter = adapter @@ -64,69 +78,103 @@ class StepsHomeFragment : } xAxis.apply { - dataWidth = 604800000.0 + dataWidth = if (isDay) 8.64e+7 else 6.048e+8 + scrollEnabled = !isDay textPaint.textSize = 32f onValueFormat = onValueFormat@{ - val formatter = DateFormat.getDateInstance( - DateFormat.SHORT, - Locale.getDefault() - ) + val formatter = if (isDay) { + DateFormat.getTimeInstance( + DateFormat.SHORT, + Locale.getDefault() + ) + } else { + DateFormat.getDateInstance( + DateFormat.SHORT, + Locale.getDefault() + ) + } return@onValueFormat formatter.format(Date(it.toLong())) } } annotator.annotationTitleFormat = { "${it.y.roundToInt()} steps" } annotator.annotationSubTitleFormat = annotationSubTitleFormat@{ - val formatter = DateFormat.getDateInstance( - DateFormat.SHORT, - Locale.getDefault() - ) + val formatter = if (isDay) { + DateFormat.getTimeInstance( + DateFormat.SHORT, + Locale.getDefault() + ) + } else { + DateFormat.getDateInstance( + DateFormat.SHORT, + Locale.getDefault() + ) + } return@annotationSubTitleFormat formatter.format(Date(it.x.toLong())) } } viewModel.goal.observe(viewLifecycleOwner) { - if (it != null) { - chart.yAxis.addLine(it.toFloat(), Line(true)) + if (it != null && !isDay) { + chart.yAxis.addLine( + it.toFloat(), + Line(true, Paint(chart.yAxis.linePaint).apply { strokeWidth = 4f }) + ) chart.refresh() } } viewModel.items.observe(viewLifecycleOwner) { list -> - adapter.set(list) - if (list.isEmpty()) { + adapter.set(arrayListOf()) return@observe } -// chart.scroller.zoomEnabled = false + val filtered = if (!isDay) list else list.filter { + it.getDay() == args.day + } + if (isDay) { + adapter.set(filtered) + serie.entries = filtered.map { + Entry( + it.timestamp.toDouble(), + it.value.toFloat() + ) + } as ArrayList + } else { + val entries: HashMap = HashMap() - val entries: HashMap = HashMap() + list.forEach { + val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + cal.timeInMillis = it.timestamp - list.forEach { - val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) - cal.timeInMillis = it.timestamp + cal.set(Calendar.HOUR, 0) + cal.set(Calendar.AM_PM, Calendar.AM) + val ts = cal.timeInMillis + if (!entries.containsKey(ts)) { + entries[ts] = Entry((ts).toDouble(), 0F, chart.yAxis.goalLinePaint.color) + } - cal.set(Calendar.HOUR, 0) - cal.set(Calendar.AM_PM, Calendar.AM) - val ts = cal.timeInMillis - if (!entries.containsKey(ts)) { - entries[ts] = Entry((ts).toDouble(), 0F, chart.yAxis.goalLinePaint.color) - } + entries[ts]!!.y += it.value.toFloat() - entries[ts]!!.y += it.value.toFloat() - - if (viewModel.goal.value != null) { - if (entries[ts]!!.y > viewModel.goal.value!!) { + if (viewModel.goal.value != null) { + if (entries[ts]!!.y > viewModel.goal.value!!) { + entries[ts]!!.color = null + } + } else { entries[ts]!!.color = null } - } else { - entries[ts]!!.color = null } + + adapter.set( + entries.map { Step(value = it.value.y.toInt(), timestamp = it.key) } + .sortedByDescending { it.timestamp } + ) + + serie.entries = ArrayList(entries.values) } - serie.entries = ArrayList(entries.values) - - chart.xAxis.x = chart.xAxis.getXMin() + chart.xAxis.x = + chart.xAxis.getXMax() - chart.xAxis.dataWidth!! + chart.xAxis.dataWidth!! / (if (isDay) 24 else 7) chart.refresh() } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt index bcdd7bf..0795092 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt @@ -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) { @@ -146,13 +170,22 @@ class ListWeightFragment : val entries: ArrayList = arrayListOf() list.forEach { - entries.add(Entry( - it.timestamp.toDouble(), - it.weight - )) + entries.add( + Entry( + it.timestamp.toDouble(), + it.weight + ) + ) } serie.entries = entries + if (viewModel.goalWeight.value != null) { + chart.yAxis.addLine( + viewModel.goalWeight.value!!, + Line(true, Paint(chart.yAxis.linePaint).apply { strokeWidth = 4f }) + ) + } + if (list.isEmpty()) { chart.xAxis.x = 0.0 } else { @@ -161,27 +194,4 @@ class ListWeightFragment : chart.refresh() } - - @Deprecated("Deprecated in Java") - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.action_add).isVisible = true - - super.onPrepareOptionsMenu(menu) - } - - @Deprecated("Deprecated in Java") - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_add -> { - findNavController().navigate( - ListWeightFragmentDirections.actionNavListWeightToNavWeightDialog( - WeightDialog.DialogTypes.ADD_WEIGHT.ordinal - ) - ) - true - } - - else -> super.onOptionsItemSelected(item) - } - } } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightViewModel.kt index 4496563..e6a44c6 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightViewModel.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightViewModel.kt @@ -21,7 +21,6 @@ class ListWeightViewModel @Inject internal constructor( private val settings: Configuration ) : BaseViewModel() { - private val _massUnit = MutableLiveData(Units.Mass.KILOGRAM) val massUnit: LiveData = _massUnit @@ -31,7 +30,6 @@ class ListWeightViewModel @Inject internal constructor( private val _weights = MutableLiveData?>(null) val weights: LiveData?> = _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( diff --git a/app/src/main/java/com/dzeio/openhealth/utils/DownloadImageTask.kt b/app/src/main/java/com/dzeio/openhealth/utils/DownloadImageTask.kt deleted file mode 100644 index 83d952a..0000000 --- a/app/src/main/java/com/dzeio/openhealth/utils/DownloadImageTask.kt +++ /dev/null @@ -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() { - @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) - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/utils/NetworkResult.kt b/app/src/main/java/com/dzeio/openhealth/utils/NetworkResult.kt new file mode 100644 index 0000000..ac1423c --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/utils/NetworkResult.kt @@ -0,0 +1,12 @@ +package com.dzeio.openhealth.utils + +data class NetworkResult( + var status: NetworkStatus = NetworkStatus.RUNNING, + var data: T? = null +) { + enum class NetworkStatus { + RUNNING, + FINISHED, + ERRORED + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/utils/NetworkUtils.kt b/app/src/main/java/com/dzeio/openhealth/utils/NetworkUtils.kt new file mode 100644 index 0000000..004d0eb --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/utils/NetworkUtils.kt @@ -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() + } + } + } +} diff --git a/app/src/main/res/drawable/ic_zoom_out_map.xml b/app/src/main/res/drawable/ic_zoom_out_map.xml new file mode 100644 index 0000000..1bdc32c --- /dev/null +++ b/app/src/main/res/drawable/ic_zoom_out_map.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/dialog_food_search_product.xml b/app/src/main/res/layout/dialog_food_search_product.xml index 427a8cd..4895165 100644 --- a/app/src/main/res/layout/dialog_food_search_product.xml +++ b/app/src/main/res/layout/dialog_food_search_product.xml @@ -19,12 +19,21 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" /> + + + tools:listitem="@layout/item_food"> + + diff --git a/app/src/main/res/layout/fragment_list_weight.xml b/app/src/main/res/layout/fragment_list_weight.xml index 2c9b4f5..47df57c 100644 --- a/app/src/main/res/layout/fragment_list_weight.xml +++ b/app/src/main/res/layout/fragment_list_weight.xml @@ -53,7 +53,7 @@ - - \ No newline at end of file + diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index bed29b2..ed403ac 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -134,9 +134,23 @@ tools:layout="@layout/fragment_activity" /> + tools:layout="@layout/fragment_steps_home" > + + + + Pas pris Erreur lors de la géneration d\'un rapport d\'erreur %1$d pas + Il semplerais que nous ne pouvons pas communiquer avec OpenFoodFact, Merci de re-essayer plus tard diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63d935d..6e6f09e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -67,4 +67,5 @@ An error occurred while making the error report %1$s (%2$.0f kcal) %1$d steps + It seems that we can\'t communicate with OpenFoodFact, please retry later diff --git a/build.gradle.kts b/build.gradle.kts index d776d86..6cfe8ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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