1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-22 10:52:13 +00:00

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH

This commit is contained in:
Florian Bouillon 2021-12-20 01:32:48 +01:00
parent fe73d7504d
commit 4d901f8a51
Signed by: Florian Bouillon
GPG Key ID: 50BD648F12C86AB6
61 changed files with 1658 additions and 729 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

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-20T00:23:13.795732Z" />
</component>
</project>

15
.idea/misc.xml generated
View File

@ -7,8 +7,21 @@
<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/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/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/menu/main.xml" value="0.1875" />
<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" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/content_main.xml" value="0.5135869565217391" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/content_main.xml" value="0.5135869565217391" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/fragment_home.xml" value="0.5135869565217391" /> <entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/layout/dialog_add_weight.xml" value="0.2604166666666667" />
<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_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/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" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/fullscreen_dialog.xml" value="0.40185185185185185" />
<entry key="..\:/git/Dzeio/OpenHealth/app/src/main/res/menu/main.xml" value="0.39947916666666666" />
</map> </map>
</option> </option>
</component> </component>

View File

@ -2,6 +2,10 @@ plugins {
id 'com.android.application' id 'com.android.application'
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt' id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
// Safe Navigation
id 'androidx.navigation.safeargs'
} }
android { android {
@ -32,6 +36,8 @@ android {
} }
buildFeatures { buildFeatures {
viewBinding true viewBinding true
dataBinding true
} }
} }
@ -39,7 +45,8 @@ dependencies {
implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0' implementation 'javax.inject:javax.inject:1'
implementation 'com.google.android.material:material:1.6.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2' implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
@ -49,6 +56,13 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Graph
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
// Hilt
implementation "com.google.dagger:hilt-android:2.38.1"
kapt "com.google.dagger:hilt-compiler:2.38.1"
// Google Fit // Google Fit
implementation "com.google.android.gms:play-services-fitness:21.0.0" implementation "com.google.android.gms:play-services-fitness:21.0.0"
implementation "com.google.android.gms:play-services-auth:20.0.0" implementation "com.google.android.gms:play-services-auth:20.0.0"
@ -61,21 +75,8 @@ dependencies {
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava2 support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - RxJava3 support for Room
implementation "androidx.room:room-rxjava3:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version" testImplementation "androidx.room:room-testing:$room_version"
// optional - Paging 3 Integration
implementation "androidx.room:room-paging:2.4.0-rc01"
} }

View File

@ -14,6 +14,7 @@
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:name=".Application"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
@ -22,7 +23,11 @@
<!-- Samsung Health--> <!-- Samsung Health-->
<meta-data <meta-data
android:name="com.samsung.android.health.permission.read" android:name="com.samsung.android.health.permission.read"
android:value="com.samsung.health.step_count;com.samsung.shealth.step_daily_trend" /> android:value="com.samsung.health.weight" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"

View File

@ -0,0 +1,7 @@
package com.dzeio.openhealth
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class Application : Application() {}

View File

@ -3,64 +3,66 @@ package com.dzeio.openhealth
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.NavController
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.dzeio.openhealth.core.BaseActivity
import com.dzeio.openhealth.databinding.ActivityMainBinding import com.dzeio.openhealth.databinding.ActivityMainBinding
import com.dzeio.openhealth.db.AppDatabase import com.dzeio.openhealth.ui.main.home.HomeFragmentDirections
import com.dzeio.openhealth.db.entities.Weight
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
class MainActivity : AppCompatActivity() { @AndroidEntryPoint
class MainActivity : BaseActivity<ActivityMainBinding>() {
private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { private lateinit var navController: NavController
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) override val bindingInflater: (LayoutInflater) -> ActivityMainBinding = ActivityMainBinding::inflate
setContentView(binding.root)
setSupportActionBar(binding.appBarMain.toolbar) override fun onCreated(savedInstanceState: Bundle?) {
super.onCreated(savedInstanceState)
setSupportActionBar(binding.toolbar)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
binding.navView.setupWithNavController(navController)
binding.appBarMain.fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
val drawerLayout: DrawerLayout = binding.drawerLayout
val navView: NavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_content_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration( appBarConfiguration = AppBarConfiguration(
setOf( setOf(
R.id.nav_home, R.id.nav_gallery, R.id.nav_import R.id.nav_home
), drawerLayout ), binding.drawerLayout
) )
setupActionBarWithNavController(navController, appBarConfiguration) setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu) menuInflater.inflate(R.menu.main, menu)
return true return true
} }
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, override fun onRequestPermissionsResult(
grantResults: IntArray) { requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
Log.d("MainActivity", "Result $requestCode") Log.d("MainActivity", "Result $requestCode")
} }

View File

@ -0,0 +1,29 @@
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
class WeightAdapter() : BaseAdapter<Weight, LayoutItemWeightBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemWeightBinding
get() = LayoutItemWeightBinding::inflate
var onItemClick: ((weight: Weight) -> Unit)? = null
override fun onBindData(
holder: BaseViewHolder<LayoutItemWeightBinding>,
item: Weight,
position: Int
) {
holder.binding.weight.text = "${item.weight}kg"
holder.binding.datetime.text = item.formatTimestamp()
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)
}
}
}

View File

@ -7,8 +7,8 @@ import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import com.dzeio.openhealth.db.AppDatabase import com.dzeio.openhealth.data.AppDatabase
import com.dzeio.openhealth.db.entities.Weight import com.dzeio.openhealth.data.weight.Weight
import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.fitness.Fitness import com.google.android.gms.fitness.Fitness
import com.google.android.gms.fitness.FitnessOptions import com.google.android.gms.fitness.FitnessOptions
@ -36,15 +36,29 @@ class GoogleFit(
companion object { companion object {
const val TAG = "GoogleFitConnector" 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() private val fitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE) // .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.addDataType(DataType.TYPE_ACTIVITY_SEGMENT) // .addDataType(DataType.TYPE_ACTIVITY_SEGMENT)
.addDataType(DataType.TYPE_SLEEP_SEGMENT) // .addDataType(DataType.TYPE_SLEEP_SEGMENT)
.addDataType(DataType.TYPE_CALORIES_EXPENDED) // .addDataType(DataType.TYPE_CALORIES_EXPENDED)
.addDataType(DataType.TYPE_BASAL_METABOLIC_RATE) // .addDataType(DataType.TYPE_BASAL_METABOLIC_RATE)
.addDataType(DataType.TYPE_POWER_SAMPLE) // .addDataType(DataType.TYPE_POWER_SAMPLE)
.addDataType(DataType.TYPE_HEART_RATE_BPM) // .addDataType(DataType.TYPE_HEART_RATE_BPM)
.addDataType(DataType.TYPE_LOCATION_SAMPLE) // .addDataType(DataType.TYPE_LOCATION_SAMPLE)
.addDataType(DataType.TYPE_WEIGHT) .addDataType(DataType.TYPE_WEIGHT)
.build() .build()
@ -196,12 +210,16 @@ class GoogleFit(
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun getHistory() { fun getHistory() {
val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()) val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
val startTime = LocalDateTime.MIN.atZone(ZoneId.systemDefault()) 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() val readRequest = DataReadRequest.Builder()
.aggregate(DataType.AGGREGATE_CALORIES_EXPENDED) .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED)
.bucketByActivityType(1, TimeUnit.SECONDS) .bucketByActivityType(1, TimeUnit.SECONDS)
.setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) .setTimeRange(startTime, endTime, TimeUnit.SECONDS)
.build() .build()
Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions)) Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions))
@ -219,13 +237,6 @@ class GoogleFit(
} }
// @RequiresApi(Build.VERSION_CODES.O)
// fun importStepCount() {
//
// runRequest(queryFitnessData())
//
// }
fun importWeight(callback : () -> Unit) { fun importWeight(callback : () -> Unit) {
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
@ -295,7 +306,7 @@ class GoogleFit(
weight.weight = dp.getValue(field).asFloat() weight.weight = dp.getValue(field).asFloat()
Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}") Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}")
} }
AppDatabase.getInstance(activity).weightDao().insert(weight) // AppDatabase.getInstance(activity).weightDao().insert(weight)
} }
} }

View File

@ -2,142 +2,133 @@ package com.dzeio.openhealth.connectors.samsunghealth
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.DialogInterface
import android.util.Log import android.util.Log
import com.samsung.android.sdk.healthdata.* import com.samsung.android.sdk.healthdata.*
import com.samsung.android.sdk.healthdata.HealthDataStore.ConnectionListener
import com.samsung.android.sdk.healthdata.HealthPermissionManager.*
import com.samsung.android.sdk.healthdata.HealthResultHolder.ResultListener
import java.lang.Boolean
import kotlin.Exception
class SamsungHealth( class SamsungHealth(
private val activity: Activity private val context: Activity
) { ) {
private var currentStartTime: Long = 0
private var isFinishing = false
init {
currentStartTime = StepCountReader.TODAY_START_UTC_TIME
}
companion object { companion object {
const val TAG = "SamsungHealth" const val TAG = "SamsungHealthConnector"
} }
private val store: HealthDataStore get() = _store!!
private var _store: HealthDataStore? = null
private val keySet: HashSet<PermissionKey> = HashSet()
// private val binningListAdapter: StepBinningData by lazy { StepBinningData() } private val permissionListener: ResultListener<PermissionResult> =
ResultListener<PermissionResult> { result ->
Log.d(TAG, "Permission callback is received.")
val resultMap: Map<PermissionKey, kotlin.Boolean> = result!!.resultMap
private val healthDataStore: HealthDataStore by lazy { HealthDataStore(activity, connectionListener) } if (resultMap.containsValue(Boolean.FALSE)) {
private val stepCountReader: StepCountReader by lazy { StepCountReader(healthDataStore, stepCountObserver) } // Requesting permission fails
Log.d(TAG, "Requesting permission fails")
private val stepCountObserver: StepCountObserver = object : StepCountObserver {
override fun onChanged(count: Int) {
Log.d(TAG, "$count")
}
override fun onBinningDataChanged(binningCountList: List<StepBinningData>) {
Log.d(TAG, "${binningCountList.size}")
}
}
private val connectionListener: HealthDataStore.ConnectionListener = object : HealthDataStore.ConnectionListener {
override fun onConnected() {
Log.d(TAG, "onConnected")
if (checkPermissionsAcquired()) {
stepCountReader.requestDailyStepCount(currentStartTime)
} else { } else {
requestPermission() // Get the current step count and display it
getStepCount()
}
}
private val connectionListener: ConnectionListener = object : ConnectionListener {
override fun onConnected() {
Log.d(TAG, "Health data service is connected.")
val pmsManager = HealthPermissionManager(store)
try {
val resultMap = pmsManager.isPermissionAcquired(keySet)
if (resultMap.containsValue(Boolean.FALSE)) {
// Request the permission for reading step counts if it is not acquired
pmsManager.requestPermissions(keySet, context)
.setResultListener(permissionListener)
} else {
// Get the current step count and display it
getStepCount()
}
} catch (e: Exception) {
Log.e(TAG, e.javaClass.name + " - " + e.message)
Log.e(TAG, "Permission setting fails.")
} }
} }
override fun onConnectionFailed(error: HealthConnectionErrorResult) { override fun onConnectionFailed(error: HealthConnectionErrorResult) {
Log.d(TAG, "onConnectionFailed") Log.d(TAG, "Health data service is not available.")
showConnectionFailureDialog(error) showConnectionFailureDialog(error)
} }
override fun onDisconnected() { override fun onDisconnected() {
Log.d(TAG, "onDisconnected") Log.d(TAG, "Health data service is disconnected.")
if (!isFinishing) {
healthDataStore.connectService()
} }
} }
init {
//keySet = HashSet()
keySet.add(PermissionKey(HealthConstants.Weight.HEALTH_DATA_TYPE, PermissionType.READ))
_store = HealthDataStore(context, connectionListener)
}
fun test() {
store.connectService()
}
private fun getStepCount() {
val resolver = HealthDataResolver(store, null)
val request = HealthDataResolver.ReadRequest.Builder()
.setDataType(HealthConstants.Weight.HEALTH_DATA_TYPE)
.setLocalTimeRange(HealthConstants.Weight.START_TIME, HealthConstants.Weight.TIME_OFFSET, 0, System.currentTimeMillis())
.build()
try {
val res = resolver.read(request).await()
res.use { res1 ->
val iterator = res1.iterator()
if (iterator.hasNext()) {
val data = iterator.next()
val value = data.getFloat(HealthConstants.Weight.WEIGHT)
val time = data.getLong(HealthConstants.Weight.CREATE_TIME)
Log.d(TAG, "Data received! $value $time")
}
}
} catch (e : Exception) {
Log.d(TAG, "Reading failed!")
}
} }
private fun showConnectionFailureDialog(error: HealthConnectionErrorResult) { private fun showConnectionFailureDialog(error: HealthConnectionErrorResult) {
if (isFinishing) { val alert: AlertDialog.Builder = AlertDialog.Builder(context)
return var message = "Connection with Samsung Health is not available"
}
val alert = AlertDialog.Builder(activity)
if (error.hasResolution()) { if (error.hasResolution()) {
when (error.errorCode) { message = when (error.errorCode) {
HealthConnectionErrorResult.PLATFORM_NOT_INSTALLED -> alert.setMessage("R.string.msg_req_install") HealthConnectionErrorResult.PLATFORM_NOT_INSTALLED -> "Please install Samsung Health"
HealthConnectionErrorResult.OLD_VERSION_PLATFORM -> alert.setMessage("R.string.msg_req_upgrade") HealthConnectionErrorResult.OLD_VERSION_PLATFORM -> "Please upgrade Samsung Health"
HealthConnectionErrorResult.PLATFORM_DISABLED -> alert.setMessage("R.string.msg_req_enable") HealthConnectionErrorResult.PLATFORM_DISABLED -> "Please enable Samsung Health"
HealthConnectionErrorResult.USER_AGREEMENT_NEEDED -> alert.setMessage("R.string.msg_req_agree") HealthConnectionErrorResult.USER_AGREEMENT_NEEDED -> "Please agree with Samsung Health policy"
else -> alert.setMessage("R.string.msg_req_available") else -> "Please make Samsung Health available"
} }
} else {
alert.setMessage("R.string.msg_conn_not_available")
} }
alert.setPositiveButton("R.string.ok") { _, _ -> alert.setMessage(message)
alert.setPositiveButton("OK", DialogInterface.OnClickListener { dialog, id ->
if (error.hasResolution()) { if (error.hasResolution()) {
error.resolve(activity) error.resolve(context)
}
} }
})
if (error.hasResolution()) { if (error.hasResolution()) {
alert.setNegativeButton("R.string.cancel", null) alert.setNegativeButton("Cancel", null)
} }
alert.show() alert.show()
} }
// Check whether the permissions that this application needs are acquired
private fun checkPermissionsAcquired(): Boolean {
val pmsManager = HealthPermissionManager(healthDataStore)
// Check whether the permissions that this application needs are acquired fun destroy() {
return runCatching { pmsManager.isPermissionAcquired(permissionKeySet) } store.disconnectService()
.onFailure { Log.e(TAG, "Permission request fails.", it) }
.map { it.values.all { it } }
.getOrDefault(false)
}
private fun requestPermission() {
val pmsManager = HealthPermissionManager(healthDataStore)
// Show user permission UI for allowing user to change options
runCatching { pmsManager.requestPermissions(permissionKeySet, activity) }
.onFailure { Log.e(TAG, "Permission setting fails.", it) }
.getOrNull()
?.setResultListener(mPermissionListener)
}
private val permissionKeySet: Set<HealthPermissionManager.PermissionKey> =
setOf(
HealthPermissionManager.PermissionKey(HealthConstants.StepCount.HEALTH_DATA_TYPE, HealthPermissionManager.PermissionType.READ),
HealthPermissionManager.PermissionKey(
HealthConstants.StepDailyTrend.HEALTH_DATA_TYPE,
HealthPermissionManager.PermissionType.READ
)
)
private val mPermissionListener = HealthResultHolder.ResultListener<HealthPermissionManager.PermissionResult> { result ->
// Show a permission alarm and clear step count if permissions are not acquired
if (result.resultMap.values.any { !it }) {
// List is now empty
showPermissionAlarmDialog()
} else {
// Get the daily step count of a particular day and display it
stepCountReader.requestDailyStepCount(currentStartTime)
}
}
private fun showPermissionAlarmDialog() {
if (isFinishing) {
return
}
AlertDialog.Builder(activity)
.setTitle("R.string.notice")
.setMessage("R.string.msg_perm_acquired")
.setPositiveButton("R.string.ok", null)
.show()
}
fun importStepCount() {
healthDataStore.connectService()
} }
} }

View File

@ -1,3 +0,0 @@
package com.dzeio.openhealth.connectors.samsunghealth
data class StepBinningData(var time: String, val count: Int)

View File

@ -1,8 +0,0 @@
package com.dzeio.openhealth.connectors.samsunghealth
interface StepCountObserver {
fun onChanged(count: Int)
fun onBinningDataChanged(binningCountList: List<StepBinningData>)
}

View File

@ -1,141 +0,0 @@
package com.dzeio.openhealth.connectors.samsunghealth
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.samsung.android.sdk.healthdata.HealthConstants.StepCount
import com.samsung.android.sdk.healthdata.HealthConstants.StepDailyTrend
import com.samsung.android.sdk.healthdata.HealthDataResolver
import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateRequest
import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateRequest.AggregateFunction
import com.samsung.android.sdk.healthdata.HealthDataResolver.AggregateRequest.TimeGroupUnit
import com.samsung.android.sdk.healthdata.HealthDataResolver.Filter
import com.samsung.android.sdk.healthdata.HealthDataResolver.ReadRequest
import com.samsung.android.sdk.healthdata.HealthDataResolver.SortOrder
import com.samsung.android.sdk.healthdata.HealthDataStore
import com.samsung.android.sdk.healthdata.HealthDataUtil
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
class StepCountReader(
store: HealthDataStore,
private val observer: StepCountObserver
) {
private val healthDataResolver: HealthDataResolver = HealthDataResolver(store, Handler(Looper.getMainLooper()))
// Get the daily total step count of a specified day
fun requestDailyStepCount(startTime: Long) {
if (startTime >= TODAY_START_UTC_TIME) {
// Get today step count
readStepCount(startTime)
} else {
// Get historical step count
readStepDailyTrend(startTime)
}
}
private fun readStepCount(startTime: Long) {
// Get sum of step counts by device
val request = AggregateRequest.Builder()
.setDataType(StepCount.HEALTH_DATA_TYPE)
.addFunction(AggregateFunction.SUM, StepCount.COUNT, ALIAS_TOTAL_COUNT)
.addGroup(StepCount.DEVICE_UUID, ALIAS_DEVICE_UUID)
.setLocalTimeRange(StepCount.START_TIME, StepCount.TIME_OFFSET, startTime, startTime + TIME_INTERVAL)
.setSort(ALIAS_TOTAL_COUNT, SortOrder.DESC)
.build()
runCatching { healthDataResolver.aggregate(request) }
.onFailure { Log.e(TAG, "Getting step count fails.", it) }
.getOrNull()
?.setResultListener {
it.use {
it.firstOrNull()
.also { observer.onChanged(it?.getInt(ALIAS_TOTAL_COUNT) ?: 0) }
?.let { readStepCountBinning(startTime, it.getString(ALIAS_DEVICE_UUID)) }
?: observer.onBinningDataChanged(emptyList())
}
}
}
private fun readStepDailyTrend(dayStartTime: Long) {
val request = ReadRequest.Builder()
.setDataType(StepDailyTrend.HEALTH_DATA_TYPE)
.setProperties(arrayOf(StepDailyTrend.COUNT, StepDailyTrend.BINNING_DATA))
.setFilter(Filter.and(
Filter.eq(StepDailyTrend.DAY_TIME, dayStartTime),
Filter.eq(StepDailyTrend.SOURCE_TYPE, StepDailyTrend.SOURCE_TYPE_ALL)))
.build()
runCatching { healthDataResolver.read(request) }
.onFailure { Log.e(TAG, "Getting daily step trend fails.", it) }
.getOrNull()
?.setResultListener {
it.use {
it.firstOrNull().also {
observer.onChanged(it?.getInt(StepDailyTrend.COUNT) ?: 0)
observer.onBinningDataChanged(
it?.getBlob(StepDailyTrend.BINNING_DATA)?.let { getBinningData(it) } ?: emptyList())
}
}
}
}
private fun getBinningData(zip: ByteArray): List<StepBinningData> {
// decompress ZIP
val binningDataList = HealthDataUtil.getStructuredDataList(zip, StepBinningData::class.java)
return binningDataList.asSequence()
.withIndex()
.filter { it.value.count != 0 }
.onEach { it.value.time = String.format(Locale.US, "%02d:%02d", it.index / 6, it.index % 6 * 10) }
.map { it.value }
.toList()
}
private fun readStepCountBinning(startTime: Long, deviceUuid: String) {
// Get 10 minute binning data of a particular device
val request = AggregateRequest.Builder()
.setDataType(StepCount.HEALTH_DATA_TYPE)
.addFunction(AggregateFunction.SUM, StepCount.COUNT, ALIAS_TOTAL_COUNT)
.setTimeGroup(TimeGroupUnit.MINUTELY, 10, StepCount.START_TIME, StepCount.TIME_OFFSET, ALIAS_BINNING_TIME)
.setLocalTimeRange(StepCount.START_TIME, StepCount.TIME_OFFSET, startTime, startTime + TIME_INTERVAL)
.setFilter(Filter.eq(StepCount.DEVICE_UUID, deviceUuid))
.setSort(ALIAS_BINNING_TIME, SortOrder.ASC)
.build()
runCatching { healthDataResolver.aggregate(request) }
.onFailure { Log.e(TAG, "Getting step binning data fails.", it) }
.getOrNull()
?.setResultListener {
it.use {
it.asSequence()
.map { it.getString(ALIAS_BINNING_TIME) to it.getInt(ALIAS_TOTAL_COUNT) }
.filter { it.first != null }
.map { StepBinningData(it.first.split(" ")[1], it.second) }
.toList()
}
.also { observer.onBinningDataChanged(it) }
}
}
companion object {
val TODAY_START_UTC_TIME = todayStartUtcTime
val TIME_INTERVAL = TimeUnit.DAYS.toMillis(1)
private const val ALIAS_TOTAL_COUNT = "count"
private const val ALIAS_DEVICE_UUID = "deviceuuid"
private const val ALIAS_BINNING_TIME = "binning_time"
const val TAG = "SamsungHealth.StepCountReader"
private val todayStartUtcTime: Long
get() {
val today = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
today[Calendar.HOUR_OF_DAY] = 0
today[Calendar.MINUTE] = 0
today[Calendar.SECOND] = 0
today[Calendar.MILLISECOND] = 0
return today.timeInMillis
}
}
}

View File

@ -0,0 +1,37 @@
package com.dzeio.openhealth.core
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.dzeio.openhealth.databinding.ActivityMainBinding
abstract class BaseActivity<VB : ViewBinding>() : AppCompatActivity() {
/**
* Function to inflate the Fragment Bindings
*
* use like this: `ViewBinding::inflater`
*/
abstract val bindingInflater: (LayoutInflater) -> VB
protected lateinit var binding: VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = bindingInflater(layoutInflater)
setContentView(binding.root)
onCreated(savedInstanceState)
}
protected open fun onCreated(savedInstanceState: Bundle?) {}
}

View File

@ -0,0 +1,46 @@
package com.dzeio.openhealth.core
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
abstract class BaseAdapter<T, VB : ViewBinding> : RecyclerView.Adapter<BaseViewHolder<VB>>() {
private var items = mutableListOf<T>()
// private var lastPosition = -1
@SuppressLint("NotifyDataSetChanged")
fun set(items: List<T>) {
this.items = items.toMutableList()
notifyDataSetChanged()
}
fun add(vararg items: T) {
val len = this.items.size
this.items.addAll(items)
notifyItemInserted(len)
}
/**
* Function to inflate the Adapter Bindings
*
* use like this: `ViewBinding::inflater`
*/
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
abstract fun onBindData(holder: BaseViewHolder<VB>, item: T, position: Int)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<VB> {
return BaseViewHolder<VB>(
bindingInflater(LayoutInflater.from(parent.context), parent, false)
)
}
override fun onBindViewHolder(holder: BaseViewHolder<VB>, position: Int) {
onBindData(holder, items[position], position)
}
override fun getItemCount(): Int = items.size
}

View File

@ -1,16 +1,21 @@
package com.dzeio.openhealth.core package com.dzeio.openhealth.core
import androidx.lifecycle.LiveData import androidx.room.Delete
import androidx.room.* import androidx.room.Insert
import com.dzeio.openhealth.db.entities.Weight import androidx.room.OnConflictStrategy
import androidx.room.Update
interface BaseDao<T> { interface BaseDao<T> {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg obj: T) suspend fun insert(vararg obj: T): List<Long>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(obj: T): Long
@Update @Update
fun update(vararg obj: T) suspend fun update(vararg obj: T)
@Delete @Delete
fun delete(vararg obj: T) suspend fun delete(vararg obj: T)
} }

View File

@ -0,0 +1,61 @@
package com.dzeio.openhealth.core
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
abstract class BaseDialog<VM : BaseViewModel, VB : ViewBinding>(private val viewModelClass: Class<VM>) : DialogFragment() {
val viewModel by lazy {
ViewModelProvider(this)[viewModelClass]
}
private var _binding: VB? = null
val binding get() = _binding!!
/**
* Setup everything!
*/
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { act ->
val builder = MaterialAlertDialogBuilder(requireContext())
_binding = bindingInflater(act.layoutInflater)
builder.setView(binding.root)
onBuilderInit(builder)
val dialog = builder.create()
onDialogInit(dialog)
onCreated()
dialog
} ?: throw IllegalStateException("Activity cannot be null")
}
open fun onBuilderInit(builder: MaterialAlertDialogBuilder): Unit {}
open fun onDialogInit(dialog: AlertDialog): Unit {}
open fun onCreated(): Unit {}
/**
* Function to inflate the Fragment Bindings
*/
abstract val bindingInflater: (LayoutInflater) -> VB
/**
* Destroy binding
*/
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,48 @@
package com.dzeio.openhealth.core
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
abstract class BaseFragment<VM : BaseViewModel, VB : ViewBinding>(private val viewModelClass: Class<VM>) : Fragment() {
val viewModel by lazy {
ViewModelProvider(this)[viewModelClass]
}
private var _binding: VB? = null
val binding get() = _binding!!
/**
* Setup everything!
*/
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
_binding = bindingInflater(inflater, container, false)
return binding.root
}
/**
* Function to inflate the Fragment Bindings
*
* use like this: `ViewBinding::inflater`
*/
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
/**
* Destroy binding
*/
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,85 @@
package com.dzeio.openhealth.core
import android.app.Dialog
import android.os.Bundle
import android.view.*
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.dzeio.openhealth.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
abstract class BaseFullscreenDialog<VM : BaseViewModel, VB : ViewBinding>(private val viewModelClass: Class<VM>) : DialogFragment() {
val viewModel by lazy {
ViewModelProvider(this)[viewModelClass]
}
private var _binding: VB? = null
val binding get() = _binding!!
/**
* Function to inflate the Fragment Bindings
*/
abstract val bindingInflater: (LayoutInflater) -> VB
abstract val isFullscreenLayout: Boolean
open fun onCreated(savedInstanceState: Bundle?) {}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bindingInflater(inflater)
setHasOptionsMenu(true)
onCreated(savedInstanceState)
return binding.root
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { act ->
val builder = MaterialAlertDialogBuilder(requireContext())
_binding = bindingInflater(act.layoutInflater)
builder.setView(binding.root)
val dialog = builder.create()
onDialogInit(dialog)
// onCreated()
dialog
} ?: throw IllegalStateException("Activity cannot be null")
}
open fun onDialogInit(dialog: Dialog): Unit {}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu.clear()
requireActivity().menuInflater.inflate(R.menu.fullscreen_dialog, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onDestroy() {
super.onDestroy()
}
/**
* Destroy binding
*/
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,9 @@
package com.dzeio.openhealth.core
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
class BaseViewHolder<VB : ViewBinding>(
val binding : VB
) : RecyclerView.ViewHolder(binding.root) {
}

View File

@ -0,0 +1,5 @@
package com.dzeio.openhealth.core
import androidx.lifecycle.ViewModel
abstract class BaseViewModel : ViewModel()

View File

@ -0,0 +1,44 @@
package com.dzeio.openhealth.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.dzeio.openhealth.data.weight.WeightDao
import com.dzeio.openhealth.data.weight.Weight
@Database(entities = [Weight::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun weightDao() : WeightDao
companion object {
private const val DATABASE_NAME = "open_health"
// For Singleton instantiation
@Volatile private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
// Create and pre-populate the database. See this article for more details:
// https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
// .addCallback(
// object : RoomDatabase.Callback() {
// override fun onCreate(db: SupportSQLiteDatabase) {
// super.onCreate(db)
// val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>()
// .setInputData(workDataOf(KEY_FILENAME to PLANT_DATA_FILENAME))
// .build()
// WorkManager.getInstance(context).enqueue(request)
// }
// }
// )
.build()
}
}
}

View File

@ -1,9 +1,10 @@
package com.dzeio.openhealth.db.entities package com.dzeio.openhealth.data.weight
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.sql.Timestamp import java.sql.Date
import java.text.DateFormat.getDateInstance
@Entity() @Entity()
data class Weight ( data class Weight (
@ -12,4 +13,6 @@ data class Weight (
@ColumnInfo(index = true) @ColumnInfo(index = true)
var timestamp: Long = System.currentTimeMillis(), var timestamp: Long = System.currentTimeMillis(),
var source: String = "" var source: String = ""
) ) {
fun formatTimestamp(): String = getDateInstance().format(Date(timestamp));
}

View File

@ -1,24 +1,23 @@
package com.dzeio.openhealth.db.dao package com.dzeio.openhealth.data.weight
import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import androidx.room.OnConflictStrategy.REPLACE
import com.dzeio.openhealth.core.BaseDao import com.dzeio.openhealth.core.BaseDao
import com.dzeio.openhealth.db.entities.Weight import dagger.Provides
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface WeightDao : BaseDao<Weight> { interface WeightDao : BaseDao<Weight> {
@Query("SELECT * FROM Weight") @Query("SELECT * FROM Weight")
fun getAll(): List<Weight> fun getAll(): Flow<List<Weight>>
@Query("SELECT * FROM Weight where id = :weightId") @Query("SELECT * FROM Weight where id = :weightId")
fun getOne(weightId: Long): Weight? fun getOne(weightId: Long): Flow<Weight?>
@Query("Select count(*) from Weight") @Query("Select count(*) from Weight")
fun getCount(): Int fun getCount(): Flow<Int>
@Query("Select * FROM Weight WHERE id=(SELECT max(id) FROM Weight)") @Query("Select * FROM Weight WHERE id=(SELECT max(id) FROM Weight)")
fun last(): Weight? fun last(): Flow<Weight?>
} }

View File

@ -0,0 +1,18 @@
package com.dzeio.openhealth.data.weight
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class WeightRepository @Inject constructor(
private val weightDao: WeightDao
) {
fun getWeights() = weightDao.getAll()
fun lastWeight() = weightDao.last()
fun getWeight(id: Long) = weightDao.getOne(id)
suspend fun addWeight(weight: Weight) = weightDao.insert(weight)
suspend fun deleteWeight(weight: Weight) = weightDao.delete(weight)
}

View File

@ -1,37 +0,0 @@
package com.dzeio.openhealth.db
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.dzeio.openhealth.db.dao.WeightDao
import com.dzeio.openhealth.db.entities.Weight
@Database(entities = [Weight::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun weightDao() : WeightDao
companion object {
// For Singleton instantiation
@Volatile
private var INSTANCE: AppDatabase? = null
private const val DATABASE_NAME = "open_health"
fun getInstance(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
DATABASE_NAME
)
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
return instance
}
}
}
}

View File

@ -0,0 +1,27 @@
package com.dzeio.openhealth.di
import android.content.Context
import com.dzeio.openhealth.data.AppDatabase
import com.dzeio.openhealth.data.weight.WeightDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
class DatabaseModule {
@Singleton
@Provides
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
@Provides
fun provideWeightDao(appDatabase: AppDatabase): WeightDao {
return appDatabase.weightDao()
}
}

View File

@ -0,0 +1,68 @@
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()
}
}

View File

@ -0,0 +1,162 @@
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)
}
}
}

View File

@ -1,42 +0,0 @@
package com.dzeio.openhealth.ui.gallery
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.dzeio.openhealth.databinding.FragmentGalleryBinding
class GalleryFragment : Fragment() {
private var _binding: FragmentGalleryBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val galleryViewModel =
ViewModelProvider(this).get(GalleryViewModel::class.java)
_binding = FragmentGalleryBinding.inflate(inflater, container, false)
val root: View = binding.root
val textView: TextView = binding.textGallery
galleryViewModel.text.observe(viewLifecycleOwner) {
textView.text = it
}
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -1,13 +0,0 @@
package com.dzeio.openhealth.ui.gallery
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class GalleryViewModel : ViewModel() {
private val _text = MutableLiveData<String>().apply {
value = "This is gallery Fragment"
}
val text: LiveData<String> = _text
}

View File

@ -1,14 +0,0 @@
package com.dzeio.openhealth.ui.home
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
class HomeViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val text = MutableLiveData<String>().apply {
value = "This is home Fragment"
}
}

View File

@ -1,74 +0,0 @@
package com.dzeio.openhealth.ui.import
import android.app.ProgressDialog
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.dzeio.openhealth.connectors.GoogleFit
import com.dzeio.openhealth.databinding.FragmentImportBinding
class ImportFragment : Fragment() {
private var _binding: FragmentImportBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private lateinit var viewModel: ImportViewModel
private lateinit var progressDialog: ProgressDialog
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(this).get(ImportViewModel::class.java)
_binding = FragmentImportBinding.inflate(inflater, container, false)
val root: View = binding.root
progressDialog = ProgressDialog(requireContext())
progressDialog.apply {
setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
setCancelable(false)
}
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.importGoogleFit.setOnClickListener {
importFromGoogleFit()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
@RequiresApi(Build.VERSION_CODES.O)
fun importFromGoogleFit() {
viewModel.importProgressTotal.postValue(-1)
val google = GoogleFit(requireActivity())
progressDialog.show()
google.importWeight {
progressDialog.dismiss()
}
}
fun importFromSamsungHealth() {
}
}

View File

@ -0,0 +1,199 @@
package com.dzeio.openhealth.ui.main.home
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.content.pm.PackageManager
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 androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFragment
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.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 dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
@AndroidEntryPoint
class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewModel::class.java) {
companion object {
const val TAG = "HomeFragment"
}
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHomeBinding
get() = FragmentHomeBinding::inflate
// private lateinit var fit: GoogleFit
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.addWeight.setOnClickListener {
AddWeightDialog().show(requireActivity().supportFragmentManager, null)
}
binding.listWeight.setOnClickListener {
Log.d("T", "Trying to move")
findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight())
// requireActivity().supportFragmentManager.commit {
// replace(R.id.nav_host_fragment_content_main, ListWeightFragment())
// addToBackStack(null)
// }
}
// Setup Graph
// binding.weightGraph.gridLabelRenderer.labelFormatter = DateAsXAxisLabelFormatter(requireContext())
// binding.weightGraph.gridLabelRenderer.numHorizontalLabels = 3 // only 4 because of the space
// binding.weightGraph.viewport.isXAxisBoundsManual = true
//// binding.weightGraph.gridLabelRenderer.setHumanRounding(false);
// binding.weightGraph.addSeries(serie)
}
// private val entries = LineGraphSeries<DataPoint>()
private fun updateGraph(list: List<Weight>) {
if (list.isNotEmpty()) {
val first = list[0].timestamp
val last = list[list.size - 1].timestamp
val weekFirst = last - 604800000
// binding.weightGraph.viewport.setMinX(if (first > weekFirst) first else weekFirst)
// binding.weightGraph.viewport.setMaxX(last);
val entries = ArrayList<Entry>()
for (item in list) {
entries.add(Entry(item.timestamp.toFloat(), item.weight))
}
val dataSet = LineDataSet(entries, "Label")
// binding.weightGraph.xAxis.axisMinimum = if (first > weekFirst) first.toFloat() else weekFirst.toFloat()
// binding.weightGraph.setExtraOffsets(
// if (first > weekFirst) first.toFloat() else weekFirst.toFloat(),
// 100f,
// last.toFloat(),
// 0f
// )
binding.weightGraph.isAutoScaleMinMaxEnabled = true
//binding.weightGraph.setvir
binding.weightGraph.isDragEnabled = true
binding.weightGraph.isScaleYEnabled = false
binding.weightGraph.description = Description().apply { isEnabled = false }
binding.weightGraph.isScaleXEnabled = true
binding.weightGraph.setPinchZoom(false)
binding.weightGraph.xAxis.setLabelCount(5, true)
binding.weightGraph.xAxis.position = XAxis.XAxisPosition.BOTTOM
binding.weightGraph.axisRight.isEnabled = false
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 super.getAxisLabel(value, axis)
}
}
binding.weightGraph.data = LineData(dataSet)
binding.weightGraph.invalidate()
}
// serie.resetData(list
// .map { DataPoint(Date(it.timestamp), it.weight.toDouble()) }
// .toTypedArray()
// )
}
override fun onStart() {
super.onStart()
lifecycleScope.launchWhenStarted {
viewModel.fetchWeights().collectLatest {
if (it.isEmpty()) {
return@collectLatest
}
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")
}
}
}
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.")
}
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.
// 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.
// 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()
}
}
}
}

View File

@ -0,0 +1,23 @@
package com.dzeio.openhealth.ui.main.home
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 HomeViewModel @Inject internal constructor(
private val weightRepository: WeightRepository
) : BaseViewModel() {
fun fetchWeights() = weightRepository.getWeights()
fun lastWeight() = weightRepository.lastWeight()
fun fetchWeight(id: Long) = weightRepository.getWeight(id)
suspend fun deleteWeight(weight: Weight) = weightRepository.deleteWeight(weight)
suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight)
}

View File

@ -1,7 +1,6 @@
package com.dzeio.openhealth.ui.home package com.dzeio.openhealth.ui.main.import
import android.app.Activity.RESULT_OK import android.app.ProgressDialog
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -9,67 +8,61 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.dzeio.openhealth.connectors.ActionRequestCode import com.dzeio.openhealth.connectors.ActionRequestCode
import com.dzeio.openhealth.connectors.GoogleFit import com.dzeio.openhealth.connectors.GoogleFit
//import com.dzeio.openhealth.connectors.GoogleFit
import com.dzeio.openhealth.connectors.samsunghealth.SamsungHealth import com.dzeio.openhealth.connectors.samsunghealth.SamsungHealth
import com.dzeio.openhealth.databinding.FragmentHomeBinding import com.dzeio.openhealth.databinding.FragmentImportBinding
import com.dzeio.openhealth.db.AppDatabase
class HomeFragment : Fragment() { class ImportFragment : Fragment() {
companion object { companion object {
const val TAG = "HomeFragment" const val TAG = "ImportFragment"
} }
private var _binding: FragmentHomeBinding? = null private var _binding: FragmentImportBinding? = null
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
// onDestroyView. // onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var viewModel: ImportViewModel
private lateinit var progressDialog: ProgressDialog
private lateinit var fit: GoogleFit private lateinit var fit: GoogleFit
private lateinit var viewModel: HomeViewModel
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
viewModel = viewModel = ViewModelProvider(this).get(ImportViewModel::class.java)
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentHomeBinding.inflate(inflater, container, false) _binding = FragmentImportBinding.inflate(inflater, container, false)
val root: View = binding.root val root: View = binding.root
// binding.button.setOnClickListener { progressDialog = ProgressDialog(requireContext())
// fit = GoogleFit(requireActivity())
// //fit.import()
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// //fit.getHistory()
// fit.importWeight()
// }
//
// //SamsungHealth(requireActivity()).importStepCount()
// }
viewModel.text.observe(viewLifecycleOwner) {
binding.textView2.text = it
}
progressDialog.apply {
setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
setCancelable(false)
}
return root return root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.importGoogleFit.setOnClickListener {
val weight = AppDatabase.getInstance(requireContext()).weightDao().last() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (weight == null) { importFromGoogleFit()
viewModel.text.postValue("No Weight Available") }
} else { }
viewModel.text.postValue("${weight.weight}kg\ndone at ${weight.timestamp}") binding.importSamsungHealth.setOnClickListener {
importFromSamsungHealth()
} }
} }
@ -78,17 +71,22 @@ class HomeFragment : Fragment() {
_binding = null _binding = null
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @RequiresApi(Build.VERSION_CODES.O)
super.onActivityResult(requestCode, resultCode, data) fun importFromGoogleFit() {
Log.d(TAG, "Activity Result!") viewModel.importProgressTotal.postValue(-1)
when (resultCode) { fit = GoogleFit(requireActivity())
RESULT_OK -> {
fit.performActionForRequestCode(ActionRequestCode.FIND_DATA_SOURCES) progressDialog.show()
} fit.import()
else -> { fit.getHistory()
Log.e(TAG, "Error: $requestCode, $resultCode") // google.importWeight {
} // progressDialog.dismiss()
// }
} }
fun importFromSamsungHealth() {
SamsungHealth(requireActivity())
.test()
} }
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,

View File

@ -1,9 +1,8 @@
package com.dzeio.openhealth.ui.import package com.dzeio.openhealth.ui.main.import
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.dzeio.openhealth.connectors.GoogleFit //import com.dzeio.openhealth.connectors.GoogleFit
class ImportViewModel : ViewModel() { class ImportViewModel : ViewModel() {

View File

@ -0,0 +1,49 @@
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.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
@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().collect {
adapter.set(it)
}
}
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M16,9v10H8V9h8m-1.5,-6h-5l-1,1H5v2h14V4h-3.5l-1,-1zM18,7H6v12c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17.2,3H6.8l-5.2,9l5.2,9h10.4l5.2,-9L17.2,3zM16.05,19H7.95l-4.04,-7l4.04,-7h8.09l4.04,7L16.05,19z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M23,8c0,1.1 -0.9,2 -2,2c-0.18,0 -0.35,-0.02 -0.51,-0.07l-3.56,3.55C16.98,13.64 17,13.82 17,14c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-0.18 0.02,-0.36 0.07,-0.52l-2.55,-2.55C10.36,10.98 10.18,11 10,11c-0.18,0 -0.36,-0.02 -0.52,-0.07l-4.55,4.56C4.98,15.65 5,15.82 5,16c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2s0.9,-2 2,-2c0.18,0 0.35,0.02 0.51,0.07l4.56,-4.55C8.02,9.36 8,9.18 8,9c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,0.18 -0.02,0.36 -0.07,0.52l2.55,2.55C14.64,12.02 14.82,12 15,12c0.18,0 0.36,0.02 0.52,0.07l3.55,-3.56C19.02,8.35 19,8.18 19,8c0,-1.1 0.9,-2 2,-2S23,6.9 23,8z"/>
</vector>

View File

@ -5,21 +5,55 @@
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" tools:openDrawer="start"
tools:openDrawer="start"> tools:context=".MainActivity">
<include <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/app_bar_main"
layout="@layout/app_bar_main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
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">
<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:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView <com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view" android:id="@+id/nav_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main" app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" /> app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.OpenHealth.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.OpenHealth.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,20 +0,0 @@
<?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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/app_bar_main">
<fragment
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:gravity="center"
android:orientation="horizontal">
<android.widget.NumberPicker
android:id="@+id/kg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<android.widget.NumberPicker
android:id="@+id/gram"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:progress="0" />
</LinearLayout>

View File

@ -0,0 +1,47 @@
<?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"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/layout_dialog_edit_weight_ll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<android.widget.NumberPicker
android:id="@+id/layout_dialog_edit_weight_kg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@+id/layout_dialog_edit_weight_gram"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="0dp" />
<android.widget.NumberPicker
android:id="@+id/layout_dialog_edit_weight_gram"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:progress="0"
app:layout_constraintEnd_toEndOf="parent"
tools:layout_editor_absoluteY="0dp" />
</LinearLayout>
<TextView
android:id="@+id/layout_dialog_edit_weight_timestamp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/layout_dialog_edit_weight_ll" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,22 +0,0 @@
<?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"
tools:context=".ui.gallery.GalleryFragment">
<TextView
android:id="@+id/text_gallery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,14 +4,91 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment"> tools:context=".ui.main.home.HomeFragment">
<TextView <com.google.android.material.card.MaterialCardView
android:id="@+id/textView2" style="?attr/materialCardViewFilledStyle"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="TextView" android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout2"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginHorizontal="16dp"
android:gravity="fill_horizontal|center_vertical"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="16dp">
<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" /> app:layout_constraintTop_toTopOf="parent" />
<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">
<ImageView
android:id="@+id/add_weight"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="16dp"
android:src="@drawable/ic_baseline_add_24" />
<ImageView
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" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<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" />
<!-- <com.jjoe64.graphview.GraphView-->
<!-- 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" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView 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:clipToPadding="false"
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
tools:listitem="@layout/layout_item_weight"
tools:context=".ui.main.list_weight.ListWeightFragment" />

View File

@ -0,0 +1,59 @@
<?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

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_fullscreen_dialog_save"
android:title="Save"
app:showAsAction="ifRoom" />
<item
android:id="@+id/menu_fullscreen_dialog_delete"
android:title="Delete"
android:icon="@drawable/ic_outline_delete_24"
app:showAsAction="ifRoom" />
</menu>

View File

@ -7,19 +7,54 @@
<fragment <fragment
android:id="@+id/nav_home" android:id="@+id/nav_home"
android:name="com.dzeio.openhealth.ui.home.HomeFragment" android:name="com.dzeio.openhealth.ui.main.home.HomeFragment"
android:label="@string/menu_home" android:label="@string/menu_home"
tools:layout="@layout/fragment_home" /> tools:layout="@layout/fragment_home" >
<action
<fragment android:id="@+id/action_nav_home_to_nav_list_weight"
android:id="@+id/nav_gallery" app:destination="@id/nav_list_weight"
android:name="com.dzeio.openhealth.ui.gallery.GalleryFragment" app:enterAnim="@android:anim/slide_in_left"
android:label="@string/menu_gallery" app:exitAnim="@android:anim/slide_out_right"
tools:layout="@layout/fragment_gallery" /> app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@+id/action_nav_home_to_nav_import"
app:destination="@id/nav_import"
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 <fragment
android:id="@+id/nav_import" android:id="@+id/nav_import"
android:name="com.dzeio.openhealth.ui.import.ImportFragment" android:name="com.dzeio.openhealth.ui.main.import.ImportFragment"
android:label="@string/menu_import" android:label="@string/menu_import"
tools:layout="@layout/fragment_import" /> tools:layout="@layout/fragment_import" />
<fragment
android:id="@+id/nav_list_weight"
android:name="com.dzeio.openhealth.ui.main.list_weight.ListWeightFragment"
android:label="@string/menu_list_weight"
tools:layout="@layout/fragment_list_weight" >
<action
android:id="@+id/action_nav_list_weight_to_nav_edit_weight"
app:destination="@id/nav_edit_weight"
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
android:id="@+id/nav_edit_weight"
android:name="com.dzeio.openhealth.ui.dialogs.EditWeightDialog"
android:label="@string/menu_edit_weight"
tools:layout="@layout/dialog_edit_weight">
<argument
android:name="id"
app:argType="long" />
</fragment>
</navigation> </navigation>

View File

@ -1,16 +1,17 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.OpenHealth" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.OpenHealth" parent="Theme.Material3.DynamicColors.DayNight">
<!-- Primary brand color. --> <!-- &lt;!&ndash; Primary brand color. &ndash;&gt;-->
<item name="colorPrimary">@color/purple_200</item> <!-- <item name="colorPrimary">@color/purple_200</item>-->
<item name="colorPrimaryVariant">@color/purple_700</item> <!-- <item name="colorPrimaryVariant">@color/purple_700</item>-->
<item name="colorOnPrimary">@color/black</item> <!-- <item name="colorOnPrimary">@color/black</item>-->
<!-- Secondary brand color. --> <!-- &lt;!&ndash; Secondary brand color. &ndash;&gt;-->
<item name="colorSecondary">@color/teal_200</item> <!-- <item name="colorSecondary">@color/teal_200</item>-->
<item name="colorSecondaryVariant">@color/teal_200</item> <!-- <item name="colorSecondaryVariant">@color/teal_200</item>-->
<item name="colorOnSecondary">@color/black</item> <!-- <item name="colorOnSecondary">@color/black</item>-->
<!-- Status bar color. --> <!-- &lt;!&ndash; Status bar color. &ndash;&gt;-->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <!-- <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>-->
<!-- Customize your theme here. --> <!-- &lt;!&ndash; Customize your theme here. &ndash;&gt;-->
</style> </style>
</resources> </resources>

View File

@ -11,4 +11,6 @@
<string name="menu_gallery">Gallery</string> <string name="menu_gallery">Gallery</string>
<string name="menu_slideshow">Slideshow</string> <string name="menu_slideshow">Slideshow</string>
<string name="menu_import">Import</string> <string name="menu_import">Import</string>
<string name="menu_list_weight">Weight List</string>
<string name="menu_edit_weight">Edit Weight</string>
</resources> </resources>

View File

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

View File

@ -1,10 +1,21 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'
// Safe Navigation
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
}
}
plugins { plugins {
id 'com.android.application' version '7.1.0-beta04' apply false id 'com.android.application' version '7.1.0-beta05' apply false
id 'com.android.library' version '7.1.0-beta04' apply false id 'com.android.library' version '7.1.0-beta05' apply false
id 'org.jetbrains.kotlin.android' version '1.6.0' apply false id 'org.jetbrains.kotlin.android' version '1.6.0' apply false
} }
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
delete project.buildDir
} }

View File

@ -21,3 +21,5 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.enableJetifier=true

View File

@ -5,11 +5,13 @@ pluginManagement {
mavenCentral() mavenCentral()
} }
} }
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven { url 'https://jitpack.io' }
} }
} }
rootProject.name = "OpenHealth" rootProject.name = "OpenHealth"