1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-06-12 17:19:18 +00:00

refactor: Create gradlew_recursive.sh (#8)

This commit is contained in:
2022-01-03 00:33:25 +01:00
committed by GitHub
parent be6c0a2b8e
commit aee0022f21
73 changed files with 1481 additions and 1000 deletions

View File

@ -9,36 +9,87 @@ plugins {
}
android {
signingConfigs {
release {
def keystorePropertiesFile = rootProject.file("./keystore.properties")
def keystoreProperties = new Properties()
try {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} catch (FileNotFoundException ignored) {
keystoreProperties = null
}
if (keystoreProperties != null) {
storePassword keystoreProperties["storePassword"]
keyPassword keystoreProperties["keyPassword"]
keyAlias keystoreProperties["keyAlias"]
storeFile file(keystoreProperties["storeFile"])
}
}
}
compileSdk 31
defaultConfig {
// App ID
applicationId "com.dzeio.openhealth"
// Android 5 Lollipop
minSdk 21
// Android 12
targetSdk 31
versionCode 1
versionName "1.0"
// Semantic Versioning
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
applicationIdSuffix ".dev"
versionNameSuffix '-dev'
debuggable true
// make it debuggable
renderscriptDebuggable true
// Optimization Level
renderscriptOptimLevel 0
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
dataBinding true
}
}
dependencies {
@ -81,6 +132,7 @@ dependencies {
// Samsung Health
implementation files('libs/samsung-health-data-1.5.0.aar')
implementation "com.google.code.gson:gson:2.8.9"
// ROOM
def room_version = "2.4.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,47 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.15074074"
android:scaleY="0.15074074"
android:translateX="29.58"
android:translateY="29.58">
<path
android:pathData="M199.975,232V199.975H312V124.025H199.975V12H124.025V124.025H12V199.975H124.025V312H199.975V286.5"
android:strokeWidth="23"
android:fillColor="#00000000"
android:strokeLineCap="square">
<aapt:attr name="android:strokeColor">
<gradient
android:startY="12"
android:startX="12"
android:endY="312"
android:endX="312"
android:type="linear">
<item android:offset="0" android:color="#FF80E27E"/>
<item android:offset="1" android:color="#FF4CAF50"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M275,161.5H48M161.5,48V275"
android:strokeLineJoin="round"
android:strokeWidth="13"
android:fillColor="#00000000"
android:strokeLineCap="square">
<aapt:attr name="android:strokeColor">
<gradient
android:startY="24.7418"
android:startX="22.416"
android:endY="301.98"
android:endX="298.723"
android:type="linear">
<item android:offset="0" android:color="#FF80E27E"/>
<item android:offset="1" android:color="#FF4CAF50"/>
</gradient>
</aapt:attr>
</path>
</group>
</vector>

View File

@ -0,0 +1,42 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="324dp"
android:height="324dp"
android:viewportWidth="324"
android:viewportHeight="324">
<path
android:pathData="M199.975,232V199.975H312V124.025H199.975V12H124.025V124.025H12V199.975H124.025V312H199.975V286.5"
android:strokeWidth="23"
android:fillColor="#00000000"
android:strokeLineCap="square">
<aapt:attr name="android:strokeColor">
<gradient
android:startY="12"
android:startX="12"
android:endY="312"
android:endX="312"
android:type="linear">
<item android:offset="0" android:color="#FF80E27E"/>
<item android:offset="1" android:color="#FF4CAF50"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M275,161.5H48M161.5,48V275"
android:strokeLineJoin="round"
android:strokeWidth="13"
android:fillColor="#00000000"
android:strokeLineCap="square">
<aapt:attr name="android:strokeColor">
<gradient
android:startY="24.7418"
android:startX="22.416"
android:endY="301.98"
android:endX="298.723"
android:type="linear">
<item android:offset="0" android:color="#FF80E27E"/>
<item android:offset="1" android:color="#FF4CAF50"/>
</gradient>
</aapt:attr>
</path>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">OpenHealth - Debug</string>
</resources>

View File

@ -6,8 +6,8 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Phone Services -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION " />
<!-- Phone Sensors -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<!-- Samsung Health-->
<queries>

View File

@ -1,6 +1,7 @@
package com.dzeio.openhealth
import android.app.Application
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
@ -8,4 +9,11 @@ class Application : Application() {
companion object {
const val TAG = "OpenHealth"
}
override fun onCreate() {
super.onCreate()
// Android Dynamics Colors
DynamicColors.applyToActivitiesIfAvailable(this)
}
}

View File

@ -10,22 +10,17 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.work.WorkManager
import com.dzeio.openhealth.core.BaseActivity
import com.dzeio.openhealth.databinding.ActivityMainBinding
import com.dzeio.openhealth.interfaces.NotificationChannels
import com.dzeio.openhealth.services.WaterReminderService
import com.dzeio.openhealth.ui.home.HomeFragmentDirections
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@ -45,6 +40,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(
@ -69,9 +65,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
return true
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onSupportNavigateUp(): Boolean =
navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
override fun onRequestPermissionsResult(
requestCode: Int,
@ -94,21 +89,23 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
private fun createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
for (channel in NotificationChannels.values()) {
notificationManager.createNotificationChannel(
NotificationChannel(
channel.id,
channel.channelName,
channel.importance
Log.d("MainActivity", channel.channelName)
try {
notificationManager.createNotificationChannel(
NotificationChannel(
channel.id,
channel.channelName,
channel.importance
)
)
)
} catch (e: Exception) {
Log.e("MainActivity", "Error Creating Notification Channel", e)
}
}
}

View File

@ -0,0 +1,28 @@
package com.dzeio.openhealth.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.databinding.LayoutExtensionItemBinding
import com.dzeio.openhealth.extensions.Extension
class ExtensionAdapter() : BaseAdapter<Extension, LayoutExtensionItemBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutExtensionItemBinding
get() = LayoutExtensionItemBinding::inflate
var onItemClick: ((weight: Extension) -> Unit)? = null
override fun onBindData(
holder: BaseViewHolder<LayoutExtensionItemBinding>,
item: Extension,
position: Int
) {
holder.binding.name.text = item.name
holder.binding.status.text = item.getStatus()
holder.binding.card.setOnClickListener {
onItemClick?.invoke(item)
}
}
}

View File

@ -1,14 +1,11 @@
package com.dzeio.openhealth.adapters
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.LayoutItemListBinding
import com.dzeio.openhealth.databinding.LayoutItemWeightBinding
class WaterAdapter() : BaseAdapter<Water, LayoutItemListBinding>() {
@ -23,7 +20,7 @@ class WaterAdapter() : BaseAdapter<Water, LayoutItemListBinding>() {
position: Int
) {
holder.binding.value.text = "${item.value}ml"
holder.binding.datetime.text = item.formatTimestamp()
holder.binding.datetime.text = "${item.formatTimestamp()} ${item.timestamp}"
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)
}

View File

@ -1,26 +1,25 @@
package com.dzeio.openhealth.adapters
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import com.dzeio.openhealth.core.BaseAdapter
import com.dzeio.openhealth.core.BaseViewHolder
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.LayoutItemWeightBinding
import com.dzeio.openhealth.databinding.LayoutItemListBinding
class WeightAdapter() : BaseAdapter<Weight, LayoutItemWeightBinding>() {
class WeightAdapter() : BaseAdapter<Weight, LayoutItemListBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemWeightBinding
get() = LayoutItemWeightBinding::inflate
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemListBinding
get() = LayoutItemListBinding::inflate
var onItemClick: ((weight: Weight) -> Unit)? = null
override fun onBindData(
holder: BaseViewHolder<LayoutItemWeightBinding>,
holder: BaseViewHolder<LayoutItemListBinding>,
item: Weight,
position: Int
) {
holder.binding.weight.text = "${item.weight}kg"
holder.binding.value.text = "${item.weight}kg"
holder.binding.datetime.text = item.formatTimestamp()
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)

View File

@ -1,14 +1,14 @@
package com.dzeio.openhealth.data.water
import androidx.room.*
import androidx.room.Dao
import androidx.room.Query
import com.dzeio.openhealth.core.BaseDao
import dagger.Provides
import kotlinx.coroutines.flow.Flow
@Dao
interface WaterDao : BaseDao<Water> {
@Query("SELECT * FROM Water ORDER BY timestamp")
@Query("SELECT * FROM Water ORDER BY timestamp DESC")
fun getAll(): Flow<List<Water>>
@Query("SELECT * FROM Water where id = :weightId")

View File

@ -7,8 +7,9 @@ import java.sql.Date
import java.text.DateFormat.getDateInstance
@Entity()
data class Weight (
@PrimaryKey(autoGenerate = true) var id: Long = 0,
data class Weight(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
var weight: Float = 0f,
@ColumnInfo(index = true)
var timestamp: Long = System.currentTimeMillis(),

View File

@ -1,16 +0,0 @@
package com.dzeio.openhealth.extensions
enum class DataType {
WEIGHT
}
/**
* STEP_COUNT_CUMULATIVE
* ACTIVITY_SEGMENT
* SLEEP_SEGMENT
* CALORIES_EXPENDED
* BASAL_METABOLIC_RATE
* POWER_SAMPLE
* HEART_RATE_BPM
* LOCATION_SAMPLE
*/

View File

@ -2,18 +2,103 @@ package com.dzeio.openhealth.extensions
import android.app.Activity
import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.data.weight.Weight
/**
* Extension Schema
*
* Version: 1.0.0
*/
abstract class Extension {
enum class Data {
WEIGHT,
STEPS
data class ImportState<T>(
val state: States = States.WIP,
val list: List<T> = ArrayList()
)
enum class States {
WIP,
DONE,
CANCELLED
}
abstract val sourceID: String
enum class Data {
/**
* Special case to handle basic errors from other activities
*/
NOTHING,
WEIGHT,
STEPS
open fun init(activity: Activity): Array<Data> = arrayOf()
/**
* STEP_COUNT_CUMULATIVE
* ACTIVITY_SEGMENT
* SLEEP_SEGMENT
* CALORIES_EXPENDED
* BASAL_METABOLIC_RATE
* POWER_SAMPLE
* HEART_RATE_BPM
* LOCATION_SAMPLE
*/
}
/**
* the Source ID
*
* DO NOT CHANGE IT AFTER THE EXTENSION IS IN PRODUCTION
*/
abstract val id: String
/**
* The Extension Display Name
*/
abstract val name: String
/**
* Initialize hte Extension
*
* It is run Before any functions is launched and after events handlers are set
*/
abstract fun init(activity: Activity): Array<Data>
/**
* A status shown on the extension list page
*/
open fun getStatus(): String {
return "No Status set..."
}
/**
* Function that will check
*/
abstract fun isAvailable(): Boolean
/**
* idk
*/
abstract fun isConnected(): Boolean
open fun connect(): LiveData<States> {
return MutableLiveData(States.DONE)
}
open fun importWeight(): LiveData<ImportState<Weight>> {
return MutableLiveData(ImportState(States.DONE))
}
/**
* function run when outgoing sync is enabled and new value is added
* or manual export is launched
*/
open fun exportWeight(weight: Weight): LiveData<States> {
return MutableLiveData(States.DONE)
}
/**
* Activity Events
*/
/**
* Same as Activity/Fragment onRequestPermissionResult
@ -26,8 +111,4 @@ abstract class Extension {
* Same as Activity/Fragment onActivityResult
*/
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

@ -0,0 +1,16 @@
package com.dzeio.openhealth.extensions
class ExtensionFactory {
companion object {
fun getExtension(extension: String): Extension? {
return when (extension) {
"GoogleFit" -> {
GoogleFit()
}
else -> {
null
}
}
}
}
}

View File

@ -7,69 +7,114 @@ import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.data.weight.Weight
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() {
class GoogleFit() : Extension() {
companion object {
const val TAG = "GoogleFitConnector"
}
override val sourceID: String = "GoogleFit"
private lateinit var activity: Activity
override val id = "GoogleFit"
override val name = "Google Fit"
override fun init(activity: Activity): Array<Data> {
this.activity = activity
return arrayOf(
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_STEP_COUNT_CUMULATIVE)
// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
.build()
private fun checkPermissionsAndRun(data: Data) {
if (permissionApproved()) {
signIn(data)
} else {
Log.d(TAG, "Asking for permission")
// Ask for permission
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
data.ordinal
)
}
}
// private 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) {
private fun permissionApproved(): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION)
Manifest.permission.ACCESS_FINE_LOCATION
)
} else {
true
}
return approved
}
private fun signIn(data: Data) {
if (oAuthPermissionsApproved()) {
startImport(data)
private val connectLiveData: MutableLiveData<States> = MutableLiveData(States.WIP)
override fun connect(): LiveData<States> {
if (!permissionApproved()) {
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
87531
)
return connectLiveData
}
if (isConnected()) {
connectLiveData.value = States.DONE
} else {
Log.d("GoogleFitImporter", "Signing In")
GoogleSignIn.requestPermissions(
activity,
data.ordinal,
getGoogleAccount(), fitnessOptions)
124887,
getGoogleAccount(), fitnessOptions
)
}
return connectLiveData
}
private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
// private fun signIn(data: Data) {
// if (isConnected()) {
// startImport(data)
// } else {
// Log.d("GoogleFitImporter", "Signing In")
// GoogleSignIn.requestPermissions(
// activity,
// data.ordinal,
// getGoogleAccount(), fitnessOptions
// )
// }
// }
private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
@ -102,85 +147,116 @@ class GoogleFit(
else -> {}
}
runRequest(DataReadRequest.Builder()
.read(type)
.setTimeRange(timeRange[0], timeRange[1], timeUnit)
.build(), data)
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))
Fitness.getHistoryClient(
activity,
GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
)
.readData(request)
.addOnSuccessListener { response ->
Log.d(TAG, "Received response! ${response.dataSets.size} ${response.buckets.size} ${response.status}")
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
Log.i(
TAG,
"Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size} ${dataSet.dataSource.toDebugString()}"
)
dataSet.dataPoints.forEach { dp ->
// 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()}")
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)}")
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)
val list = weightLiveData.value?.list?.toMutableList()
?: ArrayList()
list.add(weight)
weightLiveData.value =
ImportState(States.WIP, list)
}
else -> {}
}
}
}
when (data) {
Data.WEIGHT -> {
weightLiveData.value =
ImportState(
States.DONE, weightLiveData.value?.list
?: ArrayList()
)
}
else -> {}
}
}
}
.addOnFailureListener { e ->
Log.e(TAG,"There was an error reading data from Google Fit", 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()
/**
* Currently not usable
*/
override fun onRequestPermissionResult(
requestCode: Int,
permission: Array<String>,
grantResult: IntArray
) {
signIn(Data.values()[requestCode])
connect()
// 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")
}
if (requestCode == 0) {
return
}
connectLiveData.value = States.DONE
//signIn(Data.values()[requestCode])
}
private lateinit var weightLiveData: MutableLiveData<ImportState<Weight>>
override fun importWeight(): LiveData<ImportState<Weight>> {
weightLiveData = MutableLiveData(
ImportState(
States.WIP
)
)
startImport(Data.WEIGHT)
// checkPermissionsAndRun(Data.WEIGHT)
return weightLiveData
}
}

View File

@ -1,374 +0,0 @@
package com.dzeio.openhealth.extensions
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
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.DataSource
import com.google.android.gms.fitness.data.DataType
import com.google.android.gms.fitness.request.DataReadRequest
import com.google.android.gms.fitness.request.DataSourcesRequest
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.util.*
import java.util.concurrent.TimeUnit
enum class ActionRequestCode {
FIND_DATA_SOURCES
}
class GoogleFit(
private val activity: Activity,
) {
companion object {
const val TAG = "GoogleFitConnector"
}
// private val fitnessOptions = FitnessOptions.builder()
// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_READ)
// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_WRITE)
//
// .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_READ)
// .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_WRITE)
//
// .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_READ)
// .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_WRITE)
//
// .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_READ)
// .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_WRITE)
// .build()
private val fitnessOptions = FitnessOptions.builder()
// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT)
// .addDataType(DataType.TYPE_SLEEP_SEGMENT)
// .addDataType(DataType.TYPE_CALORIES_EXPENDED)
// .addDataType(DataType.TYPE_BASAL_METABOLIC_RATE)
// .addDataType(DataType.TYPE_POWER_SAMPLE)
// .addDataType(DataType.TYPE_HEART_RATE_BPM)
// .addDataType(DataType.TYPE_LOCATION_SAMPLE)
.addDataType(DataType.TYPE_WEIGHT)
.build()
private val runningQOrLater =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
// [START dataPointListener_variable_reference]
// Need to hold a reference to this listener, as it's passed into the "unregister"
// method in order to stop all sensors from sending data to this listener.
private var dataPointListener: OnDataPointListener? = null
fun import() {
checkPermissionsAndRun(ActionRequestCode.FIND_DATA_SOURCES)
}
private fun checkPermissionsAndRun(actionRequestCode: ActionRequestCode) {
if (permissionApproved()) {
signIn(actionRequestCode)
} else {
requestRuntimePermissions(actionRequestCode)
}
}
private fun requestRuntimePermissions(requestCode: ActionRequestCode) {
val shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION)
// Provide an additional rationale to the user. This would happen if the user denied the
// request previously, but didn't check the "Don't ask again" checkbox.
requestCode.let {
if (shouldProvideRationale) {
Log.i(TAG, "Displaying permission rationale to provide additional context.")
// ProgressDialog.show(activity, "Waiting for authorization...", "")
ActivityCompat.requestPermissions(activity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
requestCode.ordinal)
} else {
Log.i(TAG, "Requesting permission")
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
ActivityCompat.requestPermissions(activity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
requestCode.ordinal)
}
}
}
private fun permissionApproved(): Boolean {
val approved = if (runningQOrLater) {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION)
} else {
true
}
return approved
}
fun signIn(requestCode: ActionRequestCode) {
if (oAuthPermissionsApproved()) {
performActionForRequestCode(requestCode)
} else {
requestCode.let {
GoogleSignIn.requestPermissions(
activity,
it.ordinal,
getGoogleAccount(), fitnessOptions)
}
}
}
private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions)
/**
* Gets a Google account for use in creating the Fitness client. This is achieved by either
* using the last signed-in account, or if necessary, prompting the user to sign in.
* `getAccountForExtension` is recommended over `getLastSignedInAccount` as the latter can
* return `null` if there has been no sign in before.
*/
private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions)
/**
* Runs the desired method, based on the specified request code. The request code is typically
* passed to the Fit sign-in flow, and returned with the success callback. This allows the
* caller to specify which method, post-sign-in, should be called.
*
* @param requestCode The code corresponding to the action to perform.
*/
fun performActionForRequestCode(requestCode: ActionRequestCode) = when (requestCode) {
ActionRequestCode.FIND_DATA_SOURCES -> findFitnessDataSources()
}
/** Finds available data sources and attempts to register on a specific [DataType]. */
private fun findFitnessDataSources() { // [START find_data_sources]
// Note: Fitness.SensorsApi.findDataSources() requires the ACCESS_FINE_LOCATION permission.
Fitness.getSensorsClient(activity, getGoogleAccount())
.findDataSources(
DataSourcesRequest.Builder()
.setDataTypes(DataType.TYPE_LOCATION_SAMPLE)
.setDataSourceTypes(DataSource.TYPE_RAW)
.build())
.addOnSuccessListener { dataSources ->
for (dataSource in dataSources) {
Log.i(TAG, "Data source found: $dataSource")
Log.i(TAG, "Data Source type: " + dataSource.dataType.name)
// Let's register a listener to receive Activity data!
if (dataSource.dataType == DataType.TYPE_LOCATION_SAMPLE && dataPointListener == null) {
Log.i(TAG, "Data source for LOCATION_SAMPLE found! Registering.")
registerFitnessDataListener(dataSource, DataType.TYPE_LOCATION_SAMPLE)
}
}
}
.addOnFailureListener { e -> Log.e(TAG, "failed", e) }
// [END find_data_sources]
}
/**
* Registers a listener with the Sensors API for the provided [DataSource] and [DataType] combo.
*/
private fun registerFitnessDataListener(dataSource: DataSource, dataType: DataType) {
// [START register_data_listener]
dataPointListener = OnDataPointListener { dataPoint ->
for (field in dataPoint.dataType.fields) {
val value = dataPoint.getValue(field)
Log.i(TAG, "Detected DataPoint field: ${field.name}")
Log.i(TAG, "Detected DataPoint value: $value")
}
}
Fitness.getSensorsClient(activity, getGoogleAccount())
.add(
SensorRequest.Builder()
.setDataSource(dataSource) // Optional but recommended for custom data sets.
.setDataType(dataType) // Can't be omitted.
.setSamplingRate(10, TimeUnit.SECONDS)
.build(),
dataPointListener!!
)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.i(TAG, "Listener registered!")
} else {
Log.e(TAG, "Listener not registered.", task.exception)
}
}
// [END register_data_listener]
}
@RequiresApi(Build.VERSION_CODES.O)
fun getHistory() {
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
val now = Date()
calendar.time = now
val endTime = calendar.timeInMillis
calendar.set(Calendar.YEAR, 2013) // Set year to 2013 to be sure to get data from when Google Fit Started to today
val startTime = calendar.timeInMillis
val readRequest = DataReadRequest.Builder()
.aggregate(DataType.AGGREGATE_CALORIES_EXPENDED)
.bucketByActivityType(1, TimeUnit.SECONDS)
.setTimeRange(startTime, endTime, TimeUnit.SECONDS)
.build()
Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions))
.readData(readRequest)
.addOnSuccessListener { response ->
// The aggregate query puts datasets into buckets, so flatten into a
// single list of datasets
for (dataSet in response.buckets.flatMap { it.dataSets }) {
dumpDataSet(dataSet)
}
}
.addOnFailureListener { e ->
Log.w(TAG,"There was an error reading data from Google Fit", e)
}
}
fun importWeight(callback : () -> Unit) {
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
val now = Date()
calendar.time = now
val endTime = calendar.timeInMillis
calendar.set(Calendar.YEAR, 2013) // Set year to 2013 to be sure to get data from when Google Fit Started to today
val startTime = calendar.timeInMillis
val dateFormat = DateFormat.getDateInstance()
Log.i(TAG, "Range Start: ${dateFormat.format(startTime)}")
Log.i(TAG, "Range End: ${dateFormat.format(endTime)}")
runRequest(DataReadRequest.Builder()
// The data request can specify multiple data types to return, effectively
// combining multiple data queries into one call.
// In this example, it's very unlikely that the request is for several hundred
// datapoints each consisting of a few steps and a timestamp. The more likely
// scenario is wanting to see how many steps were walked per day, for 7 days.
// .aggregate()
.read(DataType.TYPE_WEIGHT)
// Analogous to a "Group By" in SQL, defines how data should be aggregated.
// bucketByTime allows for a time span, whereas bucketBySession would allow
// bucketing by "sessions", which would need to be defined in code.
// .bucketByTime(1, TimeUnit.MINUTES)
// .bucketByActivityType(1, TimeUnit.SECONDS)
// .bucketBySession()
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.build(), callback)
}
private fun runRequest(request: DataReadRequest, callback: () -> Unit) {
Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions))
.readData(request)
.addOnSuccessListener { response ->
// The aggregate query puts datasets into buckets, so flatten into a
// single list of datasets
Log.d(TAG, "Received response! ${response.dataSets.size} ${response.buckets.size}")
for (dataSet in response.dataSets) {
dumpDataSet(dataSet)
}
for (dataSet in response.buckets.flatMap { it.dataSets }) {
dumpDataSet(dataSet)
}
callback.invoke()
}
.addOnFailureListener { e ->
Log.w(TAG,"There was an error reading data from Google Fit", e)
callback.invoke()
}
}
private fun dumpDataSet(dataSet: DataSet) {
Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name} ${dataSet.dataPoints.size}")
for (dp in dataSet.dataPoints) {
val weight = Weight()
Log.i(TAG,"Data point:")
Log.i(TAG,"\tType: ${dp.dataType.name}")
Log.i(TAG,"\tStart: ${dp.getStartTimeString()}")
Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}")
weight.timestamp = dp.getStartTime(TimeUnit.SECONDS)
weight.source = "GoogleFit"
for (field in dp.dataType.fields) {
weight.weight = dp.getValue(field).asFloat()
Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}")
}
// AppDatabase.getInstance(activity).weightDao().insert(weight)
}
}
fun DataPoint.getStartTimeString(): String =
SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS", Locale.FRANCE)
.format(Date(this.getStartTime(TimeUnit.SECONDS) * 1000L))
fun DataPoint.getEndTimeString(): String =
SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS", Locale.FRANCE)
.format(Date(this.getEndTime(TimeUnit.SECONDS) * 1000L))
/** Unregisters the listener with the Sensors API. */
private fun unregisterFitnessDataListener() {
if (dataPointListener == null) {
// This code only activates one listener at a time. If there's no listener, there's
// nothing to unregister.
return
}
// [START unregister_data_listener]
// Waiting isn't actually necessary as the unregister call will complete regardless,
// even if called from within onStop, but a callback can still be added in order to
// inspect the results.
Fitness.getSensorsClient(activity, getGoogleAccount())
.remove(dataPointListener!!)
.addOnCompleteListener { task ->
if (task.isSuccessful && task.result!!) {
Log.i(TAG, "Listener was removed!")
} else {
Log.i(TAG, "Listener was not removed.")
}
}
// [END unregister_data_listener]
}
/** Returns a [DataReadRequest] for all step count changes in the past week. */
private fun queryFitnessData(): DataReadRequest {
// [START build_read_data_request]
// Setting a start and end date using a range of 1 week before this moment.
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
val now = Date()
calendar.time = now
val endTime = calendar.timeInMillis
calendar.add(Calendar.YEAR, -1)
val startTime = calendar.timeInMillis
val dateFormat = DateFormat.getDateInstance()
Log.i(TAG, "Range Start: ${dateFormat.format(startTime)}")
Log.i(TAG, "Range End: ${dateFormat.format(endTime)}")
return DataReadRequest.Builder()
// The data request can specify multiple data types to return, effectively
// combining multiple data queries into one call.
// In this example, it's very unlikely that the request is for several hundred
// datapoints each consisting of a few steps and a timestamp. The more likely
// scenario is wanting to see how many steps were walked per day, for 7 days.
.aggregate(DataType.TYPE_STEP_COUNT_DELTA)
// Analogous to a "Group By" in SQL, defines how data should be aggregated.
// bucketByTime allows for a time span, whereas bucketBySession would allow
// bucketing by "sessions", which would need to be defined in code.
.bucketByTime(1, TimeUnit.SECONDS)
// .bucketBySession()
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.build()
}
}

View File

@ -5,11 +5,12 @@ 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.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.*
@ -18,7 +19,7 @@ import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
*/
class SamsungHealth(
private val context: Activity
) : Extension() {
) {
companion object {
const val TAG = "SamsungHealthConnector"
@ -33,6 +34,7 @@ class SamsungHealth(
requestPermission()
}
}
override fun onConnectionFailed(p0: HealthConnectionErrorResult?) {
Log.d(TAG, "Health data service is not available.")
}
@ -43,7 +45,7 @@ class SamsungHealth(
}
private val store : HealthDataStore = HealthDataStore(context, listener)
private val store: HealthDataStore = HealthDataStore(context, listener)
private fun isPermissionAcquired(): Boolean {
val permKey = PermissionKey(StepCount.HEALTH_DATA_TYPE, PermissionType.READ)
@ -86,23 +88,25 @@ class SamsungHealth(
}
}
private val reporter = StepCountReporter(store, stepCountObserver, Handler(Looper.getMainLooper()))
private val reporter =
StepCountReporter(store, stepCountObserver, Handler(Looper.getMainLooper()))
/**
* Connector
*/
override val sourceID: String = "SamsungHealth"
val sourceID: String = "SamsungHealth"
override fun onRequestPermissionResult(
fun onRequestPermissionResult(
requestCode: Int,
permission: Array<String>,
grantResult: IntArray
) {}
) {
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
override fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
fun importWeight(callback: (weight: Weight, end: Boolean) -> Unit) {
store.connectService()
}
}

View File

@ -1,12 +1,10 @@
package com.dzeio.openhealth.interfaces
import android.app.NotificationManager
enum class NotificationChannels(
val id: String,
val channelName: String,
val importance: Int
) {
// 3 is IMPORTANCE_DEFAULT
DEFAULT("default", "Default Channel", 3)
DEFAULT("openhealth_default", "Default Channel", 3)
}

View File

@ -0,0 +1,59 @@
package com.dzeio.openhealth.ui.extension
import android.app.ProgressDialog
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.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 java.lang.Exception
@AndroidEntryPoint
class ExtensionFragment :
BaseFragment<ExtensionViewModel, FragmentExtensionBinding>(ExtensionViewModel::class.java) {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionBinding =
FragmentExtensionBinding::inflate
private val args: ExtensionFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val extension = ExtensionFactory.getExtension(args.extension)
?: throw Exception("No Extension found!")
extension.init(requireActivity())
binding.importButton.setOnClickListener {
val dialog = ProgressDialog(requireContext())
dialog.setTitle("Importing...")
dialog.setMessage("Imported 0 values")
dialog.show()
val data = extension.importWeight()
data.observe(viewLifecycleOwner) { state ->
Log.d("ExtensionFragment", state.state.name)
Log.d("ExtensionFragment", state.list.size.toString())
dialog.setMessage("Imported ${state.list.size} values")
if (state.state == Extension.States.DONE) {
dialog.setMessage("Finishing Import...")
lifecycleScope.launchWhenStarted {
state.list.forEach {
it.source = extension.id
viewModel.importWeight(it)
}
dialog.dismiss()
}
}
}
}
}
}

View File

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

View File

@ -10,13 +10,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.lifecycle.lifecycleScope
import com.dzeio.openhealth.extensions.Extension
import com.dzeio.openhealth.extensions.GoogleFit
//import com.dzeio.openhealth.connectors.GoogleFit
import com.dzeio.openhealth.extensions.samsunghealth.SamsungHealth
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 com.dzeio.openhealth.extensions.GoogleFit
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@ -24,92 +24,55 @@ class ExtensionsFragment :
BaseFragment<ExtensionsViewModel, FragmentExtensionsBinding>(ExtensionsViewModel::class.java) {
companion object {
const val TAG = "ImportFragment"
const val TAG = "ExtensionsFragment"
}
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentExtensionsBinding =
FragmentExtensionsBinding::inflate
private lateinit var progressDialog: ProgressDialog
private lateinit var fit: Extension
private lateinit var activeExtension: Extension
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
progressDialog = ProgressDialog(requireContext())
val recycler = binding.list
progressDialog.apply {
setCancelable(false)
setTitle("Importing from source...")
}
val manager = LinearLayoutManager(requireContext())
recycler.layoutManager = manager
binding.importGoogleFit.setOnClickListener {
importFromGoogleFit()
}
binding.importSamsungHealth.setOnClickListener {
importFromSamsungHealth()
}
}
private fun importFromGoogleFit() {
progressDialog.show()
fit = GoogleFit(requireActivity())
var imported = 0
lifecycleScope.launchWhenStarted {
viewModel.deleteFromSource(fit.sourceID)
}.invokeOnCompletion {
//progressDialog.show()
fit.importWeight { weight, end ->
Log.d("Importer", "Importing $weight")
weight.source = fit.sourceID
progressDialog.setTitle("Importing from source... ${++imported}")
lifecycleScope.launchWhenStarted {
viewModel.importWeight(weight)
val adapter = ExtensionAdapter()
adapter.onItemClick = {
activeExtension = it
Log.d(it.id, it.name)
if (it.isConnected()) {
Log.d(it.id, "Continue!")
findNavController().navigate(
ExtensionsFragmentDirections.actionNavExtensionsToNavExtension(
it.id
)
)
} else {
val ls = it.connect()
ls.observe(viewLifecycleOwner) { st ->
Log.d("States", st.name)
}
if (end) {
Log.d("Importer", "Finished Importing")
progressDialog.dismiss()
return@importWeight
}
}
}
recycler.adapter = adapter
val list = arrayOf(
GoogleFit()
).toList()
}
private fun importFromSamsungHealth() {
progressDialog.show()
fit = SamsungHealth(requireActivity())
var imported = 0
lifecycleScope.launchWhenStarted {
viewModel.deleteFromSource(fit.sourceID)
}.invokeOnCompletion {
//progressDialog.show()
fit.importWeight { weight, end ->
Log.d("Importer", "Importing $weight")
weight.source = fit.sourceID
progressDialog.setTitle("Importing from source... ${++imported}")
lifecycleScope.launchWhenStarted {
viewModel.importWeight(weight)
}
if (end) {
Log.d("Importer", "Finished Importing")
progressDialog.dismiss()
return@importWeight
}
}
list.forEach {
it.init(requireActivity())
}
adapter.set(list)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
fit.onActivityResult(requestCode, resultCode, data)
activeExtension.onActivityResult(requestCode, resultCode, data)
}
@RequiresApi(Build.VERSION_CODES.O)
@ -126,7 +89,7 @@ class ExtensionsFragment :
grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
Log.d(TAG, "Granted")
fit.onRequestPermissionResult(requestCode, permissions, grantResults)
activeExtension.onRequestPermissionResult(requestCode, permissions, grantResults)
}
else -> {
// Permission denied.

View File

@ -1,9 +1,9 @@
package com.dzeio.openhealth.ui.home
import android.animation.ValueAnimator
import android.graphics.BitmapFactory
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.RectF
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@ -13,28 +13,21 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.dzeio.openhealth.Application
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentHomeBinding
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.dzeio.openhealth.utils.GraphUtils
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.*
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.min
import kotlin.properties.Delegates
@ -53,8 +46,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.init()
binding.addWeight.setOnClickListener {
AddWeightDialog().show(requireActivity().supportFragmentManager, null)
}
@ -112,14 +103,23 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
binding.listWeight.setOnClickListener {
Log.d("T", "Trying to move")
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight())
}
binding.gotoWaterHome.setOnClickListener {
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavWaterHome())
}
GraphUtils.lineChartSetup(
binding.weightGraph,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
), MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnBackground
)
)
}
private fun updateGraph(list: List<Weight>) {
@ -130,35 +130,9 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
val dataSet = LineDataSet(entries, "Label")
binding.weightGraph.apply {
// Setup
isAutoScaleMinMaxEnabled = true
legend.isEnabled = false
isDragEnabled = true
isScaleYEnabled = false
description = Description().apply { isEnabled = false }
isScaleXEnabled = true
setPinchZoom(false)
setDrawGridBackground(false)
setDrawBorders(false)
axisLeft.setLabelCount(0, true)
xAxis.apply {
valueFormatter = object : ValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
return SimpleDateFormat(
"yyyy-MM-dd",
Locale.getDefault()
).format(Date(value.toLong()))
//return super.getAxisLabel(value, axis)
}
}
position = XAxis.XAxisPosition.BOTTOM
setDrawGridLines(false)
setLabelCount(3, true)
}
// Apply new dataset
data = LineData(dataSet)
@ -181,6 +155,8 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
viewModel.fetchWeights().collectLatest {
updateGraph(it)
}
updateWater(0)
updateWater(1234)
}
viewModel.water.observe(viewLifecycleOwner) {
@ -192,38 +168,75 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
}
}
updateWater(0)
}
private fun updateWater(water: Int) {
val oldValue = binding.fragmentHomeWaterCurrent.text.toString().replace("ml", "").toInt()
binding.fragmentHomeWaterCurrent.text = "${water}ml"
val graph = BitmapUtils.convertToMutable(
requireContext(),
BitmapFactory.decodeResource(resources, R.drawable.ellipse)
var width = 1500
var height = 750
if (binding.background.width != 0) {
width = binding.background.width
height = binding.background.height
}
val graph = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
Log.d("Test2", "$width $height")
val canvas = Canvas(graph)
val rect = RectF(
10f,
15f,
90f,
85f
)
graph?.let { btmp ->
ValueAnimator.ofFloat(min(oldValue.toFloat(), intake), min(water.toFloat(), intake))
.apply {
duration = 300
addUpdateListener {
val canvas = Canvas(btmp)
DrawUtils.drawArc(
canvas,
100 * it.animatedValue as Float / intake,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
)
)
canvas.save()
binding.background.setImageBitmap(graph)
}
start()
// DrawUtils.drawRect(
// canvas,
// RectF(
// 0f,
// 0f,
// 100f,
// 100f
// ),
// MaterialColors.getColor(
// requireView(),
// com.google.android.material.R.attr.colorOnPrimary
// ),
// 3f
// )
DrawUtils.drawArc(
canvas,
100f,
rect,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimary
),
3f
)
Log.d("Test", "${min(oldValue.toFloat(), intake)} ${min(water.toFloat(), intake)}")
ValueAnimator.ofFloat(min(oldValue.toFloat(), intake), min(water.toFloat(), intake))
.apply {
duration = 300
addUpdateListener {
DrawUtils.drawArc(
canvas,
100 * it.animatedValue as Float / intake,
rect,
MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
), 6f
)
canvas.save()
binding.background.setImageBitmap(graph)
}
}
start()
}
}
}

View File

@ -1,5 +1,6 @@
package com.dzeio.openhealth.ui.home
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.core.BaseViewModel
@ -18,8 +19,23 @@ class HomeViewModel @Inject internal constructor(
private val waterRepository: WaterRepository
) : BaseViewModel() {
init {
viewModelScope.launch {
waterRepository.todayWater().collectLatest {
_water.value = it
}
}
}
/**
* @deprecated
*/
fun fetchWeights() = weightRepository.getWeights()
/**
* @deprecated
*/
fun lastWeight() = weightRepository.lastWeight()
fun fetchWeight(id: Long) = weightRepository.getWeight(id)
@ -28,17 +44,11 @@ class HomeViewModel @Inject internal constructor(
suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight)
fun fetchTodayWater() = waterRepository.todayWater()
suspend fun fetchTodayWater() = waterRepository.todayWater()
val water: MutableLiveData<Water?> = MutableLiveData(null)
private val _water = MutableLiveData<Water?>(null)
val water: LiveData<Water?> = _water
fun init() {
viewModelScope.launch {
waterRepository.todayWater().collectLatest {
water.postValue(it)
}
}
}
fun updateWater(water: Water) {
viewModelScope.launch {
@ -49,7 +59,7 @@ class HomeViewModel @Inject internal constructor(
fun deleteWater(item: Water) {
viewModelScope.launch {
waterRepository.deleteWater(item)
water.postValue(null)
_water.postValue(null)
}
}
}

View File

@ -1,7 +1,9 @@
package com.dzeio.openhealth.ui.water
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
@ -13,8 +15,12 @@ import androidx.navigation.fragment.navArgs
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFullscreenDialog
import com.dzeio.openhealth.databinding.DialogWaterEditWaterBinding
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointBackward
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
@AndroidEntryPoint
class EditWaterDialog :
@ -40,13 +46,53 @@ class EditWaterDialog :
viewModel.water.observe(viewLifecycleOwner) {
binding.editTextNumber.setText(it.value.toString())
binding.date.text = it.formatTimestamp()
}
binding.editTextNumber.doOnTextChanged { text, start, before, count ->
newValue = text.toString().toInt()
val value = text.toString()
newValue = if (value == "") 0
else text.toString().toInt()
}
binding.date.setOnClickListener {
val water = viewModel.water.value!!
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val date = Date(water.timestamp)
val datePicker = MaterialDatePicker.Builder.datePicker()
.setTitleText("Select Date")
.setSelection(water.timestamp)
.setCalendarConstraints(
CalendarConstraints.Builder()
.setValidator(DateValidatorPointBackward.now())
.setEnd(Date().time)
.build()
)
.build()
val fragManager = requireActivity().supportFragmentManager
datePicker.addOnPositiveButtonClickListener { tsp ->
water.timestamp = tsp
binding.date.setText(water.formatTimestamp())
}
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")
}
}
viewModel.init(args.id)
}
private fun save() {

View File

@ -1,30 +1,20 @@
package com.dzeio.openhealth.ui.water
import android.graphics.Color
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.WaterAdapter
import com.dzeio.openhealth.adapters.WeightAdapter
import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding
import com.dzeio.openhealth.ui.home.HomeViewModel
import com.dzeio.openhealth.ui.weight.ListWeightFragmentDirections
import com.dzeio.openhealth.utils.GraphUtils
import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.data.Entry
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.*
@AndroidEntryPoint
@ -60,6 +50,9 @@ class WaterHomeFragment :
chart, MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorPrimary
), MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnBackground
)
)
@ -73,7 +66,7 @@ class WaterHomeFragment :
epoch.time = Date(0)
epoch.add(Calendar.MILLISECOND, it.timestamp.toInt())
return@map BarEntry(
epoch.get(Calendar.DATE).toFloat(),
(epoch.timeInMillis / 1000 / 60 / 60).toFloat(),
it.value.toFloat()
)
},

View File

@ -1,24 +1,23 @@
package com.dzeio.openhealth.utils
import android.graphics.*
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
object DrawUtils {
/**
* Fuck Graphics
*/
fun drawArc(canvas: Canvas, percent: Float, pColor: Int) {
canvas.width
val spacing = 120f
fun drawArc(canvas: Canvas, percent: Float, rect: RectF, pColor: Int, strokeWidth: Float = 1f) {
val r1 = RectF(
spacing,
spacing,
canvas.width - spacing,
canvas.height * 2 - spacing * 3
canvas.realSize(true, rect.left),
canvas.realSize(false, rect.top),
canvas.realSize(true, rect.right),
canvas.realSize(false, rect.bottom, 2)
)
val paint = Paint()
paint.apply {
strokeWidth = 200f
val paint = Paint().apply {
this.strokeWidth = canvas.realSize(true, strokeWidth)
style = Paint.Style.STROKE
color = pColor
isAntiAlias = true
@ -27,4 +26,33 @@ object DrawUtils {
canvas.drawArc(r1, 180f, 180 * percent / 100f, false, paint)
}
/**
* Fuck Graphics
*/
fun drawRect(canvas: Canvas, rect: RectF, pColor: Int, strokeWidth: Float = 1f) {
val r1 = RectF(
canvas.realSize(true, rect.left),
canvas.realSize(false, rect.top),
canvas.realSize(true, rect.right),
canvas.realSize(false, rect.bottom)
)
val paint = Paint().apply {
this.strokeWidth = canvas.realSize(true, strokeWidth)
style = Paint.Style.STROKE
color = pColor
isAntiAlias = true
}
canvas.drawRect(r1, paint)
}
private fun Canvas.realSize(isWidth: Boolean, value: Float): Float {
val it = if (isWidth) this.width else this.height
return it * value / 100
}
private fun Canvas.realSize(isWidth: Boolean, value: Float, multiplier: Int): Float {
val it = (if (isWidth) this.width else this.height) * multiplier
return it * value / 100
}
}

View File

@ -1,35 +1,34 @@
package com.dzeio.openhealth.utils
import android.graphics.Color
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase
import com.github.mikephil.charting.charts.Chart
import com.github.mikephil.charting.charts.LineChart
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.data.BarLineScatterCandleBubbleData
import com.github.mikephil.charting.data.ChartData
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.formatter.ValueFormatter
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet
import com.github.mikephil.charting.interfaces.datasets.IDataSet
import com.google.android.material.color.MaterialColors
import java.text.SimpleDateFormat
import java.util.*
object GraphUtils {
fun lineChartSetup(chart: LineChart, mainColor: Int) {
barLineChartSetup(chart, mainColor)
fun lineChartSetup(chart: LineChart, mainColor: Int, textColor: Int) {
barLineChartSetup(chart, mainColor, textColor)
}
fun barChartSetup(chart: BarChart, mainColor: Int) {
barLineChartSetup(chart, mainColor)
fun barChartSetup(chart: BarChart, mainColor: Int, textColor: Int) {
barLineChartSetup(chart, mainColor, textColor)
}
fun <T : BarLineScatterCandleBubbleData<out IBarLineScatterCandleBubbleDataSet<out Entry>>?> barLineChartSetup(chart: BarLineChartBase<T>, mainColor: Int) {
private fun <T : BarLineScatterCandleBubbleData<out IBarLineScatterCandleBubbleDataSet<out Entry>>?> barLineChartSetup(
chart: BarLineChartBase<T>,
mainColor: Int,
textColor: Int
) {
chart.apply {
// Setup
@ -49,18 +48,25 @@ object GraphUtils {
position = XAxis.XAxisPosition.BOTTOM
setDrawGridLines(false)
setLabelCount(3, true)
textColor = Color.WHITE
this.textColor = textColor
//setDrawGridLines(false)
//setDrawZeroLine(false)
setDrawAxisLine(false)
disableGridDashedLine()
invalidateOutline()
}
axisLeft.apply {
axisLineColor = mainColor
textColor = Color.WHITE
this.textColor = textColor
// setDrawZeroLine(false)
setLabelCount(0, true)
setDrawGridLines(false)
}
axisRight.apply {
textColor = Color.WHITE
this.textColor = textColor
}
setNoDataTextColor(Color.WHITE)
setNoDataTextColor(textColor)
isAutoScaleMinMaxEnabled = true
@ -70,8 +76,8 @@ object GraphUtils {
description = Description().apply { isEnabled = false }
isScaleXEnabled = true
setPinchZoom(false)
//setDrawGridBackground(false)
//setDrawBorders(false)
setDrawGridBackground(false)
setDrawBorders(false)
}
}
}

View File

@ -2,7 +2,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:pathData="M12.0001,1.7144L20.9078,6.8572V17.1429L12.0001,22.2858L3.0924,17.1429V6.8572L12.0001,1.7144Z"
android:strokeWidth="2"

View File

@ -1,55 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:openDrawer="start"
tools:context=".MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
style="@style/Widget.Material3.AppBarLayout"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
app:titleCentered="true"
style="@style/ThemeOverlay.Material3.Toolbar.Surface"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.google.android.material.appbar.AppBarLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
style="?attr/appBarLayoutStyle"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
style="?attr/toolbarStyle"
app:titleCentered="true"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
</androidx.core.widget.NestedScrollView>
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.drawerlayout.widget.DrawerLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
@ -8,8 +9,20 @@
android:id="@+id/editTextNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:ems="10"
android:inputType="number"
tools:layout_editor_absoluteX="101dp"
tools:layout_editor_absoluteY="107dp" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="2021-12-21"
app:layout_constraintEnd_toEndOf="@+id/editTextNumber"
app:layout_constraintStart_toStartOf="@+id/editTextNumber"
app:layout_constraintTop_toBottomOf="@+id/editTextNumber" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_margin="16dp"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<Button
android:id="@+id/import_button"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:text="Force Import" />
<Button
android:id="@+id/export_button"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:text="Force Export" />
</LinearLayout>
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/extension_informations" />
</LinearLayout>

View File

@ -1,122 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.extensions.ExtensionsFragment">
<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:id="@+id/import_google_fit"
android:layout_marginEnd="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_logo_fit" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginVertical="16dp"
android:gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Google Fit" />
<TextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Last Sync: Yesterday" />
</LinearLayout>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_baseline_extension_24" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/import_samsung_health"
style="?attr/materialCardViewOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/logo_shealth" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginVertical="16dp"
android:gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Samsung Health" />
<TextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Currently Unavailable" />
</LinearLayout>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_baseline_extension_24" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
android:id="@+id/list"
tools:listitem="@layout/layout_extension_item"
tools:context=".ui.extensions.ExtensionsFragment" />

View File

@ -39,8 +39,8 @@
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Weight"
android:layout_weight="1" />
android:layout_weight="1"
android:text="Weight" />
<LinearLayout
android:layout_width="wrap_content"
@ -75,7 +75,7 @@
android:id="@+id/fragment_home_water_remove"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="4dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:src="@drawable/ic_outline_hexagon_24"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
@ -86,11 +86,12 @@
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/background">
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/fragment_home_water_current"
@ -116,7 +117,7 @@
android:id="@+id/fragment_home_water_add"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="4dp"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:src="@drawable/ic_baseline_add_24"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
@ -128,9 +129,9 @@
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_constraintDimensionRatio="2:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View File

@ -10,5 +10,5 @@
android:layout_height="wrap_content"
android:padding="16dp"
tools:listitem="@layout/layout_item_weight"
tools:listitem="@layout/layout_item_list"
tools:context=".ui.weight.ListWeightFragment" />

View File

@ -56,7 +56,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
tools:listitem="@layout/layout_item_weight" />
tools:listitem="@layout/layout_item_list" />
</LinearLayout>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
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"
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:id="@+id/card"
android:layout_marginEnd="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="40dp"
android:id="@+id/logo"
android:layout_height="40dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_logo_fit" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginVertical="16dp"
android:gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.Material3.TitleMedium"
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Google Fit" />
<TextView
android:id="@+id/status"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Last Sync: Yesterday" />
</LinearLayout>
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:src="@drawable/ic_baseline_extension_24" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="?attr/materialCardViewFilledStyle"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:id="@+id/edit"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_margin="16dp"
android:layout_height="match_parent"
android:orientation="horizontal">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:layout_editor_absoluteX="16dp"
tools:layout_editor_absoluteY="0dp">
<TextView
android:id="@+id/weight"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="xkg"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/datetime"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Taken: yyyy-mm-dd"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/weight" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_edit_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -46,12 +46,8 @@
android:label="@string/menu_import"
tools:layout="@layout/fragment_extensions" >
<action
android:id="@+id/action_nav_import_to_nav_settings"
app:destination="@id/nav_settings"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
android:id="@+id/action_nav_extensions_to_nav_extension"
app:destination="@id/nav_extension" />
</fragment>
<fragment
@ -66,13 +62,6 @@
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_nav_list_weight_to_nav_settings"
app:destination="@id/nav_settings"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
</fragment>
<fragment
@ -93,15 +82,12 @@
android:label="@string/nav_water_home"
tools:layout="@layout/fragment_main_water_home">
<action
android:id="@+id/action_nav_water_home_to_nav_settings"
app:destination="@id/nav_settings"
android:id="@+id/action_nav_water_home_to_nav_water_edit"
app:destination="@id/nav_water_edit"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_nav_water_home_to_nav_water_edit"
app:destination="@id/nav_water_edit" />
</fragment>
<fragment
@ -124,4 +110,19 @@
app:argType="long" />
</fragment>
<fragment
android:id="@+id/nav_extension"
android:name="com.dzeio.openhealth.ui.extension.ExtensionFragment"
tools:layout="@layout/fragment_extension"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right">
<argument
android:name="extension"
app:argType="string" />
</fragment>
</navigation>

View File

@ -16,4 +16,5 @@
<string name="nav_list_water">Water Intake</string>
<string name="nav_water_home">Water Intake</string>
<string name="menu_extensions">Extensions</string>
<string name="extension_informations">Imports are done at app startup\nExports are done when new inputs are give to the app</string>
</resources>

View File

@ -1,29 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.OpenHealth" parent="Theme.Material3.DynamicColors.DayNight">
<!-- &lt;!&ndash; Primary brand color. &ndash;&gt;-->
<!-- <item name="colorPrimary">@color/purple_500</item>-->
<!-- <item name="colorPrimaryVariant">@color/purple_700</item>-->
<!-- <item name="colorOnPrimary">@color/white</item>-->
<!-- &lt;!&ndash; Secondary brand color. &ndash;&gt;-->
<!-- <item name="colorSecondary">@color/teal_200</item>-->
<!-- <item name="colorSecondaryVariant">@color/teal_700</item>-->
<!-- <item name="colorOnSecondary">@color/black</item>-->
<!-- &lt;!&ndash; Status bar color. &ndash;&gt;-->
<!-- <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>-->
<!-- Customize your theme here. -->
<item name="android:windowTranslucentStatus">true</item>
<item name="materialAlertDialogTheme">@style/ThemeOverlay.Material3.MaterialAlertDialog</item>
</style>
<style name="Theme.OpenHealth" parent="Theme.Material3.DayNight.NoActionBar" />
<style name="Theme.OpenHealth.NoActionBar">
<style name="Theme.OpenHealth.NoActionBar" parent="Theme.OpenHealth">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.OpenHealth.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="Theme.OpenHealth.PopupOverlay" parent="ThemeOverlay.Material3.MaterialAlertDialog" />
</resources>