1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-06-07 07:19:54 +00:00

misc: Cleanup Database

This commit is contained in:
Florian Bouillon 2023-01-07 23:34:15 +01:00
parent 9e463d7fd3
commit a9da9198be
Signed by: Florian Bouillon
GPG Key ID: BEEAF3722D0EBF64
13 changed files with 157 additions and 57 deletions

View File

@ -5,8 +5,6 @@ import androidx.room.AutoMigration
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.dzeio.openhealth.data.food.Food import com.dzeio.openhealth.data.food.Food
import com.dzeio.openhealth.data.food.FoodDao import com.dzeio.openhealth.data.food.FoodDao
import com.dzeio.openhealth.data.step.Step import com.dzeio.openhealth.data.step.Step
@ -16,6 +14,11 @@ import com.dzeio.openhealth.data.water.WaterDao
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.data.weight.WeightDao import com.dzeio.openhealth.data.weight.WeightDao
/**
* ROOM SQLite database for the application
*
* It may be replaced if I want to fully encrypt the database
*/
@Database( @Database(
entities = [ entities = [
Weight::class, Weight::class,
@ -23,16 +26,16 @@ import com.dzeio.openhealth.data.weight.WeightDao
Step::class, Step::class,
Food::class Food::class
], ],
// TODO: go back to version 1 when the app is published
version = 3, version = 3,
exportSchema = true, exportSchema = true,
autoMigrations = [ autoMigrations = [
AutoMigration(from = 1, to = 2) AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3)
] ]
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
// private val PREPOPULATE_DATA = listOf(Thing("1", "val"), Thing("2", "val 2"))
abstract fun weightDao(): WeightDao abstract fun weightDao(): WeightDao
abstract fun waterDao(): WaterDao abstract fun waterDao(): WaterDao
abstract fun stepDao(): StepDao abstract fun stepDao(): StepDao
@ -40,42 +43,29 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun foodDao(): FoodDao abstract fun foodDao(): FoodDao
companion object { companion object {
/**
* database name duh
*/
private const val DATABASE_NAME = "open_health" private const val DATABASE_NAME = "open_health"
// For Singleton instantiation // For Singleton instantiation
@Volatile @Volatile
private var instance: AppDatabase? = null private var instance: AppDatabase? = null
// get the Database instance
fun getInstance(context: Context): AppDatabase { fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) { return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it } instance ?: buildDatabase(context).also { instance = it }
} }
} }
// Create and pre-populate the database. See this article for more details: /**
// https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785 * build teh database
*/
private fun buildDatabase(context: Context): AppDatabase { private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.addMigrations(MIGRATION_2_3) // .addMigrations(MIGRATION_2_3)
// .addCallback(object : Callback() {
// override fun onCreate(db: SupportSQLiteDatabase) {
// super.onCreate(db)
// // moving to a new thread
// Executors.newSingleThreadExecutor().execute {
// getInstance(context).thingDao()
// .insert(PREPOPULATE_DATA)
// }
// }
// })
.build() .build()
} }
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Food ADD COLUMN serving TEXT NOT NULL")
database.execSQL("ALTER TABLE Food ADD COLUMN image TEXT")
database.execSQL("ALTER TABLE Food ")
}
}
} }
} }

View File

@ -1,23 +0,0 @@
package com.dzeio.openhealth.data.converters
import androidx.room.TypeConverter
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
object TiviTypeConverters {
private val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
@TypeConverter
@JvmStatic
fun toOffsetDateTime(value: String?): OffsetDateTime? {
return value?.let {
return formatter.parse(value, OffsetDateTime::from)
}
}
@TypeConverter
@JvmStatic
fun fromOffsetDateTime(date: OffsetDateTime?): String? {
return date?.format(formatter)
}
}

View File

@ -10,23 +10,62 @@ import java.util.TimeZone
data class Food( data class Food(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0, var id: Long = 0,
/**
* The product name
*/
var name: String, var name: String,
/**
* The product serving text
*
* ex: `250ml`, `520g`, etc
*/
var serving: String, var serving: String,
/**
* the quantity taken by the user
*/
var quantity: Float, var quantity: Float,
/**
* the quantity of proteins there is for 100 quantity
*/
var proteins: Float, var proteins: Float,
/**
* the quantity of carbohydrates there is for 100 quantity
*/
var carbohydrates: Float, var carbohydrates: Float,
/**
* the quantity of fat there is for 100 quantity
*/
var fat: Float, var fat: Float,
/**
* the quantity of energy there is for 100 quantity
*/
var energy: Float, var energy: Float,
/** /**
* the url of the image * the url of the image of the product
*/ */
var image: String?, var image: String?,
/**
* 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 { companion object {
/**
* Transform an OpenFoodFact product to use for our Database
*/
fun fromOpenFoodFact(food: OFFProduct, quantity: Float? = null): Food { fun fromOpenFoodFact(food: OFFProduct, quantity: Float? = null): Food {
// try to know how much was eaten by the user if not said
var eaten = quantity ?: food.servingQuantity ?: food.productQuantity ?: 0f var eaten = quantity ?: food.servingQuantity ?: food.productQuantity ?: 0f
if (eaten == 0f) { if (eaten == 0f) {
if (food.servingQuantity != null && food.servingQuantity != 0f) { if (food.servingQuantity != null && food.servingQuantity != 0f) {
@ -37,11 +76,13 @@ data class Food(
} }
return Food( return Food(
name = food.name, name = food.name,
// do some slight edit on the serving to remove strange entries like `100 g`
serving = (food.servingSize ?: food.quantity ?: "unknown").replace(Regex(" +"), ""), serving = (food.servingSize ?: food.quantity ?: "unknown").replace(Regex(" +"), ""),
quantity = eaten, quantity = eaten,
proteins = food.nutriments.proteins, proteins = food.nutriments.proteins,
carbohydrates = food.nutriments.carbohydrates, carbohydrates = food.nutriments.carbohydrates,
fat = food.nutriments.fat, 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 image = food.image
) )

View File

@ -10,9 +10,8 @@ class FoodRepository @Inject constructor(
private val dao: FoodDao, private val dao: FoodDao,
private val offSource: OpenFoodFactService private val offSource: OpenFoodFactService
) { ) {
suspend fun findOnlineFood(name: String): Response<OFFResult> { suspend fun searchOpenFoodFact(name: String): Response<OFFResult> =
return offSource.searchProducts(name) offSource.searchProducts(name)
}
fun getAll() = dao.getAll() fun getAll() = dao.getAll()

View File

@ -3,14 +3,33 @@ package com.dzeio.openhealth.data.openfoodfact
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class OFFNutriments( data class OFFNutriments(
/**
* the quantity of carbohydrates in a 100g serving
*/
@SerializedName("carbohydrates_100g") @SerializedName("carbohydrates_100g")
var carbohydrates: Float, var carbohydrates: Float,
/**
* the energy in kcal in a 100g serving
*/
@SerializedName("energy-kcal_100g") @SerializedName("energy-kcal_100g")
var energy: Float?, var energy: Float?,
/**
* the energy KL in a 100g serving
*/
@SerializedName("energy-kj_100g") @SerializedName("energy-kj_100g")
var energyKJ: Float, var energyKJ: Float,
/**
* the quantity of fat in a 100g serving
*/
@SerializedName("fat_100g") @SerializedName("fat_100g")
var fat: Float, var fat: Float,
/**
* the quantity of proteins in a 100g serving
*/
@SerializedName("proteins_100g") @SerializedName("proteins_100g")
var proteins: Float var proteins: Float
) )

View File

@ -3,26 +3,51 @@ package com.dzeio.openhealth.data.openfoodfact
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class OFFProduct( data class OFFProduct(
/**
* the OFF product id
*/
@SerializedName("_id") @SerializedName("_id")
var id: String, var id: String,
/**
* the product name
*/
@SerializedName("product_name") @SerializedName("product_name")
var name: String, var name: String,
/**
* the size of a serving
*/
@SerializedName("serving_size") @SerializedName("serving_size")
var servingSize: String?, var servingSize: String?,
/**
* the size of a serving without the `g`, `ml`, etc
*/
@SerializedName("serving_quantity") @SerializedName("serving_quantity")
var servingQuantity: Float?, var servingQuantity: Float?,
/**
* the size of a serving
*/
@SerializedName("quantity") @SerializedName("quantity")
var quantity: String?, var quantity: String?,
/**
* the size of a serving without the `g`, `ml`, etc
*/
@SerializedName("product_quantity") @SerializedName("product_quantity")
var productQuantity: Float?, var productQuantity: Float?,
/**
* the product nutriments
*/
@SerializedName("nutriments") @SerializedName("nutriments")
var nutriments: OFFNutriments, var nutriments: OFFNutriments,
/**
* the product image
*/
@SerializedName("image_url") @SerializedName("image_url")
var image: String? var image: String?
) )

View File

@ -3,6 +3,9 @@ package com.dzeio.openhealth.data.openfoodfact
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class OFFResult( data class OFFResult(
/**
* the list of products
*/
@SerializedName("products") @SerializedName("products")
var products: List<OFFProduct> var products: List<OFFProduct>
) )

View File

@ -1,5 +1,6 @@
package com.dzeio.openhealth.data.openfoodfact package com.dzeio.openhealth.data.openfoodfact
import com.dzeio.openhealth.BuildConfig
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import retrofit2.Response import retrofit2.Response
import retrofit2.Retrofit import retrofit2.Retrofit
@ -30,11 +31,17 @@ interface OpenFoodFactService {
} }
} }
@Headers("User-Agent: OpenHealth - Android - Version 1.0 - https://github.com/dzeiocom/OpenHealth") /**
* Search a product by it's name
*/
@Headers("User-Agent: OpenHealth - Android - Version ${BuildConfig.VERSION_NAME} - https://github.com/dzeiocom/OpenHealth")
@GET("/cgi/search.pl?json=true&fields=_id,nutriments,product_name,serving_quantity,serving_size,quantity,product_quantity,image_url&action=process") @GET("/cgi/search.pl?json=true&fields=_id,nutriments,product_name,serving_quantity,serving_size,quantity,product_quantity,image_url&action=process")
suspend fun searchProducts(@Query("search_terms2") name: String): Response<OFFResult> suspend fun searchProducts(@Query("search_terms2") name: String): Response<OFFResult>
@Headers("User-Agent: OpenHealth - Android - Version 1.0 - https://github.com/dzeiocom/OpenHealth") /**
* Search a product by it's barcode
*/
@Headers("User-Agent: OpenHealth - Android - Version ${BuildConfig.VERSION_NAME} - https://github.com/dzeiocom/OpenHealth")
@GET("/api/v2/search?fields=_id,nutriments,product_name,serving_quantity") @GET("/api/v2/search?fields=_id,nutriments,product_name,serving_quantity")
suspend fun findByCode(@Query("code") code: String): Response<OFFResult> suspend fun findByCode(@Query("code") code: String): Response<OFFResult>

View File

@ -12,6 +12,10 @@ import java.util.TimeZone
@Entity() @Entity()
data class Step( data class Step(
@PrimaryKey(autoGenerate = true) var id: Long = 0, @PrimaryKey(autoGenerate = true) var id: Long = 0,
/**
* the raw number of step
*/
var value: Int = 0, var value: Int = 0,
/** /**
* Timestamp down to an hour * Timestamp down to an hour
@ -20,6 +24,12 @@ data class Step(
*/ */
@ColumnInfo(index = true) @ColumnInfo(index = true)
var timestamp: Long = 0, var timestamp: Long = 0,
/**
* the source for the Entry
*
* note: Unused currently but kept for future usage
*/
var source: String = "OpenHealth" var source: String = "OpenHealth"
) { ) {

View File

@ -12,10 +12,15 @@ import com.dzeio.openhealth.Application
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
/**
* Class that allows us to get Sensor datas for the internal step counter
*
* TODO: rewrite to use the new libs
*/
class StepSource( class StepSource(
private val context: Context, private val context: Context,
private val callback: ((Float) -> Unit)? = null private val callback: ((Float) -> Unit)? = null
): SensorEventListener { ) : SensorEventListener {
companion object { companion object {
const val TAG = "${Application.TAG}/StepSource" const val TAG = "${Application.TAG}/StepSource"

View File

@ -11,9 +11,23 @@ import java.util.TimeZone
@Entity() @Entity()
data class Water( data class Water(
@PrimaryKey(autoGenerate = true) var id: Long = 0, @PrimaryKey(autoGenerate = true) var id: Long = 0,
/**
* the quantity of water in ML drank by the user
*/
var value: Int = 0, var value: Int = 0,
/**
* when the water was drank precise to the day
*/
@ColumnInfo(index = true) @ColumnInfo(index = true)
var timestamp: Long = 0, var timestamp: Long = 0,
/**
* the source for the Entry
*
* note: Unused currently but kept for future usage
*/
var source: String = "OpenHealth" var source: String = "OpenHealth"
) { ) {
init { init {

View File

@ -14,9 +14,19 @@ data class Weight(
* Store the weight in kilograms * Store the weight in kilograms
*/ */
var weight: Float = 0f, var weight: Float = 0f,
/**
* when the weight was taken precise to the millisecond
*/
@ColumnInfo(index = true) @ColumnInfo(index = true)
var timestamp: Long = System.currentTimeMillis(), var timestamp: Long = System.currentTimeMillis(),
var source: String = ""
/**
* the source for the Entry
*
* note: Unused currently but kept for future usage
*/
var source: String = "OpenHealth"
) { ) {
fun formatTimestamp(): String = getDateInstance().format(Date(timestamp)); fun formatTimestamp(): String = getDateInstance().format(Date(timestamp));
} }