From 9d3585c42c53f9627d7a54725fa78fc64f016a93 Mon Sep 17 00:00:00 2001 From: Avior Date: Thu, 24 Nov 2022 00:51:39 +0100 Subject: [PATCH] feat: Add base for Food monitoring --- README.md | 2 - app/build.gradle.kts | 8 +- .../2.json | 220 ++++++++++++++++++ app/src/main/AndroidManifest.xml | 1 + .../java/com/dzeio/openhealth/Settings.kt | 7 - .../openhealth/adapters/ExtensionAdapter.kt | 38 --- .../dzeio/openhealth/adapters/FoodAdapter.kt | 28 +++ .../com/dzeio/openhealth/data/AppDatabase.kt | 15 +- .../com/dzeio/openhealth/data/food/Food.kt | 20 ++ .../com/dzeio/openhealth/data/food/FoodDao.kt | 22 ++ .../openhealth/data/food/FoodRepository.kt | 19 ++ .../data/openfoodfact/OFFNutriments.kt | 14 ++ .../data/openfoodfact/OFFProduct.kt | 16 ++ .../openhealth/data/openfoodfact/OFFResult.kt | 8 + .../data/openfoodfact/OpenFoodFactService.kt | 38 +++ .../com/dzeio/openhealth/di/DatabaseModule.kt | 15 +- .../dzeio/openhealth/extensions/Extension.kt | 143 ------------ .../openhealth/extensions/ExtensionFactory.kt | 37 --- .../extensions/FileSystemExtension.kt.old | 68 ------ .../openhealth/extensions/GoogleFit.kt.old | 210 ----------------- .../extensions/GoogleFitExtension.kt | 187 --------------- .../extensions/HealthConnectExtension.kt | 137 ----------- .../extensions/samsunghealth/SamsungHealth.kt | 112 --------- .../samsunghealth/StepCountReporter.kt | 88 ------- .../openhealth/ui/browse/BrowseFragment.kt | 4 + .../ui/extension/ExtensionFragment.kt | 90 ------- .../ui/extension/ExtensionViewModel.kt | 30 --- .../ui/extensions/ExtensionsFragment.kt | 78 ------- .../ui/extensions/ExtensionsViewModel.kt | 16 -- .../openhealth/ui/food/FoodHomeFragment.kt | 47 ++++ .../openhealth/ui/food/FoodHomeViewModel.kt | 26 +++ .../main/res/layout/fragment_extension.xml | 39 ---- .../main/res/layout/fragment_extensions.xml | 9 - .../main/res/layout/fragment_food_home.xml | 37 +++ app/src/main/res/layout/item_food.xml | 48 ++++ .../main/res/navigation/mobile_navigation.xml | 30 +-- 36 files changed, 589 insertions(+), 1318 deletions(-) create mode 100644 app/schemas/com.dzeio.openhealth.data.AppDatabase/2.json delete mode 100644 app/src/main/java/com/dzeio/openhealth/adapters/ExtensionAdapter.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/adapters/FoodAdapter.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/data/food/Food.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/data/food/FoodDao.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/data/food/FoodRepository.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFNutriments.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFProduct.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFResult.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OpenFoodFactService.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/ExtensionFactory.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/FileSystemExtension.kt.old delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt.old delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/GoogleFitExtension.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/HealthConnectExtension.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/SamsungHealth.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/StepCountReporter.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionFragment.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionViewModel.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt delete mode 100644 app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsViewModel.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/food/FoodHomeFragment.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/food/FoodHomeViewModel.kt delete mode 100644 app/src/main/res/layout/fragment_extension.xml delete mode 100644 app/src/main/res/layout/fragment_extensions.xml create mode 100644 app/src/main/res/layout/fragment_food_home.xml create mode 100644 app/src/main/res/layout/item_food.xml diff --git a/README.md b/README.md index 35c2e78..9c366d8 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,6 @@ Permissions requests are for specifics usage and are only requests the first tim | Permission | Why is it requested | |:----------------------:|:-----------------------------------------------------------------| -| ACCESS_FINE_LOCATION | Google Fit Extension Requirement (maybe not, still have to test) | -| ACCESS_COARSE_LOCATION | Same as above | | ACTIVITY_RECOGNITION | Device Steps Usage | No other permissions are used (even the internet permission ;)). diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ecd96f1..24e0003 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -129,7 +129,7 @@ dependencies { implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.appcompat:appcompat:1.7.0-alpha01") implementation("javax.inject:javax.inject:1") - implementation("com.google.android.material:material:1.8.0-alpha02") + implementation("com.google.android.material:material:1.8.0-alpha03") implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") @@ -174,7 +174,7 @@ dependencies { // Google Fit implementation("com.google.android.gms:play-services-fitness:21.1.0") - implementation("com.google.android.gms:play-services-auth:20.3.0") + implementation("com.google.android.gms:play-services-auth:20.4.0") implementation("androidx.health.connect:connect-client:1.0.0-alpha07") // Samsung Health @@ -194,4 +194,8 @@ dependencies { // OSS Licenses implementation("com.google.android.gms:play-services-oss-licenses:17.0.0") + + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.9.1") } diff --git a/app/schemas/com.dzeio.openhealth.data.AppDatabase/2.json b/app/schemas/com.dzeio.openhealth.data.AppDatabase/2.json new file mode 100644 index 0000000..59c704e --- /dev/null +++ b/app/schemas/com.dzeio.openhealth.data.AppDatabase/2.json @@ -0,0 +1,220 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "794ed5ee15db239f9a2708b951f55552", + "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, `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": "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, '794ed5ee15db239f9a2708b951f55552')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 22b0177..e0cbd1e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + diff --git a/app/src/main/java/com/dzeio/openhealth/Settings.kt b/app/src/main/java/com/dzeio/openhealth/Settings.kt index ec50814..a5128e7 100644 --- a/app/src/main/java/com/dzeio/openhealth/Settings.kt +++ b/app/src/main/java/com/dzeio/openhealth/Settings.kt @@ -1,7 +1,5 @@ package com.dzeio.openhealth -import com.dzeio.openhealth.extensions.Extension - object Settings { /** @@ -28,9 +26,4 @@ object Settings { * format in which the weight the user want it to be displayed as */ const val MASS_UNIT = "com.dzeio.open-health.unit.mass" - - fun extensionEnabled(extension: Extension): String { - return "com.dzeio.open-health.extension.${extension.id}.enabled" - } - } diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/ExtensionAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/ExtensionAdapter.kt deleted file mode 100644 index 14a6afb..0000000 --- a/app/src/main/java/com/dzeio/openhealth/adapters/ExtensionAdapter.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.dzeio.openhealth.adapters - -import android.view.LayoutInflater -import android.view.ViewGroup -import com.dzeio.openhealth.Settings -import com.dzeio.openhealth.core.BaseAdapter -import com.dzeio.openhealth.core.BaseViewHolder -import com.dzeio.openhealth.databinding.LayoutExtensionItemBinding -import com.dzeio.openhealth.extensions.Extension -import com.dzeio.openhealth.utils.Configuration - - -class ExtensionAdapter( - private val config: Configuration -) : BaseAdapter() { - - override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> - LayoutExtensionItemBinding = LayoutExtensionItemBinding::inflate - - var onItemClick: ((weight: Extension) -> Unit)? = null - - override fun onBindData( - holder: BaseViewHolder, - item: Extension, - position: Int - ) { - val isEnabled = config.getBoolean(Settings.extensionEnabled(item)).value ?: false - holder.binding.name.text = item.name - holder.binding.card.isClickable = item.isAvailable() - holder.binding.card.isEnabled = item.isAvailable() - holder.binding.status.text = "enabled = $isEnabled" - if (item.isAvailable()) { - holder.binding.card.setOnClickListener { - onItemClick?.invoke(item) - } - } - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/FoodAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/FoodAdapter.kt new file mode 100644 index 0000000..f091dc5 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/adapters/FoodAdapter.kt @@ -0,0 +1,28 @@ +package com.dzeio.openhealth.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +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 + +class FoodAdapter() : BaseAdapter() { + + override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ItemFoodBinding + get() = ItemFoodBinding::inflate + + var onItemClick: ((weight: Food) -> Unit)? = null + + override fun onBindData( + holder: BaseViewHolder, + item: Food, + position: Int + ) { + holder.binding.foodName.text = item.name + holder.binding.foodDescription.text = item.energy.toString() + holder.binding.edit.setOnClickListener { + onItemClick?.invoke(item) + } + } +} 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 8d3adf0..b0fb3dc 100644 --- a/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt +++ b/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt @@ -1,9 +1,12 @@ 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 +import com.dzeio.openhealth.data.food.Food +import com.dzeio.openhealth.data.food.FoodDao import com.dzeio.openhealth.data.step.Step import com.dzeio.openhealth.data.step.StepDao import com.dzeio.openhealth.data.water.Water @@ -15,10 +18,14 @@ import com.dzeio.openhealth.data.weight.WeightDao entities = [ Weight::class, Water::class, - Step::class + Step::class, + Food::class ], - version = 1, - exportSchema = true + version = 2, + exportSchema = true, + autoMigrations = [ + AutoMigration(from = 1, to = 2) + ] ) abstract class AppDatabase : RoomDatabase() { @@ -28,6 +35,8 @@ abstract class AppDatabase : RoomDatabase() { abstract fun waterDao(): WaterDao abstract fun stepDao(): StepDao + abstract fun foodDao(): FoodDao + companion object { private const val DATABASE_NAME = "open_health" 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 new file mode 100644 index 0000000..896f547 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/food/Food.kt @@ -0,0 +1,20 @@ +package com.dzeio.openhealth.data.food + +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.util.Calendar +import java.util.TimeZone + +@Entity +data class Food( + @PrimaryKey(autoGenerate = true) + var id: Long = 0, + var name: String, + var quantity: Int, + var proteins: Float, + var carbohydrates: Float, + var fat: Float, + var energy: Float, + + var timestamp: Long = Calendar.getInstance(TimeZone.getTimeZone("UTC")).timeInMillis +) diff --git a/app/src/main/java/com/dzeio/openhealth/data/food/FoodDao.kt b/app/src/main/java/com/dzeio/openhealth/data/food/FoodDao.kt new file mode 100644 index 0000000..a0d633c --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/food/FoodDao.kt @@ -0,0 +1,22 @@ +package com.dzeio.openhealth.data.food + +import androidx.room.Dao +import androidx.room.Query +import com.dzeio.openhealth.core.BaseDao +import kotlinx.coroutines.flow.Flow + +@Dao +interface FoodDao : BaseDao { + @Query("SELECT * FROM Food ORDER BY timestamp DESC") + fun getAll(): Flow> + + @Query("SELECT * FROM Food where id = :weightId") + fun getOne(weightId: Long): Flow + + @Query("Select count(*) from Food") + fun getCount(): Flow + + @Query("Select * FROM Food ORDER BY timestamp DESC LIMIT 1") + fun last(): Flow + +} 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 new file mode 100644 index 0000000..a7e0fe3 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/food/FoodRepository.kt @@ -0,0 +1,19 @@ +package com.dzeio.openhealth.data.food + +import com.dzeio.openhealth.data.openfoodfact.OFFResult +import com.dzeio.openhealth.data.openfoodfact.OpenFoodFactService +import retrofit2.Response +import javax.inject.Inject + + +class FoodRepository @Inject constructor( + private val dao: FoodDao, + private val offSource: OpenFoodFactService +) { + suspend fun findOnlineFood(name: String): Response { + return offSource.searchProducts(name) + } + + fun getAll() = dao.getAll() + +} diff --git a/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFNutriments.kt b/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFNutriments.kt new file mode 100644 index 0000000..0e6ad45 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFNutriments.kt @@ -0,0 +1,14 @@ +package com.dzeio.openhealth.data.openfoodfact + +import com.google.gson.annotations.SerializedName + +data class OFFNutriments( + @SerializedName("carbohydrates_100g") + var carbohydrates: Float, + @SerializedName("energy-kcal_100g") + var energy: Float, + @SerializedName("fat_100g") + var fat: Float, + @SerializedName("proteins_100g") + var proteins: Float +) diff --git a/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFProduct.kt b/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFProduct.kt new file mode 100644 index 0000000..02e4c59 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFProduct.kt @@ -0,0 +1,16 @@ +package com.dzeio.openhealth.data.openfoodfact + +import com.google.gson.annotations.SerializedName + +data class OFFProduct( + @SerializedName("_id") + var id: String, + @SerializedName("product_name") + var name: String, + + @SerializedName("serving_quantity") + var serving: String, + + @SerializedName("nutriments") + var nutriments: OFFNutriments +) diff --git a/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFResult.kt b/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFResult.kt new file mode 100644 index 0000000..a872066 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OFFResult.kt @@ -0,0 +1,8 @@ +package com.dzeio.openhealth.data.openfoodfact + +import com.google.gson.annotations.SerializedName + +data class OFFResult( + @SerializedName("products") + var products: List +) diff --git a/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OpenFoodFactService.kt b/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OpenFoodFactService.kt new file mode 100644 index 0000000..3b020d2 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/openfoodfact/OpenFoodFactService.kt @@ -0,0 +1,38 @@ +package com.dzeio.openhealth.data.openfoodfact + +import com.google.gson.GsonBuilder +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.Query + +interface OpenFoodFactService { + + companion object { + fun getService(): OpenFoodFactService { + val interceptor = HttpLoggingInterceptor() + interceptor.setLevel(HttpLoggingInterceptor.Level.BODY) + val client = OkHttpClient.Builder() + .addInterceptor(interceptor) + .build() + val gson = GsonBuilder() + .setLenient() + .create() + val retrofit = Retrofit.Builder() + .baseUrl("https://world.openfoodfacts.org/") + .addConverterFactory(GsonConverterFactory.create(gson)) + .client(client) + .build() + + return retrofit.create(OpenFoodFactService::class.java) + } + } + + @Headers("User-Agent: OpenHealth - Android - Version 1.0 - https://github.com/dzeiocom/OpenHealth") + @GET("/api/v2/search?fields=_id,nutriments,product_name,serving_quantity") + suspend fun searchProducts(@Query("product_name") name: String): Response +} diff --git a/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt b/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt index 10841ac..c7bb1e5 100644 --- a/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt +++ b/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt @@ -2,6 +2,8 @@ package com.dzeio.openhealth.di import android.content.Context import com.dzeio.openhealth.data.AppDatabase +import com.dzeio.openhealth.data.food.FoodDao +import com.dzeio.openhealth.data.openfoodfact.OpenFoodFactService import com.dzeio.openhealth.data.step.StepDao import com.dzeio.openhealth.data.water.WaterDao import com.dzeio.openhealth.data.weight.WeightDao @@ -36,4 +38,15 @@ class DatabaseModule { fun provideStepsDao(appDatabase: AppDatabase): StepDao { return appDatabase.stepDao() } -} \ No newline at end of file + + @Provides + fun provideFoodDao(appDatabase: AppDatabase): FoodDao { + return appDatabase.foodDao() + } + + @Singleton + @Provides + fun provideOpenFoodFactService(): OpenFoodFactService { + return OpenFoodFactService.getService() + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt b/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt deleted file mode 100644 index fd45fec..0000000 --- a/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.dzeio.openhealth.extensions - -import androidx.activity.result.ActivityResultCallback -import androidx.activity.result.contract.ActivityResultContract -import androidx.fragment.app.Fragment -import com.dzeio.openhealth.data.weight.Weight -import kotlinx.coroutines.flow.Flow - -/** - * Extension Schema - * - * Version: 0.2.0 - */ -interface Extension : ActivityResultCallback { - - data class TaskProgress( - /** - * value indicating the current status of the task - */ - val state: TaskState = TaskState.INITIALIZATING, - - /** - * value between 0 and 100 indicating the progress for the task - */ - val progress: Float? = null, - - /** - * Additionnal message that will be displayed when the task has ended in a [TaskState.CANCELLED] or [TaskState.ERROR] state - */ - val statusMessage: String? = null, - - /** - * Additional information - */ - val additionalData: T? = null - ) - - enum class TaskState { - /** - * define the task as being preped - */ - INITIALIZATING, - - /** - * Define the task a bein worked on - */ - WORK_IN_PROGRESS, - - /** - * define the task as being done - */ - DONE, - - /** - * Define the task as being cancelled - */ - CANCELLED, - - /** - * define the task as being ended with an error - */ - ERROR - } - - enum class Data { - /** - * Special case to handle basic errors from other activities - */ - NOTHING, - WEIGHT, - STEPS - - /** - * Google Fit: - * - * STEP_COUNT_CUMULATIVE - * ACTIVITY_SEGMENT - * SLEEP_SEGMENT - * CALORIES_EXPENDED - * BASAL_METABOLIC_RATE - * POWER_SAMPLE - * HEART_RATE_BPM - * LOCATION_SAMPLE - */ - } - - /** - * the permissions necessary for the extension to works - */ - val permissions: Array - - /** - * the Source ID - * - * DO NOT CHANGE IT AFTER THE EXTENSION IS IN PRODUCTION - */ - val id: String - - /** - * The Extension Display Name - */ - val name: String - - /** - * the different types of data handled by the extension - */ - val data: Array - - /** - * Enable the extension, no code is gonna be run before - */ - fun enable(fragment: Fragment): Boolean - - /** - * return if the extension is already connected to remote of not - */ - suspend fun isConnected(): Boolean - - /** - * Return if the extension is runnable on the device - */ - fun isAvailable(): Boolean - - /** - * try to connect to remote - */ - suspend fun connect(): Boolean - - val contract: ActivityResultContract<*, *>? - val requestInput: Any? - suspend fun importWeight(): Flow>> - - /** - * function run when outgoing sync is enabled and new value is added - * or manual export is launched - */ - suspend fun exportWeights(weight: Array): Flow> - -// fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) = Unit - - - suspend fun permissionsGranted(): Boolean -} diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/ExtensionFactory.kt b/app/src/main/java/com/dzeio/openhealth/extensions/ExtensionFactory.kt deleted file mode 100644 index df4b895..0000000 --- a/app/src/main/java/com/dzeio/openhealth/extensions/ExtensionFactory.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.dzeio.openhealth.extensions - -import android.os.Build - -class ExtensionFactory { - companion object { - fun getExtension(extension: String): Extension? { - return when (extension) { - "GoogleFit" -> { - GoogleFitExtension() - } - "HealthConnect" -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - HealthConnectExtension() - } else { - TODO("VERSION.SDK_INT < P") - } - } - else -> { - null - } - } - } - - fun getAll(): ArrayList { - val extensions: ArrayList = arrayListOf( - GoogleFitExtension() - ) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - extensions.add(HealthConnectExtension()) - } - - return extensions - } - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/FileSystemExtension.kt.old b/app/src/main/java/com/dzeio/openhealth/extensions/FileSystemExtension.kt.old deleted file mode 100644 index c5e6ec0..0000000 --- a/app/src/main/java/com/dzeio/openhealth/extensions/FileSystemExtension.kt.old +++ /dev/null @@ -1,68 +0,0 @@ -package com.dzeio.openhealth.extensions - -import android.Manifest -import android.app.Activity -import android.content.Intent -import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.dzeio.openhealth.data.weight.Weight - -class FileSystemExtension : Extension { - companion object { - const val TAG = "FSExtension" - } - - private lateinit var activity: Activity - - override val id = "FileSystem" - override val name = "File System" - - override val permissions = arrayOf( - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - - override val permissionsText: String = "Please" - - override fun init(activity: Activity): Array { - this.activity = activity - return Extension.Data.values() - } - - override fun getStatus(): String { - return "" - } - - override fun isAvailable(): Boolean { - return true - } - - override fun isConnected(): Boolean = true - - private val connectLiveData: MutableLiveData = MutableLiveData(Extension.States.DONE) - - override fun connect(): LiveData = connectLiveData - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - Log.d(this.name, "[$requestCode] -> [$resultCode]: $data") - if (requestCode == 0) { - return - } - - if (resultCode == Activity.RESULT_OK) connectLiveData.value = Extension.States.DONE - // signIn(Data.values()[requestCode]) - } - - override fun importWeight(): LiveData> { - - weightLiveData = MutableLiveData( - Extension.ImportState( - Extension.States.WIP - ) - ) - - startImport(Extension.Data.WEIGHT) - - return weightLiveData - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt.old b/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt.old deleted file mode 100644 index e5b5d40..0000000 --- a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt.old +++ /dev/null @@ -1,210 +0,0 @@ -package com.dzeio.openhealth.extensions - -import android.Manifest -import android.app.Activity -import android.content.Intent -import android.util.Log -import androidx.core.app.ActivityCompat -import androidx.fragment.app.Fragment -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.dzeio.openhealth.data.weight.Weight -import com.google.android.gms.auth.api.signin.GoogleSignIn -import com.google.android.gms.fitness.Fitness -import com.google.android.gms.fitness.FitnessOptions -import com.google.android.gms.fitness.data.DataType -import com.google.android.gms.fitness.request.DataReadRequest -import java.text.DateFormat -import java.util.Calendar -import java.util.Date -import java.util.TimeZone -import java.util.concurrent.TimeUnit - -class GoogleFit: Extension { - companion object { - const val TAG = "GoogleFitConnector" - } - - private lateinit var activity: Activity - - override val id = "GoogleFit" - override val name = "Google Fit" - - override val permissions = arrayOf( - Manifest.permission.ACCESS_FINE_LOCATION - ) - - override val permissionsText: String = "Please" - - override fun init(activity: Fragment): Array { - this.activity = activity.register - return arrayOf( - Extension.Data.WEIGHT - ) - } - - override fun getStatus(): String { - return if (isConnected()) "Connected" else "Not Connected" - } - - override fun isAvailable(): Boolean { - return true - } - - override fun isConnected(): Boolean = - GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions) - - private val fitnessOptions = FitnessOptions.builder() - .addDataType(DataType.TYPE_WEIGHT) -// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE) -// .addDataType(DataType.TYPE_CALORIES_EXPENDED) - .build() - - private val connectLiveData: MutableLiveData = MutableLiveData(Extension.States.WIP) - - override fun connect(): LiveData { - - if (isConnected()) { - connectLiveData.value = Extension.States.DONE - } else { - Log.d(this.name, "Signing In") - GoogleSignIn.requestPermissions( - activity, - 124887, - getGoogleAccount(), fitnessOptions - ) - } - return connectLiveData - } - - private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions) - - private val timeRange by lazy { - val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) - calendar.time = Date() - val endTime = calendar.timeInMillis - - // Set year to 2013 to be sure to get data from when Google Fit Started to today - calendar.set(Calendar.YEAR, 2013) - val startTime = calendar.timeInMillis - return@lazy arrayOf(startTime, endTime) - } - - private fun startImport(data: Extension.Data) { - Log.d("GoogleFitImporter", "Importing for ${data.name}") - - val dateFormat = DateFormat.getDateInstance() - Log.i(TAG, "Range Start: ${dateFormat.format(timeRange[0])}") - Log.i(TAG, "Range End: ${dateFormat.format(timeRange[1])}") - - var type = DataType.TYPE_WEIGHT - var timeUnit = TimeUnit.MILLISECONDS - - when (data) { - Extension.Data.STEPS -> { - type = DataType.TYPE_STEP_COUNT_CUMULATIVE - } - else -> {} - } - - runRequest( - DataReadRequest.Builder() - .read(type) - .setTimeRange(timeRange[0], timeRange[1], timeUnit) - .build(), - data - ) - } - - private fun runRequest(request: DataReadRequest, data: Extension.Data) { - Fitness.getHistoryClient( - activity, - GoogleSignIn.getAccountForExtension(activity, fitnessOptions) - ) - .readData(request) - .addOnSuccessListener { response -> - Log.d( - TAG, - "Received response! ${response.dataSets.size} ${response.buckets.size} ${response.status}" - ) - for (dataSet in response.dataSets) { - Log.i( - TAG, - "Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size} ${dataSet.dataSource}" - ) - dataSet.dataPoints.forEach { dp -> - - // Global - Log.i(TAG, "Importing Data point:") - Log.i(TAG, "\tType: ${dp.dataType.name}") - Log.i( - TAG, - "\tStart: ${Date(dp.getStartTime(TimeUnit.SECONDS) * 1000L).toLocaleString()}" - ) - Log.i( - TAG, - "\tEnd: ${Date(dp.getEndTime(TimeUnit.SECONDS) * 1000L).toLocaleString()}" - ) - - // Field Specifics - for (field in dp.dataType.fields) { - Log.i(TAG, "\tField: ${field.name} Value: ${dp.getValue(field)}") - when (data) { - Extension.Data.WEIGHT -> { - val weight = Weight() - weight.timestamp = dp.getStartTime(TimeUnit.MILLISECONDS) - weight.weight = dp.getValue(field).asFloat() - val list = weightLiveData.value?.list?.toMutableList() - ?: ArrayList() - list.add(weight) - weightLiveData.value = - - Extension.ImportState(Extension.States.WIP, list) - } - else -> {} - } - } - } - when (data) { - Extension.Data.WEIGHT -> { - weightLiveData.value = - Extension.ImportState( - Extension.States.DONE, - weightLiveData.value?.list - ?: ArrayList() - ) - } - else -> {} - } - } - } - .addOnFailureListener { e -> - Log.e(TAG, "There was an error reading data from Google Fit", e) - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - Log.d(this.name, "[$requestCode] -> [$resultCode]: $data") - if (requestCode == 0) { - return - } - - if (resultCode == Activity.RESULT_OK) connectLiveData.value = Extension.States.DONE - // signIn(Data.values()[requestCode]) - } - - private lateinit var weightLiveData: MutableLiveData> - - override fun importWeight(): LiveData> { - - weightLiveData = MutableLiveData( - Extension.ImportState( - Extension.States.WIP - ) - ) - - startImport(Extension.Data.WEIGHT) - - return weightLiveData - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFitExtension.kt b/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFitExtension.kt deleted file mode 100644 index a347645..0000000 --- a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFitExtension.kt +++ /dev/null @@ -1,187 +0,0 @@ -package com.dzeio.openhealth.extensions - -import android.Manifest -import android.util.Log -import androidx.activity.result.contract.ActivityResultContract -import androidx.activity.result.contract.ActivityResultContracts -import androidx.fragment.app.Fragment -import com.dzeio.openhealth.core.Observable -import com.dzeio.openhealth.data.weight.Weight -import com.dzeio.openhealth.utils.PermissionsManager -import com.google.android.gms.auth.api.signin.GoogleSignIn -import com.google.android.gms.fitness.Fitness -import com.google.android.gms.fitness.FitnessOptions -import com.google.android.gms.fitness.data.DataType -import com.google.android.gms.fitness.request.DataReadRequest -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.suspendCancellableCoroutine -import java.util.Calendar -import java.util.Date -import java.util.TimeZone -import java.util.concurrent.TimeUnit - -class GoogleFitExtension : Extension { - companion object { - const val TAG = "GoogleFitConnector" - } - - override val id = "GoogleFit" - override val name = "Google Fit" - - override val permissions = arrayOf( - Manifest.permission.ACCESS_FINE_LOCATION - ) - - override val data: Array = arrayOf( - Extension.Data.WEIGHT - ) - - override suspend fun isConnected(): Boolean = - GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions) - - - private val fitnessOptions = FitnessOptions.builder() - .addDataType(DataType.TYPE_WEIGHT) -// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE) -// .addDataType(DataType.TYPE_CALORIES_EXPENDED) - .build() - - private val connectionStatus = Observable(false) - - private lateinit var fragment: Fragment - - override fun isAvailable(): Boolean = true - - override fun enable(fragment: Fragment): Boolean { - this.fragment = fragment - return true - } - - @OptIn(ExperimentalCoroutinesApi::class) - override suspend fun connect(): Boolean { - - if (isConnected()) { - return true - } - - return suspendCancellableCoroutine { cancellableContinuation -> - Log.d(this.name, "Signing In") - GoogleSignIn.requestPermissions( - fragment, - 124887, - getGoogleAccount(), fitnessOptions - ) - connectionStatus.addOneTimeObserver { it: Boolean -> - cancellableContinuation.resume(it) { - - } - } - } - } - - override val contract: ActivityResultContract<*, Map>? = ActivityResultContracts.RequestMultiplePermissions() - override val requestInput = permissions - - private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(fragment.requireContext(), fitnessOptions) - - private val timeRange by lazy { - val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) - calendar.time = Date() - val endTime = calendar.timeInMillis - - // Set year to 2013 to be sure to get data from when Google Fit Started to today - calendar.set(Calendar.YEAR, 2013) - val startTime = calendar.timeInMillis - return@lazy arrayOf(startTime, endTime) - } - - override suspend fun importWeight(): Flow>> = - channelFlow { - send( - Extension.TaskProgress( - Extension.TaskState.INITIALIZATING - ) - ) - - val type = DataType.TYPE_WEIGHT - val timeUnit = TimeUnit.MILLISECONDS - - val request = DataReadRequest.Builder() - .read(type) - .setTimeRange(timeRange[0], timeRange[1], timeUnit) - .build() - - Fitness.getHistoryClient( - fragment.requireContext(), - GoogleSignIn.getAccountForExtension(fragment.requireContext(), fitnessOptions) - ) - .readData(request) - .addOnSuccessListener { response -> - val weights: ArrayList = ArrayList() - var index = 0 - var total = response.dataSets.size - for (dataset in response.dataSets) { - total += dataset.dataPoints.size - 1 - for (dataPoint in dataset.dataPoints) { - total += dataPoint.dataType.fields.size - 1 - for (field in dataPoint.dataType.fields) { - val weight = Weight().apply { - timestamp = dataPoint.getStartTime(TimeUnit.MILLISECONDS) - weight = dataPoint.getValue(field).asFloat() - source = this@GoogleFitExtension.id - } - weights.add(weight) - runBlocking { - send( - Extension.TaskProgress( - Extension.TaskState.WORK_IN_PROGRESS, - progress = index++ / total.toFloat() - ) - ) - } - } - } - } - runBlocking { - send( - Extension.TaskProgress( - Extension.TaskState.DONE, - additionalData = weights - ) - ) - } - } - .addOnFailureListener { - runBlocking { - send( - Extension.TaskProgress( - Extension.TaskState.ERROR, - statusMessage = it.localizedMessage ?: it.message ?: "Unknown error" - ) - ) - } - } - - - } - - override suspend fun exportWeights(weight: Array): Flow> { - TODO("Not yet implemented") - } - - override suspend fun permissionsGranted(): Boolean { - return PermissionsManager.hasPermission(this.fragment.requireContext(), permissions) - } - - override fun onActivityResult(result: Any) { - if ((result as Map<*, *>).containsValue(false)) { - return - } - - connectionStatus.value = true - } - -} diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/HealthConnectExtension.kt b/app/src/main/java/com/dzeio/openhealth/extensions/HealthConnectExtension.kt deleted file mode 100644 index 808764e..0000000 --- a/app/src/main/java/com/dzeio/openhealth/extensions/HealthConnectExtension.kt +++ /dev/null @@ -1,137 +0,0 @@ -package com.dzeio.openhealth.extensions - -import android.Manifest -import android.os.Build -import androidx.activity.result.contract.ActivityResultContract -import androidx.annotation.RequiresApi -import androidx.fragment.app.Fragment -import androidx.health.connect.client.HealthConnectClient -import androidx.health.connect.client.PermissionController -import androidx.health.connect.client.permission.HealthPermission -import androidx.health.connect.client.records.HeartRateRecord -import androidx.health.connect.client.records.StepsRecord -import androidx.health.connect.client.records.WeightRecord -import androidx.health.connect.client.request.ReadRecordsRequest -import androidx.health.connect.client.time.TimeRangeFilter -import com.dzeio.openhealth.core.Observable -import com.dzeio.openhealth.data.weight.Weight -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.runBlocking -import java.time.Instant - -@RequiresApi(Build.VERSION_CODES.P) -class HealthConnectExtension : Extension { - companion object { - const val TAG = "HealthConnectExtension" - } - - // build a set of permissions for required data types - val PERMISSIONS = - setOf( - HealthPermission.createReadPermission(HeartRateRecord::class), - HealthPermission.createWritePermission(HeartRateRecord::class), - HealthPermission.createReadPermission(StepsRecord::class), - HealthPermission.createWritePermission(StepsRecord::class) - ) - - - override val id = "HealthConnect" - override val name = "Health Connect" - - override val permissions = arrayOf( - Manifest.permission.ACCESS_FINE_LOCATION - ) - - override val requestInput = PERMISSIONS - - override val data: Array = arrayOf( - Extension.Data.WEIGHT - ) - - override suspend fun isConnected(): Boolean = true - - - - private val connectionStatus = Observable(false) - - private lateinit var fragment: Fragment - private lateinit var client: HealthConnectClient - - override fun isAvailable(): Boolean { - return HealthConnectClient.isAvailable(fragment.requireContext()) - } - - override fun enable(fragment: Fragment): Boolean { - this.fragment = fragment - if (!isAvailable()) { - return false - } - - this.client = HealthConnectClient.getOrCreate(fragment.requireContext()) - - return true - } - - override suspend fun connect(): Boolean = true - - override suspend fun importWeight(): Flow>> = - channelFlow { - send( - Extension.TaskProgress( - Extension.TaskState.INITIALIZATING - ) - ) - - val response = client.readRecords( - ReadRecordsRequest( - WeightRecord::class, - timeRangeFilter = TimeRangeFilter.before(Instant.now()) - ) - ) - - val weights: ArrayList = ArrayList() - var index = 0 - for (record in response.records) { - val weight = Weight().apply { - timestamp = record.time.toEpochMilli() - weight = record.weight.inKilograms.toFloat() - source = this@HealthConnectExtension.id - } - weights.add(weight) - runBlocking { - send( - Extension.TaskProgress( - Extension.TaskState.WORK_IN_PROGRESS, - progress = index++ / response.records.size.toFloat() - ) - ) - } - } - runBlocking { - send( - Extension.TaskProgress( - Extension.TaskState.DONE, - additionalData = weights - ) - ) - } - - } - - override suspend fun exportWeights(weight: Array): Flow> { - TODO("Not yet implemented") - } - - override fun onActivityResult(result: Any) { - if ((result as Set<*>).containsAll(this.PERMISSIONS)) connectionStatus.value = true - // signIn(Data.values()[requestCode]) - } - - override val contract: ActivityResultContract, Set> - get() = PermissionController.createRequestPermissionResultContract() - - override suspend fun permissionsGranted(): Boolean { - return this.client.permissionController.getGrantedPermissions(this.PERMISSIONS).containsAll(this.PERMISSIONS) - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/SamsungHealth.kt b/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/SamsungHealth.kt deleted file mode 100644 index e301f7c..0000000 --- a/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/SamsungHealth.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.dzeio.openhealth.extensions.samsunghealth - -import android.app.Activity -import android.content.Intent -import android.os.Handler -import android.os.Looper -import android.util.Log -import com.dzeio.openhealth.data.weight.Weight -import com.samsung.android.sdk.healthdata.HealthConnectionErrorResult -import com.samsung.android.sdk.healthdata.HealthConstants.StepCount -import com.samsung.android.sdk.healthdata.HealthDataStore -import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener -import com.samsung.android.sdk.healthdata.HealthPermissionManager -import com.samsung.android.sdk.healthdata.HealthPermissionManager.* - - -/** - * Does not FUCKING work - */ -class SamsungHealth( - private val context: Activity -) { - - companion object { - const val TAG = "SamsungHealthConnector" - } - - private val listener = object : ConnectionListener { - override fun onConnected() { - Log.d(TAG, "Connected!") - if (isPermissionAcquired()) { - reporter.start() - } else { - requestPermission() - } - } - - override fun onConnectionFailed(p0: HealthConnectionErrorResult?) { - Log.d(TAG, "Health data service is not available.") - } - - override fun onDisconnected() { - Log.d(TAG, "Health data service is disconnected.") - } - - } - - private val store: HealthDataStore = HealthDataStore(context, listener) - - private fun isPermissionAcquired(): Boolean { - val permKey = PermissionKey(StepCount.HEALTH_DATA_TYPE, PermissionType.READ) - val pmsManager = HealthPermissionManager(store) - try { - // Check whether the permissions that this application needs are acquired - val resultMap = pmsManager.isPermissionAcquired(setOf(permKey)) - return !resultMap.containsValue(java.lang.Boolean.FALSE) - } catch (e: java.lang.Exception) { - Log.e(TAG, "Permission request fails.", e) - } - return false - } - - private fun requestPermission() { - val permKey = PermissionKey(StepCount.HEALTH_DATA_TYPE, PermissionType.READ) - val pmsManager = HealthPermissionManager(store) - try { - // Show user permission UI for allowing user to change options - pmsManager.requestPermissions(setOf(permKey), context) - .setResultListener { result: PermissionResult -> - Log.d(TAG, "Permission callback is received.") - val resultMap = - result.resultMap - if (resultMap.containsValue(java.lang.Boolean.FALSE)) { - Log.d(TAG, "No Data???") - } else { - // Get the current step count and display it - reporter.start() - } - } - } catch (e: Exception) { - Log.e(TAG, "Permission setting fails.", e) - } - } - - private val stepCountObserver = object : StepCountReporter.StepCountObserver { - override fun onChanged(count: Int) { - Log.d(TAG, "Step reported : $count") - } - } - - private val reporter = - StepCountReporter(store, stepCountObserver, Handler(Looper.getMainLooper())) - - /** - * Connector - */ - - val sourceID: String = "SamsungHealth" - - fun onRequestPermissionResult( - requestCode: Int, - permission: Array, - grantResult: IntArray - ) { - } - - fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {} - - fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) { - store.connectService() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/StepCountReporter.kt b/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/StepCountReporter.kt deleted file mode 100644 index 83a1731..0000000 --- a/app/src/main/java/com/dzeio/openhealth/extensions/samsunghealth/StepCountReporter.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.dzeio.openhealth.extensions.samsunghealth - -import android.os.Handler -import android.util.Log -import com.samsung.android.sdk.healthdata.HealthConstants.StepCount -import com.samsung.android.sdk.healthdata.HealthData -import com.samsung.android.sdk.healthdata.HealthDataObserver -import com.samsung.android.sdk.healthdata.HealthDataResolver -import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateRequest -import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateRequest.AggregateFunction -import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateResult -import com.samsung.android.sdk.healthdata.HealthDataStore -import java.util.* -import java.util.concurrent.TimeUnit - -class StepCountReporter( - private val mStore: HealthDataStore, private val mStepCountObserver: StepCountObserver, - resultHandler: Handler? -) { - private val mHealthDataResolver: HealthDataResolver - private val mHealthDataObserver: HealthDataObserver - fun start() { - // Register an observer to listen changes of step count and get today step count - HealthDataObserver.addObserver(mStore, StepCount.HEALTH_DATA_TYPE, mHealthDataObserver) - readTodayStepCount() - } - - fun stop() { - HealthDataObserver.removeObserver(mStore, mHealthDataObserver) - } - - // Read the today's step count on demand - private fun readTodayStepCount() { - // Set time range from start time of today to the current time - val startTime = getUtcStartOfDay(System.currentTimeMillis(), TimeZone.getDefault()) - val endTime = startTime + TimeUnit.DAYS.toMillis(1) - val request = AggregateRequest.Builder() - .setDataType(StepCount.HEALTH_DATA_TYPE) - .addFunction(AggregateFunction.SUM, StepCount.COUNT, "total_step") - .setLocalTimeRange(StepCount.START_TIME, StepCount.TIME_OFFSET, startTime, endTime) - .build() - try { - mHealthDataResolver.aggregate(request) - .setResultListener { aggregateResult: AggregateResult -> - aggregateResult.use { result -> - val iterator: Iterator = result.iterator() - if (iterator.hasNext()) { - mStepCountObserver.onChanged(iterator.next().getInt("total_step")) - } - } - } - } catch (e: Exception) { - Log.e("APP_TAG", "Getting step count fails.", e) - } - } - - private fun getUtcStartOfDay(time: Long, tz: TimeZone): Long { - val cal = Calendar.getInstance(tz) - cal.timeInMillis = time - val year = cal[Calendar.YEAR] - val month = cal[Calendar.MONTH] - val date = cal[Calendar.DATE] - cal.timeZone = TimeZone.getTimeZone("UTC") - cal[Calendar.YEAR] = year - cal[Calendar.MONTH] = month - cal[Calendar.DATE] = date - cal[Calendar.HOUR_OF_DAY] = 0 - cal[Calendar.MINUTE] = 0 - cal[Calendar.SECOND] = 0 - cal[Calendar.MILLISECOND] = 0 - return cal.timeInMillis - } - - interface StepCountObserver { - fun onChanged(count: Int) - } - - init { - mHealthDataResolver = HealthDataResolver(mStore, resultHandler) - mHealthDataObserver = object : HealthDataObserver(resultHandler) { - // Update the step count when a change event is received - override fun onChange(dataTypeName: String) { - Log.d("APP_TAG", "Observer receives a data changed event") - readTodayStepCount() - } - } - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/browse/BrowseFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/browse/BrowseFragment.kt index 998f765..2fe51ee 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/browse/BrowseFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/browse/BrowseFragment.kt @@ -53,6 +53,10 @@ class BrowseFragment : findNavController().navigate(BrowseFragmentDirections.actionNavBrowseToNavWaterHome()) } + binding.foodCalories.setOnClickListener { + findNavController().navigate(BrowseFragmentDirections.actionNavBrowseToFoodHomeFragment()) + } + binding.steps.setOnClickListener { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val activityPermission = PermissionsManager.hasPermission( diff --git a/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionFragment.kt deleted file mode 100644 index c383d8d..0000000 --- a/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionFragment.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.dzeio.openhealth.ui.extension - -import android.app.ProgressDialog -import android.content.Context -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContract -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.navArgs -import com.dzeio.openhealth.core.BaseFragment -import com.dzeio.openhealth.databinding.FragmentExtensionBinding -import com.dzeio.openhealth.extensions.Extension -import com.dzeio.openhealth.extensions.ExtensionFactory -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch - -@AndroidEntryPoint -class ExtensionFragment : - BaseFragment(ExtensionViewModel::class.java) { - - override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionBinding = - FragmentExtensionBinding::inflate - - private val args: ExtensionFragmentArgs by navArgs() - - private val extension by lazy { - ExtensionFactory.getExtension(args.extension) - ?: throw Exception("No Extension found!") - } - - private var request: ActivityResultLauncher? = null - - override fun onAttach(context: Context) { - if (this.extension.contract != null) { - this.request = - registerForActivityResult(this.extension.contract!! as ActivityResultContract) { - this.extension.onActivityResult(it as Any) - } - } - super.onAttach(context) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val enabled = extension.enable(this) - - if (!enabled) { - throw Exception("Extension can't be enabled (${extension.id})") - } - - requireActivity().actionBar?.title = extension.name - -// extension.init(requireActivity()) - - binding.importButton.setOnClickListener { - val dialog = ProgressDialog(requireContext()) - dialog.setTitle("Importing...") - dialog.setMessage("Imported 0 values") - dialog.show() - lifecycleScope.launch { - extension.importWeight().collectLatest { state -> - Log.d("ExtensionFragment", state.state.name) - dialog.setMessage(state.statusMessage ?: "progress ${state.progress}%") - if (state.state == Extension.TaskState.DONE) { - dialog.setMessage("Finishing Import...") - lifecycleScope.launchWhenStarted { - state.additionalData!!.forEach { - it.source = extension.id - viewModel.importWeight(it) - } - dialog.dismiss() - } - } - } - } - } - - lifecycleScope.launch { - if (!extension.permissionsGranted() && request != null) { - request!!.launch(extension.requestInput) - } - } - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionViewModel.kt deleted file mode 100644 index 74597fc..0000000 --- a/app/src/main/java/com/dzeio/openhealth/ui/extension/ExtensionViewModel.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.dzeio.openhealth.ui.extension - -import androidx.lifecycle.MutableLiveData -import com.dzeio.openhealth.core.BaseViewModel -import com.dzeio.openhealth.data.weight.Weight -import com.dzeio.openhealth.data.weight.WeightRepository -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class ExtensionViewModel @Inject internal constructor( - private val weightRepository: WeightRepository -) : BaseViewModel() { - - val text = MutableLiveData().apply { - value = "This is slideshow Fragment" - } - val importProgress = MutableLiveData().apply { - value = 0 - } - // If -1 progress is undetermined - // If 0 no progress bar - // Else progress bar - val importProgressTotal = MutableLiveData().apply { - value = 0 - } - - suspend fun importWeight(weight: Weight) = weightRepository.addWeight(weight) - suspend fun deleteFromSource(source: String) = weightRepository.deleteFromSource(source) -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt deleted file mode 100644 index 8186e99..0000000 --- a/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.dzeio.openhealth.ui.extensions - -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import com.dzeio.openhealth.adapters.ExtensionAdapter -import com.dzeio.openhealth.core.BaseFragment -import com.dzeio.openhealth.databinding.FragmentExtensionsBinding -import com.dzeio.openhealth.extensions.Extension -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch - -@AndroidEntryPoint -class ExtensionsFragment : - BaseFragment(ExtensionsViewModel::class.java) { - - companion object { - const val TAG = "ExtensionsFragment" - } - - override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionsBinding = - FragmentExtensionsBinding::inflate - - private lateinit var activeExtension: Extension - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val recycler = binding.list - - val manager = LinearLayoutManager(requireContext()) - recycler.layoutManager = manager - - val adapter = ExtensionAdapter(viewModel.config) - adapter.onItemClick = { - activeExtension = it - activeExtension.enable(this) - Log.d(TAG, "${it.id}: ${it.name}") - - lifecycleScope.launch { - extensionIsConnected(it) - } - } - recycler.adapter = adapter - - val list = viewModel.extensions - list.forEach { - it.enable(this) - } - - adapter.set(list) - } - - private suspend fun extensionIsConnected(it: Extension) { - // check if it is connected - if (it.isConnected()) { - gotoExtension(it) - return - } - - val ld = it.connect() - if (ld) { - gotoExtension(it) - } - // handle if extension can't be connected - } - - private fun gotoExtension(it: Extension) { - findNavController().navigate( - ExtensionsFragmentDirections.actionNavExtensionsToNavExtension(it.id) - ) - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsViewModel.kt deleted file mode 100644 index 646e69e..0000000 --- a/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsViewModel.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.dzeio.openhealth.ui.extensions - -import com.dzeio.openhealth.core.BaseViewModel -import com.dzeio.openhealth.extensions.ExtensionFactory -import com.dzeio.openhealth.utils.Configuration -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class ExtensionsViewModel @Inject internal constructor( - val config: Configuration -) : BaseViewModel() { - - val extensions = ExtensionFactory.getAll() - -} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/food/FoodHomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/food/FoodHomeFragment.kt new file mode 100644 index 0000000..44f0155 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/food/FoodHomeFragment.kt @@ -0,0 +1,47 @@ +package com.dzeio.openhealth.ui.food + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import com.dzeio.openhealth.Application +import com.dzeio.openhealth.adapters.FoodAdapter +import com.dzeio.openhealth.core.BaseFragment +import com.dzeio.openhealth.databinding.FragmentFoodHomeBinding +import com.dzeio.openhealth.ui.steps.FoodHomeViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class FoodHomeFragment : + BaseFragment(FoodHomeViewModel::class.java) { + + companion object { + const val TAG = "${Application.TAG}/SHFragment" + } + + override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentFoodHomeBinding = + FragmentFoodHomeBinding::inflate + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewModel.init() + + val recycler = binding.list + + val manager = LinearLayoutManager(requireContext()) + recycler.layoutManager = manager + + val adapter = FoodAdapter() + adapter.onItemClick = { +// findNavController().navigate( +// WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterEdit( +// it.id +// ) +// ) + } + recycler.adapter = adapter + + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/food/FoodHomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/food/FoodHomeViewModel.kt new file mode 100644 index 0000000..c0b547e --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/food/FoodHomeViewModel.kt @@ -0,0 +1,26 @@ +package com.dzeio.openhealth.ui.steps + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.dzeio.openhealth.core.BaseViewModel +import com.dzeio.openhealth.data.food.Food +import com.dzeio.openhealth.data.food.FoodRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class FoodHomeViewModel @Inject internal constructor( + private val foodRepository: FoodRepository +) : BaseViewModel() { + val items: MutableLiveData> = MutableLiveData() + + fun init() { + viewModelScope.launch { + foodRepository.getAll().collectLatest { + items.postValue(it) + } + } + } +} diff --git a/app/src/main/res/layout/fragment_extension.xml b/app/src/main/res/layout/fragment_extension.xml deleted file mode 100644 index d5f2a80..0000000 --- a/app/src/main/res/layout/fragment_extension.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - -