1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-23 03:12:14 +00:00

fix: Steps Servicevnotvworking as intended

This commit is contained in:
Florian Bouillon 2022-07-19 00:29:15 +02:00
parent c4c6e45687
commit 375bad46a7
Signed by: Florian Bouillon
GPG Key ID: 0A288052C94BD2C8
13 changed files with 169 additions and 116 deletions

View File

@ -46,8 +46,7 @@
<service <service
android:name=".services.OpenHealthService" android:name=".services.OpenHealthService"
android:permission="android.permission.ACTIVITY_RECOGNITION" android:permission="android.permission.ACTIVITY_RECOGNITION" />
android:exported="false"/>
</application> </application>
</manifest> </manifest>

View File

@ -19,7 +19,7 @@ class StepsAdapter() : BaseAdapter<Step, LayoutItemListBinding>() {
item: Step, item: Step,
position: Int position: Int
) { ) {
holder.binding.value.text = "${item.value}ml" holder.binding.value.text = "${item.value}steps"
holder.binding.datetime.text = item.formatTimestamp() holder.binding.datetime.text = item.formatTimestamp()
holder.binding.edit.setOnClickListener { holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item) onItemClick?.invoke(item)

View File

@ -3,21 +3,22 @@ package com.dzeio.openhealth.data.step
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.sql.Date
import java.text.DateFormat import java.text.DateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
@Entity() @Entity()
data class Step( data class Step(
@PrimaryKey(autoGenerate = true) var id: Long = 0, @PrimaryKey(autoGenerate = true) var id: Long = 0,
var value: Float = 0f, var value: Int = 0,
@ColumnInfo(index = true)
/** /**
* Timestamp down to an hour * Timestamp down to an hour
* *
* ex: 2022-09-22 10:00:00 * ex: 2022-09-22 10:00:00
*/ */
@ColumnInfo(index = true)
var timestamp: Long = 0, var timestamp: Long = 0,
var source: String = "OpenHealth" 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 { 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")) val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
cal.set(Calendar.MINUTE, 0) cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0) cal.set(Calendar.SECOND, 0)

View File

@ -1,9 +1,7 @@
package com.dzeio.openhealth.data.step package com.dzeio.openhealth.data.step
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update
import com.dzeio.openhealth.core.BaseDao import com.dzeio.openhealth.core.BaseDao
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -24,12 +22,4 @@ interface StepDao : BaseDao<Step> {
@Query("DELETE FROM Step where source = :source") @Query("DELETE FROM Step where source = :source")
suspend fun deleteFromSource(source: String) 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?
} }

View File

@ -1,6 +1,8 @@
package com.dzeio.openhealth.data.step package com.dzeio.openhealth.data.step
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withTimeoutOrNull
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -20,7 +22,22 @@ class StepRepository @Inject constructor(
suspend fun deleteStep(value: Step) = stepDao.delete(value) suspend fun deleteStep(value: Step) = stepDao.delete(value)
suspend fun deleteFromSource(value: String) = stepDao.deleteFromSource(value) suspend fun deleteFromSource(value: String) = stepDao.deleteFromSource(value)
fun todayStep() = lastStep().filter { suspend fun todaySteps(): Int {
return@filter it != null && it.isToday() 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()
}
} }

View File

@ -32,18 +32,30 @@ import kotlinx.coroutines.withTimeoutOrNull
class OpenHealthService : Service() { class OpenHealthService : Service() {
companion object { companion object {
private const val TAG = "${Application.TAG}/OpenHealthService" private const val TAG = "${Application.TAG}/Service"
} }
val stepRepository: StepRepository private val stepRepository: StepRepository
get() = StepRepository_Factory.newInstance(AppDatabase.getInstance(applicationContext).stepDao()) get() = StepRepository_Factory.newInstance(
AppDatabase.getInstance(applicationContext).stepDao()
)
private var mNM: NotificationManager? = null 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. * Displayed steps in the notification panel
private val NOTIFICATION: Int = 8942 */
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 job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job) private val scope = CoroutineScope(Dispatchers.IO + job)
@ -54,8 +66,8 @@ class OpenHealthService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.i("LocalService", "Received start id $startId: $intent") Log.i("LocalService", "Received start id $startId: $intent")
scope.launch { scope.launch {
val source = StepSource(this@OpenHealthService) val source = StepSource(this@OpenHealthService)
source.events.receiveAsFlow().collectLatest { source.events.receiveAsFlow().collectLatest {
Log.d(TAG, "Received value: $it") Log.d(TAG, "Received value: $it")
@ -64,21 +76,24 @@ class OpenHealthService : Service() {
return@collectLatest return@collectLatest
} }
Log.d(TAG, "New steps registered: $it") Log.d(TAG, "New steps registered: $it")
val step = withTimeoutOrNull(100) { stepsTaken += it.toUInt()
return@withTimeoutOrNull stepRepository.todayStep().firstOrNull() stepsBuffer += it.toInt()
showNotification()
val step = withTimeoutOrNull(1000) {
return@withTimeoutOrNull stepRepository.currentStep().firstOrNull()
} }
Log.d(TAG, "stepRepository: $step") Log.d(TAG, "stepRepository: $step")
if (step != null) { if (step != null) {
step.value += it step.value += stepsBuffer
stepRepository.updateStep(step) stepRepository.updateStep(step)
} else { } else {
stepRepository.addStep(Step(value = it)) stepRepository.addStep(Step(value = stepsBuffer))
} }
stepsBuffer = 0
Log.d(TAG, "Added step!") Log.d(TAG, "Added step!")
} }
} }
// Display a notification about us starting. We put an icon in the status bar. // Display a notification about us starting. We put an icon in the status bar.
startForeground(NotificationIds.Service.ordinal, showNotification()) startForeground(NotificationIds.Service.ordinal, showNotification())
@ -89,10 +104,10 @@ class OpenHealthService : Service() {
override fun onDestroy() { override fun onDestroy() {
stopForeground(true) stopForeground(true)
// Tell the user we stopped. // Tell the user we stopped.
Toast.makeText(this, "Service stopped", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Service stopped", Toast.LENGTH_SHORT).show()
super.onDestroy()
} }
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
@ -115,12 +130,15 @@ class OpenHealthService : Service() {
.getPendingIntent(0, flag) .getPendingIntent(0, flag)
// Set the info for the views that show in the notification panel. // 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) .setSmallIcon(R.drawable.ic_logo_small)
// .setTicker("Pouet") // the status text // .setTicker("Pouet") // the status text
.setWhen(System.currentTimeMillis()) // the time stamp .setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle("Open Health Service") // the label of the entry .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 .setContentIntent(intent) // The intent to send when the entry is clicked
.build() .build()

View File

@ -1,14 +1,21 @@
package com.dzeio.openhealth.ui.browse package com.dzeio.openhealth.ui.browse
import android.Manifest
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentBrowseBinding import com.dzeio.openhealth.databinding.FragmentBrowseBinding
import com.dzeio.openhealth.utils.PermissionsManager
import com.google.android.material.card.MaterialCardView
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@ -22,6 +29,19 @@ class BrowseFragment :
PreferenceManager.getDefaultSharedPreferences(requireContext()) 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -34,7 +54,25 @@ class BrowseFragment :
} }
binding.steps.setOnClickListener { 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")
}
} }
} }

View File

@ -1,8 +1,25 @@
package com.dzeio.openhealth.ui.browse 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.core.BaseViewModel
import com.dzeio.openhealth.data.step.StepRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @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())
}
}
}

View File

@ -32,7 +32,9 @@ class ExtensionsFragment :
private lateinit var activeExtension: Extension private lateinit var activeExtension: Extension
private val activityResult = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { map -> private val activityResult = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { map ->
if (map.containsValue(false)) { if (map.containsValue(false)) {
// TODO: Show a popup with choice to change it // TODO: Show a popup with choice to change it
Toast.makeText(requireContext(), R.string.permission_declined, Toast.LENGTH_LONG).show() Toast.makeText(requireContext(), R.string.permission_declined, Toast.LENGTH_LONG).show()
@ -77,7 +79,11 @@ class ExtensionsFragment :
private fun extensionPermissionsVerified(it: Extension) { private fun extensionPermissionsVerified(it: Extension) {
// Check for the extension permissions // 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 // TODO: show popup explaining the permissions requested
// show permissions // show permissions

View File

@ -57,7 +57,7 @@ class StepsHomeFragment :
) )
) )
chart.xAxis.valueFormatter = GraphUtils.DateValueFormatter(1000 * 60 * 60) chart.xAxis.valueFormatter = GraphUtils.DateTimeValueFormatter()
viewModel.items.observe(viewLifecycleOwner) { list -> viewModel.items.observe(viewLifecycleOwner) { list ->
adapter.set(list) adapter.set(list)
@ -65,8 +65,8 @@ class StepsHomeFragment :
val dataset = BarDataSet( val dataset = BarDataSet(
list.map { list.map {
return@map BarEntry( return@map BarEntry(
(it.timestamp / 60 / 60 / 24).toFloat(), (it.timestamp).toFloat(),
it.value it.value.toFloat()
) )
}, },
"" ""

View File

@ -39,9 +39,7 @@ object GraphUtils {
mainColor: Int, mainColor: Int,
textColor: Int textColor: Int
) { ) {
chart.apply { chart.apply {
// Setup // Setup
legend.isEnabled = true legend.isEnabled = true
description = Description().apply { isEnabled = false } description = Description().apply { isEnabled = false }
@ -60,6 +58,7 @@ object GraphUtils {
} }
axisLeft.apply { axisLeft.apply {
axisMinimum = 0f
isEnabled = false isEnabled = false
axisLineColor = mainColor axisLineColor = mainColor
this.textColor = textColor this.textColor = textColor
@ -69,6 +68,7 @@ object GraphUtils {
setDrawBorders(false) setDrawBorders(false)
} }
axisRight.apply { axisRight.apply {
axisMinimum = 0f
this.textColor = textColor this.textColor = textColor
setLabelCount(4, true) setLabelCount(4, true)
} }
@ -96,4 +96,16 @@ object GraphUtils {
// return super.getAxisLabel(value, axis) // 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)
}
}
} }

View File

@ -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()
}
}

View File

@ -73,6 +73,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:id="@+id/steps_text"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="xxxx of xxxx steps" /> android:text="xxxx of xxxx steps" />