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:
parent
357024770a
commit
2628bc1403
@ -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")
|
||||
|
65
app/proguard-rules.pro
vendored
65
app/proguard-rules.pro
vendored
@ -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.*
|
||||
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -10,7 +10,6 @@ import androidx.viewbinding.ViewBinding
|
||||
*/
|
||||
abstract class BaseActivity<VB : ViewBinding>() : AppCompatActivity() {
|
||||
|
||||
|
||||
/**
|
||||
* Function to inflate the Fragment Bindings
|
||||
*
|
||||
|
@ -47,5 +47,4 @@ abstract class BaseAdapter<T, VB : ViewBinding> : RecyclerView.Adapter<BaseViewH
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -39,7 +39,6 @@ abstract class BaseStaticFragment<VB : ViewBinding> : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destroy binding
|
||||
*/
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
39
app/src/main/java/com/dzeio/openhealth/utils/NetworkUtils.kt
Normal file
39
app/src/main/java/com/dzeio/openhealth/utils/NetworkUtils.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
app/src/main/res/drawable/ic_zoom_out_map.xml
Normal file
5
app/src/main/res/drawable/ic_zoom_out_map.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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" />
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user