mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-23 03:12:14 +00:00
feat: add back extensions
This commit is contained in:
parent
fd645f7e67
commit
c59481546f
143
app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt
Normal file
143
app/src/main/java/com/dzeio/openhealth/extensions/Extension.kt
Normal file
@ -0,0 +1,143 @@
|
||||
package com.dzeio.openhealth.extensions
|
||||
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Extension Schema
|
||||
*
|
||||
* Version: 0.2.0
|
||||
*/
|
||||
interface Extension : ActivityResultCallback<Any> {
|
||||
|
||||
data class TaskProgress<T>(
|
||||
/**
|
||||
* value indicating the current status of the task
|
||||
*/
|
||||
val state: TaskState = TaskState.INITIALIZATING,
|
||||
|
||||
/**
|
||||
* value between 0 and 100 indicating the progress for the task
|
||||
*/
|
||||
val progress: Float? = null,
|
||||
|
||||
/**
|
||||
* Additionnal message that will be displayed when the task has ended in a [TaskState.CANCELLED] or [TaskState.ERROR] state
|
||||
*/
|
||||
val statusMessage: String? = null,
|
||||
|
||||
/**
|
||||
* Additional information
|
||||
*/
|
||||
val additionalData: T? = null
|
||||
)
|
||||
|
||||
enum class TaskState {
|
||||
/**
|
||||
* define the task as being preped
|
||||
*/
|
||||
INITIALIZATING,
|
||||
|
||||
/**
|
||||
* Define the task a bein worked on
|
||||
*/
|
||||
WORK_IN_PROGRESS,
|
||||
|
||||
/**
|
||||
* define the task as being done
|
||||
*/
|
||||
DONE,
|
||||
|
||||
/**
|
||||
* Define the task as being cancelled
|
||||
*/
|
||||
CANCELLED,
|
||||
|
||||
/**
|
||||
* define the task as being ended with an error
|
||||
*/
|
||||
ERROR
|
||||
}
|
||||
|
||||
enum class Data {
|
||||
/**
|
||||
* Special case to handle basic errors from other activities
|
||||
*/
|
||||
NOTHING,
|
||||
WEIGHT,
|
||||
STEPS
|
||||
|
||||
/**
|
||||
* Google Fit:
|
||||
*
|
||||
* STEP_COUNT_CUMULATIVE
|
||||
* ACTIVITY_SEGMENT
|
||||
* SLEEP_SEGMENT
|
||||
* CALORIES_EXPENDED
|
||||
* BASAL_METABOLIC_RATE
|
||||
* POWER_SAMPLE
|
||||
* HEART_RATE_BPM
|
||||
* LOCATION_SAMPLE
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* the permissions necessary for the extension to works
|
||||
*/
|
||||
val permissions: Array<String>
|
||||
|
||||
/**
|
||||
* the Source ID
|
||||
*
|
||||
* DO NOT CHANGE IT AFTER THE EXTENSION IS IN PRODUCTION
|
||||
*/
|
||||
val id: String
|
||||
|
||||
/**
|
||||
* The Extension Display Name
|
||||
*/
|
||||
val name: String
|
||||
|
||||
/**
|
||||
* the different types of data handled by the extension
|
||||
*/
|
||||
val data: Array<Data>
|
||||
|
||||
/**
|
||||
* Enable the extension, no code is gonna be run before
|
||||
*/
|
||||
fun enable(fragment: Fragment): Boolean
|
||||
|
||||
/**
|
||||
* return if the extension is already connected to remote of not
|
||||
*/
|
||||
suspend fun isConnected(): Boolean
|
||||
|
||||
/**
|
||||
* Return if the extension is runnable on the device
|
||||
*/
|
||||
fun isAvailable(): Boolean
|
||||
|
||||
/**
|
||||
* try to connect to remote
|
||||
*/
|
||||
suspend fun connect(): Boolean
|
||||
|
||||
val contract: ActivityResultContract<*, *>?
|
||||
val requestInput: Any?
|
||||
suspend fun importWeight(): Flow<TaskProgress<ArrayList<Weight>>>
|
||||
|
||||
/**
|
||||
* function run when outgoing sync is enabled and new value is added
|
||||
* or manual export is launched
|
||||
*/
|
||||
suspend fun exportWeights(weight: Array<Weight>): Flow<TaskProgress<Unit>>
|
||||
|
||||
// fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) = Unit
|
||||
|
||||
|
||||
suspend fun permissionsGranted(): Boolean
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.dzeio.openhealth.extensions
|
||||
|
||||
import android.os.Build
|
||||
|
||||
class ExtensionFactory {
|
||||
companion object {
|
||||
fun getExtension(extension: String): Extension? {
|
||||
return when (extension) {
|
||||
"GoogleFit" -> {
|
||||
GoogleFitExtension()
|
||||
}
|
||||
"HealthConnect" -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
HealthConnectExtension()
|
||||
} else {
|
||||
TODO("VERSION.SDK_INT < P")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAll(): ArrayList<Extension> {
|
||||
val extensions: ArrayList<Extension> = arrayListOf(
|
||||
GoogleFitExtension()
|
||||
)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
extensions.add(HealthConnectExtension())
|
||||
}
|
||||
|
||||
return extensions
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.dzeio.openhealth.ui.extensions
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.dzeio.openhealth.adapters.ExtensionAdapter
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
import com.dzeio.openhealth.databinding.FragmentExtensionsBinding
|
||||
import com.dzeio.openhealth.extensions.Extension
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ExtensionsFragment :
|
||||
BaseFragment<ExtensionsViewModel, FragmentExtensionsBinding>(ExtensionsViewModel::class.java) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "ExtensionsFragment"
|
||||
}
|
||||
|
||||
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionsBinding =
|
||||
FragmentExtensionsBinding::inflate
|
||||
|
||||
private lateinit var activeExtension: Extension
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val recycler = binding.list
|
||||
|
||||
val manager = LinearLayoutManager(requireContext())
|
||||
recycler.layoutManager = manager
|
||||
|
||||
val adapter = ExtensionAdapter(viewModel.config)
|
||||
adapter.onItemClick = {
|
||||
activeExtension = it
|
||||
activeExtension.enable(this)
|
||||
Log.d(TAG, "${it.id}: ${it.name}")
|
||||
|
||||
lifecycleScope.launch {
|
||||
extensionIsConnected(it)
|
||||
}
|
||||
}
|
||||
recycler.adapter = adapter
|
||||
|
||||
val list = viewModel.extensions
|
||||
list.forEach {
|
||||
it.enable(this)
|
||||
}
|
||||
|
||||
adapter.set(list)
|
||||
}
|
||||
|
||||
private suspend fun extensionIsConnected(it: Extension) {
|
||||
// check if it is connected
|
||||
if (it.isConnected()) {
|
||||
gotoExtension(it)
|
||||
return
|
||||
}
|
||||
|
||||
val ld = it.connect()
|
||||
if (ld) {
|
||||
gotoExtension(it)
|
||||
}
|
||||
// handle if extension can't be connected
|
||||
}
|
||||
|
||||
private fun gotoExtension(it: Extension) {
|
||||
findNavController().navigate(
|
||||
ExtensionsFragmentDirections.actionNavExtensionsToNavExtension(it.id)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.dzeio.openhealth.ui.extensions
|
||||
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.extensions.ExtensionFactory
|
||||
import com.dzeio.openhealth.utils.Configuration
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ExtensionsViewModel @Inject internal constructor(
|
||||
val config: Configuration
|
||||
) : BaseViewModel() {
|
||||
|
||||
val extensions = ExtensionFactory.getAll()
|
||||
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package com.dzeio.openhealth.extensions
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
|
||||
class FileSystemExtension : Extension {
|
||||
companion object {
|
||||
const val TAG = "FSExtension"
|
||||
}
|
||||
|
||||
private lateinit var activity: Activity
|
||||
|
||||
override val id = "FileSystem"
|
||||
override val name = "File System"
|
||||
|
||||
override val permissions = arrayOf(
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
override val permissionsText: String = "Please"
|
||||
|
||||
override fun init(activity: Activity): Array<Extension.Data> {
|
||||
this.activity = activity
|
||||
return Extension.Data.values()
|
||||
}
|
||||
|
||||
override fun getStatus(): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun isAvailable(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isConnected(): Boolean = true
|
||||
|
||||
private val connectLiveData: MutableLiveData<Extension.States> = MutableLiveData(Extension.States.DONE)
|
||||
|
||||
override fun connect(): LiveData<Extension.States> = connectLiveData
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
Log.d(this.name, "[$requestCode] -> [$resultCode]: $data")
|
||||
if (requestCode == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) connectLiveData.value = Extension.States.DONE
|
||||
// signIn(Data.values()[requestCode])
|
||||
}
|
||||
|
||||
override fun importWeight(): LiveData<Extension.ImportState<Weight>> {
|
||||
|
||||
weightLiveData = MutableLiveData(
|
||||
Extension.ImportState(
|
||||
Extension.States.WIP
|
||||
)
|
||||
)
|
||||
|
||||
startImport(Extension.Data.WEIGHT)
|
||||
|
||||
return weightLiveData
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
package com.dzeio.openhealth.extensions
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.fitness.Fitness
|
||||
import com.google.android.gms.fitness.FitnessOptions
|
||||
import com.google.android.gms.fitness.data.DataType
|
||||
import com.google.android.gms.fitness.request.DataReadRequest
|
||||
import java.text.DateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class GoogleFit: Extension {
|
||||
companion object {
|
||||
const val TAG = "GoogleFitConnector"
|
||||
}
|
||||
|
||||
private lateinit var activity: Activity
|
||||
|
||||
override val id = "GoogleFit"
|
||||
override val name = "Google Fit"
|
||||
|
||||
override val permissions = arrayOf(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
)
|
||||
|
||||
override val permissionsText: String = "Please"
|
||||
|
||||
override fun init(activity: Fragment): Array<Extension.Data> {
|
||||
this.activity = activity.register
|
||||
return arrayOf(
|
||||
Extension.Data.WEIGHT
|
||||
)
|
||||
}
|
||||
|
||||
override fun getStatus(): String {
|
||||
return if (isConnected()) "Connected" else "Not Connected"
|
||||
}
|
||||
|
||||
override fun isAvailable(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isConnected(): Boolean =
|
||||
GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
|
||||
|
||||
private val fitnessOptions = FitnessOptions.builder()
|
||||
.addDataType(DataType.TYPE_WEIGHT)
|
||||
// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
|
||||
// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
|
||||
.build()
|
||||
|
||||
private val connectLiveData: MutableLiveData<Extension.States> = MutableLiveData(Extension.States.WIP)
|
||||
|
||||
override fun connect(): LiveData<Extension.States> {
|
||||
|
||||
if (isConnected()) {
|
||||
connectLiveData.value = Extension.States.DONE
|
||||
} else {
|
||||
Log.d(this.name, "Signing In")
|
||||
GoogleSignIn.requestPermissions(
|
||||
activity,
|
||||
124887,
|
||||
getGoogleAccount(), fitnessOptions
|
||||
)
|
||||
}
|
||||
return connectLiveData
|
||||
}
|
||||
|
||||
private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
|
||||
|
||||
private val timeRange by lazy {
|
||||
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
calendar.time = Date()
|
||||
val endTime = calendar.timeInMillis
|
||||
|
||||
// Set year to 2013 to be sure to get data from when Google Fit Started to today
|
||||
calendar.set(Calendar.YEAR, 2013)
|
||||
val startTime = calendar.timeInMillis
|
||||
return@lazy arrayOf(startTime, endTime)
|
||||
}
|
||||
|
||||
private fun startImport(data: Extension.Data) {
|
||||
Log.d("GoogleFitImporter", "Importing for ${data.name}")
|
||||
|
||||
val dateFormat = DateFormat.getDateInstance()
|
||||
Log.i(TAG, "Range Start: ${dateFormat.format(timeRange[0])}")
|
||||
Log.i(TAG, "Range End: ${dateFormat.format(timeRange[1])}")
|
||||
|
||||
var type = DataType.TYPE_WEIGHT
|
||||
var timeUnit = TimeUnit.MILLISECONDS
|
||||
|
||||
when (data) {
|
||||
Extension.Data.STEPS -> {
|
||||
type = DataType.TYPE_STEP_COUNT_CUMULATIVE
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
runRequest(
|
||||
DataReadRequest.Builder()
|
||||
.read(type)
|
||||
.setTimeRange(timeRange[0], timeRange[1], timeUnit)
|
||||
.build(),
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
private fun runRequest(request: DataReadRequest, data: Extension.Data) {
|
||||
Fitness.getHistoryClient(
|
||||
activity,
|
||||
GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
|
||||
)
|
||||
.readData(request)
|
||||
.addOnSuccessListener { response ->
|
||||
Log.d(
|
||||
TAG,
|
||||
"Received response! ${response.dataSets.size} ${response.buckets.size} ${response.status}"
|
||||
)
|
||||
for (dataSet in response.dataSets) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size} ${dataSet.dataSource}"
|
||||
)
|
||||
dataSet.dataPoints.forEach { dp ->
|
||||
|
||||
// Global
|
||||
Log.i(TAG, "Importing Data point:")
|
||||
Log.i(TAG, "\tType: ${dp.dataType.name}")
|
||||
Log.i(
|
||||
TAG,
|
||||
"\tStart: ${Date(dp.getStartTime(TimeUnit.SECONDS) * 1000L).toLocaleString()}"
|
||||
)
|
||||
Log.i(
|
||||
TAG,
|
||||
"\tEnd: ${Date(dp.getEndTime(TimeUnit.SECONDS) * 1000L).toLocaleString()}"
|
||||
)
|
||||
|
||||
// Field Specifics
|
||||
for (field in dp.dataType.fields) {
|
||||
Log.i(TAG, "\tField: ${field.name} Value: ${dp.getValue(field)}")
|
||||
when (data) {
|
||||
Extension.Data.WEIGHT -> {
|
||||
val weight = Weight()
|
||||
weight.timestamp = dp.getStartTime(TimeUnit.MILLISECONDS)
|
||||
weight.weight = dp.getValue(field).asFloat()
|
||||
val list = weightLiveData.value?.list?.toMutableList()
|
||||
?: ArrayList()
|
||||
list.add(weight)
|
||||
weightLiveData.value =
|
||||
|
||||
Extension.ImportState(Extension.States.WIP, list)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
when (data) {
|
||||
Extension.Data.WEIGHT -> {
|
||||
weightLiveData.value =
|
||||
Extension.ImportState(
|
||||
Extension.States.DONE,
|
||||
weightLiveData.value?.list
|
||||
?: ArrayList()
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
Log.e(TAG, "There was an error reading data from Google Fit", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
Log.d(this.name, "[$requestCode] -> [$resultCode]: $data")
|
||||
if (requestCode == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (resultCode == Activity.RESULT_OK) connectLiveData.value = Extension.States.DONE
|
||||
// signIn(Data.values()[requestCode])
|
||||
}
|
||||
|
||||
private lateinit var weightLiveData: MutableLiveData<Extension.ImportState<Weight>>
|
||||
|
||||
override fun importWeight(): LiveData<Extension.ImportState<Weight>> {
|
||||
|
||||
weightLiveData = MutableLiveData(
|
||||
Extension.ImportState(
|
||||
Extension.States.WIP
|
||||
)
|
||||
)
|
||||
|
||||
startImport(Extension.Data.WEIGHT)
|
||||
|
||||
return weightLiveData
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
package com.dzeio.openhealth.extensions
|
||||
|
||||
import android.Manifest
|
||||
import android.util.Log
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.dzeio.openhealth.core.Observable
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import com.dzeio.openhealth.utils.PermissionsManager
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.fitness.Fitness
|
||||
import com.google.android.gms.fitness.FitnessOptions
|
||||
import com.google.android.gms.fitness.data.DataType
|
||||
import com.google.android.gms.fitness.request.DataReadRequest
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class GoogleFitExtension : Extension {
|
||||
companion object {
|
||||
const val TAG = "GoogleFitConnector"
|
||||
}
|
||||
|
||||
override val id = "GoogleFit"
|
||||
override val name = "Google Fit"
|
||||
|
||||
override val permissions = arrayOf(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
)
|
||||
|
||||
override val data: Array<Extension.Data> = arrayOf(
|
||||
Extension.Data.WEIGHT
|
||||
)
|
||||
|
||||
override suspend fun isConnected(): Boolean =
|
||||
GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
|
||||
|
||||
|
||||
private val fitnessOptions = FitnessOptions.builder()
|
||||
.addDataType(DataType.TYPE_WEIGHT)
|
||||
// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
|
||||
// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
|
||||
.build()
|
||||
|
||||
private val connectionStatus = Observable(false)
|
||||
|
||||
private lateinit var fragment: Fragment
|
||||
|
||||
override fun isAvailable(): Boolean = true
|
||||
|
||||
override fun enable(fragment: Fragment): Boolean {
|
||||
this.fragment = fragment
|
||||
return true
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override suspend fun connect(): Boolean {
|
||||
|
||||
if (isConnected()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return suspendCancellableCoroutine { cancellableContinuation ->
|
||||
Log.d(this.name, "Signing In")
|
||||
GoogleSignIn.requestPermissions(
|
||||
fragment,
|
||||
124887,
|
||||
getGoogleAccount(), fitnessOptions
|
||||
)
|
||||
connectionStatus.addOneTimeObserver { it: Boolean ->
|
||||
cancellableContinuation.resume(it) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val contract: ActivityResultContract<*, Map<String, @JvmSuppressWildcards Boolean>>? = ActivityResultContracts.RequestMultiplePermissions()
|
||||
override val requestInput = permissions
|
||||
|
||||
private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(fragment.requireContext(), fitnessOptions)
|
||||
|
||||
private val timeRange by lazy {
|
||||
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
calendar.time = Date()
|
||||
val endTime = calendar.timeInMillis
|
||||
|
||||
// Set year to 2013 to be sure to get data from when Google Fit Started to today
|
||||
calendar.set(Calendar.YEAR, 2013)
|
||||
val startTime = calendar.timeInMillis
|
||||
return@lazy arrayOf(startTime, endTime)
|
||||
}
|
||||
|
||||
override suspend fun importWeight(): Flow<Extension.TaskProgress<ArrayList<Weight>>> =
|
||||
channelFlow {
|
||||
send(
|
||||
Extension.TaskProgress(
|
||||
Extension.TaskState.INITIALIZATING
|
||||
)
|
||||
)
|
||||
|
||||
val type = DataType.TYPE_WEIGHT
|
||||
val timeUnit = TimeUnit.MILLISECONDS
|
||||
|
||||
val request = DataReadRequest.Builder()
|
||||
.read(type)
|
||||
.setTimeRange(timeRange[0], timeRange[1], timeUnit)
|
||||
.build()
|
||||
|
||||
Fitness.getHistoryClient(
|
||||
fragment.requireContext(),
|
||||
GoogleSignIn.getAccountForExtension(fragment.requireContext(), fitnessOptions)
|
||||
)
|
||||
.readData(request)
|
||||
.addOnSuccessListener { response ->
|
||||
val weights: ArrayList<Weight> = ArrayList()
|
||||
var index = 0
|
||||
var total = response.dataSets.size
|
||||
for (dataset in response.dataSets) {
|
||||
total += dataset.dataPoints.size - 1
|
||||
for (dataPoint in dataset.dataPoints) {
|
||||
total += dataPoint.dataType.fields.size - 1
|
||||
for (field in dataPoint.dataType.fields) {
|
||||
val weight = Weight().apply {
|
||||
timestamp = dataPoint.getStartTime(TimeUnit.MILLISECONDS)
|
||||
weight = dataPoint.getValue(field).asFloat()
|
||||
source = this@GoogleFitExtension.id
|
||||
}
|
||||
weights.add(weight)
|
||||
runBlocking {
|
||||
send(
|
||||
Extension.TaskProgress(
|
||||
Extension.TaskState.WORK_IN_PROGRESS,
|
||||
progress = index++ / total.toFloat()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
runBlocking {
|
||||
send(
|
||||
Extension.TaskProgress(
|
||||
Extension.TaskState.DONE,
|
||||
additionalData = weights
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.addOnFailureListener {
|
||||
runBlocking {
|
||||
send(
|
||||
Extension.TaskProgress(
|
||||
Extension.TaskState.ERROR,
|
||||
statusMessage = it.localizedMessage ?: it.message ?: "Unknown error"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override suspend fun exportWeights(weight: Array<Weight>): Flow<Extension.TaskProgress<Unit>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun permissionsGranted(): Boolean {
|
||||
return PermissionsManager.hasPermission(this.fragment.requireContext(), permissions)
|
||||
}
|
||||
|
||||
override fun onActivityResult(result: Any) {
|
||||
if ((result as Map<*, *>).containsValue(false)) {
|
||||
return
|
||||
}
|
||||
|
||||
connectionStatus.value = true
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
package com.dzeio.openhealth.extensions
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.health.connect.client.HealthConnectClient
|
||||
import androidx.health.connect.client.PermissionController
|
||||
import androidx.health.connect.client.permission.HealthPermission
|
||||
import androidx.health.connect.client.records.HeartRateRecord
|
||||
import androidx.health.connect.client.records.StepsRecord
|
||||
import androidx.health.connect.client.records.WeightRecord
|
||||
import androidx.health.connect.client.request.ReadRecordsRequest
|
||||
import androidx.health.connect.client.time.TimeRangeFilter
|
||||
import com.dzeio.openhealth.core.Observable
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.time.Instant
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
class HealthConnectExtension : Extension {
|
||||
companion object {
|
||||
const val TAG = "HealthConnectExtension"
|
||||
}
|
||||
|
||||
// build a set of permissions for required data types
|
||||
val PERMISSIONS =
|
||||
setOf(
|
||||
HealthPermission.createReadPermission(HeartRateRecord::class),
|
||||
HealthPermission.createWritePermission(HeartRateRecord::class),
|
||||
HealthPermission.createReadPermission(StepsRecord::class),
|
||||
HealthPermission.createWritePermission(StepsRecord::class)
|
||||
)
|
||||
|
||||
|
||||
override val id = "HealthConnect"
|
||||
override val name = "Health Connect"
|
||||
|
||||
override val permissions = arrayOf(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
)
|
||||
|
||||
override val requestInput = PERMISSIONS
|
||||
|
||||
override val data: Array<Extension.Data> = arrayOf(
|
||||
Extension.Data.WEIGHT
|
||||
)
|
||||
|
||||
override suspend fun isConnected(): Boolean = true
|
||||
|
||||
|
||||
|
||||
private val connectionStatus = Observable(false)
|
||||
|
||||
private lateinit var fragment: Fragment
|
||||
private lateinit var client: HealthConnectClient
|
||||
|
||||
override fun isAvailable(): Boolean {
|
||||
return HealthConnectClient.isAvailable(fragment.requireContext())
|
||||
}
|
||||
|
||||
override fun enable(fragment: Fragment): Boolean {
|
||||
this.fragment = fragment
|
||||
if (!isAvailable()) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.client = HealthConnectClient.getOrCreate(fragment.requireContext())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun connect(): Boolean = true
|
||||
|
||||
override suspend fun importWeight(): Flow<Extension.TaskProgress<ArrayList<Weight>>> =
|
||||
channelFlow {
|
||||
send(
|
||||
Extension.TaskProgress(
|
||||
Extension.TaskState.INITIALIZATING
|
||||
)
|
||||
)
|
||||
|
||||
val response = client.readRecords(
|
||||
ReadRecordsRequest(
|
||||
WeightRecord::class,
|
||||
timeRangeFilter = TimeRangeFilter.before(Instant.now())
|
||||
)
|
||||
)
|
||||
|
||||
val weights: ArrayList<Weight> = ArrayList()
|
||||
var index = 0
|
||||
for (record in response.records) {
|
||||
val weight = Weight().apply {
|
||||
timestamp = record.time.toEpochMilli()
|
||||
weight = record.weight.inKilograms.toFloat()
|
||||
source = this@HealthConnectExtension.id
|
||||
}
|
||||
weights.add(weight)
|
||||
runBlocking {
|
||||
send(
|
||||
Extension.TaskProgress(
|
||||
Extension.TaskState.WORK_IN_PROGRESS,
|
||||
progress = index++ / response.records.size.toFloat()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
runBlocking {
|
||||
send(
|
||||
Extension.TaskProgress(
|
||||
Extension.TaskState.DONE,
|
||||
additionalData = weights
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun exportWeights(weight: Array<Weight>): Flow<Extension.TaskProgress<Unit>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onActivityResult(result: Any) {
|
||||
if ((result as Set<*>).containsAll(this.PERMISSIONS)) connectionStatus.value = true
|
||||
// signIn(Data.values()[requestCode])
|
||||
}
|
||||
|
||||
override val contract: ActivityResultContract<Set<HealthPermission>, Set<HealthPermission>>
|
||||
get() = PermissionController.createRequestPermissionResultContract()
|
||||
|
||||
override suspend fun permissionsGranted(): Boolean {
|
||||
return this.client.permissionController.getGrantedPermissions(this.PERMISSIONS).containsAll(this.PERMISSIONS)
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package com.dzeio.openhealth.extensions.samsunghealth
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import com.samsung.android.sdk.healthdata.HealthConnectionErrorResult
|
||||
import com.samsung.android.sdk.healthdata.HealthConstants.StepCount
|
||||
import com.samsung.android.sdk.healthdata.HealthDataStore
|
||||
import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
|
||||
import com.samsung.android.sdk.healthdata.HealthPermissionManager
|
||||
import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
|
||||
|
||||
|
||||
/**
|
||||
* Does not FUCKING work
|
||||
*/
|
||||
class SamsungHealth(
|
||||
private val context: Activity
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "SamsungHealthConnector"
|
||||
}
|
||||
|
||||
private val listener = object : ConnectionListener {
|
||||
override fun onConnected() {
|
||||
Log.d(TAG, "Connected!")
|
||||
if (isPermissionAcquired()) {
|
||||
reporter.start()
|
||||
} else {
|
||||
requestPermission()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConnectionFailed(p0: HealthConnectionErrorResult?) {
|
||||
Log.d(TAG, "Health data service is not available.")
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
Log.d(TAG, "Health data service is disconnected.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val store: HealthDataStore = HealthDataStore(context, listener)
|
||||
|
||||
private fun isPermissionAcquired(): Boolean {
|
||||
val permKey = PermissionKey(StepCount.HEALTH_DATA_TYPE, PermissionType.READ)
|
||||
val pmsManager = HealthPermissionManager(store)
|
||||
try {
|
||||
// Check whether the permissions that this application needs are acquired
|
||||
val resultMap = pmsManager.isPermissionAcquired(setOf(permKey))
|
||||
return !resultMap.containsValue(java.lang.Boolean.FALSE)
|
||||
} catch (e: java.lang.Exception) {
|
||||
Log.e(TAG, "Permission request fails.", e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun requestPermission() {
|
||||
val permKey = PermissionKey(StepCount.HEALTH_DATA_TYPE, PermissionType.READ)
|
||||
val pmsManager = HealthPermissionManager(store)
|
||||
try {
|
||||
// Show user permission UI for allowing user to change options
|
||||
pmsManager.requestPermissions(setOf(permKey), context)
|
||||
.setResultListener { result: PermissionResult ->
|
||||
Log.d(TAG, "Permission callback is received.")
|
||||
val resultMap =
|
||||
result.resultMap
|
||||
if (resultMap.containsValue(java.lang.Boolean.FALSE)) {
|
||||
Log.d(TAG, "No Data???")
|
||||
} else {
|
||||
// Get the current step count and display it
|
||||
reporter.start()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Permission setting fails.", e)
|
||||
}
|
||||
}
|
||||
|
||||
private val stepCountObserver = object : StepCountReporter.StepCountObserver {
|
||||
override fun onChanged(count: Int) {
|
||||
Log.d(TAG, "Step reported : $count")
|
||||
}
|
||||
}
|
||||
|
||||
private val reporter =
|
||||
StepCountReporter(store, stepCountObserver, Handler(Looper.getMainLooper()))
|
||||
|
||||
/**
|
||||
* Connector
|
||||
*/
|
||||
|
||||
val sourceID: String = "SamsungHealth"
|
||||
|
||||
fun onRequestPermissionResult(
|
||||
requestCode: Int,
|
||||
permission: Array<String>,
|
||||
grantResult: IntArray
|
||||
) {
|
||||
}
|
||||
|
||||
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
|
||||
|
||||
fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
|
||||
store.connectService()
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.dzeio.openhealth.extensions.samsunghealth
|
||||
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import com.samsung.android.sdk.healthdata.HealthConstants.StepCount
|
||||
import com.samsung.android.sdk.healthdata.HealthData
|
||||
import com.samsung.android.sdk.healthdata.HealthDataObserver
|
||||
import com.samsung.android.sdk.healthdata.HealthDataResolver
|
||||
import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateRequest
|
||||
import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateRequest.AggregateFunction
|
||||
import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateResult
|
||||
import com.samsung.android.sdk.healthdata.HealthDataStore
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class StepCountReporter(
|
||||
private val mStore: HealthDataStore, private val mStepCountObserver: StepCountObserver,
|
||||
resultHandler: Handler?
|
||||
) {
|
||||
private val mHealthDataResolver: HealthDataResolver
|
||||
private val mHealthDataObserver: HealthDataObserver
|
||||
fun start() {
|
||||
// Register an observer to listen changes of step count and get today step count
|
||||
HealthDataObserver.addObserver(mStore, StepCount.HEALTH_DATA_TYPE, mHealthDataObserver)
|
||||
readTodayStepCount()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
HealthDataObserver.removeObserver(mStore, mHealthDataObserver)
|
||||
}
|
||||
|
||||
// Read the today's step count on demand
|
||||
private fun readTodayStepCount() {
|
||||
// Set time range from start time of today to the current time
|
||||
val startTime = getUtcStartOfDay(System.currentTimeMillis(), TimeZone.getDefault())
|
||||
val endTime = startTime + TimeUnit.DAYS.toMillis(1)
|
||||
val request = AggregateRequest.Builder()
|
||||
.setDataType(StepCount.HEALTH_DATA_TYPE)
|
||||
.addFunction(AggregateFunction.SUM, StepCount.COUNT, "total_step")
|
||||
.setLocalTimeRange(StepCount.START_TIME, StepCount.TIME_OFFSET, startTime, endTime)
|
||||
.build()
|
||||
try {
|
||||
mHealthDataResolver.aggregate(request)
|
||||
.setResultListener { aggregateResult: AggregateResult ->
|
||||
aggregateResult.use { result ->
|
||||
val iterator: Iterator<HealthData> = result.iterator()
|
||||
if (iterator.hasNext()) {
|
||||
mStepCountObserver.onChanged(iterator.next().getInt("total_step"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("APP_TAG", "Getting step count fails.", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUtcStartOfDay(time: Long, tz: TimeZone): Long {
|
||||
val cal = Calendar.getInstance(tz)
|
||||
cal.timeInMillis = time
|
||||
val year = cal[Calendar.YEAR]
|
||||
val month = cal[Calendar.MONTH]
|
||||
val date = cal[Calendar.DATE]
|
||||
cal.timeZone = TimeZone.getTimeZone("UTC")
|
||||
cal[Calendar.YEAR] = year
|
||||
cal[Calendar.MONTH] = month
|
||||
cal[Calendar.DATE] = date
|
||||
cal[Calendar.HOUR_OF_DAY] = 0
|
||||
cal[Calendar.MINUTE] = 0
|
||||
cal[Calendar.SECOND] = 0
|
||||
cal[Calendar.MILLISECOND] = 0
|
||||
return cal.timeInMillis
|
||||
}
|
||||
|
||||
interface StepCountObserver {
|
||||
fun onChanged(count: Int)
|
||||
}
|
||||
|
||||
init {
|
||||
mHealthDataResolver = HealthDataResolver(mStore, resultHandler)
|
||||
mHealthDataObserver = object : HealthDataObserver(resultHandler) {
|
||||
// Update the step count when a change event is received
|
||||
override fun onChange(dataTypeName: String) {
|
||||
Log.d("APP_TAG", "Observer receives a data changed event")
|
||||
readTodayStepCount()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package com.dzeio.openhealth.ui.extension
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
import com.dzeio.openhealth.databinding.FragmentExtensionBinding
|
||||
import com.dzeio.openhealth.extensions.Extension
|
||||
import com.dzeio.openhealth.extensions.ExtensionFactory
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ExtensionFragment :
|
||||
BaseFragment<ExtensionViewModel, FragmentExtensionBinding>(ExtensionViewModel::class.java) {
|
||||
|
||||
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionBinding =
|
||||
FragmentExtensionBinding::inflate
|
||||
|
||||
private val args: ExtensionFragmentArgs by navArgs()
|
||||
|
||||
private val extension by lazy {
|
||||
ExtensionFactory.getExtension(args.extension)
|
||||
?: throw Exception("No Extension found!")
|
||||
}
|
||||
|
||||
private var request: ActivityResultLauncher<Any>? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
if (this.extension.contract != null) {
|
||||
this.request =
|
||||
registerForActivityResult<Any, Any>(this.extension.contract!! as ActivityResultContract<Any, Any>) {
|
||||
this.extension.onActivityResult(it as Any)
|
||||
}
|
||||
}
|
||||
super.onAttach(context)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val enabled = extension.enable(this)
|
||||
|
||||
if (!enabled) {
|
||||
throw Exception("Extension can't be enabled (${extension.id})")
|
||||
}
|
||||
|
||||
requireActivity().actionBar?.title = extension.name
|
||||
|
||||
// extension.init(requireActivity())
|
||||
|
||||
binding.importButton.setOnClickListener {
|
||||
val dialog = ProgressDialog(requireContext())
|
||||
dialog.setTitle("Importing...")
|
||||
dialog.setMessage("Imported 0 values")
|
||||
dialog.show()
|
||||
lifecycleScope.launch {
|
||||
extension.importWeight().collectLatest { state ->
|
||||
Log.d("ExtensionFragment", state.state.name)
|
||||
dialog.setMessage(state.statusMessage ?: "progress ${state.progress}%")
|
||||
if (state.state == Extension.TaskState.DONE) {
|
||||
dialog.setMessage("Finishing Import...")
|
||||
lifecycleScope.launchWhenStarted {
|
||||
state.additionalData!!.forEach {
|
||||
it.source = extension.id
|
||||
viewModel.importWeight(it)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
if (!extension.permissionsGranted() && request != null) {
|
||||
request!!.launch(extension.requestInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.dzeio.openhealth.ui.extension
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import com.dzeio.openhealth.data.weight.WeightRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ExtensionViewModel @Inject internal constructor(
|
||||
private val weightRepository: WeightRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
val text = MutableLiveData<String>().apply {
|
||||
value = "This is slideshow Fragment"
|
||||
}
|
||||
val importProgress = MutableLiveData<Int>().apply {
|
||||
value = 0
|
||||
}
|
||||
// If -1 progress is undetermined
|
||||
// If 0 no progress bar
|
||||
// Else progress bar
|
||||
val importProgressTotal = MutableLiveData<Int>().apply {
|
||||
value = 0
|
||||
}
|
||||
|
||||
suspend fun importWeight(weight: Weight) = weightRepository.addWeight(weight)
|
||||
suspend fun deleteFromSource(source: String) = weightRepository.deleteFromSource(source)
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.dzeio.openhealth.ui.extensions
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
import com.dzeio.openhealth.databinding.FragmentExtensionsBinding
|
||||
import com.dzeio.openhealth.extensions.Extension
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ExtensionsFragment :
|
||||
BaseFragment<ExtensionsViewModel, FragmentExtensionsBinding>(ExtensionsViewModel::class.java) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "ExtensionsFragment"
|
||||
}
|
||||
|
||||
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionsBinding =
|
||||
FragmentExtensionsBinding::inflate
|
||||
|
||||
private lateinit var activeExtension: Extension
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val recycler = binding.list
|
||||
|
||||
val manager = LinearLayoutManager(requireContext())
|
||||
recycler.layoutManager = manager
|
||||
|
||||
val adapter = ExtensionAdapter(viewModel.config)
|
||||
adapter.onItemClick = {
|
||||
activeExtension = it
|
||||
activeExtension.enable(this)
|
||||
Log.d(TAG, "${it.id}: ${it.name}")
|
||||
|
||||
lifecycleScope.launch {
|
||||
extensionIsConnected(it)
|
||||
}
|
||||
}
|
||||
recycler.adapter = adapter
|
||||
|
||||
val list = viewModel.extensions
|
||||
list.forEach {
|
||||
it.enable(this)
|
||||
}
|
||||
|
||||
adapter.set(list)
|
||||
}
|
||||
|
||||
private suspend fun extensionIsConnected(it: Extension) {
|
||||
// check if it is connected
|
||||
if (it.isConnected()) {
|
||||
gotoExtension(it)
|
||||
return
|
||||
}
|
||||
|
||||
val ld = it.connect()
|
||||
if (ld) {
|
||||
gotoExtension(it)
|
||||
}
|
||||
// handle if extension can't be connected
|
||||
}
|
||||
|
||||
private fun gotoExtension(it: Extension) {
|
||||
findNavController().navigate(
|
||||
ExtensionsFragmentDirections.actionNavExtensionsToNavExtension(it.id)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.dzeio.openhealth.ui.extensions
|
||||
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.extensions.ExtensionFactory
|
||||
import com.dzeio.openhealth.utils.Configuration
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ExtensionsViewModel @Inject internal constructor(
|
||||
val config: Configuration
|
||||
) : BaseViewModel() {
|
||||
|
||||
val extensions = ExtensionFactory.getAll()
|
||||
|
||||
}
|
@ -212,4 +212,16 @@
|
||||
tools:layout="@layout/dialog_search"
|
||||
android:name="com.dzeio.openhealth.ui.weight.ScanScalesDialog"
|
||||
android:label="ScanScalesDialog" />
|
||||
<fragment
|
||||
android:id="@+id/extensionsFragment"
|
||||
android:name="com.dzeio.openhealth.ui.extensions.ExtensionsFragment"
|
||||
android:label="ExtensionsFragment" >
|
||||
<action
|
||||
android:id="@+id/action_extensionsFragment_to_extensionFragment"
|
||||
app:destination="@id/extensionFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/extensionFragment"
|
||||
android:name="com.dzeio.openhealth.ui.extension.ExtensionFragment"
|
||||
android:label="ExtensionFragment" />
|
||||
</navigation>
|
||||
|
Loading…
x
Reference in New Issue
Block a user