diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..7e7f500 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 7726348..e30b29b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,8 +7,21 @@ + + - + + + + + + + + + + + + diff --git a/app/build.gradle b/app/build.gradle index 71281fd..0e58460 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,10 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'kotlin-kapt' + id 'dagger.hilt.android.plugin' + + // Safe Navigation + id 'androidx.navigation.safeargs' } android { @@ -32,6 +36,8 @@ android { } buildFeatures { viewBinding true + dataBinding true + } } @@ -39,7 +45,8 @@ dependencies { implementation 'androidx.core:core-ktx:1.7.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.lifecycle:lifecycle-livedata-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.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 implementation "com.google.android.gms:play-services-fitness:21.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" kapt "androidx.room:room-compiler:$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 + implementation "androidx.room:room-ktx:$room_version" testImplementation "androidx.room:room-testing:$room_version" - // optional - Paging 3 Integration - implementation "androidx.room:room-paging:2.4.0-rc01" - } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 628ae02..069b7f2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ + android:value="com.samsung.health.weight" /> + + () { private lateinit var appBarConfiguration: AppBarConfiguration - private lateinit var binding: ActivityMainBinding - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + private lateinit var navController: NavController - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) + override val bindingInflater: (LayoutInflater) -> ActivityMainBinding = ActivityMainBinding::inflate - 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( setOf( - R.id.nav_home, R.id.nav_gallery, R.id.nav_import - ), drawerLayout + R.id.nav_home + ), binding.drawerLayout ) + setupActionBarWithNavController(navController, appBarConfiguration) - navView.setupWithNavController(navController) + } 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) return true } override fun onSupportNavigateUp(): Boolean { - val navController = findNavController(R.id.nav_host_fragment_content_main) return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, - grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) Log.d("MainActivity", "Result $requestCode") } diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt new file mode 100644 index 0000000..f88f3ab --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt @@ -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() { + + override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemWeightBinding + get() = LayoutItemWeightBinding::inflate + + var onItemClick: ((weight: Weight) -> Unit)? = null + + override fun onBindData( + holder: BaseViewHolder, + item: Weight, + position: Int + ) { + holder.binding.weight.text = "${item.weight}kg" + holder.binding.datetime.text = item.formatTimestamp() + holder.binding.edit.setOnClickListener { + onItemClick?.invoke(item) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt b/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt index b06bc4d..b9f6231 100644 --- a/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt +++ b/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt @@ -7,8 +7,8 @@ import android.os.Build import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat -import com.dzeio.openhealth.db.AppDatabase -import com.dzeio.openhealth.db.entities.Weight +import com.dzeio.openhealth.data.AppDatabase +import com.dzeio.openhealth.data.weight.Weight import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.fitness.Fitness import com.google.android.gms.fitness.FitnessOptions @@ -36,15 +36,29 @@ class GoogleFit( companion object { const val TAG = "GoogleFitConnector" } +// private val fitnessOptions = FitnessOptions.builder() +// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_READ) +// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT, FitnessOptions.ACCESS_WRITE) +// +// .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_READ) +// .addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_WRITE) +// +// .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_READ) +// .addDataType(DataType.TYPE_HEIGHT, FitnessOptions.ACCESS_WRITE) +// +// .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_READ) +// .addDataType(DataType.TYPE_WEIGHT, FitnessOptions.ACCESS_WRITE) +// .build() + private val fitnessOptions = FitnessOptions.builder() - .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE) - .addDataType(DataType.TYPE_ACTIVITY_SEGMENT) - .addDataType(DataType.TYPE_SLEEP_SEGMENT) - .addDataType(DataType.TYPE_CALORIES_EXPENDED) - .addDataType(DataType.TYPE_BASAL_METABOLIC_RATE) - .addDataType(DataType.TYPE_POWER_SAMPLE) - .addDataType(DataType.TYPE_HEART_RATE_BPM) - .addDataType(DataType.TYPE_LOCATION_SAMPLE) +// .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE) +// .addDataType(DataType.TYPE_ACTIVITY_SEGMENT) +// .addDataType(DataType.TYPE_SLEEP_SEGMENT) +// .addDataType(DataType.TYPE_CALORIES_EXPENDED) +// .addDataType(DataType.TYPE_BASAL_METABOLIC_RATE) +// .addDataType(DataType.TYPE_POWER_SAMPLE) +// .addDataType(DataType.TYPE_HEART_RATE_BPM) +// .addDataType(DataType.TYPE_LOCATION_SAMPLE) .addDataType(DataType.TYPE_WEIGHT) .build() @@ -196,12 +210,16 @@ class GoogleFit( @RequiresApi(Build.VERSION_CODES.O) fun getHistory() { - val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()) - val startTime = LocalDateTime.MIN.atZone(ZoneId.systemDefault()) + val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + val now = Date() + calendar.time = now + val endTime = calendar.timeInMillis + calendar.set(Calendar.YEAR, 2013) // Set year to 2013 to be sure to get data from when Google Fit Started to today + val startTime = calendar.timeInMillis val readRequest = DataReadRequest.Builder() .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED) .bucketByActivityType(1, TimeUnit.SECONDS) - .setTimeRange(startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) + .setTimeRange(startTime, endTime, TimeUnit.SECONDS) .build() 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) { val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) @@ -295,7 +306,7 @@ class GoogleFit( weight.weight = dp.getValue(field).asFloat() Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}") } - AppDatabase.getInstance(activity).weightDao().insert(weight) + // AppDatabase.getInstance(activity).weightDao().insert(weight) } } diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/SamsungHealth.kt b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/SamsungHealth.kt index fe3be0a..a6e080e 100644 --- a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/SamsungHealth.kt +++ b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/SamsungHealth.kt @@ -1,143 +1,134 @@ -package com.dzeio.openhealth.connectors.samsunghealth - -import android.app.Activity -import android.app.AlertDialog -import android.util.Log -import com.samsung.android.sdk.healthdata.* - -class SamsungHealth( - private val activity: Activity -) { - - private var currentStartTime: Long = 0 - private var isFinishing = false - - init { - currentStartTime = StepCountReader.TODAY_START_UTC_TIME - } - - companion object { - const val TAG = "SamsungHealth" - } - -// private val binningListAdapter: StepBinningData by lazy { StepBinningData() } - - private val healthDataStore: HealthDataStore by lazy { HealthDataStore(activity, connectionListener) } - private val stepCountReader: StepCountReader by lazy { StepCountReader(healthDataStore, stepCountObserver) } - - private val stepCountObserver: StepCountObserver = object : StepCountObserver { - override fun onChanged(count: Int) { - Log.d(TAG, "$count") - } - - override fun onBinningDataChanged(binningCountList: List) { - 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 { - requestPermission() - } - } - - override fun onConnectionFailed(error: HealthConnectionErrorResult) { - Log.d(TAG, "onConnectionFailed") - showConnectionFailureDialog(error) - } - - override fun onDisconnected() { - Log.d(TAG, "onDisconnected") - if (!isFinishing) { - healthDataStore.connectService() - } - } - } - - private fun showConnectionFailureDialog(error: HealthConnectionErrorResult) { - if (isFinishing) { - return - } - val alert = AlertDialog.Builder(activity) - if (error.hasResolution()) { - when (error.errorCode) { - HealthConnectionErrorResult.PLATFORM_NOT_INSTALLED -> alert.setMessage("R.string.msg_req_install") - HealthConnectionErrorResult.OLD_VERSION_PLATFORM -> alert.setMessage("R.string.msg_req_upgrade") - HealthConnectionErrorResult.PLATFORM_DISABLED -> alert.setMessage("R.string.msg_req_enable") - HealthConnectionErrorResult.USER_AGREEMENT_NEEDED -> alert.setMessage("R.string.msg_req_agree") - else -> alert.setMessage("R.string.msg_req_available") - } - } else { - alert.setMessage("R.string.msg_conn_not_available") - } - alert.setPositiveButton("R.string.ok") { _, _ -> - if (error.hasResolution()) { - error.resolve(activity) - } - } - if (error.hasResolution()) { - alert.setNegativeButton("R.string.cancel", null) - } - 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 - return runCatching { pmsManager.isPermissionAcquired(permissionKeySet) } - .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 = - 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 { 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() - } +package com.dzeio.openhealth.connectors.samsunghealth + +import android.app.Activity +import android.app.AlertDialog +import android.content.DialogInterface +import android.util.Log +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( + private val context: Activity +) { + companion object { + const val TAG = "SamsungHealthConnector" + } + private val store: HealthDataStore get() = _store!! + private var _store: HealthDataStore? = null + private val keySet: HashSet = HashSet() + + private val permissionListener: ResultListener = + ResultListener { result -> + Log.d(TAG, "Permission callback is received.") + val resultMap: Map = result!!.resultMap + + if (resultMap.containsValue(Boolean.FALSE)) { + // Requesting permission fails + Log.d(TAG, "Requesting permission fails") + + } else { + // 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) { + Log.d(TAG, "Health data service is not available.") + showConnectionFailureDialog(error) + } + + override fun onDisconnected() { + Log.d(TAG, "Health data service is disconnected.") + } + } + + 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) { + val alert: AlertDialog.Builder = AlertDialog.Builder(context) + var message = "Connection with Samsung Health is not available" + if (error.hasResolution()) { + message = when (error.errorCode) { + HealthConnectionErrorResult.PLATFORM_NOT_INSTALLED -> "Please install Samsung Health" + HealthConnectionErrorResult.OLD_VERSION_PLATFORM -> "Please upgrade Samsung Health" + HealthConnectionErrorResult.PLATFORM_DISABLED -> "Please enable Samsung Health" + HealthConnectionErrorResult.USER_AGREEMENT_NEEDED -> "Please agree with Samsung Health policy" + else -> "Please make Samsung Health available" + } + } + alert.setMessage(message) + alert.setPositiveButton("OK", DialogInterface.OnClickListener { dialog, id -> + if (error.hasResolution()) { + error.resolve(context) + } + }) + if (error.hasResolution()) { + alert.setNegativeButton("Cancel", null) + } + alert.show() + } + + + fun destroy() { + store.disconnectService() + } } \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepBinningData.kt b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepBinningData.kt deleted file mode 100644 index cd402a9..0000000 --- a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepBinningData.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.dzeio.openhealth.connectors.samsunghealth - -data class StepBinningData(var time: String, val count: Int) \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountObserver.kt b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountObserver.kt deleted file mode 100644 index a512416..0000000 --- a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountObserver.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.dzeio.openhealth.connectors.samsunghealth - -interface StepCountObserver { - - fun onChanged(count: Int) - - fun onBinningDataChanged(binningCountList: List) -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountReader.kt b/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountReader.kt deleted file mode 100644 index b9e5125..0000000 --- a/app/src/main/java/com/dzeio/openhealth/connectors/samsunghealth/StepCountReader.kt +++ /dev/null @@ -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 { - // 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 - } - } -} diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt new file mode 100644 index 0000000..e5467fe --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseActivity.kt @@ -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() : 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?) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt new file mode 100644 index 0000000..eaa9481 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseAdapter.kt @@ -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 : RecyclerView.Adapter>() { + private var items = mutableListOf() + // private var lastPosition = -1 + + @SuppressLint("NotifyDataSetChanged") + fun set(items: List) { + 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, item: T, position: Int) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { + return BaseViewHolder( + bindingInflater(LayoutInflater.from(parent.context), parent, false) + ) + } + + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + onBindData(holder, items[position], position) + } + + override fun getItemCount(): Int = items.size + +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseDao.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseDao.kt index ceab0f8..aaafc60 100644 --- a/app/src/main/java/com/dzeio/openhealth/core/BaseDao.kt +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseDao.kt @@ -1,16 +1,21 @@ package com.dzeio.openhealth.core -import androidx.lifecycle.LiveData -import androidx.room.* -import com.dzeio.openhealth.db.entities.Weight +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Update + interface BaseDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(vararg obj: T) + suspend fun insert(vararg obj: T): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(obj: T): Long @Update - fun update(vararg obj: T) + suspend fun update(vararg obj: T) @Delete - fun delete(vararg obj: T) + suspend fun delete(vararg obj: T) } \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseDialog.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseDialog.kt new file mode 100644 index 0000000..79e57e3 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseDialog.kt @@ -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(private val viewModelClass: Class) : 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt new file mode 100644 index 0000000..75e4109 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt @@ -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(private val viewModelClass: Class) : 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt new file mode 100644 index 0000000..8f5848a --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseFullscreenDialog.kt @@ -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(private val viewModelClass: Class) : 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt new file mode 100644 index 0000000..fcadf2c --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseViewHolder.kt @@ -0,0 +1,9 @@ +package com.dzeio.openhealth.core + +import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding + +class BaseViewHolder( + val binding : VB +) : RecyclerView.ViewHolder(binding.root) { +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseViewModel.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseViewModel.kt new file mode 100644 index 0000000..fe5c162 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseViewModel.kt @@ -0,0 +1,5 @@ +package com.dzeio.openhealth.core + +import androidx.lifecycle.ViewModel + +abstract class BaseViewModel : ViewModel() \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt b/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt new file mode 100644 index 0000000..f5dbd79 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/AppDatabase.kt @@ -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() +// .setInputData(workDataOf(KEY_FILENAME to PLANT_DATA_FILENAME)) +// .build() +// WorkManager.getInstance(context).enqueue(request) +// } +// } +// ) + .build() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/db/entities/Weight.kt b/app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt similarity index 62% rename from app/src/main/java/com/dzeio/openhealth/db/entities/Weight.kt rename to app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt index d7c3a59..8199d34 100644 --- a/app/src/main/java/com/dzeio/openhealth/db/entities/Weight.kt +++ b/app/src/main/java/com/dzeio/openhealth/data/weight/Weight.kt @@ -1,9 +1,10 @@ -package com.dzeio.openhealth.db.entities +package com.dzeio.openhealth.data.weight import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import java.sql.Timestamp +import java.sql.Date +import java.text.DateFormat.getDateInstance @Entity() data class Weight ( @@ -12,4 +13,6 @@ data class Weight ( @ColumnInfo(index = true) var timestamp: Long = System.currentTimeMillis(), var source: String = "" -) \ No newline at end of file +) { + fun formatTimestamp(): String = getDateInstance().format(Date(timestamp)); +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/db/dao/WeightDao.kt b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt similarity index 53% rename from app/src/main/java/com/dzeio/openhealth/db/dao/WeightDao.kt rename to app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt index 27a3fc8..4ec48f7 100644 --- a/app/src/main/java/com/dzeio/openhealth/db/dao/WeightDao.kt +++ b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightDao.kt @@ -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.OnConflictStrategy.REPLACE import com.dzeio.openhealth.core.BaseDao -import com.dzeio.openhealth.db.entities.Weight +import dagger.Provides +import kotlinx.coroutines.flow.Flow @Dao interface WeightDao : BaseDao { @Query("SELECT * FROM Weight") - fun getAll(): List + fun getAll(): Flow> @Query("SELECT * FROM Weight where id = :weightId") - fun getOne(weightId: Long): Weight? + fun getOne(weightId: Long): Flow @Query("Select count(*) from Weight") - fun getCount(): Int + fun getCount(): Flow @Query("Select * FROM Weight WHERE id=(SELECT max(id) FROM Weight)") - fun last(): Weight? + fun last(): Flow } \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/data/weight/WeightRepository.kt b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightRepository.kt new file mode 100644 index 0000000..ac68454 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/data/weight/WeightRepository.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/db/AppDatabase.kt b/app/src/main/java/com/dzeio/openhealth/db/AppDatabase.kt deleted file mode 100644 index f39432a..0000000 --- a/app/src/main/java/com/dzeio/openhealth/db/AppDatabase.kt +++ /dev/null @@ -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 - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt b/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt new file mode 100644 index 0000000..cf320b2 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/di/DatabaseModule.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/dialogs/AddWeightDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/dialogs/AddWeightDialog.kt new file mode 100644 index 0000000..8b30f87 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/dialogs/AddWeightDialog.kt @@ -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::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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/dialogs/EditWeightDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/dialogs/EditWeightDialog.kt new file mode 100644 index 0000000..6c5ad5e --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/dialogs/EditWeightDialog.kt @@ -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::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) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryFragment.kt deleted file mode 100644 index ece59f6..0000000 --- a/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryFragment.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryViewModel.kt deleted file mode 100644 index 251bce1..0000000 --- a/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryViewModel.kt +++ /dev/null @@ -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().apply { - value = "This is gallery Fragment" - } - val text: LiveData = _text -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt deleted file mode 100644 index 9a07365..0000000 --- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt +++ /dev/null @@ -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().apply { - value = "This is home Fragment" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/import/ImportFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/import/ImportFragment.kt deleted file mode 100644 index f7f0977..0000000 --- a/app/src/main/java/com/dzeio/openhealth/ui/import/ImportFragment.kt +++ /dev/null @@ -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() { - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeFragment.kt new file mode 100644 index 0000000..42e8629 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeFragment.kt @@ -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::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() + + private fun updateGraph(list: List) { + 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() + 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, + 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() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeViewModel.kt new file mode 100644 index 0000000..db1a7ce --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/main/home/HomeViewModel.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportFragment.kt similarity index 66% rename from app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt rename to app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportFragment.kt index 9f9707b..6801fa6 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportFragment.kt @@ -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.content.Intent +import android.app.ProgressDialog import android.content.pm.PackageManager import android.os.Build import android.os.Bundle @@ -9,67 +8,61 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.RequiresApi import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.dzeio.openhealth.connectors.ActionRequestCode import com.dzeio.openhealth.connectors.GoogleFit +//import com.dzeio.openhealth.connectors.GoogleFit import com.dzeio.openhealth.connectors.samsunghealth.SamsungHealth -import com.dzeio.openhealth.databinding.FragmentHomeBinding -import com.dzeio.openhealth.db.AppDatabase +import com.dzeio.openhealth.databinding.FragmentImportBinding -class HomeFragment : Fragment() { +class ImportFragment : Fragment() { 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 // onDestroyView. private val binding get() = _binding!! + private lateinit var viewModel: ImportViewModel + + private lateinit var progressDialog: ProgressDialog private lateinit var fit: GoogleFit - private lateinit var viewModel: HomeViewModel - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - viewModel = - ViewModelProvider(this).get(HomeViewModel::class.java) + viewModel = ViewModelProvider(this).get(ImportViewModel::class.java) - _binding = FragmentHomeBinding.inflate(inflater, container, false) + _binding = FragmentImportBinding.inflate(inflater, container, false) val root: View = binding.root -// binding.button.setOnClickListener { -// 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 = ProgressDialog(requireContext()) + progressDialog.apply { + setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + setCancelable(false) + } return root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - val weight = AppDatabase.getInstance(requireContext()).weightDao().last() - if (weight == null) { - viewModel.text.postValue("No Weight Available") - } else { - viewModel.text.postValue("${weight.weight}kg\ndone at ${weight.timestamp}") + binding.importGoogleFit.setOnClickListener { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + importFromGoogleFit() + } + } + binding.importSamsungHealth.setOnClickListener { + importFromSamsungHealth() } } @@ -78,17 +71,22 @@ class HomeFragment : Fragment() { _binding = null } - 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") - } - } + @RequiresApi(Build.VERSION_CODES.O) + fun importFromGoogleFit() { + viewModel.importProgressTotal.postValue(-1) + fit = GoogleFit(requireActivity()) + + progressDialog.show() + fit.import() + fit.getHistory() +// google.importWeight { +// progressDialog.dismiss() +// } + } + + fun importFromSamsungHealth() { + SamsungHealth(requireActivity()) + .test() } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, @@ -100,8 +98,8 @@ class HomeFragment : Fragment() { Log.i(TAG, "User interaction was cancelled.") } - grantResults[0] == PackageManager.PERMISSION_GRANTED -> { - Log.d(TAG, "Granted") + grantResults[0] == PackageManager.PERMISSION_GRANTED -> { + Log.d(TAG, "Granted") // Permission was granted. val fitActionRequestCode = ActionRequestCode.values()[requestCode] fitActionRequestCode.let { diff --git a/app/src/main/java/com/dzeio/openhealth/ui/import/ImportViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportViewModel.kt similarity index 79% rename from app/src/main/java/com/dzeio/openhealth/ui/import/ImportViewModel.kt rename to app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportViewModel.kt index 870b6bb..f66033f 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/import/ImportViewModel.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/main/import/ImportViewModel.kt @@ -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.ViewModel -import com.dzeio.openhealth.connectors.GoogleFit +//import com.dzeio.openhealth.connectors.GoogleFit class ImportViewModel : ViewModel() { diff --git a/app/src/main/java/com/dzeio/openhealth/ui/main/list_weight/ListWeightFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/main/list_weight/ListWeightFragment.kt new file mode 100644 index 0000000..24ff6d8 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/main/list_weight/ListWeightFragment.kt @@ -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::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) + } + } + + + + + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml new file mode 100644 index 0000000..eb23254 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_add_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_edit_24.xml b/app/src/main/res/drawable/ic_baseline_edit_24.xml new file mode 100644 index 0000000..2844baf --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_edit_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_delete_24.xml b/app/src/main/res/drawable/ic_outline_delete_24.xml new file mode 100644 index 0000000..04aa8f2 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_delete_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_hexagon_24.xml b/app/src/main/res/drawable/ic_outline_hexagon_24.xml new file mode 100644 index 0000000..938ab4a --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_hexagon_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_timeline_24.xml b/app/src/main/res/drawable/ic_outline_timeline_24.xml new file mode 100644 index 0000000..978339f --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_timeline_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6c7dd7c..340c80d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,21 +5,55 @@ android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" - tools:openDrawer="start"> + tools:openDrawer="start" + tools:context=".MainActivity"> - + android:layout_height="match_parent" + > + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml deleted file mode 100644 index c0dbb85..0000000 --- a/app/src/main/res/layout/app_bar_main.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml deleted file mode 100644 index 6e0ea39..0000000 --- a/app/src/main/res/layout/content_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_add_weight.xml b/app/src/main/res/layout/dialog_add_weight.xml new file mode 100644 index 0000000..d261b93 --- /dev/null +++ b/app/src/main/res/layout/dialog_add_weight.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_edit_weight.xml b/app/src/main/res/layout/dialog_edit_weight.xml new file mode 100644 index 0000000..67c52b6 --- /dev/null +++ b/app/src/main/res/layout/dialog_edit_weight.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_gallery.xml b/app/src/main/res/layout/fragment_gallery.xml deleted file mode 100644 index 643fe25..0000000 --- a/app/src/main/res/layout/fragment_gallery.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 5d67c26..c466300 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -4,14 +4,91 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.home.HomeFragment"> + tools:context=".ui.main.home.HomeFragment"> - + app:layout_constraintTop_toTopOf="parent"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_import.xml b/app/src/main/res/layout/fragment_import.xml index d7dd7ea..433c21e 100644 --- a/app/src/main/res/layout/fragment_import.xml +++ b/app/src/main/res/layout/fragment_import.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.import.ImportFragment"> + tools:context=".ui.main.import.ImportFragment"> + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_item_weight.xml b/app/src/main/res/layout/layout_item_weight.xml new file mode 100644 index 0000000..d850e1d --- /dev/null +++ b/app/src/main/res/layout/layout_item_weight.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/fullscreen_dialog.xml b/app/src/main/res/menu/fullscreen_dialog.xml new file mode 100644 index 0000000..1ba44da --- /dev/null +++ b/app/src/main/res/menu/fullscreen_dialog.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 9e724ee..78aaf0d 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -7,19 +7,54 @@ - - + tools:layout="@layout/fragment_home" > + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 5c63e93..265741a 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,16 +1,17 @@ - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a63ac9..7ffffbb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,4 +11,6 @@ Gallery Slideshow Import + Weight List + Edit Weight \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 3d85d54..854d44b 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,17 +1,20 @@ - -