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

Stated Adding Water Intake

This commit is contained in:
Florian Bouillon 2021-12-22 01:44:34 +01:00
parent e1f11e1ae2
commit 077397749c
Signed by: Florian Bouillon
GPG Key ID: 50BD648F12C86AB6
33 changed files with 1109 additions and 694 deletions

7
.gitignore vendored
View File

@ -1,12 +1,7 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea
.DS_Store
/build
/captures

17
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="14e181ff" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-12-21T23:29:01.995426900Z" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Reformat" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>

5
.idea/misc.xml generated
View File

@ -7,6 +7,8 @@
<entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_import.xml" value="0.1" />
<entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/menu/activity_main_drawer.xml" value="0.10989583333333333" />
<entry key="..\:/Git/Dzeio/OpenHealth/app/src/main/res/menu/main.xml" value="0.1875" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/half_circle.xml" value="0.421" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/ic_ellipse_2.xml" value="0.4165" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/drawable/ic_logo_app.xml" value="0.163" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/activity_main.xml" value="0.5" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/app_bar_main.xml" value="0.5192107995846313" />
@ -15,9 +17,10 @@
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_edit_weight.xml" value="0.33" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_register_weight.xml" value="0.5" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_gallery.xml" value="0.3109375" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_home.xml" value="0.25" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_home.xml" value="0.6845493562231759" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_import.xml" value="0.55" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_list_weight.xml" value="0.33" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_water_home.xml" value="0.55" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/layout_item_weight.xml" value="0.5" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/nav_header_main.xml" value="0.3109375" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/activity_main_drawer.xml" value="0.39947916666666666" />

View File

@ -5,10 +5,7 @@ import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import androidx.appcompat.app.AppCompatActivity
import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
@ -16,9 +13,6 @@ import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.dzeio.openhealth.core.BaseActivity
import com.dzeio.openhealth.databinding.ActivityMainBinding
import com.dzeio.openhealth.ui.main.home.HomeFragmentDirections
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint

View File

@ -4,18 +4,27 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.dzeio.openhealth.data.weight.Water
import com.dzeio.openhealth.data.weight.WaterDao
import com.dzeio.openhealth.data.weight.WeightDao
import com.dzeio.openhealth.data.weight.Weight
@Database(entities = [Weight::class], version = 1, exportSchema = false)
@Database(
entities = [
Weight::class,
Water::class
], version = 1, exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun weightDao() : WeightDao
abstract fun weightDao(): WeightDao
abstract fun waterDao(): WaterDao
companion object {
private const val DATABASE_NAME = "open_health"
// For Singleton instantiation
@Volatile private var instance: AppDatabase? = null
@Volatile
private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {

View File

@ -0,0 +1,29 @@
package com.dzeio.openhealth.data.weight
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.sql.Date
import java.text.DateFormat.getDateInstance
import java.util.*
@Entity()
data class Water(
@PrimaryKey(autoGenerate = true) var id: Long = 0,
var value: Int = 0,
@ColumnInfo(index = true)
var timestamp: Long = System.currentTimeMillis(),
var source: String = "OpenHealth"
) {
init {
val cal = Calendar.getInstance()
cal.set(Calendar.HOUR_OF_DAY, 0)
cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0)
cal.set(Calendar.MILLISECOND, 0)
timestamp = cal.timeInMillis
}
fun formatTimestamp(): String = getDateInstance().format(Date(timestamp))
}

View File

@ -0,0 +1,26 @@
package com.dzeio.openhealth.data.weight
import androidx.room.*
import com.dzeio.openhealth.core.BaseDao
import dagger.Provides
import kotlinx.coroutines.flow.Flow
@Dao
interface WaterDao : BaseDao<Water> {
@Query("SELECT * FROM Water")
fun getAll(): Flow<List<Water>>
@Query("SELECT * FROM Water where id = :weightId")
fun getOne(weightId: Long): Flow<Water?>
@Query("Select count(*) from Water")
fun getCount(): Flow<Int>
@Query("Select * FROM Water WHERE id=(SELECT max(id) FROM Water)")
fun last(): Flow<Water?>
@Query("DELETE FROM Water where source = :source")
suspend fun deleteFromSource(source: String)
}

View File

@ -0,0 +1,32 @@
package com.dzeio.openhealth.data.weight
import android.util.Log
import kotlinx.coroutines.flow.*
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class WaterRepository @Inject constructor(
private val waterDao: WaterDao
) {
fun getWaters() = waterDao.getAll()
fun lastWater() = waterDao.last()
fun getWater(id: Long) = waterDao.getOne(id)
suspend fun addWater(value: Water) = waterDao.insert(value)
suspend fun deleteWater(value: Water) = waterDao.delete(value)
suspend fun deleteFromSource(value: String) = waterDao.deleteFromSource(value)
fun todayWater() = lastWater().filter {
val cal = Calendar.getInstance()
cal.set(Calendar.HOUR_OF_DAY, 0)
cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0)
cal.set(Calendar.MILLISECOND, 0)
Log.d("WaterRepository", "${it?.timestamp} ${cal.timeInMillis}")
return@filter it?.timestamp == cal.timeInMillis
}
}

View File

@ -2,6 +2,7 @@ package com.dzeio.openhealth.di
import android.content.Context
import com.dzeio.openhealth.data.AppDatabase
import com.dzeio.openhealth.data.weight.WaterDao
import com.dzeio.openhealth.data.weight.WeightDao
import dagger.Module
import dagger.Provides
@ -24,4 +25,9 @@ class DatabaseModule {
fun provideWeightDao(appDatabase: AppDatabase): WeightDao {
return appDatabase.weightDao()
}
@Provides
fun provideWaterDao(appDatabase: AppDatabase): WaterDao {
return appDatabase.waterDao()
}
}

View File

@ -1,4 +1,4 @@
package com.dzeio.openhealth.connectors
package com.dzeio.openhealth.extensions
enum class DataType {
WEIGHT

View File

@ -1,10 +1,10 @@
package com.dzeio.openhealth.connectors
package com.dzeio.openhealth.extensions
import android.app.Activity
import android.content.Intent
import com.dzeio.openhealth.data.weight.Weight
abstract class Connector {
abstract class Extension {
enum class Data {
WEIGHT,
@ -13,7 +13,7 @@ abstract class Connector {
abstract val sourceID: String
open fun init(activity: Activity) {}
open fun init(activity: Activity): Array<Data> = arrayOf()
/**
* Same as Activity/Fragment onRequestPermissionResult
@ -27,5 +27,7 @@ abstract class Connector {
*/
open fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
open fun <T> import(data: Data, cb: (item: T, end: Boolean) -> Unit) {}
open fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {}
}

View File

@ -1,173 +1,186 @@
package com.dzeio.openhealth.connectors
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 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.DataPoint
import com.google.android.gms.fitness.data.DataSet
import com.google.android.gms.fitness.data.DataType
import com.google.android.gms.fitness.request.DataReadRequest
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
class GoogleFit(
private val activity: Activity,
) : Connector() {
companion object {
const val TAG = "GoogleFitConnector"
}
override val sourceID: String = "GoogleFit"
private val fitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_WEIGHT)
.addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.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 fun permissionApproved(): Boolean {
val approved = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION)
} else {
true
}
return approved
}
private fun signIn(data: Data) {
if (oAuthPermissionsApproved()) {
startImport(data)
} else {
Log.d("GoogleFitImporter", "Signing In")
GoogleSignIn.requestPermissions(
activity,
data.ordinal,
getGoogleAccount(), fitnessOptions)
}
}
private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
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
arrayOf(startTime, endTime)
}
private fun startImport(data: 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) {
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: 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.dataPoints.forEachIndexed { index, dp ->
val isLast = (index + 1) == dataSet.dataPoints.size
// Global
Log.i(TAG,"Importing Data point:")
Log.i(TAG,"\tType: ${dp.dataType.name}")
Log.i(TAG,"\tStart: ${dp.getStartTimeString()}")
Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}")
// Field Specifics
for (field in dp.dataType.fields) {
Log.i(TAG,"\tField: ${field.name} Value: ${dp.getValue(field)}")
when (data) {
Data.WEIGHT -> {
val weight = Weight()
weight.timestamp = dp.getStartTime(TimeUnit.MILLISECONDS)
weight.weight = dp.getValue(field).asFloat()
weightCallback(weight, isLast)
}
else -> {}
}
}
}
}
}
.addOnFailureListener { e ->
Log.e(TAG,"There was an error reading data from Google Fit", e)
}
}
private fun DataPoint.getStartTimeString(): String = Date(this.getStartTime(TimeUnit.SECONDS) * 1000L).toLocaleString()
private fun DataPoint.getEndTimeString(): String = Date(this.getEndTime(TimeUnit.SECONDS) * 1000L).toLocaleString()
override fun onRequestPermissionResult(
requestCode: Int,
permission: Array<String>,
grantResult: IntArray
) {
signIn(Data.values()[requestCode])
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
signIn(Data.values()[requestCode])
}
private lateinit var weightCallback: (weight: Weight, end: Boolean) -> Unit
override fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
this.weightCallback = callback
checkPermissionsAndRun(Data.WEIGHT)
}
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 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.DataPoint
import com.google.android.gms.fitness.data.DataType
import com.google.android.gms.fitness.request.DataReadRequest
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
class GoogleFit(
private val activity: Activity,
) : Extension() {
companion object {
const val TAG = "GoogleFitConnector"
}
override val sourceID: String = "GoogleFit"
private val fitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_WEIGHT)
.addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.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 fun permissionApproved(): Boolean {
val approved = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION)
} else {
true
}
return approved
}
private fun signIn(data: Data) {
if (oAuthPermissionsApproved()) {
startImport(data)
} else {
Log.d("GoogleFitImporter", "Signing In")
GoogleSignIn.requestPermissions(
activity,
data.ordinal,
getGoogleAccount(), fitnessOptions)
}
}
private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
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
arrayOf(startTime, endTime)
}
private fun startImport(data: 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) {
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: 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.dataPoints.forEachIndexed { index, dp ->
val isLast = (index + 1) == dataSet.dataPoints.size
// Global
Log.i(TAG,"Importing Data point:")
Log.i(TAG,"\tType: ${dp.dataType.name}")
Log.i(TAG,"\tStart: ${dp.getStartTimeString()}")
Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}")
// Field Specifics
for (field in dp.dataType.fields) {
Log.i(TAG,"\tField: ${field.name} Value: ${dp.getValue(field)}")
when (data) {
Data.WEIGHT -> {
val weight = Weight()
weight.timestamp = dp.getStartTime(TimeUnit.MILLISECONDS)
weight.weight = dp.getValue(field).asFloat()
weightCallback(weight, isLast)
}
else -> {}
}
}
}
}
}
.addOnFailureListener { e ->
Log.e(TAG,"There was an error reading data from Google Fit", e)
}
}
private fun DataPoint.getStartTimeString(): String = Date(this.getStartTime(TimeUnit.SECONDS) * 1000L).toLocaleString()
private fun DataPoint.getEndTimeString(): String = Date(this.getEndTime(TimeUnit.SECONDS) * 1000L).toLocaleString()
override fun onRequestPermissionResult(
requestCode: Int,
permission: Array<String>,
grantResult: IntArray
) {
signIn(Data.values()[requestCode])
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
signIn(Data.values()[requestCode])
}
private lateinit var weightCallback: (weight: Weight, end: Boolean) -> Unit
override fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
this.weightCallback = callback
checkPermissionsAndRun(Data.WEIGHT)
}
private lateinit var callback : (item: Any, end: Boolean) -> Unit
override fun <T> import(data: Data, cb: (item: T, end: Boolean) -> Unit) {
callback = cb as (item: Any, end: Boolean) -> Unit
when (data) {
Data.WEIGHT -> {
checkPermissionsAndRun(data)
}
else -> {
Log.d(TAG, "PRRRRRRRRRRRRR")
}
}
}
}

View File

@ -1,4 +1,4 @@
package com.dzeio.openhealth.connectors
package com.dzeio.openhealth.extensions
import android.Manifest
import android.app.Activity
@ -7,7 +7,6 @@ import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import com.dzeio.openhealth.data.AppDatabase
import com.dzeio.openhealth.data.weight.Weight
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.fitness.Fitness
@ -22,7 +21,6 @@ import com.google.android.gms.fitness.request.OnDataPointListener
import com.google.android.gms.fitness.request.SensorRequest
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.time.*
import java.util.*
import java.util.concurrent.TimeUnit

View File

@ -1,108 +1,108 @@
package com.dzeio.openhealth.connectors.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.connectors.Connector
import com.dzeio.openhealth.data.weight.Weight
import com.samsung.android.sdk.healthdata.*
import com.samsung.android.sdk.healthdata.HealthConstants.StepCount
import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
/**
* Does not FUCKING work
*/
class SamsungHealth(
private val context: Activity
) : Connector() {
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
*/
override val sourceID: String = "SamsungHealth"
override fun onRequestPermissionResult(
requestCode: Int,
permission: Array<String>,
grantResult: IntArray
) {}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
override fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
store.connectService()
}
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.extensions.Extension
import com.dzeio.openhealth.data.weight.Weight
import com.samsung.android.sdk.healthdata.*
import com.samsung.android.sdk.healthdata.HealthConstants.StepCount
import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
/**
* Does not FUCKING work
*/
class SamsungHealth(
private val context: Activity
) : Extension() {
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
*/
override val sourceID: String = "SamsungHealth"
override fun onRequestPermissionResult(
requestCode: Int,
permission: Array<String>,
grantResult: IntArray
) {}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
override fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
store.connectService()
}
}

View File

@ -1,4 +1,4 @@
package com.dzeio.openhealth.connectors.samsunghealth
package com.dzeio.openhealth.extensions.samsunghealth
import android.os.Handler
import android.util.Log

View File

@ -1,32 +1,42 @@
package com.dzeio.openhealth.ui.main.home
package com.dzeio.openhealth.ui.home
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.commit
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.weight.Water
import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.ui.dialogs.AddWeightDialog
import com.dzeio.openhealth.ui.main.list_weight.ListWeightFragment
import com.dzeio.openhealth.ui.weight.AddWeightDialog
import com.dzeio.openhealth.utils.BitmapUtils
import com.dzeio.openhealth.utils.DrawUtils
import com.github.mikephil.charting.components.AxisBase
import com.github.mikephil.charting.components.Description
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.formatter.ValueFormatter
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
@ -50,6 +60,54 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
AddWeightDialog().show(requireActivity().supportFragmentManager, null)
}
binding.fragmentHomeWaterAdd.setOnClickListener {
lifecycleScope.launchWhenStarted {
Log.d(TAG, "Collecting latest $this")
if (viewModel.fetchTodayWater().count() == 0) {
Log.d(TAG, "No value, Adding...")
val w = Water()
w.value = 200
viewModel.updateWater(w)
return@launchWhenStarted
}
try {
viewModel.fetchTodayWater().count()
val item = viewModel.fetchTodayWater().lastOrNull()
Log.d(TAG, "Collected latest $item")
if (item == null) {
val w = Water()
w.value = 200
viewModel.updateWater(w)
} else {
item.value += 200
viewModel.updateWater(item)
}
} catch (e: IOException) {
Log.e(TAG, "EXCEPTION", e)
}
}
}
binding.fragmentHomeWaterRemove.setOnClickListener {
lifecycleScope.launch {
val item = viewModel.fetchTodayWater().first()
Log.d(TAG, "Collected latest $it")
if (item != null) {
item.value -= 200
if (item.value == 0) {
viewModel.deleteWater(item)
} else {
viewModel.updateWater(item)
}
}
}
}
binding.listWeight.setOnClickListener {
Log.d("T", "Trying to move")
@ -106,7 +164,10 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
binding.weightGraph.axisRight.setDrawGridLines(false)
binding.weightGraph.xAxis.valueFormatter = object : ValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(value.toLong()))
return SimpleDateFormat(
"yyyy-MM-dd",
Locale.getDefault()
).format(Date(value.toLong()))
//return super.getAxisLabel(value, axis)
}
}
@ -121,6 +182,7 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
// )
}
@SuppressLint("PrivateResource")
override fun onStart() {
super.onStart()
@ -132,68 +194,48 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
updateGraph(it)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d(TAG, "Activity Result!")
when (resultCode) {
RESULT_OK -> {
// fit.performActionForRequestCode(ActionRequestCode.FIND_DATA_SOURCES)
}
else -> {
Log.e(TAG, "Error: $requestCode, $resultCode")
lifecycleScope.launchWhenStarted {
viewModel.fetchTodayWater().collect {
Log.d(TAG, "Pouet? $it")
if (it != null) {
updateWater(it.value)
} else {
updateWater(0)
}
}
}
updateWater(0)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
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 updateWater(water: Int) {
binding.fragmentHomeWaterCurrent.text = water.toString()
grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
Log.d(TAG, "Granted")
// Permission was granted.
// val fitActionRequestCode = ActionRequestCode.values()[requestCode]
// fitActionRequestCode.let {
// // fit.signIn(ActionRequestCode.FIND_DATA_SOURCES)
// }
}
else -> {
// Permission denied.
val graph = BitmapUtils.convertToMutable(
requireContext(),
BitmapFactory.decodeResource(resources, R.drawable.ellipse)
)
// 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.
graph?.let { btmp ->
val canvas = Canvas(btmp)
DrawUtils.drawArc(
canvas,
100 * water / 1200,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
)
)
canvas.save()
// 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()
}
// val params = binding.background.layoutParams
// params.height = binding.background.measuredWidth
// binding.background.layoutParams = params
binding.background.setImageBitmap(graph)
}
}
}

View File

@ -1,14 +1,20 @@
package com.dzeio.openhealth.ui.main.home
package com.dzeio.openhealth.ui.home
import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.weight.Water
import com.dzeio.openhealth.data.weight.WaterRepository
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.data.weight.WeightRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject internal constructor(
private val weightRepository: WeightRepository
private val weightRepository: WeightRepository,
private val waterRepository: WaterRepository
) : BaseViewModel() {
fun fetchWeights() = weightRepository.getWeights()
@ -20,4 +26,11 @@ class HomeViewModel @Inject internal constructor(
suspend fun deleteWeight(weight: Weight) = weightRepository.deleteWeight(weight)
suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight)
fun fetchTodayWater() = waterRepository.todayWater()
suspend fun updateWater(water: Water) = waterRepository.addWater(water)
suspend fun deleteWater(water: Water) = waterRepository.deleteWater(water)
}

View File

@ -1,4 +1,4 @@
package com.dzeio.openhealth.ui.main.imports
package com.dzeio.openhealth.ui.imports
import android.app.ProgressDialog
import android.content.Intent
@ -11,10 +11,10 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.lifecycle.lifecycleScope
import com.dzeio.openhealth.connectors.Connector
import com.dzeio.openhealth.connectors.GoogleFit
import com.dzeio.openhealth.extensions.Extension
import com.dzeio.openhealth.extensions.GoogleFit
//import com.dzeio.openhealth.connectors.GoogleFit
import com.dzeio.openhealth.connectors.samsunghealth.SamsungHealth
import com.dzeio.openhealth.extensions.samsunghealth.SamsungHealth
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentImportBinding
import dagger.hilt.android.AndroidEntryPoint
@ -30,7 +30,7 @@ class ImportFragment : BaseFragment<ImportViewModel, FragmentImportBinding>(Impo
private lateinit var progressDialog: ProgressDialog
private lateinit var fit: Connector
private lateinit var fit: Extension
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -43,9 +43,7 @@ class ImportFragment : BaseFragment<ImportViewModel, FragmentImportBinding>(Impo
}
binding.importGoogleFit.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
importFromGoogleFit()
}
importFromGoogleFit()
}
binding.importSamsungHealth.setOnClickListener {
importFromSamsungHealth()

View File

@ -1,11 +1,10 @@
package com.dzeio.openhealth.ui.main.imports
package com.dzeio.openhealth.ui.imports
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 java.security.CodeSource
import javax.inject.Inject

View File

@ -0,0 +1,2 @@
package com.dzeio.openhealth.ui.water

View File

@ -1,68 +1,67 @@
package com.dzeio.openhealth.ui.dialogs
import android.app.AlertDialog
import android.view.LayoutInflater
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseDialog
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.DialogAddWeightBinding
import com.dzeio.openhealth.ui.main.home.HomeViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
@AndroidEntryPoint
class AddWeightDialog : BaseDialog<HomeViewModel, DialogAddWeightBinding>(HomeViewModel::class.java) {
override val bindingInflater: (LayoutInflater) -> DialogAddWeightBinding = DialogAddWeightBinding::inflate
override fun onBuilderInit(builder: MaterialAlertDialogBuilder) {
super.onBuilderInit(builder)
builder.apply {
setTitle("Add your weight (kg)")
setIcon(activity?.let { ContextCompat.getDrawable(it, R.drawable.ic_outline_timeline_24) })
setPositiveButton("Validate") { dialog, _ ->
save()
}
setNegativeButton("Cancel") { dialog, _ ->
dialog.cancel()
}
}
}
override fun onCreated() {
super.onCreated()
lifecycleScope.launchWhenStarted {
viewModel.lastWeight().collect {
if (it != null) {
binding.kg.value = it.weight.toInt()
binding.gram.value = ((it.weight - it.weight.toInt()) * 10 ).toInt()
}
}
}
binding.kg.maxValue = 636
binding.kg.minValue = 0
binding.gram.maxValue = 9
binding.gram.minValue = 0
}
private fun save() {
val weight = Weight().apply {
weight = binding.kg.value + (binding.gram.value.toFloat() / 10)
source = "OpenHealth"
}
lifecycleScope.launchWhenCreated {
viewModel.addWeight(weight)
}
//callback?.invoke()
dialog?.dismiss()
}
package com.dzeio.openhealth.ui.weight
import android.view.LayoutInflater
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseDialog
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.DialogAddWeightBinding
import com.dzeio.openhealth.ui.home.HomeViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
@AndroidEntryPoint
class AddWeightDialog : BaseDialog<HomeViewModel, DialogAddWeightBinding>(HomeViewModel::class.java) {
override val bindingInflater: (LayoutInflater) -> DialogAddWeightBinding = DialogAddWeightBinding::inflate
override fun onBuilderInit(builder: MaterialAlertDialogBuilder) {
super.onBuilderInit(builder)
builder.apply {
setTitle("Add your weight (kg)")
setIcon(activity?.let { ContextCompat.getDrawable(it, R.drawable.ic_outline_timeline_24) })
setPositiveButton("Validate") { dialog, _ ->
save()
}
setNegativeButton("Cancel") { dialog, _ ->
dialog.cancel()
}
}
}
override fun onCreated() {
super.onCreated()
lifecycleScope.launchWhenStarted {
viewModel.lastWeight().collect {
if (it != null) {
binding.kg.value = it.weight.toInt()
binding.gram.value = ((it.weight - it.weight.toInt()) * 10 ).toInt()
}
}
}
binding.kg.maxValue = 636
binding.kg.minValue = 0
binding.gram.maxValue = 9
binding.gram.minValue = 0
}
private fun save() {
val weight = Weight().apply {
weight = binding.kg.value + (binding.gram.value.toFloat() / 10)
source = "OpenHealth"
}
lifecycleScope.launchWhenCreated {
viewModel.addWeight(weight)
}
//callback?.invoke()
dialog?.dismiss()
}
}

View File

@ -1,162 +1,165 @@
package com.dzeio.openhealth.ui.dialogs
import android.app.AlertDialog
import android.app.DatePickerDialog
import android.app.Dialog
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.ViewGroup
import android.view.Window
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFullscreenDialog
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.DialogAddWeightBinding
import com.dzeio.openhealth.databinding.DialogEditWeightBinding
import com.dzeio.openhealth.ui.main.home.HomeViewModel
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.timepicker.MaterialTimePicker
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import java.util.*
@AndroidEntryPoint
class EditWeightDialog : BaseFullscreenDialog<HomeViewModel, DialogEditWeightBinding>(HomeViewModel::class.java) {
override val bindingInflater: (LayoutInflater) -> DialogEditWeightBinding = DialogEditWeightBinding::inflate
override val isFullscreenLayout = true
val args: EditWeightDialogArgs by navArgs()
lateinit var weight: Weight
// override fun onBuilderInit(builder: AlertDialog.Builder) {
// super.onBuilderInit(builder)
//
// builder.apply {
// setTitle("Add your weight (kg)")
// setIcon(activity?.let { ContextCompat.getDrawable(it, R.drawable.ic_outline_timeline_24) })
// setPositiveButton("Validate") { dialog, _ ->
// save()
// }
// setNegativeButton("Cancel") { dialog, _ ->
// dialog.cancel()
// }
//
// }
// }
override fun onDialogInit(dialog: Dialog) {
super.onDialogInit(dialog)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
}
override fun onCreated(savedInstanceState: Bundle?) {
super.onCreated(savedInstanceState)
lifecycleScope.launchWhenStarted {
viewModel.fetchWeight(args.id).collect {
weight = it!!
binding.layoutDialogEditWeightKg.value = it.weight.toInt()
binding.layoutDialogEditWeightGram.value = ((it.weight - it.weight.toInt()) * 10 ).toInt()
binding.layoutDialogEditWeightTimestamp.setText(it.formatTimestamp())
}
}
binding.layoutDialogEditWeightKg.maxValue = 636
binding.layoutDialogEditWeightKg.minValue = 0
binding.layoutDialogEditWeightGram.maxValue = 9
binding.layoutDialogEditWeightGram.minValue = 0
binding.layoutDialogEditWeightTimestamp.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val date = Date(weight.timestamp)
val datePicker = MaterialDatePicker.Builder.datePicker()
.setTitleText("Select Date")
.setSelection(weight.timestamp)
.build()
val fragManager = requireActivity().supportFragmentManager
datePicker.addOnPositiveButtonClickListener { tsp ->
val timePicker = MaterialTimePicker.Builder()
.setHour(date.hours)
.setMinute(date.minutes)
.setTitleText("Pouet")
.build()
timePicker.addOnPositiveButtonClickListener {
Log.d("T", "${timePicker.hour} ${timePicker.minute}")
val newDate = Date(tsp)
newDate.hours = timePicker.hour
newDate.minutes = timePicker.minute
weight.timestamp = newDate.time
binding.layoutDialogEditWeightTimestamp.setText(weight.formatTimestamp())
Log.d("Res", newDate.time.toString())
}
timePicker.show(fragManager, "dialog")
}
datePicker.show(fragManager, "dialog")
Log.d("Tag", "${date.year + 1900}, ${date.month}, ${date.day}")
// val dg = DatePickerDialog(requireActivity())
// dg.setOnDateSetListener { _, year, month, day ->
//
// }
// dg.updateDate(date.year + 1900, date.month, date.day)
// dg.show()
} else {
TODO("VERSION.SDK_INT < N")
}
}
}
private fun save() {
lifecycleScope.launchWhenCreated {
weight.weight = binding.layoutDialogEditWeightKg.value + (binding.layoutDialogEditWeightGram.value.toFloat() / 10)
viewModel.addWeight(weight)
findNavController().popBackStack()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_fullscreen_dialog_save -> {
save()
true
}
R.id.menu_fullscreen_dialog_delete -> {
MaterialAlertDialogBuilder(requireContext())
.setTitle("Delete Weight?")
.setMessage("Are you sure you want to delete this weight?")
.setPositiveButton("Yes") {_, _ ->
lifecycleScope.launchWhenStarted {
viewModel.deleteWeight(weight)
findNavController().popBackStack()
}
}
.setIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_outline_delete_24))
.setNegativeButton("No") { _, _ ->}
.show()
true
}
else -> super.onOptionsItemSelected(item)
}
}
package com.dzeio.openhealth.ui.weight
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.Window
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFullscreenDialog
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.DialogEditWeightBinding
import com.dzeio.openhealth.ui.home.HomeViewModel
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.timepicker.MaterialTimePicker
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import java.util.*
@AndroidEntryPoint
class EditWeightDialog :
BaseFullscreenDialog<HomeViewModel, DialogEditWeightBinding>(HomeViewModel::class.java) {
override val bindingInflater: (LayoutInflater) -> DialogEditWeightBinding =
DialogEditWeightBinding::inflate
override val isFullscreenLayout = true
val args: EditWeightDialogArgs by navArgs()
lateinit var weight: Weight
// override fun onBuilderInit(builder: AlertDialog.Builder) {
// super.onBuilderInit(builder)
//
// builder.apply {
// setTitle("Add your weight (kg)")
// setIcon(activity?.let { ContextCompat.getDrawable(it, R.drawable.ic_outline_timeline_24) })
// setPositiveButton("Validate") { dialog, _ ->
// save()
// }
// setNegativeButton("Cancel") { dialog, _ ->
// dialog.cancel()
// }
//
// }
// }
override fun onDialogInit(dialog: Dialog) {
super.onDialogInit(dialog)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
}
override fun onCreated(savedInstanceState: Bundle?) {
super.onCreated(savedInstanceState)
lifecycleScope.launchWhenStarted {
viewModel.fetchWeight(args.id).collect {
weight = it!!
binding.layoutDialogEditWeightKg.value = it.weight.toInt()
binding.layoutDialogEditWeightGram.value =
((it.weight - it.weight.toInt()) * 10).toInt()
binding.layoutDialogEditWeightTimestamp.setText(it.formatTimestamp())
}
}
binding.layoutDialogEditWeightKg.maxValue = 636
binding.layoutDialogEditWeightKg.minValue = 0
binding.layoutDialogEditWeightGram.maxValue = 9
binding.layoutDialogEditWeightGram.minValue = 0
binding.layoutDialogEditWeightTimestamp.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val date = Date(weight.timestamp)
val datePicker = MaterialDatePicker.Builder.datePicker()
.setTitleText("Select Date")
.setSelection(weight.timestamp)
.build()
val fragManager = requireActivity().supportFragmentManager
datePicker.addOnPositiveButtonClickListener { tsp ->
val timePicker = MaterialTimePicker.Builder()
.setHour(date.hours)
.setMinute(date.minutes)
.setTitleText("Pouet")
.build()
timePicker.addOnPositiveButtonClickListener {
Log.d("T", "${timePicker.hour} ${timePicker.minute}")
val newDate = Date(tsp)
newDate.hours = timePicker.hour
newDate.minutes = timePicker.minute
weight.timestamp = newDate.time
binding.layoutDialogEditWeightTimestamp.setText(weight.formatTimestamp())
Log.d("Res", newDate.time.toString())
}
timePicker.show(fragManager, "dialog")
}
datePicker.show(fragManager, "dialog")
Log.d("Tag", "${date.year + 1900}, ${date.month}, ${date.day}")
// val dg = DatePickerDialog(requireActivity())
// dg.setOnDateSetListener { _, year, month, day ->
//
// }
// dg.updateDate(date.year + 1900, date.month, date.day)
// dg.show()
} else {
TODO("VERSION.SDK_INT < N")
}
}
}
private fun save() {
lifecycleScope.launchWhenCreated {
weight.weight =
binding.layoutDialogEditWeightKg.value + (binding.layoutDialogEditWeightGram.value.toFloat() / 10)
viewModel.addWeight(weight)
findNavController().popBackStack()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_fullscreen_dialog_save -> {
save()
true
}
R.id.menu_fullscreen_dialog_delete -> {
MaterialAlertDialogBuilder(requireContext())
.setTitle("Delete Weight?")
.setMessage("Are you sure you want to delete this weight?")
.setPositiveButton("Yes") { _, _ ->
lifecycleScope.launchWhenStarted {
viewModel.deleteWeight(weight)
findNavController().popBackStack()
}
}
.setIcon(
ContextCompat.getDrawable(
requireContext(),
R.drawable.ic_outline_delete_24
)
)
.setNegativeButton("No") { _, _ -> }
.show()
true
}
else -> super.onOptionsItemSelected(item)
}
}
}

View File

@ -1,54 +1,53 @@
package com.dzeio.openhealth.ui.main.list_weight
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.dzeio.openhealth.adapters.WeightAdapter
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.ui.dialogs.EditWeightDialog
import com.dzeio.openhealth.ui.main.home.HomeViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import java.util.*
@AndroidEntryPoint
class ListWeightFragment : BaseFragment<HomeViewModel, FragmentListWeightBinding>(HomeViewModel::class.java) {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentListWeightBinding = FragmentListWeightBinding::inflate
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recycler = binding.list
val manager = LinearLayoutManager(requireContext())
recycler.layoutManager = manager
val adapter = WeightAdapter()
adapter.onItemClick = {
findNavController().navigate(ListWeightFragmentDirections.actionNavListWeightToNavEditWeight(it.id))
//EditWeightDialog().show(requireActivity().supportFragmentManager, "dialog")
}
recycler.adapter = adapter
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
viewModel.fetchWeights().collectLatest {
val itt = it.toMutableList()
itt.sortWith { o1, o2 -> if (o1.timestamp > o2.timestamp) -1 else 1 }
adapter.set(itt)
}
}
}
package com.dzeio.openhealth.ui.weight
import android.os.Bundle
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.WeightAdapter
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.ui.home.HomeViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
@AndroidEntryPoint
class ListWeightFragment :
BaseFragment<HomeViewModel, FragmentListWeightBinding>(HomeViewModel::class.java) {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentListWeightBinding =
FragmentListWeightBinding::inflate
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recycler = binding.list
val manager = LinearLayoutManager(requireContext())
recycler.layoutManager = manager
val adapter = WeightAdapter()
adapter.onItemClick = {
findNavController().navigate(
ListWeightFragmentDirections.actionNavListWeightToNavEditWeight(
it.id
)
)
//EditWeightDialog().show(requireActivity().supportFragmentManager, "dialog")
}
recycler.adapter = adapter
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
viewModel.fetchWeights().collectLatest {
val itt = it.toMutableList()
itt.sortWith { o1, o2 -> if (o1.timestamp > o2.timestamp) -1 else 1 }
adapter.set(itt)
}
}
}
}

View File

@ -0,0 +1,49 @@
package com.dzeio.openhealth.utils
import android.content.Context
import android.graphics.Bitmap
import java.io.File
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
object BitmapUtils {
/**
* Find source of function lol
*/
fun convertToMutable(context: Context, imgIn: Bitmap): Bitmap? {
val width = imgIn.width
val height = imgIn.height
val type = imgIn.config
var outputFile: File? = null
val outputDir = context.cacheDir
try {
outputFile = File.createTempFile(
System.currentTimeMillis().toString(),
null,
outputDir
)
outputFile.deleteOnExit()
val randomAccessFile = RandomAccessFile(outputFile, "rw")
val channel = randomAccessFile.channel
val map = channel.map(
FileChannel.MapMode.READ_WRITE,
0,
(imgIn.rowBytes * height).toLong()
)
imgIn.copyPixelsToBuffer(map)
imgIn.recycle()
val result = Bitmap.createBitmap(width, height, type)
map.position(0)
result.copyPixelsFromBuffer(map)
channel.close()
randomAccessFile.close()
outputFile.delete()
return result
} catch (e: Exception) {
} finally {
outputFile?.delete()
}
return null
}
}

View File

@ -0,0 +1,30 @@
package com.dzeio.openhealth.utils
import android.graphics.*
object DrawUtils {
/**
* Fuck Graphics
*/
fun drawArc(canvas: Canvas, percent: Int, pColor: Int) {
canvas.width
val spacing = 120f
val r1 = RectF(
spacing,
spacing,
canvas.width - spacing,
canvas.height * 2 - spacing * 3
)
val paint = Paint()
paint.apply {
strokeWidth = 200f
style = Paint.Style.STROKE
color = pColor
isAntiAlias = true
strokeCap = Paint.Cap.ROUND
}
canvas.drawArc(r1, 180f, 180 * percent / 100f, false, paint)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="156dp"
android:height="81dp"
android:viewportWidth="156"
android:viewportHeight="81">
<path
android:pathData="M153,78C153,36.579 119.421,3 78,3C36.579,3 3,36.579 3,78"
android:strokeWidth="5"
android:fillColor="#00000000"
android:strokeColor="#C9C5CA"
android:strokeLineCap="round"/>
</vector>

View File

@ -1,10 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.home.HomeFragment">
android:orientation="vertical"
tools:context=".ui.home.HomeFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginHorizontal="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:weightSum="2">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Weight"
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="16dp"
android:src="@drawable/ic_baseline_add_24" />
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_outline_hexagon_24" />
</LinearLayout>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="8dp"
android:gravity="bottom"
android:weightSum="3">
<ImageView
android:id="@+id/fragment_home_water_remove"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="4dp"
android:layout_weight="1"
android:src="@drawable/ic_outline_hexagon_24"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
app:layout_constraintEnd_toStartOf="@+id/linearLayout"
app:layout_constraintTop_toTopOf="@+id/linearLayout" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/background">
<TextView
android:id="@+id/fragment_home_water_current"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="900ml"
android:textAlignment="center" />
<TextView
android:id="@+id/fragment_home_water_total"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="1200ml"
android:textAlignment="center" />
</LinearLayout>
<ImageView
android:id="@+id/fragment_home_water_add"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:src="@drawable/ic_baseline_add_24"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
app:layout_constraintStart_toEndOf="@+id/linearLayout"
app:layout_constraintTop_toTopOf="@+id/linearLayout" />
<ImageView
android:id="@+id/background"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="2:1"
android:src="@drawable/ic_outline_hexagon_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Unused Currently -->
<!-- <com.google.android.material.card.MaterialCardView-->
<!-- style="?attr/materialCardViewFilledStyle"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="16dp"-->
<!-- android:layout_marginTop="16dp"-->
<!-- android:layout_weight="1"-->
<!-- android:layout_marginEnd="16dp">-->
<!-- </com.google.android.material.card.MaterialCardView>-->
</LinearLayout>
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle"
@ -12,41 +159,34 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:layout_marginEnd="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout2"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginHorizontal="16dp"
android:gravity="fill_horizontal|center_vertical"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="16dp">
android:weightSum="2">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Weight"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:layout_weight="1"
android:gravity="end">
<ImageView
android:id="@+id/add_weight"
android:layout_width="24dp"
@ -58,24 +198,17 @@
android:id="@+id/list_weight"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_outline_hexagon_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:src="@drawable/ic_outline_hexagon_24" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/weight_graph"
android:layout_width="match_parent"
android:layout_height="200dp"
android:minHeight="200dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/constraintLayout2" />
android:minHeight="200dp" />
<!-- <com.jjoe64.graphview.GraphView-->
<!-- android:id="@+id/weight_graph"-->
@ -87,8 +220,8 @@
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toBottomOf="@+id/constraintLayout2" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.imports.ImportFragment">
tools:context=".ui.imports.ImportFragment">
<TextView
android:id="@+id/text_slideshow"

View File

@ -11,4 +11,4 @@
android:padding="16dp"
tools:listitem="@layout/layout_item_weight"
tools:context=".ui.main.list_weight.ListWeightFragment" />
tools:context=".ui.weight.ListWeightFragment" />

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,7 +7,7 @@
<fragment
android:id="@+id/nav_home"
android:name="com.dzeio.openhealth.ui.main.home.HomeFragment"
android:name="com.dzeio.openhealth.ui.home.HomeFragment"
android:label="@string/menu_home"
tools:layout="@layout/fragment_home" >
<action
@ -28,13 +28,13 @@
<fragment
android:id="@+id/nav_import"
android:name="com.dzeio.openhealth.ui.main.imports.ImportFragment"
android:name="com.dzeio.openhealth.ui.imports.ImportFragment"
android:label="@string/menu_import"
tools:layout="@layout/fragment_import" />
<fragment
android:id="@+id/nav_list_weight"
android:name="com.dzeio.openhealth.ui.main.list_weight.ListWeightFragment"
android:name="com.dzeio.openhealth.ui.weight.ListWeightFragment"
android:label="@string/menu_list_weight"
tools:layout="@layout/fragment_list_weight" >
<action
@ -48,7 +48,7 @@
<fragment
android:id="@+id/nav_edit_weight"
android:name="com.dzeio.openhealth.ui.dialogs.EditWeightDialog"
android:name="com.dzeio.openhealth.ui.weight.EditWeightDialog"
android:label="@string/menu_edit_weight"
tools:layout="@layout/dialog_edit_weight">