mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-23 11:22:10 +00:00
fix: Steps Servicevnotvworking as intended
This commit is contained in:
parent
c4c6e45687
commit
375bad46a7
@ -46,8 +46,7 @@
|
||||
|
||||
<service
|
||||
android:name=".services.OpenHealthService"
|
||||
android:permission="android.permission.ACTIVITY_RECOGNITION"
|
||||
android:exported="false"/>
|
||||
android:permission="android.permission.ACTIVITY_RECOGNITION" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -19,7 +19,7 @@ class StepsAdapter() : BaseAdapter<Step, LayoutItemListBinding>() {
|
||||
item: Step,
|
||||
position: Int
|
||||
) {
|
||||
holder.binding.value.text = "${item.value}ml"
|
||||
holder.binding.value.text = "${item.value}steps"
|
||||
holder.binding.datetime.text = item.formatTimestamp()
|
||||
holder.binding.edit.setOnClickListener {
|
||||
onItemClick?.invoke(item)
|
||||
|
@ -3,21 +3,22 @@ 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.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
@Entity()
|
||||
data class Step(
|
||||
@PrimaryKey(autoGenerate = true) var id: Long = 0,
|
||||
var value: Float = 0f,
|
||||
@ColumnInfo(index = true)
|
||||
var value: Int = 0,
|
||||
/**
|
||||
* Timestamp down to an hour
|
||||
*
|
||||
* ex: 2022-09-22 10:00:00
|
||||
*/
|
||||
@ColumnInfo(index = true)
|
||||
var timestamp: Long = 0,
|
||||
var source: String = "OpenHealth"
|
||||
) {
|
||||
@ -33,9 +34,30 @@ data class Step(
|
||||
}
|
||||
}
|
||||
|
||||
fun formatTimestamp(): String = DateFormat.getDateInstance().format(Date(timestamp))
|
||||
fun formatTimestamp(): String {
|
||||
val formatter = DateFormat.getDateTimeInstance(
|
||||
DateFormat.SHORT,
|
||||
DateFormat.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
return formatter.format(Date(this.timestamp))
|
||||
}
|
||||
|
||||
fun isToday(): Boolean {
|
||||
val it = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
it.timeInMillis = timestamp
|
||||
it.set(Calendar.HOUR, 0)
|
||||
|
||||
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
|
||||
cal.set(Calendar.HOUR, 0)
|
||||
cal.set(Calendar.MINUTE, 0)
|
||||
cal.set(Calendar.SECOND, 0)
|
||||
cal.set(Calendar.MILLISECOND, 0)
|
||||
return it.timeInMillis == cal.timeInMillis
|
||||
}
|
||||
|
||||
fun isCurrent(): Boolean {
|
||||
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
cal.set(Calendar.MINUTE, 0)
|
||||
cal.set(Calendar.SECOND, 0)
|
||||
|
@ -1,9 +1,7 @@
|
||||
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
|
||||
|
||||
@ -24,12 +22,4 @@ interface StepDao : BaseDao<Step> {
|
||||
|
||||
@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?
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package com.dzeio.openhealth.data.step
|
||||
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -20,7 +22,22 @@ class StepRepository @Inject constructor(
|
||||
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()
|
||||
suspend fun todaySteps(): Int {
|
||||
val steps = getSteps().firstOrNull()
|
||||
if (steps == null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
var total = 0
|
||||
for (item in steps) {
|
||||
total += item.value
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
fun currentStep() = lastStep().filter {
|
||||
return@filter it != null && it.isCurrent()
|
||||
}
|
||||
|
||||
}
|
@ -32,18 +32,30 @@ import kotlinx.coroutines.withTimeoutOrNull
|
||||
class OpenHealthService : Service() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "${Application.TAG}/OpenHealthService"
|
||||
private const val TAG = "${Application.TAG}/Service"
|
||||
}
|
||||
|
||||
val stepRepository: StepRepository
|
||||
get() = StepRepository_Factory.newInstance(AppDatabase.getInstance(applicationContext).stepDao())
|
||||
private val stepRepository: StepRepository
|
||||
get() = StepRepository_Factory.newInstance(
|
||||
AppDatabase.getInstance(applicationContext).stepDao()
|
||||
)
|
||||
|
||||
private var mNM: NotificationManager? = null
|
||||
private lateinit var notification: Notification
|
||||
|
||||
// Unique Identification Number for the Notification.
|
||||
// We use it on Notification start, and to cancel it.
|
||||
private val NOTIFICATION: Int = 8942
|
||||
/**
|
||||
* Displayed steps in the notification panel
|
||||
*/
|
||||
private var stepsTaken = 0u
|
||||
|
||||
/**
|
||||
* Need a buffer because sometime it does not complete the function
|
||||
*/
|
||||
private var stepsBuffer = 0
|
||||
|
||||
/**
|
||||
* Coroutine shit
|
||||
*/
|
||||
private val job = SupervisorJob()
|
||||
private val scope = CoroutineScope(Dispatchers.IO + job)
|
||||
|
||||
@ -54,8 +66,8 @@ class OpenHealthService : Service() {
|
||||
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)
|
||||
scope.launch {
|
||||
val source = StepSource(this@OpenHealthService)
|
||||
source.events.receiveAsFlow().collectLatest {
|
||||
Log.d(TAG, "Received value: $it")
|
||||
|
||||
@ -64,21 +76,24 @@ class OpenHealthService : Service() {
|
||||
return@collectLatest
|
||||
}
|
||||
Log.d(TAG, "New steps registered: $it")
|
||||
val step = withTimeoutOrNull(100) {
|
||||
return@withTimeoutOrNull stepRepository.todayStep().firstOrNull()
|
||||
stepsTaken += it.toUInt()
|
||||
stepsBuffer += it.toInt()
|
||||
showNotification()
|
||||
val step = withTimeoutOrNull(1000) {
|
||||
return@withTimeoutOrNull stepRepository.currentStep().firstOrNull()
|
||||
}
|
||||
Log.d(TAG, "stepRepository: $step")
|
||||
if (step != null) {
|
||||
step.value += it
|
||||
step.value += stepsBuffer
|
||||
stepRepository.updateStep(step)
|
||||
} else {
|
||||
stepRepository.addStep(Step(value = it))
|
||||
stepRepository.addStep(Step(value = stepsBuffer))
|
||||
}
|
||||
stepsBuffer = 0
|
||||
Log.d(TAG, "Added step!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Display a notification about us starting. We put an icon in the status bar.
|
||||
|
||||
startForeground(NotificationIds.Service.ordinal, showNotification())
|
||||
@ -89,10 +104,10 @@ class OpenHealthService : Service() {
|
||||
override fun onDestroy() {
|
||||
stopForeground(true)
|
||||
|
||||
|
||||
|
||||
// Tell the user we stopped.
|
||||
Toast.makeText(this, "Service stopped", Toast.LENGTH_SHORT).show()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
@ -115,12 +130,15 @@ class OpenHealthService : Service() {
|
||||
.getPendingIntent(0, flag)
|
||||
|
||||
// Set the info for the views that show in the notification panel.
|
||||
val notification: Notification = NotificationCompat.Builder(this, NotificationChannels.SERVICE.id)
|
||||
this.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
|
||||
.setContentText("Watching for your steps ($stepsTaken)") // the contents of the entry
|
||||
.setContentIntent(intent) // The intent to send when the entry is clicked
|
||||
.build()
|
||||
|
||||
|
@ -1,14 +1,21 @@
|
||||
package com.dzeio.openhealth.ui.browse
|
||||
|
||||
import android.Manifest
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.dzeio.openhealth.R
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
import com.dzeio.openhealth.databinding.FragmentBrowseBinding
|
||||
import com.dzeio.openhealth.utils.PermissionsManager
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -22,6 +29,19 @@ class BrowseFragment :
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
}
|
||||
|
||||
private lateinit var button: MaterialCardView
|
||||
private val activityResult = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) {
|
||||
if (!it) {
|
||||
// TODO: Show a popup with choice to change it
|
||||
Toast.makeText(requireContext(), R.string.permission_declined, Toast.LENGTH_LONG).show()
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
button.callOnClick()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@ -34,7 +54,25 @@ class BrowseFragment :
|
||||
}
|
||||
|
||||
binding.steps.setOnClickListener {
|
||||
findNavController().navigate(BrowseFragmentDirections.actionNavBrowseToStepsHomeFragment())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val hasPermission = PermissionsManager.hasPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.ACTIVITY_RECOGNITION
|
||||
)
|
||||
|
||||
if (!hasPermission) {
|
||||
activityResult.launch(Manifest.permission.ACTIVITY_RECOGNITION)
|
||||
return@setOnClickListener
|
||||
}
|
||||
}
|
||||
findNavController().navigate(
|
||||
BrowseFragmentDirections.actionNavBrowseToStepsHomeFragment()
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.steps.observe(viewLifecycleOwner) {
|
||||
binding.stepsText.setText("$it of xxx steps")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,25 @@
|
||||
package com.dzeio.openhealth.ui.browse
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.data.step.StepRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class BrowseViewModel @Inject internal constructor() : BaseViewModel()
|
||||
class BrowseViewModel @Inject internal constructor(
|
||||
stepRepository: StepRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _steps = MutableLiveData(0)
|
||||
val steps: LiveData<Int> = _steps
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
_steps.postValue(stepRepository.todaySteps())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ class ExtensionsFragment :
|
||||
|
||||
private lateinit var activeExtension: Extension
|
||||
|
||||
private val activityResult = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { map ->
|
||||
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()
|
||||
@ -77,7 +79,11 @@ class ExtensionsFragment :
|
||||
|
||||
private fun extensionPermissionsVerified(it: Extension) {
|
||||
// Check for the extension permissions
|
||||
if (it.permissions != null && !PermissionsManager.hasPermission(requireContext(), it.permissions!!)) {
|
||||
if (it.permissions != null && !PermissionsManager.hasPermission(
|
||||
requireContext(),
|
||||
it.permissions!!
|
||||
)
|
||||
) {
|
||||
// TODO: show popup explaining the permissions requested
|
||||
|
||||
// show permissions
|
||||
|
@ -57,7 +57,7 @@ class StepsHomeFragment :
|
||||
)
|
||||
)
|
||||
|
||||
chart.xAxis.valueFormatter = GraphUtils.DateValueFormatter(1000 * 60 * 60)
|
||||
chart.xAxis.valueFormatter = GraphUtils.DateTimeValueFormatter()
|
||||
|
||||
viewModel.items.observe(viewLifecycleOwner) { list ->
|
||||
adapter.set(list)
|
||||
@ -65,8 +65,8 @@ class StepsHomeFragment :
|
||||
val dataset = BarDataSet(
|
||||
list.map {
|
||||
return@map BarEntry(
|
||||
(it.timestamp / 60 / 60 / 24).toFloat(),
|
||||
it.value
|
||||
(it.timestamp).toFloat(),
|
||||
it.value.toFloat()
|
||||
)
|
||||
},
|
||||
""
|
||||
|
@ -39,9 +39,7 @@ object GraphUtils {
|
||||
mainColor: Int,
|
||||
textColor: Int
|
||||
) {
|
||||
|
||||
chart.apply {
|
||||
|
||||
// Setup
|
||||
legend.isEnabled = true
|
||||
description = Description().apply { isEnabled = false }
|
||||
@ -60,6 +58,7 @@ object GraphUtils {
|
||||
}
|
||||
|
||||
axisLeft.apply {
|
||||
axisMinimum = 0f
|
||||
isEnabled = false
|
||||
axisLineColor = mainColor
|
||||
this.textColor = textColor
|
||||
@ -69,6 +68,7 @@ object GraphUtils {
|
||||
setDrawBorders(false)
|
||||
}
|
||||
axisRight.apply {
|
||||
axisMinimum = 0f
|
||||
this.textColor = textColor
|
||||
setLabelCount(4, true)
|
||||
}
|
||||
@ -96,4 +96,16 @@ object GraphUtils {
|
||||
// return super.getAxisLabel(value, axis)
|
||||
}
|
||||
}
|
||||
|
||||
class DateTimeValueFormatter(
|
||||
private val transformer: Int = 1
|
||||
) : ValueFormatter() {
|
||||
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
|
||||
return SimpleDateFormat(
|
||||
"yyyy-MM-dd hh",
|
||||
Locale.getDefault()
|
||||
).format(Date(value.toLong() * transformer))
|
||||
// return super.getAxisLabel(value, axis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +0,0 @@
|
||||
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<StepCountService>(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()
|
||||
}
|
||||
}
|
@ -73,6 +73,7 @@
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/steps_text"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="xxxx of xxxx steps" />
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user