diff --git a/app/build.gradle b/app/build.gradle
index 83ae9ed..6821d9f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -101,11 +101,14 @@ dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'javax.inject:javax.inject:1'
- implementation 'com.google.android.material:material:1.7.0-alpha02'
+ implementation 'com.google.android.material:material:1.7.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
+ // Coroutines
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3"
+
// Settings
implementation "androidx.preference:preference-ktx:1.2.0"
@@ -116,6 +119,11 @@ dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
+ // Paging
+ implementation "androidx.paging:paging-runtime:3.1.1"
+ implementation "androidx.paging:paging-runtime-ktx:3.1.1"
+
+
// Services
implementation 'androidx.work:work-runtime-ktx:2.7.1'
@@ -128,8 +136,8 @@ dependencies {
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
// Hilt
- implementation "com.google.dagger:hilt-android:2.40.5"
- kapt "com.google.dagger:hilt-compiler:2.40.5"
+ implementation 'com.google.dagger:hilt-android:2.42'
+ kapt 'com.google.dagger:hilt-compiler:2.42'
// Google Fit
implementation "com.google.android.gms:play-services-fitness:21.1.0"
@@ -137,11 +145,16 @@ dependencies {
// Samsung Health
implementation files('libs/samsung-health-data-1.5.0.aar')
- implementation "com.google.code.gson:gson:2.8.9"
+ implementation 'com.google.code.gson:gson:2.9.0'
// ROOM
implementation "androidx.room:room-runtime:2.4.2"
kapt "androidx.room:room-compiler:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
testImplementation "androidx.room:room-testing:2.4.2"
+
+ // Futures
+ implementation 'com.google.guava:guava:31.1-jre'
+ implementation "androidx.concurrent:concurrent-futures:1.1.0"
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.3'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 600b804..233d020 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,7 +34,7 @@
android:value="@integer/google_play_services_version" />
@@ -43,6 +43,11 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/Application.kt b/app/src/main/java/com/dzeio/openhealth/Application.kt
index f5ad855..cd5fc34 100644
--- a/app/src/main/java/com/dzeio/openhealth/Application.kt
+++ b/app/src/main/java/com/dzeio/openhealth/Application.kt
@@ -1,9 +1,7 @@
package com.dzeio.openhealth
import android.app.Application
-import android.content.Context
import android.content.SharedPreferences
-import android.content.res.Resources
import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
@@ -27,9 +25,15 @@ class Application : Application() {
val locale = Locale(lang)
Locale.setDefault(locale)
- val overrideConfiguration = baseContext.resources.configuration
- overrideConfiguration.locale = locale
- val context: Context = createConfigurationContext(overrideConfiguration)
- val resources: Resources = context.getResources()
+// val overrideConfiguration = baseContext.resources.configuration
+// overrideConfiguration.locale = locale
+// val context: Context = createConfigurationContext(overrideConfiguration)
+// val resources: Resources = context.resources
+ }
+
+ /**
+ * SharedPreferences Key
+ */
+ object Settings {
}
}
diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/StepsAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/StepsAdapter.kt
new file mode 100644
index 0000000..c6f7fb6
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/adapters/StepsAdapter.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.step.Step
+import com.dzeio.openhealth.databinding.LayoutItemListBinding
+
+class StepsAdapter() : BaseAdapter() {
+
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemListBinding
+ get() = LayoutItemListBinding::inflate
+
+ var onItemClick: ((weight: Step) -> Unit)? = null
+
+ override fun onBindData(
+ holder: BaseViewHolder,
+ item: Step,
+ position: Int
+ ) {
+ holder.binding.value.text = "${item.value}ml"
+ holder.binding.datetime.text = item.formatTimestamp()
+ holder.binding.edit.setOnClickListener {
+ onItemClick?.invoke(item)
+ }
+ }
+}
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt
index eaa9481..1e5e2f8 100644
--- a/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt
@@ -32,7 +32,7 @@ abstract class BaseAdapter : RecyclerView.Adapter, item: T, position: Int)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
- return BaseViewHolder(
+ return BaseViewHolder(
bindingInflater(LayoutInflater.from(parent.context), parent, false)
)
}
diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseService.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseService.kt
index 7af8e40..17bfff7 100644
--- a/app/src/main/java/com/dzeio/openhealth/core/BaseService.kt
+++ b/app/src/main/java/com/dzeio/openhealth/core/BaseService.kt
@@ -2,24 +2,20 @@ package com.dzeio.openhealth.core
import android.content.Context
import android.util.Log
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
-import androidx.work.WorkRequest
import androidx.work.Worker
import androidx.work.WorkerParameters
abstract class BaseService(context: Context, params: WorkerParameters) : Worker(context, params) {
-
companion object {
- fun schedule(tag: String, request: WorkRequest, context: Context) {
- WorkManager.getInstance(context)
- .cancelAllWorkByTag(tag)
-
+ fun schedule(tag: String, request: PeriodicWorkRequest, context: Context) {
Log.d("OpenHealth/BaseService", "Scheduled Job $tag")
WorkManager.getInstance(context)
- .enqueue(request)
-
+ .enqueueUniquePeriodicWork(tag, ExistingPeriodicWorkPolicy.KEEP, request)
}
}
}
\ 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
index 4850ce6..96fb723 100644
--- a/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt
+++ b/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt
@@ -4,6 +4,8 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
+import com.dzeio.openhealth.data.step.Step
+import com.dzeio.openhealth.data.step.StepDao
import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.water.WaterDao
import com.dzeio.openhealth.data.weight.Weight
@@ -12,7 +14,8 @@ import com.dzeio.openhealth.data.weight.WeightDao
@Database(
entities = [
Weight::class,
- Water::class
+ Water::class,
+ Step::class
],
version = 1,
exportSchema = false
@@ -23,6 +26,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun weightDao(): WeightDao
abstract fun waterDao(): WaterDao
+ abstract fun stepDao(): StepDao
companion object {
private const val DATABASE_NAME = "open_health"
diff --git a/app/src/main/java/com/dzeio/openhealth/data/step/Step.kt b/app/src/main/java/com/dzeio/openhealth/data/step/Step.kt
new file mode 100644
index 0000000..6a6b361
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/data/step/Step.kt
@@ -0,0 +1,45 @@
+package com.dzeio.openhealth.data.step
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.sql.Date
+import java.text.DateFormat
+import java.util.Calendar
+import java.util.TimeZone
+
+@Entity()
+data class Step(
+ @PrimaryKey(autoGenerate = true) var id: Long = 0,
+ var value: Float = 0f,
+ @ColumnInfo(index = true)
+ /**
+ * Timestamp down to an hour
+ *
+ * ex: 2022-09-22 10:00:00
+ */
+ var timestamp: Long = 0,
+ var source: String = "OpenHealth"
+) {
+
+ init {
+ if (timestamp == 0L) {
+ val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
+ cal.set(Calendar.MINUTE, 0)
+ cal.set(Calendar.SECOND, 0)
+ cal.set(Calendar.MILLISECOND, 0)
+
+ timestamp = cal.timeInMillis
+ }
+ }
+
+ fun formatTimestamp(): String = DateFormat.getDateInstance().format(Date(timestamp))
+
+ fun isToday(): Boolean {
+ val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
+ cal.set(Calendar.MINUTE, 0)
+ cal.set(Calendar.SECOND, 0)
+ cal.set(Calendar.MILLISECOND, 0)
+ return timestamp == cal.timeInMillis
+ }
+}
diff --git a/app/src/main/java/com/dzeio/openhealth/data/step/StepDao.kt b/app/src/main/java/com/dzeio/openhealth/data/step/StepDao.kt
new file mode 100644
index 0000000..5d97152
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/data/step/StepDao.kt
@@ -0,0 +1,35 @@
+package com.dzeio.openhealth.data.step
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.Update
+import com.dzeio.openhealth.core.BaseDao
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface StepDao : BaseDao {
+
+ @Query("SELECT * FROM Step ORDER BY timestamp DESC")
+ fun getAll(): Flow>
+
+ @Query("SELECT * FROM Step where id = :weightId")
+ fun getOne(weightId: Long): Flow
+
+ @Query("Select count(*) from Step")
+ fun getCount(): Flow
+
+ @Query("Select * FROM Step ORDER BY timestamp DESC LIMIT 1")
+ fun last(): Flow
+
+ @Query("DELETE FROM Step where source = :source")
+ suspend fun deleteFromSource(source: String)
+
+ @Update
+ fun updateNF(vararg obj: Step)
+ @Insert
+ fun insertNF(vararg obj: Step)
+
+ @Query("Select * FROM Step ORDER BY timestamp DESC LIMIT 1")
+ fun lastNF(): Step?
+}
diff --git a/app/src/main/java/com/dzeio/openhealth/data/step/StepRepository.kt b/app/src/main/java/com/dzeio/openhealth/data/step/StepRepository.kt
new file mode 100644
index 0000000..ad6774a
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/data/step/StepRepository.kt
@@ -0,0 +1,26 @@
+package com.dzeio.openhealth.data.step
+
+import kotlinx.coroutines.flow.filter
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class StepRepository @Inject constructor(
+ private val stepDao: StepDao
+) {
+ fun getSteps() = stepDao.getAll()
+
+ fun lastStep() = stepDao.last()
+
+ fun getStep(id: Long) = stepDao.getOne(id)
+
+ suspend fun addStep(value: Step) = stepDao.insert(value)
+ suspend fun updateStep(value: Step) = stepDao.update(value)
+
+ suspend fun deleteStep(value: Step) = stepDao.delete(value)
+ suspend fun deleteFromSource(value: String) = stepDao.deleteFromSource(value)
+
+ fun todayStep() = lastStep().filter {
+ return@filter it != null && it.isToday()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/data/step/StepSource.kt b/app/src/main/java/com/dzeio/openhealth/data/step/StepSource.kt
new file mode 100644
index 0000000..79a8e6c
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/data/step/StepSource.kt
@@ -0,0 +1,90 @@
+package com.dzeio.openhealth.data.step
+
+import android.content.Context
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.os.SystemClock
+import android.util.Log
+import androidx.preference.PreferenceManager
+import com.dzeio.openhealth.Application
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+
+class StepSource(
+ private val context: Context,
+ private val callback: ((Float) -> Unit)? = null
+): SensorEventListener {
+
+ companion object {
+ const val TAG = "${Application.TAG}/StepSource"
+ }
+
+ private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+
+ private var timeSinceLastRecord: Long
+ get() {
+ return prefs.getLong("steps_time_since_last_record", Long.MAX_VALUE)
+ }
+ set(value) {
+ val editor = prefs.edit()
+ editor.putLong("steps_time_since_last_record", value)
+ editor.commit()
+ }
+ private var stepsAsOfLastRecord: Float
+ get() {
+ return prefs.getFloat("steps_as_of_last_record", 0f)
+ }
+ set(value) {
+ val editor = prefs.edit()
+ editor.putFloat("steps_as_of_last_record", value)
+ editor.commit()
+ }
+
+
+ init {
+ Log.d(TAG, "Setting up")
+ val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
+ val stepCountSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
+ stepCountSensor.let {
+ sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_NORMAL)
+ Log.d(TAG, "should be setup :D")
+ }
+ }
+
+ val events = Channel(10)
+
+ override fun onSensorChanged(ev: SensorEvent?) {
+ if (ev == null) {
+ return
+ }
+ // Log.d(TAG, "Sensor changed: $ev")
+ ev.values.firstOrNull()?.let {
+ val timeSinceLastBoot = System.currentTimeMillis() - SystemClock.elapsedRealtime()
+
+ val diff = it - stepsAsOfLastRecord
+ stepsAsOfLastRecord = it
+
+ // don't send changes since it wasn't made when the app was running
+ if (timeSinceLastBoot < timeSinceLastRecord) {
+ Log.d(TAG, "Skipping since we don't know when many steps are taken since last boot ($timeSinceLastRecord, $timeSinceLastBoot)")
+ timeSinceLastRecord = timeSinceLastBoot
+ return@let
+ }
+
+ timeSinceLastRecord = timeSinceLastBoot
+
+ runBlocking {
+ events.send(diff)
+ }
+ callback?.invoke(diff)
+ }
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
+ Log.d(TAG, "[Accuracy changed]: Sensor: $sensor, Accuracy: $accuracy")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt
index 22df54b..9423bc9 100644
--- a/app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt
+++ b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt
@@ -1,8 +1,8 @@
package com.dzeio.openhealth.data.weight
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Query
import com.dzeio.openhealth.core.BaseDao
-import dagger.Provides
import kotlinx.coroutines.flow.Flow
@Dao
@@ -11,10 +11,9 @@ interface WeightDao : BaseDao {
@Query("SELECT * FROM Weight ORDER BY timestamp")
fun getAll(): Flow>
- @Query("SELECT * FROM Weight where id = :weightId")
+ @Query("SELECT * FROM Weight WHERE id = :weightId")
fun getOne(weightId: Long): Flow
-
@Query("Select count(*) from Weight")
fun getCount(): Flow
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 15ad53a..10841ac 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,7 @@ package com.dzeio.openhealth.di
import android.content.Context
import com.dzeio.openhealth.data.AppDatabase
+import com.dzeio.openhealth.data.step.StepDao
import com.dzeio.openhealth.data.water.WaterDao
import com.dzeio.openhealth.data.weight.WeightDao
import dagger.Module
@@ -30,4 +31,9 @@ class DatabaseModule {
fun provideWaterDao(appDatabase: AppDatabase): WaterDao {
return appDatabase.waterDao()
}
+
+ @Provides
+ fun provideStepsDao(appDatabase: AppDatabase): StepDao {
+ return appDatabase.stepDao()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt b/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt
index 6e0ce44..ccce665 100644
--- a/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt
+++ b/app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt
@@ -9,9 +9,9 @@ import com.dzeio.openhealth.data.weight.Weight
/**
* Extension Schema
*
- * Version: 1.0.0
+ * Version: 0.1.0
*/
-abstract class Extension {
+interface Extension {
data class ImportState(
val state: States = States.WIP,
@@ -33,6 +33,8 @@ abstract class Extension {
STEPS
/**
+ * Google Fit:
+ *
* STEP_COUNT_CUMULATIVE
* ACTIVITY_SEGMENT
* SLEEP_SEGMENT
@@ -44,47 +46,52 @@ abstract class Extension {
*/
}
+
+ val permissions: Array?
+
+ val permissionsText: String?
+
/**
* the Source ID
*
* DO NOT CHANGE IT AFTER THE EXTENSION IS IN PRODUCTION
*/
- abstract val id: String
+ val id: String
/**
* The Extension Display Name
*/
- abstract val name: String
+ val name: String
/**
* Initialize hte Extension
*
* It is run Before any functions is launched and after events handlers are set
*/
- abstract fun init(activity: Activity): Array
+ fun init(activity: Activity): Array
/**
* A status shown on the extension list page
*/
- open fun getStatus(): String {
+ fun getStatus(): String {
return "No Status set..."
}
/**
* Function that will check
*/
- abstract fun isAvailable(): Boolean
+ fun isAvailable(): Boolean
/**
- * idk
+ * return if the extension is already connected to remote of not
*/
- abstract fun isConnected(): Boolean
+ fun isConnected(): Boolean
- open fun connect(): LiveData {
+ fun connect(): LiveData {
return MutableLiveData(States.DONE)
}
- open fun importWeight(): LiveData> {
+ fun importWeight(): LiveData> {
return MutableLiveData(ImportState(States.DONE))
}
@@ -92,7 +99,7 @@ abstract class Extension {
* function run when outgoing sync is enabled and new value is added
* or manual export is launched
*/
- open fun exportWeight(weight: Weight): LiveData {
+ fun exportWeight(weight: Weight): LiveData {
return MutableLiveData(States.DONE)
}
@@ -100,20 +107,8 @@ abstract class Extension {
* Activity Events
*/
- /**
- * Same as Activity/Fragment onRequestPermissionResult
- *
- * But it will only be launched if grantResults[0] == PackageManager.PERMISSION_GRANTED
- */
- open fun onRequestPermissionResult(
- requestCode: Int,
- permission: Array,
- grantResult: IntArray
- ) {
- }
-
/**
* Same as Activity/Fragment onActivityResult
*/
- open fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
+ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
}
diff --git a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt b/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt
index 3df4c13..9585045 100644
--- a/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt
+++ b/app/src/main/java/com/dzeio/openhealth/extensions/GoogleFit.kt
@@ -3,10 +3,7 @@ package com.dzeio.openhealth.extensions
import android.Manifest
import android.app.Activity
import android.content.Intent
-import android.content.pm.PackageManager
-import android.os.Build
import android.util.Log
-import androidx.core.app.ActivityCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.data.weight.Weight
@@ -21,7 +18,7 @@ import java.util.Date
import java.util.TimeZone
import java.util.concurrent.TimeUnit
-class GoogleFit() : Extension() {
+class GoogleFit: Extension {
companion object {
const val TAG = "GoogleFitConnector"
}
@@ -31,10 +28,16 @@ class GoogleFit() : Extension() {
override val id = "GoogleFit"
override val name = "Google Fit"
- override fun init(activity: Activity): Array {
+ override val permissions = arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION
+ )
+
+ override val permissionsText: String = "Please"
+
+ override fun init(activity: Activity): Array {
this.activity = activity
return arrayOf(
- Data.WEIGHT
+ Extension.Data.WEIGHT
)
}
@@ -55,47 +58,14 @@ class GoogleFit() : Extension() {
// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
.build()
-// private fun checkPermissionsAndRun(data: Data) {
-// if (permissionApproved()) {
-// signIn(data)
-// } else {
-// Log.d(TAG, "Asking for permission")
-// // Ask for permission
-// ActivityCompat.requestPermissions(
-// activity,
-// arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
-// data.ordinal
-// )
-// }
-// }
+ private val connectLiveData: MutableLiveData = MutableLiveData(Extension.States.WIP)
- private fun permissionApproved(): Boolean =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
- activity,
- Manifest.permission.ACCESS_FINE_LOCATION
- )
- } else {
- true
- }
-
- private val connectLiveData: MutableLiveData = MutableLiveData(States.WIP)
-
- override fun connect(): LiveData {
-
- if (!permissionApproved()) {
- ActivityCompat.requestPermissions(
- activity,
- arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
- 87531
- )
- return connectLiveData
- }
+ override fun connect(): LiveData {
if (isConnected()) {
- connectLiveData.value = States.DONE
+ connectLiveData.value = Extension.States.DONE
} else {
- Log.d("GoogleFitImporter", "Signing In")
+ Log.d(this.name, "Signing In")
GoogleSignIn.requestPermissions(
activity,
124887,
@@ -105,19 +75,6 @@ class GoogleFit() : Extension() {
return connectLiveData
}
-// private fun signIn(data: Data) {
-// if (isConnected()) {
-// startImport(data)
-// } else {
-// Log.d("GoogleFitImporter", "Signing In")
-// GoogleSignIn.requestPermissions(
-// activity,
-// data.ordinal,
-// getGoogleAccount(), fitnessOptions
-// )
-// }
-// }
-
private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
private val timeRange by lazy {
@@ -131,7 +88,7 @@ class GoogleFit() : Extension() {
return@lazy arrayOf(startTime, endTime)
}
- private fun startImport(data: Data) {
+ private fun startImport(data: Extension.Data) {
Log.d("GoogleFitImporter", "Importing for ${data.name}")
val dateFormat = DateFormat.getDateInstance()
@@ -142,7 +99,7 @@ class GoogleFit() : Extension() {
var timeUnit = TimeUnit.MILLISECONDS
when (data) {
- Data.STEPS -> {
+ Extension.Data.STEPS -> {
type = DataType.TYPE_STEP_COUNT_CUMULATIVE
}
else -> {}
@@ -157,7 +114,7 @@ class GoogleFit() : Extension() {
)
}
- private fun runRequest(request: DataReadRequest, data: Data) {
+ private fun runRequest(request: DataReadRequest, data: Extension.Data) {
Fitness.getHistoryClient(
activity,
GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
@@ -191,7 +148,7 @@ class GoogleFit() : Extension() {
for (field in dp.dataType.fields) {
Log.i(TAG, "\tField: ${field.name} Value: ${dp.getValue(field)}")
when (data) {
- Data.WEIGHT -> {
+ Extension.Data.WEIGHT -> {
val weight = Weight()
weight.timestamp = dp.getStartTime(TimeUnit.MILLISECONDS)
weight.weight = dp.getValue(field).asFloat()
@@ -200,17 +157,17 @@ class GoogleFit() : Extension() {
list.add(weight)
weightLiveData.value =
- ImportState(States.WIP, list)
+ Extension.ImportState(Extension.States.WIP, list)
}
else -> {}
}
}
}
when (data) {
- Data.WEIGHT -> {
+ Extension.Data.WEIGHT -> {
weightLiveData.value =
- ImportState(
- States.DONE,
+ Extension.ImportState(
+ Extension.States.DONE,
weightLiveData.value?.list
?: ArrayList()
)
@@ -224,39 +181,27 @@ class GoogleFit() : Extension() {
}
}
- /**
- * Currently not usable
- */
- override fun onRequestPermissionResult(
- requestCode: Int,
- permission: Array,
- grantResult: IntArray
- ) {
- connect()
- // signIn(Data.values()[requestCode])
- }
-
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ Log.d(this.name, "[$requestCode] -> [$resultCode]: $data")
if (requestCode == 0) {
return
}
- connectLiveData.value = States.DONE
+
+ if (resultCode == Activity.RESULT_OK) connectLiveData.value = Extension.States.DONE
// signIn(Data.values()[requestCode])
}
- private lateinit var weightLiveData: MutableLiveData>
+ private lateinit var weightLiveData: MutableLiveData>
- override fun importWeight(): LiveData> {
+ override fun importWeight(): LiveData> {
weightLiveData = MutableLiveData(
- ImportState(
- States.WIP
+ Extension.ImportState(
+ Extension.States.WIP
)
)
- startImport(Data.WEIGHT)
-
-// checkPermissionsAndRun(Data.WEIGHT)
+ startImport(Extension.Data.WEIGHT)
return weightLiveData
}
diff --git a/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt b/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt
index 9ee9bf3..1ed7fb1 100644
--- a/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt
+++ b/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt
@@ -1,10 +1,12 @@
package com.dzeio.openhealth.interfaces
+import android.app.NotificationManager
+
enum class NotificationChannels(
val id: String,
val channelName: String,
val importance: Int
) {
- // 3 is IMPORTANCE_DEFAULT
- WATER("water", "Water Notifications", 3)
+ WATER("water", "Water Notifications", NotificationManager.IMPORTANCE_DEFAULT),
+ SERVICE("service", "Open Health Service", NotificationManager.IMPORTANCE_MIN)
}
diff --git a/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationIds.kt b/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationIds.kt
index 8f9af97..8668f81 100644
--- a/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationIds.kt
+++ b/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationIds.kt
@@ -1,5 +1,10 @@
package com.dzeio.openhealth.interfaces
enum class NotificationIds {
- WaterIntake
+ WaterIntake,
+
+ /**
+ * Open Health Main Service Notification ID
+ */
+ Service
}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/services/OpenHealthService.kt b/app/src/main/java/com/dzeio/openhealth/services/OpenHealthService.kt
new file mode 100644
index 0000000..8ec23d3
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/services/OpenHealthService.kt
@@ -0,0 +1,132 @@
+package com.dzeio.openhealth.services
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import android.util.Log
+import android.widget.Toast
+import androidx.core.app.NotificationCompat
+import androidx.navigation.NavDeepLinkBuilder
+import com.dzeio.openhealth.Application
+import com.dzeio.openhealth.R
+import com.dzeio.openhealth.data.AppDatabase
+import com.dzeio.openhealth.data.step.Step
+import com.dzeio.openhealth.data.step.StepRepository
+import com.dzeio.openhealth.data.step.StepRepository_Factory
+import com.dzeio.openhealth.data.step.StepSource
+import com.dzeio.openhealth.interfaces.NotificationChannels
+import com.dzeio.openhealth.interfaces.NotificationIds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
+
+class OpenHealthService : Service() {
+
+ companion object {
+ private const val TAG = "${Application.TAG}/OpenHealthService"
+ }
+
+ val stepRepository: StepRepository
+ get() = StepRepository_Factory.newInstance(AppDatabase.getInstance(applicationContext).stepDao())
+
+ private var mNM: NotificationManager? = null
+
+ // Unique Identification Number for the Notification.
+ // We use it on Notification start, and to cancel it.
+ private val NOTIFICATION: Int = 8942
+
+ private val job = SupervisorJob()
+ private val scope = CoroutineScope(Dispatchers.IO + job)
+
+ override fun onCreate() {
+ mNM = getSystemService(NOTIFICATION_SERVICE) as NotificationManager?
+ }
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ Log.i("LocalService", "Received start id $startId: $intent")
+
+ scope.launch {
+ val source = StepSource(this@OpenHealthService)
+ source.events.receiveAsFlow().collectLatest {
+ Log.d(TAG, "Received value: $it")
+
+ if (it <= 0f) {
+ Log.d(TAG, "No new steps registered ($it)")
+ return@collectLatest
+ }
+ Log.d(TAG, "New steps registered: $it")
+ val step = withTimeoutOrNull(100) {
+ return@withTimeoutOrNull stepRepository.todayStep().firstOrNull()
+ }
+ Log.d(TAG, "stepRepository: $step")
+ if (step != null) {
+ step.value += it
+ stepRepository.updateStep(step)
+ } else {
+ stepRepository.addStep(Step(value = it))
+ }
+ Log.d(TAG, "Added step!")
+ }
+ }
+
+
+ // Display a notification about us starting. We put an icon in the status bar.
+
+ startForeground(NotificationIds.Service.ordinal, showNotification())
+
+ return START_STICKY
+ }
+
+ override fun onDestroy() {
+ stopForeground(true)
+
+
+
+ // Tell the user we stopped.
+ Toast.makeText(this, "Service stopped", Toast.LENGTH_SHORT).show()
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+
+ /**
+ * Show a notification while this service is running.
+ */
+ private fun showNotification(): Notification {
+ // The PendingIntent to launch our activity if the user selects this notification
+ val flag =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_IMMUTABLE else 0
+ val intent = NavDeepLinkBuilder(this)
+ .setGraph(R.navigation.mobile_navigation)
+ .setDestination(R.id.nav_home)
+ // Will nav to water home when there will be a way to add it there
+// .setDestination(R.id.nav_water_home)
+ .createTaskStackBuilder()
+ .getPendingIntent(0, flag)
+
+ // Set the info for the views that show in the notification panel.
+ val notification: Notification = NotificationCompat.Builder(this, NotificationChannels.SERVICE.id)
+ .setSmallIcon(R.drawable.ic_logo_small)
+// .setTicker("Pouet") // the status text
+ .setWhen(System.currentTimeMillis()) // the time stamp
+ .setContentTitle("Open Health Service") // the label of the entry
+ .setContentText("Watching for your steps") // the contents of the entry
+ .setContentIntent(intent) // The intent to send when the entry is clicked
+ .build()
+
+ // Send the notification.
+ mNM!!.notify(NotificationIds.Service.ordinal, notification)
+
+ return notification
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/services/StepCountService.kt b/app/src/main/java/com/dzeio/openhealth/services/StepCountService.kt
deleted file mode 100644
index 6c3c5a5..0000000
--- a/app/src/main/java/com/dzeio/openhealth/services/StepCountService.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.dzeio.openhealth.services
-
-import android.annotation.SuppressLint
-import android.app.job.JobParameters
-import android.app.job.JobService
-import android.content.Context
-import android.hardware.Sensor
-import android.hardware.SensorEvent
-import android.hardware.SensorEventListener
-import android.hardware.SensorManager
-import android.util.Log
-
-@SuppressLint("SpecifyJobSchedulerIdRange")
-class StepCountService : JobService(), SensorEventListener {
- override fun onStartJob(params: JobParameters?): Boolean {
- Log.d("StepCountService", "Service Started")
- val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
- val stepCountSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
- stepCountSensor.let {
- sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL)
- }
- return true
- }
-
- override fun onStopJob(params: JobParameters?): Boolean {
- Log.d("StepCountService", "Service Stopped :(")
- return true
- }
-
- override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
- Log.d("StepCountService", "Accuracy changed $sensor, $accuracy")
- }
-
- override fun onSensorChanged(event: SensorEvent?) {
- event?.let {
- Log.d("StepCountService", "Event Triggered: $it")
- it.values.firstOrNull()?.let { value ->
- Log.d("StepCountService", "Step Count $value")
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/MainActivity.kt b/app/src/main/java/com/dzeio/openhealth/ui/MainActivity.kt
similarity index 77%
rename from app/src/main/java/com/dzeio/openhealth/MainActivity.kt
rename to app/src/main/java/com/dzeio/openhealth/ui/MainActivity.kt
index e65f806..87e43f2 100644
--- a/app/src/main/java/com/dzeio/openhealth/MainActivity.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/MainActivity.kt
@@ -1,5 +1,6 @@
-package com.dzeio.openhealth
+package com.dzeio.openhealth.ui
+import android.app.ActivityManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
@@ -17,16 +18,22 @@ import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
-import androidx.work.WorkManager
+import com.dzeio.openhealth.Application
+import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseActivity
import com.dzeio.openhealth.databinding.ActivityMainBinding
import com.dzeio.openhealth.interfaces.NotificationChannels
-import com.dzeio.openhealth.services.WaterReminderService
+import com.dzeio.openhealth.services.OpenHealthService
+import com.dzeio.openhealth.workers.WaterReminderService
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : BaseActivity() {
+ companion object {
+ const val TAG = "${Application.TAG}/MainActivity"
+ }
+
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
@@ -57,21 +64,30 @@ class MainActivity : BaseActivity() {
binding.bottomNav.setupWithNavController(navController)
-// binding.bottomNav.setOnItemSelectedListener {
-// val currentFragment = supportFragmentManager.fragments.last()
-// // currentFragment.javaClass.canonicalName
+// registerForActivityResult(ActivityResultContracts.RequestPermission()) {
//
-// navController.
-//
-// false
// }
+// .launch(Manifest.permission.ACTIVITY_RECOGNITION)
createNotificationChannel()
// Services
- WorkManager.getInstance(this)
- .cancelAllWork()
WaterReminderService.setup(this)
+// StepCountService.setup(this)
+
+ this.betterStartService(OpenHealthService::class.java)
+ }
+
+ private fun betterStartService(service: Class) {
+ val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ for (runninService in activityManager.getRunningServices(Integer.MAX_VALUE)) {
+ if (service.name.equals(runninService.service.className)) {
+ Log.w(TAG, "Service already existing, not starting again")
+ return
+ }
+ }
+ Log.i(TAG, "Starting service ${service.name}")
+ Intent(this, service).also { intent -> startService(intent) }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -87,18 +103,6 @@ class MainActivity : BaseActivity() {
NavigationUI.onNavDestinationSelected(item, navController) ||
super.onOptionsItemSelected(item)
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray
- ) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- Log.d("MainActivity", "Result $requestCode")
- for (fragment in supportFragmentManager.primaryNavigationFragment!!.childFragmentManager.fragments) {
- fragment.onRequestPermissionsResult(requestCode, permissions, grantResults)
- }
- }
-
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
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 e1e29d9..932f42b 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
@@ -32,5 +32,9 @@ class BrowseFragment :
binding.waterIntake.setOnClickListener {
findNavController().navigate(BrowseFragmentDirections.actionNavBrowseToNavWaterHome())
}
+
+ binding.steps.setOnClickListener {
+ findNavController().navigate(BrowseFragmentDirections.actionNavBrowseToStepsHomeFragment())
+ }
}
}
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
index cdb3831..72386ca 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/extensions/ExtensionsFragment.kt
@@ -1,22 +1,22 @@
package com.dzeio.openhealth.ui.extensions
-import android.app.ProgressDialog
import android.content.Intent
-import android.content.pm.PackageManager
-import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.annotation.RequiresApi
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
+import com.dzeio.openhealth.R
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 com.dzeio.openhealth.extensions.GoogleFit
+import com.dzeio.openhealth.utils.PermissionsManager
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -32,6 +32,16 @@ class ExtensionsFragment :
private lateinit var activeExtension: Extension
+ private val activityResult = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { map ->
+ if (map.containsValue(false)) {
+ // TODO: Show a popup with choice to change it
+ Toast.makeText(requireContext(), R.string.permission_declined, Toast.LENGTH_LONG).show()
+ return@registerForActivityResult
+ }
+
+ extensionIsConnected(activeExtension)
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -43,20 +53,9 @@ class ExtensionsFragment :
val adapter = ExtensionAdapter()
adapter.onItemClick = {
activeExtension = it
- Log.d(it.id, it.name)
- if (it.isConnected()) {
- Log.d(it.id, "Continue!")
- findNavController().navigate(
- ExtensionsFragmentDirections.actionNavExtensionsToNavExtension(
- it.id
- )
- )
- } else {
- val ls = it.connect()
- ls.observe(viewLifecycleOwner) { st ->
- Log.d("States", st.name)
- }
- }
+ Log.d(TAG, "${it.id}: ${it.name}")
+
+ this.extensionPermissionsVerified(it)
}
recycler.adapter = adapter
@@ -71,56 +70,43 @@ class ExtensionsFragment :
adapter.set(list)
}
+ @Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
activeExtension.onActivityResult(requestCode, resultCode, data)
}
- @RequiresApi(Build.VERSION_CODES.O)
- 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.")
- }
+ private fun extensionPermissionsVerified(it: Extension) {
+ // Check for the extension permissions
+ if (it.permissions != null && !PermissionsManager.hasPermission(requireContext(), it.permissions!!)) {
+ // TODO: show popup explaining the permissions requested
- grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
- Log.d(TAG, "Granted")
- activeExtension.onRequestPermissionResult(requestCode, permissions, grantResults)
- }
- else -> {
- // Permission denied.
+ // show permissions
+ activityResult.launch(it.permissions)
+ return
+ }
+ extensionIsConnected(it)
+ }
- // 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.
+ private fun extensionIsConnected(it: Extension) {
+ // check if it is connected
+ if (it.isConnected()) {
+ gotoExtension(it)
+ return
+ }
- // 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()
+ // IDK: maybe give less liberty to the extension IDK
+ val ld = it.connect()
+ ld.observe(viewLifecycleOwner) { state ->
+ Log.d(TAG, state.toString())
+ if (state == Extension.States.DONE) {
+ gotoExtension(it)
}
}
}
+
+ private fun gotoExtension(it: Extension) {
+ findNavController().navigate(
+ ExtensionsFragmentDirections.actionNavExtensionsToNavExtension(it.id)
+ )
+ }
}
\ 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/home/HomeFragment.kt
index 8f9df22..1e33b0c 100644
--- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt
@@ -6,7 +6,6 @@ import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.RectF
import android.os.Bundle
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -24,9 +23,9 @@ import com.dzeio.openhealth.utils.DrawUtils
import com.dzeio.openhealth.utils.GraphUtils
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
-import kotlin.math.min
import kotlinx.coroutines.flow.collectLatest
import kotlin.math.max
+import kotlin.math.min
@AndroidEntryPoint
class HomeFragment : BaseFragment(HomeViewModel::class.java) {
@@ -204,7 +203,7 @@ class HomeFragment : BaseFragment(HomeViewMo
animator.addUpdateListener {
this.oldValue = 100 * it.animatedValue as Int / viewModel.dailyWaterIntake.toFloat()
- Log.d("Test2", "${this.oldValue}")
+// Log.d("Test2", "${this.oldValue}")
DrawUtils.drawArc(
canvas,
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeFragment.kt
new file mode 100644
index 0000000..b4a0734
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeFragment.kt
@@ -0,0 +1,79 @@
+package com.dzeio.openhealth.ui.steps
+
+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.adapters.StepsAdapter
+import com.dzeio.openhealth.core.BaseFragment
+import com.dzeio.openhealth.databinding.FragmentStepsHomeBinding
+import com.dzeio.openhealth.ui.water.StepsHomeViewModel
+import com.dzeio.openhealth.utils.GraphUtils
+import com.github.mikephil.charting.data.BarData
+import com.github.mikephil.charting.data.BarDataSet
+import com.github.mikephil.charting.data.BarEntry
+import com.google.android.material.color.MaterialColors
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class StepsHomeFragment :
+ BaseFragment(StepsHomeViewModel::class.java) {
+
+ override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentStepsHomeBinding =
+ FragmentStepsHomeBinding::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 = StepsAdapter()
+ adapter.onItemClick = {
+// findNavController().navigate(
+// WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterEdit(
+// it.id
+// )
+// )
+ }
+ recycler.adapter = adapter
+
+ val chart = binding.chart
+
+ GraphUtils.barChartSetup(
+ chart,
+ MaterialColors.getColor(
+ requireView(),
+ com.google.android.material.R.attr.colorPrimary
+ ),
+ MaterialColors.getColor(
+ requireView(),
+ com.google.android.material.R.attr.colorOnBackground
+ )
+ )
+
+ chart.xAxis.valueFormatter = GraphUtils.DateValueFormatter(1000 * 60 * 60)
+
+ viewModel.items.observe(viewLifecycleOwner) { list ->
+ adapter.set(list)
+
+ val dataset = BarDataSet(
+ list.map {
+ return@map BarEntry(
+ (it.timestamp / 60 / 60 / 24).toFloat(),
+ it.value
+ )
+ },
+ ""
+ )
+
+ chart.data = BarData(dataset)
+ chart.invalidate()
+ }
+ }
+}
diff --git a/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeViewModel.kt
new file mode 100644
index 0000000..276a4c2
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/ui/steps/StepsHomeViewModel.kt
@@ -0,0 +1,26 @@
+package com.dzeio.openhealth.ui.water
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import com.dzeio.openhealth.core.BaseViewModel
+import com.dzeio.openhealth.data.step.Step
+import com.dzeio.openhealth.data.step.StepRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class StepsHomeViewModel@Inject internal constructor(
+ private val stepRepository: StepRepository
+) : BaseViewModel() {
+ val items: MutableLiveData> = MutableLiveData()
+
+ fun init() {
+ viewModelScope.launch {
+ stepRepository.getSteps().collectLatest {
+ items.postValue(it)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/utils/PermissionsManager.kt b/app/src/main/java/com/dzeio/openhealth/utils/PermissionsManager.kt
new file mode 100644
index 0000000..0014a1e
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/utils/PermissionsManager.kt
@@ -0,0 +1,20 @@
+package com.dzeio.openhealth.utils
+
+import android.content.Context
+import android.content.pm.PackageManager
+
+object PermissionsManager {
+
+ fun hasPermission(context: Context, permission: String): Boolean = context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
+
+ fun hasPermission(context: Context, permissions: Array): Boolean {
+ for (permission in permissions) {
+ val res = hasPermission(context, permission)
+ if (!res) {
+ return false
+ }
+ }
+ return true
+ }
+}
+
diff --git a/app/src/main/java/com/dzeio/openhealth/workers/StepCountService.kt b/app/src/main/java/com/dzeio/openhealth/workers/StepCountService.kt
new file mode 100644
index 0000000..b529d28
--- /dev/null
+++ b/app/src/main/java/com/dzeio/openhealth/workers/StepCountService.kt
@@ -0,0 +1,67 @@
+package com.dzeio.openhealth.workers
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.Log
+import androidx.work.CoroutineWorker
+import androidx.work.PeriodicWorkRequest
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkerParameters
+import com.dzeio.openhealth.Application
+import com.dzeio.openhealth.core.BaseService
+import com.dzeio.openhealth.data.AppDatabase
+import com.dzeio.openhealth.data.step.Step
+import com.dzeio.openhealth.data.step.StepRepository
+import com.dzeio.openhealth.data.step.StepSource
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.withTimeoutOrNull
+import java.util.concurrent.TimeUnit
+
+@SuppressLint("SpecifyJobSchedulerIdRange")
+class StepCountService(
+ private val context: Context,
+ params: WorkerParameters
+) : CoroutineWorker(context, params) {
+
+
+ companion object {
+ const val TAG = "${Application.TAG}/StepCountService"
+
+ fun setup(context: Context) {
+ BaseService.schedule(
+ TAG,
+ PeriodicWorkRequestBuilder(PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS)
+ .addTag(TAG)
+ .build(),
+ context
+ )
+ }
+ }
+
+ override suspend fun doWork(): Result {
+ Log.d(TAG, "Service Started")
+ val appDatabase = AppDatabase.getInstance(this.context)
+ val repo = StepRepository(appDatabase.stepDao())
+
+ val source = StepSource(this.context)
+
+ val value = withTimeoutOrNull(10000) {
+ source.events.receive()
+ }
+ if (value == null || value == 0f) {
+ Log.d(TAG, "No new steps registered ($value)")
+ return Result.success()
+ }
+ Log.d(TAG, "New steps registered: $value")
+ coroutineContext
+ val step = repo.todayStep().first()
+ if (step != null) {
+ step.value += value
+ repo.updateStep(step)
+ } else {
+ repo.addStep(Step(value = value))
+ }
+
+ return Result.success()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dzeio/openhealth/services/WaterReminderService.kt b/app/src/main/java/com/dzeio/openhealth/workers/WaterReminderService.kt
similarity index 95%
rename from app/src/main/java/com/dzeio/openhealth/services/WaterReminderService.kt
rename to app/src/main/java/com/dzeio/openhealth/workers/WaterReminderService.kt
index 97ce28d..3466bdc 100644
--- a/app/src/main/java/com/dzeio/openhealth/services/WaterReminderService.kt
+++ b/app/src/main/java/com/dzeio/openhealth/workers/WaterReminderService.kt
@@ -1,9 +1,8 @@
-package com.dzeio.openhealth.services
+package com.dzeio.openhealth.workers
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
-import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
@@ -12,12 +11,11 @@ import androidx.navigation.NavDeepLinkBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkerParameters
import com.dzeio.openhealth.Application
-import com.dzeio.openhealth.MainActivity
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseService
import com.dzeio.openhealth.interfaces.NotificationChannels
import com.dzeio.openhealth.interfaces.NotificationIds
-import java.util.*
+import java.util.Date
import java.util.concurrent.TimeUnit
class WaterReminderService(
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index a6ac584..eedec14 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -14,7 +14,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- tools:context=".MainActivity">
+ tools:context=".ui.MainActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
index ee80a64..8043ba8 100644
--- a/app/src/main/res/navigation/mobile_navigation.xml
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -168,6 +168,9 @@
+
@@ -176,4 +179,9 @@
android:name="com.dzeio.openhealth.ui.activity.ActivityFragment"
android:label="@string/menu_activity"
tools:layout="@layout/fragment_activity" />
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 8d4c2fe..d7a4dcd 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -37,5 +37,7 @@
Activity
Ajouter un objectif
Modifier l\'objectif
+ Modifier le but journalier
+ Vous avez décliné une permission, vous ne pouvez pas utiliser cette extension suaf si vous réactivez la permission manuellement
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bc3235a..0e41f7a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -48,4 +48,7 @@
Add Goal
Modify Goal
+ You declined a permission, you can\'t use this extension unless you enable it manually
+ Modifiy daily goal
+ Steps