diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..94f480d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..7e7f500
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7726348..e30b29b 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -7,8 +7,21 @@
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 71281fd..0e58460 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,6 +2,10 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
+ id 'dagger.hilt.android.plugin'
+
+ // Safe Navigation
+ id 'androidx.navigation.safeargs'
}
android {
@@ -32,6 +36,8 @@ android {
}
buildFeatures {
viewBinding true
+ dataBinding true
+
}
}
@@ -39,7 +45,8 @@ dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
- implementation 'com.google.android.material:material:1.4.0'
+ implementation 'javax.inject:javax.inject:1'
+ implementation 'com.google.android.material:material:1.6.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
@@ -49,6 +56,13 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ // Graph
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
+
+ // Hilt
+ implementation "com.google.dagger:hilt-android:2.38.1"
+ kapt "com.google.dagger:hilt-compiler:2.38.1"
+
// Google Fit
implementation "com.google.android.gms:play-services-fitness:21.0.0"
implementation "com.google.android.gms:play-services-auth:20.0.0"
@@ -61,21 +75,8 @@ dependencies {
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
-
- // optional - RxJava2 support for Room
- implementation "androidx.room:room-rxjava2:$room_version"
-
- // optional - RxJava3 support for Room
- implementation "androidx.room:room-rxjava3:$room_version"
-
- // optional - Guava support for Room, including Optional and ListenableFuture
- implementation "androidx.room:room-guava:$room_version"
-
- // optional - Test helpers
+ implementation "androidx.room:room-ktx:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
- // optional - Paging 3 Integration
- implementation "androidx.room:room-paging:2.4.0-rc01"
-
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 628ae02..069b7f2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,6 +14,7 @@
+ android:value="com.samsung.health.weight" />
+
+
() {
private lateinit var appBarConfiguration: AppBarConfiguration
- private lateinit var binding: ActivityMainBinding
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
+ private lateinit var navController: NavController
- binding = ActivityMainBinding.inflate(layoutInflater)
- setContentView(binding.root)
+ override val bindingInflater: (LayoutInflater) -> ActivityMainBinding = ActivityMainBinding::inflate
- setSupportActionBar(binding.appBarMain.toolbar)
+ override fun onCreated(savedInstanceState: Bundle?) {
+ super.onCreated(savedInstanceState)
+
+ setSupportActionBar(binding.toolbar)
+
+ val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
+ navController = navHostFragment.navController
+ binding.navView.setupWithNavController(navController)
- binding.appBarMain.fab.setOnClickListener { view ->
- Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
- .setAction("Action", null).show()
- }
- val drawerLayout: DrawerLayout = binding.drawerLayout
- val navView: NavigationView = binding.navView
- val navController = findNavController(R.id.nav_host_fragment_content_main)
- // Passing each menu ID as a set of Ids because each
- // menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
- R.id.nav_home, R.id.nav_gallery, R.id.nav_import
- ), drawerLayout
+ R.id.nav_home
+ ), binding.drawerLayout
)
+
setupActionBarWithNavController(navController, appBarConfiguration)
- navView.setupWithNavController(navController)
+
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
- // Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onSupportNavigateUp(): Boolean {
- val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array,
- grantResults: IntArray) {
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
Log.d("MainActivity", "Result $requestCode")
}
diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt
new file mode 100644
index 0000000..f88f3ab
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt
@@ -0,0 +1,29 @@
+package com.dzeio.openhealth.adapters
+
+import android.util.Log
+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.weight.Weight
+import com.dzeio.openhealth.databinding.LayoutItemWeightBinding
+
+class WeightAdapter() : BaseAdapter() {
+
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemWeightBinding
+ get() = LayoutItemWeightBinding::inflate
+
+ var onItemClick: ((weight: Weight) -> Unit)? = null
+
+ override fun onBindData(
+ holder: BaseViewHolder,
+ item: Weight,
+ position: Int
+ ) {
+ holder.binding.weight.text = "${item.weight}kg"
+ holder.binding.datetime.text = item.formatTimestamp()
+ holder.binding.edit.setOnClickListener {
+ onItemClick?.invoke(item)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt b/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt
index b06bc4d..b9f6231 100644
--- a/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt
+++ b/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt
@@ -7,8 +7,8 @@ import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
-import com.dzeio.openhealth.db.AppDatabase
-import com.dzeio.openhealth.db.entities.Weight
+import com.dzeio.openhealth.data.AppDatabase
+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
@@ -36,15 +36,29 @@ class GoogleFit(
companion object {
const val TAG = "GoogleFitConnector"
}
+// private val fitnessOptions = FitnessOptions.builder()
+// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_READ)
+// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_WRITE)
+//
+// .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_READ)
+// .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_WRITE)
+//
+// .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_READ)
+// .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_WRITE)
+//
+// .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_READ)
+// .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_WRITE)
+// .build()
+
private val fitnessOptions = FitnessOptions.builder()
- .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
- .addDataType(DataType.TYPE_ACTIVITY_SEGMENT)
- .addDataType(DataType.TYPE_SLEEP_SEGMENT)
- .addDataType(DataType.TYPE_CALORIES_EXPENDED)
- .addDataType(DataType.TYPE_BASAL_METABOLIC_RATE)
- .addDataType(DataType.TYPE_POWER_SAMPLE)
- .addDataType(DataType.TYPE_HEART_RATE_BPM)
- .addDataType(DataType.TYPE_LOCATION_SAMPLE)
+// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
+// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT)
+// .addDataType(DataType.TYPE_SLEEP_SEGMENT)
+// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
+// .addDataType(DataType.TYPE_BASAL_METABOLIC_RATE)
+// .addDataType(DataType.TYPE_POWER_SAMPLE)
+// .addDataType(DataType.TYPE_HEART_RATE_BPM)
+// .addDataType(DataType.TYPE_LOCATION_SAMPLE)
.addDataType(DataType.TYPE_WEIGHT)
.build()
@@ -196,12 +210,16 @@ class GoogleFit(
@RequiresApi(Build.VERSION_CODES.O)
fun getHistory() {
- val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault())
- val startTime = LocalDateTime.MIN.atZone(ZoneId.systemDefault())
+ val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
+ val now = Date()
+ calendar.time = now
+ val endTime = calendar.timeInMillis
+ calendar.set(Calendar.YEAR, 2013) // Set year to 2013 to be sure to get data from when Google Fit Started to today
+ val startTime = calendar.timeInMillis
val readRequest = DataReadRequest.Builder()
.aggregate(DataType.AGGREGATE_CALORIES_EXPENDED)
.bucketByActivityType(1, TimeUnit.SECONDS)
- .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS)
+ .setTimeRange(startTime, endTime, TimeUnit.SECONDS)
.build()
Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions))
@@ -219,13 +237,6 @@ class GoogleFit(
}
-// @RequiresApi(Build.VERSION_CODES.O)
-// fun importStepCount() {
-//
-// runRequest(queryFitnessData())
-//
-// }
-
fun importWeight(callback : () -> Unit) {
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
@@ -295,7 +306,7 @@ class GoogleFit(
weight.weight = dp.getValue(field).asFloat()
Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}")
}
- AppDatabase.getInstance(activity).weightDao().insert(weight)
+ // AppDatabase.getInstance(activity).weightDao().insert(weight)
}
}
diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/SamsungHealth.kt b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/SamsungHealth.kt
index fe3be0a..a6e080e 100644
--- a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/SamsungHealth.kt
+++ b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/SamsungHealth.kt
@@ -1,143 +1,134 @@
-package com.dzeio.openhealth.connectors.samsunghealth
-
-import android.app.Activity
-import android.app.AlertDialog
-import android.util.Log
-import com.samsung.android.sdk.healthdata.*
-
-class SamsungHealth(
- private val activity: Activity
-) {
-
- private var currentStartTime: Long = 0
- private var isFinishing = false
-
- init {
- currentStartTime = StepCountReader.TODAY_START_UTC_TIME
- }
-
- companion object {
- const val TAG = "SamsungHealth"
- }
-
-// private val binningListAdapter: StepBinningData by lazy { StepBinningData() }
-
- private val healthDataStore: HealthDataStore by lazy { HealthDataStore(activity, connectionListener) }
- private val stepCountReader: StepCountReader by lazy { StepCountReader(healthDataStore, stepCountObserver) }
-
- private val stepCountObserver: StepCountObserver = object : StepCountObserver {
- override fun onChanged(count: Int) {
- Log.d(TAG, "$count")
- }
-
- override fun onBinningDataChanged(binningCountList: List) {
- Log.d(TAG, "${binningCountList.size}")
- }
- }
-
- private val connectionListener: HealthDataStore.ConnectionListener = object : HealthDataStore.ConnectionListener {
- override fun onConnected() {
- Log.d(TAG, "onConnected")
- if (checkPermissionsAcquired()) {
- stepCountReader.requestDailyStepCount(currentStartTime)
- } else {
- requestPermission()
- }
- }
-
- override fun onConnectionFailed(error: HealthConnectionErrorResult) {
- Log.d(TAG, "onConnectionFailed")
- showConnectionFailureDialog(error)
- }
-
- override fun onDisconnected() {
- Log.d(TAG, "onDisconnected")
- if (!isFinishing) {
- healthDataStore.connectService()
- }
- }
- }
-
- private fun showConnectionFailureDialog(error: HealthConnectionErrorResult) {
- if (isFinishing) {
- return
- }
- val alert = AlertDialog.Builder(activity)
- if (error.hasResolution()) {
- when (error.errorCode) {
- HealthConnectionErrorResult.PLATFORM_NOT_INSTALLED -> alert.setMessage("R.string.msg_req_install")
- HealthConnectionErrorResult.OLD_VERSION_PLATFORM -> alert.setMessage("R.string.msg_req_upgrade")
- HealthConnectionErrorResult.PLATFORM_DISABLED -> alert.setMessage("R.string.msg_req_enable")
- HealthConnectionErrorResult.USER_AGREEMENT_NEEDED -> alert.setMessage("R.string.msg_req_agree")
- else -> alert.setMessage("R.string.msg_req_available")
- }
- } else {
- alert.setMessage("R.string.msg_conn_not_available")
- }
- alert.setPositiveButton("R.string.ok") { _, _ ->
- if (error.hasResolution()) {
- error.resolve(activity)
- }
- }
- if (error.hasResolution()) {
- alert.setNegativeButton("R.string.cancel", null)
- }
- alert.show()
- }
-
- // Check whether the permissions that this application needs are acquired
- private fun checkPermissionsAcquired(): Boolean {
- val pmsManager = HealthPermissionManager(healthDataStore)
-
- // Check whether the permissions that this application needs are acquired
- return runCatching { pmsManager.isPermissionAcquired(permissionKeySet) }
- .onFailure { Log.e(TAG, "Permission request fails.", it) }
- .map { it.values.all { it } }
- .getOrDefault(false)
- }
-
- private fun requestPermission() {
- val pmsManager = HealthPermissionManager(healthDataStore)
-
- // Show user permission UI for allowing user to change options
- runCatching { pmsManager.requestPermissions(permissionKeySet, activity) }
- .onFailure { Log.e(TAG, "Permission setting fails.", it) }
- .getOrNull()
- ?.setResultListener(mPermissionListener)
- }
-
- private val permissionKeySet: Set =
- setOf(
- HealthPermissionManager.PermissionKey(HealthConstants.StepCount.HEALTH_DATA_TYPE, HealthPermissionManager.PermissionType.READ),
- HealthPermissionManager.PermissionKey(
- HealthConstants.StepDailyTrend.HEALTH_DATA_TYPE,
- HealthPermissionManager.PermissionType.READ
- )
- )
-
- private val mPermissionListener = HealthResultHolder.ResultListener { result ->
- // Show a permission alarm and clear step count if permissions are not acquired
- if (result.resultMap.values.any { !it }) {
- // List is now empty
- showPermissionAlarmDialog()
- } else {
- // Get the daily step count of a particular day and display it
- stepCountReader.requestDailyStepCount(currentStartTime)
- }
- }
-
- private fun showPermissionAlarmDialog() {
- if (isFinishing) {
- return
- }
- AlertDialog.Builder(activity)
- .setTitle("R.string.notice")
- .setMessage("R.string.msg_perm_acquired")
- .setPositiveButton("R.string.ok", null)
- .show()
- }
-
- fun importStepCount() {
- healthDataStore.connectService()
- }
+package com.dzeio.openhealth.connectors.samsunghealth
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.DialogInterface
+import android.util.Log
+import com.samsung.android.sdk.healthdata.*
+import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
+import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
+import com.samsung.android.sdk.healthdata.HealthResultHolder.ResultListener
+import java.lang.Boolean
+import kotlin.Exception
+
+
+class SamsungHealth(
+ private val context: Activity
+) {
+ companion object {
+ const val TAG = "SamsungHealthConnector"
+ }
+ private val store: HealthDataStore get() = _store!!
+ private var _store: HealthDataStore? = null
+ private val keySet: HashSet = HashSet()
+
+ private val permissionListener: ResultListener =
+ ResultListener { result ->
+ Log.d(TAG, "Permission callback is received.")
+ val resultMap: Map = result!!.resultMap
+
+ if (resultMap.containsValue(Boolean.FALSE)) {
+ // Requesting permission fails
+ Log.d(TAG, "Requesting permission fails")
+
+ } else {
+ // Get the current step count and display it
+ getStepCount()
+ }
+ }
+
+ private val connectionListener: ConnectionListener = object : ConnectionListener {
+ override fun onConnected() {
+ Log.d(TAG, "Health data service is connected.")
+ val pmsManager = HealthPermissionManager(store)
+ try {
+ val resultMap = pmsManager.isPermissionAcquired(keySet)
+
+ if (resultMap.containsValue(Boolean.FALSE)) {
+ // Request the permission for reading step counts if it is not acquired
+ pmsManager.requestPermissions(keySet, context)
+ .setResultListener(permissionListener)
+ } else {
+ // Get the current step count and display it
+ getStepCount()
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, e.javaClass.name + " - " + e.message)
+ Log.e(TAG, "Permission setting fails.")
+ }
+ }
+
+ override fun onConnectionFailed(error: HealthConnectionErrorResult) {
+ Log.d(TAG, "Health data service is not available.")
+ showConnectionFailureDialog(error)
+ }
+
+ override fun onDisconnected() {
+ Log.d(TAG, "Health data service is disconnected.")
+ }
+ }
+
+ init {
+ //keySet = HashSet()
+ keySet.add(PermissionKey(HealthConstants.Weight.HEALTH_DATA_TYPE, PermissionType.READ))
+
+ _store = HealthDataStore(context, connectionListener)
+ }
+
+ fun test() {
+ store.connectService()
+ }
+
+ private fun getStepCount() {
+ val resolver = HealthDataResolver(store, null)
+ val request = HealthDataResolver.ReadRequest.Builder()
+ .setDataType(HealthConstants.Weight.HEALTH_DATA_TYPE)
+ .setLocalTimeRange(HealthConstants.Weight.START_TIME, HealthConstants.Weight.TIME_OFFSET, 0, System.currentTimeMillis())
+ .build()
+
+ try {
+ val res = resolver.read(request).await()
+
+ res.use { res1 ->
+ val iterator = res1.iterator()
+ if (iterator.hasNext()) {
+ val data = iterator.next()
+ val value = data.getFloat(HealthConstants.Weight.WEIGHT)
+ val time = data.getLong(HealthConstants.Weight.CREATE_TIME)
+ Log.d(TAG, "Data received! $value $time")
+ }
+ }
+ } catch (e : Exception) {
+ Log.d(TAG, "Reading failed!")
+ }
+ }
+
+ private fun showConnectionFailureDialog(error: HealthConnectionErrorResult) {
+ val alert: AlertDialog.Builder = AlertDialog.Builder(context)
+ var message = "Connection with Samsung Health is not available"
+ if (error.hasResolution()) {
+ message = when (error.errorCode) {
+ HealthConnectionErrorResult.PLATFORM_NOT_INSTALLED -> "Please install Samsung Health"
+ HealthConnectionErrorResult.OLD_VERSION_PLATFORM -> "Please upgrade Samsung Health"
+ HealthConnectionErrorResult.PLATFORM_DISABLED -> "Please enable Samsung Health"
+ HealthConnectionErrorResult.USER_AGREEMENT_NEEDED -> "Please agree with Samsung Health policy"
+ else -> "Please make Samsung Health available"
+ }
+ }
+ alert.setMessage(message)
+ alert.setPositiveButton("OK", DialogInterface.OnClickListener { dialog, id ->
+ if (error.hasResolution()) {
+ error.resolve(context)
+ }
+ })
+ if (error.hasResolution()) {
+ alert.setNegativeButton("Cancel", null)
+ }
+ alert.show()
+ }
+
+
+ fun destroy() {
+ store.disconnectService()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepBinningData.kt b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepBinningData.kt
deleted file mode 100644
index cd402a9..0000000
--- a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepBinningData.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.dzeio.openhealth.connectors.samsunghealth
-
-data class StepBinningData(var time: String, val count: Int)
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountObserver.kt b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountObserver.kt
deleted file mode 100644
index a512416..0000000
--- a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountObserver.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.dzeio.openhealth.connectors.samsunghealth
-
-interface StepCountObserver {
-
- fun onChanged(count: Int)
-
- fun onBinningDataChanged(binningCountList: List)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountReader.kt b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountReader.kt
deleted file mode 100644
index b9e5125..0000000
--- a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountReader.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-package com.dzeio.openhealth.connectors.samsunghealth
-
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import com.samsung.android.sdk.healthdata.HealthConstants.StepCount
-import com.samsung.android.sdk.healthdata.HealthConstants.StepDailyTrend
-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.AggregateRequest.TimeGroupUnit
-import com.samsung.android.sdk.healthdata.HealthDataResolver.Filter
-import com.samsung.android.sdk.healthdata.HealthDataResolver.ReadRequest
-import com.samsung.android.sdk.healthdata.HealthDataResolver.SortOrder
-import com.samsung.android.sdk.healthdata.HealthDataStore
-import com.samsung.android.sdk.healthdata.HealthDataUtil
-import java.util.Calendar
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.TimeUnit
-
-class StepCountReader(
- store: HealthDataStore,
- private val observer: StepCountObserver
-) {
- private val healthDataResolver: HealthDataResolver = HealthDataResolver(store, Handler(Looper.getMainLooper()))
-
- // Get the daily total step count of a specified day
- fun requestDailyStepCount(startTime: Long) {
- if (startTime >= TODAY_START_UTC_TIME) {
- // Get today step count
- readStepCount(startTime)
- } else {
- // Get historical step count
- readStepDailyTrend(startTime)
- }
- }
-
- private fun readStepCount(startTime: Long) {
- // Get sum of step counts by device
- val request = AggregateRequest.Builder()
- .setDataType(StepCount.HEALTH_DATA_TYPE)
- .addFunction(AggregateFunction.SUM, StepCount.COUNT, ALIAS_TOTAL_COUNT)
- .addGroup(StepCount.DEVICE_UUID, ALIAS_DEVICE_UUID)
- .setLocalTimeRange(StepCount.START_TIME, StepCount.TIME_OFFSET, startTime, startTime + TIME_INTERVAL)
- .setSort(ALIAS_TOTAL_COUNT, SortOrder.DESC)
- .build()
-
- runCatching { healthDataResolver.aggregate(request) }
- .onFailure { Log.e(TAG, "Getting step count fails.", it) }
- .getOrNull()
- ?.setResultListener {
- it.use {
- it.firstOrNull()
- .also { observer.onChanged(it?.getInt(ALIAS_TOTAL_COUNT) ?: 0) }
- ?.let { readStepCountBinning(startTime, it.getString(ALIAS_DEVICE_UUID)) }
- ?: observer.onBinningDataChanged(emptyList())
- }
- }
- }
-
- private fun readStepDailyTrend(dayStartTime: Long) {
- val request = ReadRequest.Builder()
- .setDataType(StepDailyTrend.HEALTH_DATA_TYPE)
- .setProperties(arrayOf(StepDailyTrend.COUNT, StepDailyTrend.BINNING_DATA))
- .setFilter(Filter.and(
- Filter.eq(StepDailyTrend.DAY_TIME, dayStartTime),
- Filter.eq(StepDailyTrend.SOURCE_TYPE, StepDailyTrend.SOURCE_TYPE_ALL)))
- .build()
-
- runCatching { healthDataResolver.read(request) }
- .onFailure { Log.e(TAG, "Getting daily step trend fails.", it) }
- .getOrNull()
- ?.setResultListener {
- it.use {
- it.firstOrNull().also {
- observer.onChanged(it?.getInt(StepDailyTrend.COUNT) ?: 0)
- observer.onBinningDataChanged(
- it?.getBlob(StepDailyTrend.BINNING_DATA)?.let { getBinningData(it) } ?: emptyList())
- }
- }
- }
- }
-
- private fun getBinningData(zip: ByteArray): List {
- // decompress ZIP
- val binningDataList = HealthDataUtil.getStructuredDataList(zip, StepBinningData::class.java)
- return binningDataList.asSequence()
- .withIndex()
- .filter { it.value.count != 0 }
- .onEach { it.value.time = String.format(Locale.US, "%02d:%02d", it.index / 6, it.index % 6 * 10) }
- .map { it.value }
- .toList()
- }
-
- private fun readStepCountBinning(startTime: Long, deviceUuid: String) {
-
- // Get 10 minute binning data of a particular device
- val request = AggregateRequest.Builder()
- .setDataType(StepCount.HEALTH_DATA_TYPE)
- .addFunction(AggregateFunction.SUM, StepCount.COUNT, ALIAS_TOTAL_COUNT)
- .setTimeGroup(TimeGroupUnit.MINUTELY, 10, StepCount.START_TIME, StepCount.TIME_OFFSET, ALIAS_BINNING_TIME)
- .setLocalTimeRange(StepCount.START_TIME, StepCount.TIME_OFFSET, startTime, startTime + TIME_INTERVAL)
- .setFilter(Filter.eq(StepCount.DEVICE_UUID, deviceUuid))
- .setSort(ALIAS_BINNING_TIME, SortOrder.ASC)
- .build()
-
- runCatching { healthDataResolver.aggregate(request) }
- .onFailure { Log.e(TAG, "Getting step binning data fails.", it) }
- .getOrNull()
- ?.setResultListener {
- it.use {
- it.asSequence()
- .map { it.getString(ALIAS_BINNING_TIME) to it.getInt(ALIAS_TOTAL_COUNT) }
- .filter { it.first != null }
- .map { StepBinningData(it.first.split(" ")[1], it.second) }
- .toList()
- }
- .also { observer.onBinningDataChanged(it) }
- }
- }
-
- companion object {
- val TODAY_START_UTC_TIME = todayStartUtcTime
- val TIME_INTERVAL = TimeUnit.DAYS.toMillis(1)
- private const val ALIAS_TOTAL_COUNT = "count"
- private const val ALIAS_DEVICE_UUID = "deviceuuid"
- private const val ALIAS_BINNING_TIME = "binning_time"
- const val TAG = "SamsungHealth.StepCountReader"
-
- private val todayStartUtcTime: Long
- get() {
- val today = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
- today[Calendar.HOUR_OF_DAY] = 0
- today[Calendar.MINUTE] = 0
- today[Calendar.SECOND] = 0
- today[Calendar.MILLISECOND] = 0
- return today.timeInMillis
- }
- }
-}
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt
new file mode 100644
index 0000000..e5467fe
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt
@@ -0,0 +1,37 @@
+package com.dzeio.openhealth.core
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.lifecycle.ViewModelProvider
+import androidx.viewbinding.ViewBinding
+import com.dzeio.openhealth.databinding.ActivityMainBinding
+
+abstract class BaseActivity() : AppCompatActivity() {
+
+
+ /**
+ * Function to inflate the Fragment Bindings
+ *
+ * use like this: `ViewBinding::inflater`
+ */
+ abstract val bindingInflater: (LayoutInflater) -> VB
+
+ protected lateinit var binding: VB
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = bindingInflater(layoutInflater)
+
+ setContentView(binding.root)
+
+ onCreated(savedInstanceState)
+ }
+
+ protected open fun onCreated(savedInstanceState: Bundle?) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt
new file mode 100644
index 0000000..eaa9481
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt
@@ -0,0 +1,46 @@
+package com.dzeio.openhealth.core
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+
+abstract class BaseAdapter : RecyclerView.Adapter>() {
+ private var items = mutableListOf()
+ // private var lastPosition = -1
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun set(items: List) {
+ this.items = items.toMutableList()
+ notifyDataSetChanged()
+ }
+
+ fun add(vararg items: T) {
+ val len = this.items.size
+ this.items.addAll(items)
+ notifyItemInserted(len)
+ }
+
+ /**
+ * Function to inflate the Adapter Bindings
+ *
+ * use like this: `ViewBinding::inflater`
+ */
+ abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
+
+ abstract fun onBindData(holder: BaseViewHolder, item: T, position: Int)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
+ return BaseViewHolder(
+ bindingInflater(LayoutInflater.from(parent.context), parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
+ onBindData(holder, items[position], position)
+ }
+
+ override fun getItemCount(): Int = items.size
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseDao.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseDao.kt
index ceab0f8..aaafc60 100644
--- a/app/src/main/java/com/dzeio/openhealth/core/BaseDao.kt
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseDao.kt
@@ -1,16 +1,21 @@
package com.dzeio.openhealth.core
-import androidx.lifecycle.LiveData
-import androidx.room.*
-import com.dzeio.openhealth.db.entities.Weight
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Update
+
interface BaseDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insert(vararg obj: T)
+ suspend fun insert(vararg obj: T): List
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(obj: T): Long
@Update
- fun update(vararg obj: T)
+ suspend fun update(vararg obj: T)
@Delete
- fun delete(vararg obj: T)
+ suspend fun delete(vararg obj: T)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseDialog.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseDialog.kt
new file mode 100644
index 0000000..79e57e3
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseDialog.kt
@@ -0,0 +1,61 @@
+package com.dzeio.openhealth.core
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.viewbinding.ViewBinding
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+abstract class BaseDialog(private val viewModelClass: Class) : DialogFragment() {
+ val viewModel by lazy {
+ ViewModelProvider(this)[viewModelClass]
+ }
+
+ private var _binding: VB? = null
+ val binding get() = _binding!!
+
+ /**
+ * Setup everything!
+ */
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return activity?.let { act ->
+ val builder = MaterialAlertDialogBuilder(requireContext())
+
+ _binding = bindingInflater(act.layoutInflater)
+
+ builder.setView(binding.root)
+
+ onBuilderInit(builder)
+
+ val dialog = builder.create()
+
+ onDialogInit(dialog)
+
+ onCreated()
+
+ dialog
+ } ?: throw IllegalStateException("Activity cannot be null")
+ }
+
+ open fun onBuilderInit(builder: MaterialAlertDialogBuilder): Unit {}
+
+ open fun onDialogInit(dialog: AlertDialog): Unit {}
+
+ open fun onCreated(): Unit {}
+
+ /**
+ * Function to inflate the Fragment Bindings
+ */
+ abstract val bindingInflater: (LayoutInflater) -> VB
+
+ /**
+ * Destroy binding
+ */
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt
new file mode 100644
index 0000000..75e4109
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt
@@ -0,0 +1,48 @@
+package com.dzeio.openhealth.core
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.viewbinding.ViewBinding
+
+abstract class BaseFragment(private val viewModelClass: Class) : Fragment() {
+ val viewModel by lazy {
+ ViewModelProvider(this)[viewModelClass]
+ }
+
+ private var _binding: VB? = null
+ val binding get() = _binding!!
+
+ /**
+ * Setup everything!
+ */
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ super.onCreateView(inflater, container, savedInstanceState)
+
+ _binding = bindingInflater(inflater, container, false)
+
+ return binding.root
+ }
+
+ /**
+ * Function to inflate the Fragment Bindings
+ *
+ * use like this: `ViewBinding::inflater`
+ */
+ abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
+
+ /**
+ * Destroy binding
+ */
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt
new file mode 100644
index 0000000..8f5848a
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt
@@ -0,0 +1,85 @@
+package com.dzeio.openhealth.core
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.*
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.viewbinding.ViewBinding
+import com.dzeio.openhealth.R
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+abstract class BaseFullscreenDialog(private val viewModelClass: Class) : DialogFragment() {
+
+ val viewModel by lazy {
+ ViewModelProvider(this)[viewModelClass]
+ }
+
+ private var _binding: VB? = null
+ val binding get() = _binding!!
+
+
+ /**
+ * Function to inflate the Fragment Bindings
+ */
+ abstract val bindingInflater: (LayoutInflater) -> VB
+
+ abstract val isFullscreenLayout: Boolean
+
+ open fun onCreated(savedInstanceState: Bundle?) {}
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+
+ _binding = bindingInflater(inflater)
+
+ setHasOptionsMenu(true)
+
+ onCreated(savedInstanceState)
+
+ return binding.root
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return activity?.let { act ->
+ val builder = MaterialAlertDialogBuilder(requireContext())
+
+ _binding = bindingInflater(act.layoutInflater)
+
+ builder.setView(binding.root)
+
+ val dialog = builder.create()
+
+ onDialogInit(dialog)
+
+// onCreated()
+
+ dialog
+ } ?: throw IllegalStateException("Activity cannot be null")
+ }
+
+ open fun onDialogInit(dialog: Dialog): Unit {}
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+
+ menu.clear()
+ requireActivity().menuInflater.inflate(R.menu.fullscreen_dialog, menu)
+ super.onCreateOptionsMenu(menu, inflater)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+
+ /**
+ * Destroy binding
+ */
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt
new file mode 100644
index 0000000..fcadf2c
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt
@@ -0,0 +1,9 @@
+package com.dzeio.openhealth.core
+
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+
+class BaseViewHolder(
+ val binding : VB
+) : RecyclerView.ViewHolder(binding.root) {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseViewModel.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseViewModel.kt
new file mode 100644
index 0000000..fe5c162
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseViewModel.kt
@@ -0,0 +1,5 @@
+package com.dzeio.openhealth.core
+
+import androidx.lifecycle.ViewModel
+
+abstract class BaseViewModel : ViewModel()
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt b/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt
new file mode 100644
index 0000000..f5dbd79
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt
@@ -0,0 +1,44 @@
+package com.dzeio.openhealth.data
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import com.dzeio.openhealth.data.weight.WeightDao
+import com.dzeio.openhealth.data.weight.Weight
+
+@Database(entities = [Weight::class], version = 1, exportSchema = false)
+abstract class AppDatabase : RoomDatabase() {
+ abstract fun weightDao() : WeightDao
+
+ companion object {
+ private const val DATABASE_NAME = "open_health"
+
+ // For Singleton instantiation
+ @Volatile private var instance: AppDatabase? = null
+
+ fun getInstance(context: Context): AppDatabase {
+ return instance ?: synchronized(this) {
+ 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
+ private fun buildDatabase(context: Context): AppDatabase {
+ return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
+// .addCallback(
+// object : RoomDatabase.Callback() {
+// override fun onCreate(db: SupportSQLiteDatabase) {
+// super.onCreate(db)
+// val request = OneTimeWorkRequestBuilder()
+// .setInputData(workDataOf(KEY_FILENAME to PLANT_DATA_FILENAME))
+// .build()
+// WorkManager.getInstance(context).enqueue(request)
+// }
+// }
+// )
+ .build()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/db/entities/Weight.kt b/app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt
similarity index 62%
rename from app/src/main/java/com/dzeio/openhealth/db/entities/Weight.kt
rename to app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt
index d7c3a59..8199d34 100644
--- a/app/src/main/java/com/dzeio/openhealth/db/entities/Weight.kt
+++ b/app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt
@@ -1,9 +1,10 @@
-package com.dzeio.openhealth.db.entities
+package com.dzeio.openhealth.data.weight
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
-import java.sql.Timestamp
+import java.sql.Date
+import java.text.DateFormat.getDateInstance
@Entity()
data class Weight (
@@ -12,4 +13,6 @@ data class Weight (
@ColumnInfo(index = true)
var timestamp: Long = System.currentTimeMillis(),
var source: String = ""
-)
\ No newline at end of file
+) {
+ fun formatTimestamp(): String = getDateInstance().format(Date(timestamp));
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/db/dao/WeightDao.kt b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt
similarity index 53%
rename from app/src/main/java/com/dzeio/openhealth/db/dao/WeightDao.kt
rename to app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt
index 27a3fc8..4ec48f7 100644
--- a/app/src/main/java/com/dzeio/openhealth/db/dao/WeightDao.kt
+++ b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt
@@ -1,24 +1,23 @@
-package com.dzeio.openhealth.db.dao
+package com.dzeio.openhealth.data.weight
-import androidx.lifecycle.LiveData
import androidx.room.*
-import androidx.room.OnConflictStrategy.REPLACE
import com.dzeio.openhealth.core.BaseDao
-import com.dzeio.openhealth.db.entities.Weight
+import dagger.Provides
+import kotlinx.coroutines.flow.Flow
@Dao
interface WeightDao : BaseDao {
@Query("SELECT * FROM Weight")
- fun getAll(): List
+ fun getAll(): Flow>
@Query("SELECT * FROM Weight where id = :weightId")
- fun getOne(weightId: Long): Weight?
+ fun getOne(weightId: Long): Flow
@Query("Select count(*) from Weight")
- fun getCount(): Int
+ fun getCount(): Flow
@Query("Select * FROM Weight WHERE id=(SELECT max(id) FROM Weight)")
- fun last(): Weight?
+ fun last(): Flow
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/data/weight/WeightRepository.kt b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightRepository.kt
new file mode 100644
index 0000000..ac68454
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightRepository.kt
@@ -0,0 +1,18 @@
+package com.dzeio.openhealth.data.weight
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class WeightRepository @Inject constructor(
+ private val weightDao: WeightDao
+) {
+ fun getWeights() = weightDao.getAll()
+
+ fun lastWeight() = weightDao.last()
+
+ fun getWeight(id: Long) = weightDao.getOne(id)
+
+ suspend fun addWeight(weight: Weight) = weightDao.insert(weight)
+ suspend fun deleteWeight(weight: Weight) = weightDao.delete(weight)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/db/AppDatabase.kt b/app/src/main/java/com/dzeio/openhealth/db/AppDatabase.kt
deleted file mode 100644
index f39432a..0000000
--- a/app/src/main/java/com/dzeio/openhealth/db/AppDatabase.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.dzeio.openhealth.db
-
-import android.content.Context
-import androidx.room.Database
-import androidx.room.Room
-import androidx.room.RoomDatabase
-import com.dzeio.openhealth.db.dao.WeightDao
-import com.dzeio.openhealth.db.entities.Weight
-
-@Database(entities = [Weight::class], version = 1, exportSchema = false)
-abstract class AppDatabase : RoomDatabase() {
- abstract fun weightDao() : WeightDao
-
- companion object {
- // For Singleton instantiation
- @Volatile
- private var INSTANCE: AppDatabase? = null
- private const val DATABASE_NAME = "open_health"
-
- fun getInstance(context: Context): AppDatabase {
- return INSTANCE ?: synchronized(this) {
- var instance = INSTANCE
- if (instance == null) {
- instance = Room.databaseBuilder(
- context.applicationContext,
- AppDatabase::class.java,
- DATABASE_NAME
- )
- .fallbackToDestructiveMigration()
- .allowMainThreadQueries()
- .build()
- }
- return instance
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt b/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt
new file mode 100644
index 0000000..cf320b2
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt
@@ -0,0 +1,27 @@
+package com.dzeio.openhealth.di
+
+import android.content.Context
+import com.dzeio.openhealth.data.AppDatabase
+import com.dzeio.openhealth.data.weight.WeightDao
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+class DatabaseModule {
+
+ @Singleton
+ @Provides
+ fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
+ return AppDatabase.getInstance(context)
+ }
+
+ @Provides
+ fun provideWeightDao(appDatabase: AppDatabase): WeightDao {
+ return appDatabase.weightDao()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/dialogs/AddWeightDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/dialogs/AddWeightDialog.kt
new file mode 100644
index 0000000..8b30f87
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/dialogs/AddWeightDialog.kt
@@ -0,0 +1,68 @@
+package com.dzeio.openhealth.ui.dialogs
+
+import android.app.AlertDialog
+import android.view.LayoutInflater
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
+import com.dzeio.openhealth.R
+import com.dzeio.openhealth.core.BaseDialog
+import com.dzeio.openhealth.data.weight.Weight
+import com.dzeio.openhealth.databinding.DialogAddWeightBinding
+import com.dzeio.openhealth.ui.main.home.HomeViewModel
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.collect
+
+@AndroidEntryPoint
+class AddWeightDialog : BaseDialog(HomeViewModel::class.java) {
+
+ override val bindingInflater: (LayoutInflater) -> DialogAddWeightBinding = DialogAddWeightBinding::inflate
+
+ override fun onBuilderInit(builder: MaterialAlertDialogBuilder) {
+ super.onBuilderInit(builder)
+
+ builder.apply {
+ setTitle("Add your weight (kg)")
+ setIcon(activity?.let { ContextCompat.getDrawable(it, R.drawable.ic_outline_timeline_24) })
+ setPositiveButton("Validate") { dialog, _ ->
+ save()
+ }
+ setNegativeButton("Cancel") { dialog, _ ->
+ dialog.cancel()
+ }
+
+ }
+ }
+
+ override fun onCreated() {
+ super.onCreated()
+
+ lifecycleScope.launchWhenStarted {
+ viewModel.lastWeight().collect {
+ if (it != null) {
+ binding.kg.value = it.weight.toInt()
+ binding.gram.value = ((it.weight - it.weight.toInt()) * 10 ).toInt()
+ }
+ }
+ }
+
+ binding.kg.maxValue = 636
+ binding.kg.minValue = 0
+
+ binding.gram.maxValue = 9
+ binding.gram.minValue = 0
+ }
+
+ private fun save() {
+ val weight = Weight().apply {
+ weight = binding.kg.value + (binding.gram.value.toFloat() / 10)
+ source = "OpenHealth"
+
+ }
+ lifecycleScope.launchWhenCreated {
+ viewModel.addWeight(weight)
+ }
+ //callback?.invoke()
+ dialog?.dismiss()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/dialogs/EditWeightDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/dialogs/EditWeightDialog.kt
new file mode 100644
index 0000000..6c5ad5e
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/dialogs/EditWeightDialog.kt
@@ -0,0 +1,162 @@
+package com.dzeio.openhealth.ui.dialogs
+
+import android.app.AlertDialog
+import android.app.DatePickerDialog
+import android.app.Dialog
+import android.content.res.Resources
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.ViewGroup
+import android.view.Window
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.dzeio.openhealth.R
+import com.dzeio.openhealth.core.BaseFullscreenDialog
+import com.dzeio.openhealth.data.weight.Weight
+import com.dzeio.openhealth.databinding.DialogAddWeightBinding
+import com.dzeio.openhealth.databinding.DialogEditWeightBinding
+import com.dzeio.openhealth.ui.main.home.HomeViewModel
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.timepicker.MaterialTimePicker
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.collect
+import java.util.*
+
+@AndroidEntryPoint
+class EditWeightDialog : BaseFullscreenDialog(HomeViewModel::class.java) {
+
+ override val bindingInflater: (LayoutInflater) -> DialogEditWeightBinding = DialogEditWeightBinding::inflate
+
+ override val isFullscreenLayout = true
+
+ val args: EditWeightDialogArgs by navArgs()
+
+ lateinit var weight: Weight
+
+// override fun onBuilderInit(builder: AlertDialog.Builder) {
+// super.onBuilderInit(builder)
+//
+// builder.apply {
+// setTitle("Add your weight (kg)")
+// setIcon(activity?.let { ContextCompat.getDrawable(it, R.drawable.ic_outline_timeline_24) })
+// setPositiveButton("Validate") { dialog, _ ->
+// save()
+// }
+// setNegativeButton("Cancel") { dialog, _ ->
+// dialog.cancel()
+// }
+//
+// }
+// }
+
+ override fun onDialogInit(dialog: Dialog) {
+ super.onDialogInit(dialog)
+
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
+ }
+
+ override fun onCreated(savedInstanceState: Bundle?) {
+ super.onCreated(savedInstanceState)
+
+ lifecycleScope.launchWhenStarted {
+ viewModel.fetchWeight(args.id).collect {
+ weight = it!!
+ binding.layoutDialogEditWeightKg.value = it.weight.toInt()
+ binding.layoutDialogEditWeightGram.value = ((it.weight - it.weight.toInt()) * 10 ).toInt()
+
+ binding.layoutDialogEditWeightTimestamp.setText(it.formatTimestamp())
+ }
+ }
+
+ binding.layoutDialogEditWeightKg.maxValue = 636
+ binding.layoutDialogEditWeightKg.minValue = 0
+
+ binding.layoutDialogEditWeightGram.maxValue = 9
+ binding.layoutDialogEditWeightGram.minValue = 0
+
+ binding.layoutDialogEditWeightTimestamp.setOnClickListener {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ val date = Date(weight.timestamp)
+ val datePicker = MaterialDatePicker.Builder.datePicker()
+ .setTitleText("Select Date")
+ .setSelection(weight.timestamp)
+ .build()
+
+ val fragManager = requireActivity().supportFragmentManager
+
+ datePicker.addOnPositiveButtonClickListener { tsp ->
+ val timePicker = MaterialTimePicker.Builder()
+ .setHour(date.hours)
+ .setMinute(date.minutes)
+ .setTitleText("Pouet")
+ .build()
+
+ timePicker.addOnPositiveButtonClickListener {
+ Log.d("T", "${timePicker.hour} ${timePicker.minute}")
+ val newDate = Date(tsp)
+ newDate.hours = timePicker.hour
+ newDate.minutes = timePicker.minute
+ weight.timestamp = newDate.time
+ binding.layoutDialogEditWeightTimestamp.setText(weight.formatTimestamp())
+ Log.d("Res", newDate.time.toString())
+ }
+ timePicker.show(fragManager, "dialog")
+ }
+ datePicker.show(fragManager, "dialog")
+ Log.d("Tag", "${date.year + 1900}, ${date.month}, ${date.day}")
+// val dg = DatePickerDialog(requireActivity())
+// dg.setOnDateSetListener { _, year, month, day ->
+//
+// }
+// dg.updateDate(date.year + 1900, date.month, date.day)
+// dg.show()
+ } else {
+ TODO("VERSION.SDK_INT < N")
+ }
+
+
+ }
+
+ }
+
+ private fun save() {
+ lifecycleScope.launchWhenCreated {
+ weight.weight = binding.layoutDialogEditWeightKg.value + (binding.layoutDialogEditWeightGram.value.toFloat() / 10)
+ viewModel.addWeight(weight)
+ findNavController().popBackStack()
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.menu_fullscreen_dialog_save -> {
+ save()
+ true
+ }
+ R.id.menu_fullscreen_dialog_delete -> {
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle("Delete Weight?")
+ .setMessage("Are you sure you want to delete this weight?")
+ .setPositiveButton("Yes") {_, _ ->
+ lifecycleScope.launchWhenStarted {
+ viewModel.deleteWeight(weight)
+ findNavController().popBackStack()
+ }
+ }
+ .setIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_outline_delete_24))
+ .setNegativeButton("No") { _, _ ->}
+ .show()
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryFragment.kt
deleted file mode 100644
index ece59f6..0000000
--- a/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryFragment.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.dzeio.openhealth.ui.gallery
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
-import com.dzeio.openhealth.databinding.FragmentGalleryBinding
-
-class GalleryFragment : Fragment() {
-
- private var _binding: FragmentGalleryBinding? = null
-
- // This property is only valid between onCreateView and
- // onDestroyView.
- private val binding get() = _binding!!
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- val galleryViewModel =
- ViewModelProvider(this).get(GalleryViewModel::class.java)
-
- _binding = FragmentGalleryBinding.inflate(inflater, container, false)
- val root: View = binding.root
-
- val textView: TextView = binding.textGallery
- galleryViewModel.text.observe(viewLifecycleOwner) {
- textView.text = it
- }
- return root
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryViewModel.kt
deleted file mode 100644
index 251bce1..0000000
--- a/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryViewModel.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.dzeio.openhealth.ui.gallery
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-
-class GalleryViewModel : ViewModel() {
-
- private val _text = MutableLiveData().apply {
- value = "This is gallery Fragment"
- }
- val text: LiveData = _text
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt
deleted file mode 100644
index 9a07365..0000000
--- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.dzeio.openhealth.ui.home
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
-
-class HomeViewModel(
- private val savedStateHandle: SavedStateHandle
-) : ViewModel() {
- val text = MutableLiveData().apply {
- value = "This is home Fragment"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/import/ImportFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/import/ImportFragment.kt
deleted file mode 100644
index f7f0977..0000000
--- a/app/src/main/java/com/dzeio/openhealth/ui/import/ImportFragment.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.dzeio.openhealth.ui.import
-
-import android.app.ProgressDialog
-import android.os.Build
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ProgressBar
-import android.widget.TextView
-import androidx.annotation.RequiresApi
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
-import com.dzeio.openhealth.connectors.GoogleFit
-import com.dzeio.openhealth.databinding.FragmentImportBinding
-
-class ImportFragment : Fragment() {
-
- private var _binding: FragmentImportBinding? = null
-
- // This property is only valid between onCreateView and
- // onDestroyView.
- private val binding get() = _binding!!
- private lateinit var viewModel: ImportViewModel
-
- private lateinit var progressDialog: ProgressDialog
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- viewModel = ViewModelProvider(this).get(ImportViewModel::class.java)
-
- _binding = FragmentImportBinding.inflate(inflater, container, false)
- val root: View = binding.root
-
- progressDialog = ProgressDialog(requireContext())
-
- progressDialog.apply {
- setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
- setCancelable(false)
- }
-
- return root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- binding.importGoogleFit.setOnClickListener {
- importFromGoogleFit()
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- }
-
- @RequiresApi(Build.VERSION_CODES.O)
- fun importFromGoogleFit() {
- viewModel.importProgressTotal.postValue(-1)
- val google = GoogleFit(requireActivity())
-
- progressDialog.show()
- google.importWeight {
- progressDialog.dismiss()
- }
- }
-
- fun importFromSamsungHealth() {
-
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeFragment.kt
new file mode 100644
index 0000000..42e8629
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeFragment.kt
@@ -0,0 +1,199 @@
+package com.dzeio.openhealth.ui.main.home
+
+import android.app.Activity.RESULT_OK
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.commit
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import com.dzeio.openhealth.R
+import com.dzeio.openhealth.core.BaseFragment
+import com.dzeio.openhealth.databinding.FragmentHomeBinding
+import com.dzeio.openhealth.data.weight.Weight
+import com.dzeio.openhealth.ui.dialogs.AddWeightDialog
+import com.dzeio.openhealth.ui.main.list_weight.ListWeightFragment
+import com.github.mikephil.charting.components.AxisBase
+import com.github.mikephil.charting.components.Description
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.components.YAxis
+import com.github.mikephil.charting.data.Entry
+import com.github.mikephil.charting.data.LineData
+import com.github.mikephil.charting.data.LineDataSet
+import com.github.mikephil.charting.formatter.ValueFormatter
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.collectLatest
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.collections.ArrayList
+
+@AndroidEntryPoint
+class HomeFragment : BaseFragment(HomeViewModel::class.java) {
+
+ companion object {
+ const val TAG = "HomeFragment"
+ }
+
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHomeBinding
+ get() = FragmentHomeBinding::inflate
+
+ // private lateinit var fit: GoogleFit
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.addWeight.setOnClickListener {
+ AddWeightDialog().show(requireActivity().supportFragmentManager, null)
+ }
+
+ binding.listWeight.setOnClickListener {
+ Log.d("T", "Trying to move")
+
+ findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight())
+
+// requireActivity().supportFragmentManager.commit {
+// replace(R.id.nav_host_fragment_content_main, ListWeightFragment())
+// addToBackStack(null)
+// }
+ }
+
+ // Setup Graph
+// binding.weightGraph.gridLabelRenderer.labelFormatter = DateAsXAxisLabelFormatter(requireContext())
+// binding.weightGraph.gridLabelRenderer.numHorizontalLabels = 3 // only 4 because of the space
+// binding.weightGraph.viewport.isXAxisBoundsManual = true
+//// binding.weightGraph.gridLabelRenderer.setHumanRounding(false);
+// binding.weightGraph.addSeries(serie)
+ }
+
+// private val entries = LineGraphSeries()
+
+ private fun updateGraph(list: List) {
+ if (list.isNotEmpty()) {
+ val first = list[0].timestamp
+ val last = list[list.size - 1].timestamp
+ val weekFirst = last - 604800000
+// binding.weightGraph.viewport.setMinX(if (first > weekFirst) first else weekFirst)
+// binding.weightGraph.viewport.setMaxX(last);
+
+
+ val entries = ArrayList()
+ for (item in list) {
+ entries.add(Entry(item.timestamp.toFloat(), item.weight))
+ }
+
+ val dataSet = LineDataSet(entries, "Label")
+// binding.weightGraph.xAxis.axisMinimum = if (first > weekFirst) first.toFloat() else weekFirst.toFloat()
+// binding.weightGraph.setExtraOffsets(
+// if (first > weekFirst) first.toFloat() else weekFirst.toFloat(),
+// 100f,
+// last.toFloat(),
+// 0f
+// )
+ binding.weightGraph.isAutoScaleMinMaxEnabled = true
+ //binding.weightGraph.setvir
+ binding.weightGraph.isDragEnabled = true
+ binding.weightGraph.isScaleYEnabled = false
+ binding.weightGraph.description = Description().apply { isEnabled = false }
+ binding.weightGraph.isScaleXEnabled = true
+ binding.weightGraph.setPinchZoom(false)
+ binding.weightGraph.xAxis.setLabelCount(5, true)
+ binding.weightGraph.xAxis.position = XAxis.XAxisPosition.BOTTOM
+ binding.weightGraph.axisRight.isEnabled = false
+ binding.weightGraph.axisRight.setDrawGridLines(false)
+ binding.weightGraph.xAxis.valueFormatter = object : ValueFormatter() {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String {
+ return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(value.toLong()))
+ //return super.getAxisLabel(value, axis)
+ }
+ }
+ binding.weightGraph.data = LineData(dataSet)
+ binding.weightGraph.invalidate()
+ }
+
+
+// serie.resetData(list
+// .map { DataPoint(Date(it.timestamp), it.weight.toDouble()) }
+// .toTypedArray()
+// )
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ lifecycleScope.launchWhenStarted {
+ viewModel.fetchWeights().collectLatest {
+ if (it.isEmpty()) {
+ return@collectLatest
+ }
+ updateGraph(it)
+ }
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ Log.d(TAG, "Activity Result!")
+ when (resultCode) {
+ RESULT_OK -> {
+ // fit.performActionForRequestCode(ActionRequestCode.FIND_DATA_SOURCES)
+ }
+ else -> {
+ Log.e(TAG, "Error: $requestCode, $resultCode")
+ }
+ }
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array,
+ grantResults: IntArray) {
+ when {
+ grantResults.isEmpty() -> {
+ // If user interaction was interrupted, the permission request
+ // is cancelled and you receive empty arrays.
+ Log.i(TAG, "User interaction was cancelled.")
+ }
+
+ grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
+ Log.d(TAG, "Granted")
+ // Permission was granted.
+// val fitActionRequestCode = ActionRequestCode.values()[requestCode]
+// fitActionRequestCode.let {
+// // fit.signIn(ActionRequestCode.FIND_DATA_SOURCES)
+// }
+ }
+ else -> {
+ // Permission denied.
+
+ // In this Activity we've chosen to notify the user that they
+ // have rejected a core permission for the app since it makes the Activity useless.
+ // We're communicating this message in a Snackbar since this is a sample app, but
+ // core permissions would typically be best requested during a welcome-screen flow.
+
+ // Additionally, it is important to remember that a permission might have been
+ // rejected without asking the user for permission (device policy or "Never ask
+ // again" prompts). Therefore, a user interface affordance is typically implemented
+ // when permissions are denied. Otherwise, your app could appear unresponsive to
+ // touches or interactions which have required permissions.
+ Log.e(TAG, "Error")
+// Snackbar.make(
+// findViewById(R.id.main_activity_view),
+// R.string.permission_denied_explanation,
+// Snackbar.LENGTH_INDEFINITE)
+// .setAction(R.string.settings) {
+// // Build intent that displays the App settings screen.
+// val intent = Intent()
+// intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+// val uri = Uri.fromParts("package",
+// BuildConfig.APPLICATION_ID, null)
+// intent.data = uri
+// intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+// startActivity(intent)
+// }
+// .show()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeViewModel.kt
new file mode 100644
index 0000000..db1a7ce
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeViewModel.kt
@@ -0,0 +1,23 @@
+package com.dzeio.openhealth.ui.main.home
+
+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 HomeViewModel @Inject internal constructor(
+ private val weightRepository: WeightRepository
+) : BaseViewModel() {
+
+ fun fetchWeights() = weightRepository.getWeights()
+
+ fun lastWeight() = weightRepository.lastWeight()
+
+ fun fetchWeight(id: Long) = weightRepository.getWeight(id)
+
+ suspend fun deleteWeight(weight: Weight) = weightRepository.deleteWeight(weight)
+
+ suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportFragment.kt
similarity index 66%
rename from app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt
rename to app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportFragment.kt
index 9f9707b..6801fa6 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportFragment.kt
@@ -1,7 +1,6 @@
-package com.dzeio.openhealth.ui.home
+package com.dzeio.openhealth.ui.main.import
-import android.app.Activity.RESULT_OK
-import android.content.Intent
+import android.app.ProgressDialog
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
@@ -9,67 +8,61 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.dzeio.openhealth.connectors.ActionRequestCode
import com.dzeio.openhealth.connectors.GoogleFit
+//import com.dzeio.openhealth.connectors.GoogleFit
import com.dzeio.openhealth.connectors.samsunghealth.SamsungHealth
-import com.dzeio.openhealth.databinding.FragmentHomeBinding
-import com.dzeio.openhealth.db.AppDatabase
+import com.dzeio.openhealth.databinding.FragmentImportBinding
-class HomeFragment : Fragment() {
+class ImportFragment : Fragment() {
companion object {
- const val TAG = "HomeFragment"
+ const val TAG = "ImportFragment"
}
- private var _binding: FragmentHomeBinding? = null
+ private var _binding: FragmentImportBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
+ private lateinit var viewModel: ImportViewModel
+
+ private lateinit var progressDialog: ProgressDialog
private lateinit var fit: GoogleFit
- private lateinit var viewModel: HomeViewModel
-
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- viewModel =
- ViewModelProvider(this).get(HomeViewModel::class.java)
+ viewModel = ViewModelProvider(this).get(ImportViewModel::class.java)
- _binding = FragmentHomeBinding.inflate(inflater, container, false)
+ _binding = FragmentImportBinding.inflate(inflater, container, false)
val root: View = binding.root
-// binding.button.setOnClickListener {
-// fit = GoogleFit(requireActivity())
-// //fit.import()
-// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-// //fit.getHistory()
-// fit.importWeight()
-// }
-//
-// //SamsungHealth(requireActivity()).importStepCount()
-// }
- viewModel.text.observe(viewLifecycleOwner) {
- binding.textView2.text = it
- }
+ progressDialog = ProgressDialog(requireContext())
+ progressDialog.apply {
+ setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
+ setCancelable(false)
+ }
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
-
- val weight = AppDatabase.getInstance(requireContext()).weightDao().last()
- if (weight == null) {
- viewModel.text.postValue("No Weight Available")
- } else {
- viewModel.text.postValue("${weight.weight}kg\ndone at ${weight.timestamp}")
+ binding.importGoogleFit.setOnClickListener {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ importFromGoogleFit()
+ }
+ }
+ binding.importSamsungHealth.setOnClickListener {
+ importFromSamsungHealth()
}
}
@@ -78,17 +71,22 @@ class HomeFragment : Fragment() {
_binding = null
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- Log.d(TAG, "Activity Result!")
- when (resultCode) {
- RESULT_OK -> {
- fit.performActionForRequestCode(ActionRequestCode.FIND_DATA_SOURCES)
- }
- else -> {
- Log.e(TAG, "Error: $requestCode, $resultCode")
- }
- }
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun importFromGoogleFit() {
+ viewModel.importProgressTotal.postValue(-1)
+ fit = GoogleFit(requireActivity())
+
+ progressDialog.show()
+ fit.import()
+ fit.getHistory()
+// google.importWeight {
+// progressDialog.dismiss()
+// }
+ }
+
+ fun importFromSamsungHealth() {
+ SamsungHealth(requireActivity())
+ .test()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array,
@@ -100,8 +98,8 @@ class HomeFragment : Fragment() {
Log.i(TAG, "User interaction was cancelled.")
}
- grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
- Log.d(TAG, "Granted")
+ grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
+ Log.d(TAG, "Granted")
// Permission was granted.
val fitActionRequestCode = ActionRequestCode.values()[requestCode]
fitActionRequestCode.let {
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/import/ImportViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportViewModel.kt
similarity index 79%
rename from app/src/main/java/com/dzeio/openhealth/ui/import/ImportViewModel.kt
rename to app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportViewModel.kt
index 870b6bb..f66033f 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/import/ImportViewModel.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportViewModel.kt
@@ -1,9 +1,8 @@
-package com.dzeio.openhealth.ui.import
+package com.dzeio.openhealth.ui.main.import
-import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.dzeio.openhealth.connectors.GoogleFit
+//import com.dzeio.openhealth.connectors.GoogleFit
class ImportViewModel : ViewModel() {
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/main/list_weight/ListWeightFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/list_weight/ListWeightFragment.kt
new file mode 100644
index 0000000..24ff6d8
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/main/list_weight/ListWeightFragment.kt
@@ -0,0 +1,49 @@
+package com.dzeio.openhealth.ui.main.list_weight
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.bundleOf
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.dzeio.openhealth.adapters.WeightAdapter
+import com.dzeio.openhealth.core.BaseFragment
+import com.dzeio.openhealth.databinding.FragmentListWeightBinding
+import com.dzeio.openhealth.ui.dialogs.EditWeightDialog
+import com.dzeio.openhealth.ui.main.home.HomeViewModel
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.collect
+
+@AndroidEntryPoint
+class ListWeightFragment : BaseFragment(HomeViewModel::class.java) {
+
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentListWeightBinding = FragmentListWeightBinding::inflate
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val recycler = binding.list
+
+ val manager = LinearLayoutManager(requireContext())
+ recycler.layoutManager = manager
+
+ val adapter = WeightAdapter()
+ adapter.onItemClick = {
+ findNavController().navigate(ListWeightFragmentDirections.actionNavListWeightToNavEditWeight(it.id))
+ //EditWeightDialog().show(requireActivity().supportFragmentManager, "dialog")
+ }
+ recycler.adapter = adapter
+
+ viewLifecycleOwner.lifecycleScope.launchWhenCreated {
+ viewModel.fetchWeights().collect {
+ adapter.set(it)
+ }
+ }
+
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml
new file mode 100644
index 0000000..eb23254
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_add_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_edit_24.xml b/app/src/main/res/drawable/ic_baseline_edit_24.xml
new file mode 100644
index 0000000..2844baf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_edit_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_delete_24.xml b/app/src/main/res/drawable/ic_outline_delete_24.xml
new file mode 100644
index 0000000..04aa8f2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_delete_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_hexagon_24.xml b/app/src/main/res/drawable/ic_outline_hexagon_24.xml
new file mode 100644
index 0000000..938ab4a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_hexagon_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_timeline_24.xml b/app/src/main/res/drawable/ic_outline_timeline_24.xml
new file mode 100644
index 0000000..978339f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_timeline_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 6c7dd7c..340c80d 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -5,21 +5,55 @@
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:fitsSystemWindows="true"
- tools:openDrawer="start">
+ tools:openDrawer="start"
+ tools:context=".MainActivity">
-
+ android:layout_height="match_parent"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml
deleted file mode 100644
index c0dbb85..0000000
--- a/app/src/main/res/layout/app_bar_main.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
deleted file mode 100644
index 6e0ea39..0000000
--- a/app/src/main/res/layout/content_main.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_add_weight.xml b/app/src/main/res/layout/dialog_add_weight.xml
new file mode 100644
index 0000000..d261b93
--- /dev/null
+++ b/app/src/main/res/layout/dialog_add_weight.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_edit_weight.xml b/app/src/main/res/layout/dialog_edit_weight.xml
new file mode 100644
index 0000000..67c52b6
--- /dev/null
+++ b/app/src/main/res/layout/dialog_edit_weight.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_gallery.xml b/app/src/main/res/layout/fragment_gallery.xml
deleted file mode 100644
index 643fe25..0000000
--- a/app/src/main/res/layout/fragment_gallery.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index 5d67c26..c466300 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -4,14 +4,91 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".ui.home.HomeFragment">
+ tools:context=".ui.main.home.HomeFragment">
-
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_import.xml b/app/src/main/res/layout/fragment_import.xml
index d7dd7ea..433c21e 100644
--- a/app/src/main/res/layout/fragment_import.xml
+++ b/app/src/main/res/layout/fragment_import.xml
@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".ui.import.ImportFragment">
+ tools:context=".ui.main.import.ImportFragment">
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_item_weight.xml b/app/src/main/res/layout/layout_item_weight.xml
new file mode 100644
index 0000000..d850e1d
--- /dev/null
+++ b/app/src/main/res/layout/layout_item_weight.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/fullscreen_dialog.xml b/app/src/main/res/menu/fullscreen_dialog.xml
new file mode 100644
index 0000000..1ba44da
--- /dev/null
+++ b/app/src/main/res/menu/fullscreen_dialog.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
index 9e724ee..78aaf0d 100644
--- a/app/src/main/res/navigation/mobile_navigation.xml
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -7,19 +7,54 @@
-
-
+ tools:layout="@layout/fragment_home" >
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 5c63e93..265741a 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,16 +1,17 @@
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6a63ac9..7ffffbb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -11,4 +11,6 @@
Gallery
Slideshow
Import
+ Weight List
+ Edit Weight
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 3d85d54..854d44b 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,17 +1,20 @@
-
-
+
+
+
-
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index a7d16e1..62067e3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,21 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ dependencies {
+ classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'
+
+
+ // Safe Navigation
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
+ }
+}
+
plugins {
- id 'com.android.application' version '7.1.0-beta04' apply false
- id 'com.android.library' version '7.1.0-beta04' apply false
+ id 'com.android.application' version '7.1.0-beta05' apply false
+ id 'com.android.library' version '7.1.0-beta05' apply false
id 'org.jetbrains.kotlin.android' version '1.6.0' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
+ delete project.buildDir
}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index cd0519b..e1c4e18 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,4 +20,6 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
+android.nonTransitiveRClass=true
+
+android.enableJetifier=true
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index a116b7f..b83db14 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,11 +5,13 @@ pluginManagement {
mavenCentral()
}
}
+
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
+ maven { url 'https://jitpack.io' }
}
}
rootProject.name = "OpenHealth"