diff --git a/.idea/misc.xml b/.idea/misc.xml
index 1c422d0..59e6591 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -10,6 +10,10 @@
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 012daca..801e482 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,6 +3,9 @@ plugins {
id 'org.jetbrains.kotlin.android'
// chaquopy lib for running python code
id 'com.chaquo.python'
+ id 'androidx.navigation.safeargs.kotlin'
+ id 'com.google.gms.google-services'
+ id "kotlin-parcelize"
}
android {
@@ -44,6 +47,7 @@ android {
buildFeatures{
dataBinding = true
}
+ namespace 'com.devsoc.hrmaa'
}
dependencies {
@@ -53,6 +57,8 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.1'
+ implementation 'androidx.browser:browser:1.4.0'
+ implementation 'com.google.firebase:firebase-firestore-ktx:24.4.1'
testImplementation 'junit:junit:4.13.2'
implementation 'com.airbnb.android:lottie:5.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
@@ -71,4 +77,17 @@ dependencies {
//Splash Screen
implementation 'androidx.core:core-splashscreen:1.0.0'
+
+ //retrofit
+ implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+
+ //Gson
+ implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
+
+ //Http
+ implementation 'com.squareup.okhttp3:okhttp:4.9.0'
+
+ //Graph plotter
+ implementation 'com.jjoe64:graphview:4.2.2'
+
}
\ No newline at end of file
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 0000000..c2f8f70
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,39 @@
+{
+ "project_info": {
+ "project_number": "557049753871",
+ "project_id": "hrmaa-fitbit",
+ "storage_bucket": "hrmaa-fitbit.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:557049753871:android:dd440e381f00815b5ace7a",
+ "android_client_info": {
+ "package_name": "com.devsoc.hrmaa"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "557049753871-31hlvd6d5m4rc314nm1aiq39ukh2u1lb.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyDnJPVzxFEmzK6d-lA73IddXnysYifj0kc"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "557049753871-31hlvd6d5m4rc314nm1aiq39ukh2u1lb.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 30467ef..a05c297 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,11 +2,13 @@
-
+
+
+
@@ -24,12 +26,22 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
- android:theme="@style/Theme.App.Starting"
+ android:theme="@style/Theme.HRMAA_PROTOTYPE"
tools:targetApi="31">
+
+
+
+ android:theme="@style/Theme.HRMAA_PROTOTYPE.NoActionBar"
+ android:screenOrientation="portrait">
@@ -37,23 +49,52 @@
+ android:theme="@style/Theme.HRMAA_PROTOTYPE.NoActionBar"
+ android:screenOrientation="portrait"/>
+
+
+
+
+
+
+
+
+
+ android:theme="@style/Theme.HRMAA_PROTOTYPE.NoActionBar"
+ android:screenOrientation="portrait"/>
+ android:exported="true"
+ android:theme="@style/Theme.HRMAA_PROTOTYPE.NoActionBar">
+
+
+
+
+
+
+
+
+ android:theme="@style/Theme.HRMAA_PROTOTYPE.NoActionBar"
+ android:screenOrientation="portrait"/>
+ android:exported="true"
+ android:theme="@style/Theme.HRMAA_PROTOTYPE.NoActionBar">
diff --git a/app/src/main/java/com/devsoc/hrmaa/MainActivity.kt b/app/src/main/java/com/devsoc/hrmaa/MainActivity.kt
index 3abb51c..950e4af 100644
--- a/app/src/main/java/com/devsoc/hrmaa/MainActivity.kt
+++ b/app/src/main/java/com/devsoc/hrmaa/MainActivity.kt
@@ -3,10 +3,11 @@ package com.devsoc.hrmaa
import android.content.Intent
import android.os.Bundle
+import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.databinding.DataBindingUtil
-import com.devsoc.hrmaa.bluetooth.AvailableDevicesActivity
+import androidx.health.connect.client.HealthConnectClient
import com.devsoc.hrmaa.bluetooth.ECGHome
import com.devsoc.hrmaa.databinding.ActivityMainBinding
import com.devsoc.hrmaa.fitbit.FitbitActivity
@@ -21,20 +22,29 @@ class MainActivity : AppCompatActivity() {
installSplashScreen()
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
-
- binding.hcCvMa.setOnClickListener{
- startActivity(Intent(this,HealthConnectActivity::class.java))
+ binding.hcCvMa.setOnClickListener {
+ //launch only if Health Connect is installed on device
+ if (HealthConnectClient.isAvailable(this)) {
+ startActivity(Intent(this, HealthConnectActivity::class.java))
+ } else {
+ Toast.makeText(
+ this,
+ "Please install the Google Health Connect App first.",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
}
- binding.fitbitCvMa.setOnClickListener{
- startActivity(Intent(this,FitbitActivity::class.java))
+ binding.fitbitCvMa.setOnClickListener {
+ startActivity(Intent(this, FitbitActivity::class.java))
}
- binding.ppgCvMa.setOnClickListener{
+ binding.ppgCvMa.setOnClickListener {
startActivity(Intent(this, PPGActivity::class.java))
}
binding.ecgCvMa.setOnClickListener{
- startActivity(Intent(this,ECGHome::class.java))
+ startActivity(Intent(this, ECGHome::class.java))
}
+
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/bluetooth/ECGHome.kt b/app/src/main/java/com/devsoc/hrmaa/bluetooth/ECGHome.kt
index 2d4461e..83ee68e 100644
--- a/app/src/main/java/com/devsoc/hrmaa/bluetooth/ECGHome.kt
+++ b/app/src/main/java/com/devsoc/hrmaa/bluetooth/ECGHome.kt
@@ -237,6 +237,7 @@ class ECGHome : AppCompatActivity() {
}
apnaSocket?.close()
bluetoothAdapter?.cancelDiscovery()
+
btDevice?.let {
apnaSocket = it.createInsecureRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
try {
@@ -251,72 +252,77 @@ class ECGHome : AppCompatActivity() {
Toast.makeText(this@ECGHome, e.message, Toast.LENGTH_SHORT).show()
}
}
- }
-
- inStream =apnaSocket?.inputStream
- outStream=apnaSocket?.outputStream
- try{
- outStream?.write(0)
- if(outStream==null){
- Log.d(TAG,"outStream is null")
- }
- }
- catch (e: IOException) {
- Log.e(TAG, "Error occurred when sending data", e)
- }
-
- val reader = BufferedReader(InputStreamReader(inStream))
+ inStream =apnaSocket?.inputStream
+ outStream=apnaSocket?.outputStream
- var beforeLoopTime = System.currentTimeMillis()
- while (true) {
try{
- currStr = reader.readLine()
- withContext(Dispatchers.Main) {
- val compositeData = currStr.toLong()
- ECGDataList.add((compositeData % 10000).toInt())
- timeStampList.add(compositeData / 10000)
+ outStream?.write(0)
+ if(outStream==null){
+ Log.d(TAG,"outStream is null")
}
- }
- catch(e: java.lang.NumberFormatException){
-
}
catch (e: IOException) {
- Log.d(TAG, "Input stream was disconnected", e)
- withContext(Dispatchers.Main){
- Toast.makeText(this@ECGHome,e.message, Toast.LENGTH_LONG).show()
- }
- break
+ Log.e(TAG, "Error occurred when sending data", e)
}
- if( (System.currentTimeMillis() - beforeLoopTime) > 20000 ){
- Log.d("HeartList",ECGDataList.toString())
- Log.d("TimeList",timeStampList.toString())
-
- try {
+ val reader = BufferedReader(InputStreamReader(inStream))
+ var beforeLoopTime = System.currentTimeMillis()
+ while (true) {
+ try{
+ currStr = reader.readLine()
withContext(Dispatchers.Main) {
- val dataList = module.callAttr(
- "get_bpm_metric",
- ECGDataList.toIntArray(),
- timeStampList.toLongArray()
- ).asList()
- binding.tvHeartRate.text = dataList.get(0).toString()
- Log.d("Contents of List", dataList.toString())
- ECGDataList.clear()
- timeStampList.clear()
+ val compositeData = currStr.toLong()
+ ECGDataList.add((compositeData % 10000).toInt())
+ timeStampList.add(compositeData / 10000)
}
}
- catch(e: PyException){
- withContext(Dispatchers.Main) {
- Toast.makeText(this@ECGHome, e.message, Toast.LENGTH_SHORT).show()
- Log.e("Error in python script",e.message + "\n" + e.cause + "\n" + e.toString())
- }
+ catch(e: java.lang.NumberFormatException){
+
}
- finally {
- beforeLoopTime = System.currentTimeMillis()
+ catch (e: IOException) {
+ Log.d(TAG, "Input stream was disconnected", e)
+ withContext(Dispatchers.Main){
+ Toast.makeText(this@ECGHome,e.message, Toast.LENGTH_LONG).show()
+ }
+ break
}
+ if( (System.currentTimeMillis() - beforeLoopTime) > 20000 ){
+ Log.d("HeartList",ECGDataList.toString())
+ Log.d("TimeList",timeStampList.toString())
+
+ try {
+
+ withContext(Dispatchers.Main) {
+ val dataList = module.callAttr(
+ "get_bpm_metric",
+ ECGDataList.toIntArray(),
+ timeStampList.toLongArray()
+ ).asList()
+ binding.tvHeartRate.text = dataList.get(0).toString()
+ Log.d("Contents of List", dataList.toString())
+ ECGDataList.clear()
+ timeStampList.clear()
+ }
+ }
+ catch(e: PyException){
+ withContext(Dispatchers.Main) {
+ Toast.makeText(this@ECGHome, e.message, Toast.LENGTH_SHORT).show()
+ Log.e("Error in python script",e.message + "\n" + e.cause + "\n" + e.toString())
+ }
+ }
+ finally {
+ beforeLoopTime = System.currentTimeMillis()
+ }
+
+ }
+ }
+ }
+ if( btDevice == null){
+ withContext(Dispatchers.Main){
+ Toast.makeText(this@ECGHome,"No Bluetooth Device Selected", Toast.LENGTH_SHORT).show()
}
}
}
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/FitbitActivity.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/FitbitActivity.kt
index f8e7898..bdd3a79 100644
--- a/app/src/main/java/com/devsoc/hrmaa/fitbit/FitbitActivity.kt
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/FitbitActivity.kt
@@ -1,12 +1,27 @@
package com.devsoc.hrmaa.fitbit
+import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import androidx.navigation.findNavController
import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.databinding.ActivityFitbitBinding
class FitbitActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityFitbitBinding
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_fitbit)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_fitbit)
+ val view = binding.root
+ setContentView(view)
}
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ findNavController(R.id.fitbit_nav_graph).handleDeepLink(intent)
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/EcgAdapter.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/EcgAdapter.kt
new file mode 100644
index 0000000..c59fccf
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/EcgAdapter.kt
@@ -0,0 +1,37 @@
+package com.devsoc.hrmaa.fitbit.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.fitbit.dataclasses.ActivitiesHeart
+import com.devsoc.hrmaa.fitbit.dataclasses.EcgReading
+
+class EcgAdapter(val ecgSeries: List): RecyclerView.Adapter() {
+
+ var onItemClick : ((EcgReading) -> Unit)? = null
+ inner class EcgViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EcgViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.heart_rate_log, parent, false)
+ return EcgViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: EcgViewHolder, position: Int) {
+ val startTime = ecgSeries[position].startTime
+ holder.itemView.apply {
+ findViewById(R.id.date_tv_hrl).text = startTime.substring(0,10)+" "+startTime.substring(11)
+ findViewById(R.id.heart_rate_tv_hrl).text = "${ecgSeries[position].averageHeartRate} BPM"
+
+ setOnClickListener {
+ onItemClick?.invoke(ecgSeries[position])
+ }
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return ecgSeries.size
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/HeartRateAdapter.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/HeartRateAdapter.kt
new file mode 100644
index 0000000..6e9206d
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/HeartRateAdapter.kt
@@ -0,0 +1,39 @@
+package com.devsoc.hrmaa.fitbit.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.cardview.widget.CardView
+import androidx.recyclerview.widget.RecyclerView
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.fitbit.dataclasses.ActivitiesHeart
+import com.devsoc.hrmaa.fitbit.dataclasses.HeartRateSeries
+import com.devsoc.hrmaa.fitbit.dataclasses.HeartRateZone
+
+class HeartRateAdapter(val heartRateSeries: List): RecyclerView.Adapter() {
+ var onItemClick : ((ActivitiesHeart) -> Unit)? = null
+
+ inner class HeartRateViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeartRateViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.heart_rate_log, parent, false)
+ return HeartRateViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: HeartRateViewHolder, position: Int) {
+ holder.itemView.apply {
+ findViewById(R.id.date_tv_hrl).text = heartRateSeries[position].dateTime
+ findViewById(R.id.heart_rate_tv_hrl).text = "${heartRateSeries[position].value.restingHeartRate} BPM"
+
+ setOnClickListener {
+ onItemClick?.invoke(heartRateSeries[position])
+ }
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return heartRateSeries.size
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/HeartRateZoneAdapter.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/HeartRateZoneAdapter.kt
new file mode 100644
index 0000000..9244aa7
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/adapters/HeartRateZoneAdapter.kt
@@ -0,0 +1,33 @@
+package com.devsoc.hrmaa.fitbit.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.fitbit.dataclasses.HeartRateZone
+
+class HeartRateZoneAdapter(val heartRateZones: List): RecyclerView.Adapter() {
+
+ inner class HeartRateZoneViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeartRateZoneViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.heart_rate_zone, parent, false)
+ return HeartRateZoneViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: HeartRateZoneViewHolder, position: Int) {
+ holder.itemView.apply {
+ findViewById(R.id.zone_name_tv).text = "Zone Name: ${heartRateZones[position].name}"
+ findViewById(R.id.max_tv).text = "Max: ${heartRateZones[position].max}"
+ findViewById(R.id.min_tv).text = "Min: ${heartRateZones[position].min}"
+ findViewById(R.id.time_tv).text = "Minutes: ${heartRateZones[position].minutes}"
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return heartRateZones.size
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/ActivitiesHeart.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/ActivitiesHeart.kt
new file mode 100644
index 0000000..61195ba
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/ActivitiesHeart.kt
@@ -0,0 +1,10 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class ActivitiesHeart(
+ val dateTime: String?,
+ val value: Value
+): Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/AuthInfo.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/AuthInfo.kt
new file mode 100644
index 0000000..50ca8bd
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/AuthInfo.kt
@@ -0,0 +1,10 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+import com.google.gson.annotations.SerializedName
+
+data class AuthInfo(
+ @SerializedName("clientId") val clientId: String,
+ @SerializedName("grant_type") val grant_type: String,
+ @SerializedName("redirect_uri") val redirect_uri: String,
+ @SerializedName("code") val code: String
+)
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/CustomHeartRateZone.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/CustomHeartRateZone.kt
new file mode 100644
index 0000000..dbcd5cb
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/CustomHeartRateZone.kt
@@ -0,0 +1,13 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class CustomHeartRateZone(
+ val caloriesOut: Double,
+ val max: Int,
+ val min: Int,
+ val minutes: Int,
+ val name: String
+): Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/EcgData.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/EcgData.kt
new file mode 100644
index 0000000..cac49d4
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/EcgData.kt
@@ -0,0 +1,6 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+data class EcgData(
+ val ecgReadings: List,
+ val pagination : Pagination
+)
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/EcgReading.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/EcgReading.kt
new file mode 100644
index 0000000..86bad47
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/EcgReading.kt
@@ -0,0 +1,15 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+data class EcgReading(
+ val averageHeartRate: Int,
+ val deviceName: String,
+ val featureVersion: String,
+ val firmwareVersion: String,
+ val leadNumber: Int,
+ val numberOfWaveformSample: Int,
+ val resultClassification: String,
+ val samplingFrequencyHz: String,
+ val scalingFactor: Int,
+ val startTime: String,
+ val waveformSamples: List
+)
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/HeartRateSeries.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/HeartRateSeries.kt
new file mode 100644
index 0000000..bed23bb
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/HeartRateSeries.kt
@@ -0,0 +1,5 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+data class HeartRateSeries(
+ val activities_heart: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/HeartRateZone.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/HeartRateZone.kt
new file mode 100644
index 0000000..d12d845
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/HeartRateZone.kt
@@ -0,0 +1,13 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class HeartRateZone(
+ val caloriesOut: Double,
+ val max: Int,
+ val min: Int,
+ val minutes: Int,
+ val name: String
+): Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/Pagination.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/Pagination.kt
new file mode 100644
index 0000000..de44a5d
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/Pagination.kt
@@ -0,0 +1,10 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+data class Pagination(
+ val afterDate : String,
+ val limit : Int,
+ val next : String,
+ val offset : Int,
+ val previous : String,
+ val sort : String
+)
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/TokenData.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/TokenData.kt
new file mode 100644
index 0000000..ef3bf2c
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/TokenData.kt
@@ -0,0 +1,12 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+import com.google.gson.annotations.SerializedName
+
+data class TokenData(
+ @SerializedName("access_token") val access_token: String,
+ @SerializedName("expires_in") val expires_in: Int,
+ @SerializedName("refresh_token") val refresh_token: String,
+ @SerializedName("scope") val scope: String,
+ @SerializedName("token_type") val token_type: String,
+ @SerializedName("user_id") val user_id: String
+)
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/Value.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/Value.kt
new file mode 100644
index 0000000..4290e36
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/dataclasses/Value.kt
@@ -0,0 +1,11 @@
+package com.devsoc.hrmaa.fitbit.dataclasses
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class Value(
+ val customHeartRateZones: List,
+ val heartRateZones: List,
+ val restingHeartRate: Int
+): Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/EcgDataFragment.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/EcgDataFragment.kt
new file mode 100644
index 0000000..600cb88
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/EcgDataFragment.kt
@@ -0,0 +1,187 @@
+package com.devsoc.hrmaa.fitbit.fragments
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.databinding.FragmentEcgDataBinding
+import com.devsoc.hrmaa.fitbit.adapters.EcgAdapter
+import com.devsoc.hrmaa.fitbit.dataclasses.*
+import com.devsoc.hrmaa.fitbit.interfaces.RestApi
+import com.devsoc.hrmaa.fitbit.objects.ServiceBuilder
+import com.google.firebase.firestore.FirebaseFirestore
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import java.util.*
+
+class EcgDataFragment : Fragment() {
+ private lateinit var binding: FragmentEcgDataBinding
+ private val clientId: String = "238QCY"
+ private val redirectUri: String = "hrmaa://www.example.com/getCode"
+ private val fStore = FirebaseFirestore.getInstance()
+ private val cRef = fStore.collection("oauth")
+ private val dRef = cRef.document("test")
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ // Inflate the layout for this fragment
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_ecg_data, container, false)
+ activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ return binding.root
+
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val sharedPreference =
+ activity?.getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
+ val code: String = sharedPreference?.getString("userId", null)!!
+ val authInfo = AuthInfo(clientId, "authorization_code", redirectUri, code)
+
+ dRef.get().addOnCompleteListener {task ->
+ if(task.isSuccessful){
+ val doc = task.result
+ if(doc.exists()){
+ dRef.addSnapshotListener { value, _ ->
+ val time = value!!.getLong("date")!!
+ //check if access token has expired and refresh if expired
+ if (Date().time - time < 28800000) {
+ val accToken = value.getString("access_token")!!
+ getEcgInfo(accToken)
+ } else {
+ val refToken = value.getString("refresh_token")!!
+ refresh(refToken, authInfo)
+ }
+ }
+ }
+ else {
+ getTokenInfo(authInfo)
+ }
+ }
+ }
+
+ }
+
+ private fun getTokenInfo(authInfo: AuthInfo) {
+ val retrofit = ServiceBuilder.buildService(RestApi::class.java)
+ retrofit.getTokenInfo(
+ authInfo.clientId,
+ authInfo.grant_type,
+ authInfo.redirect_uri,
+ authInfo.code
+ ).enqueue(
+ object : Callback {
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("Service", t.message + "")
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val tokenData = response.body()
+ Log.d("Access Response Code", "$response")
+ if (tokenData != null && response.raw().code == 200) {
+ val accessToken = tokenData.access_token
+ val refreshToken = tokenData.refresh_token
+ val uid = tokenData.user_id
+ val timestamp = hashMapOf(
+ "date" to Date().time,
+ "access_token" to accessToken,
+ "refresh_token" to refreshToken,
+ "uid" to uid
+ )
+ dRef.set(timestamp)
+
+ getEcgInfo(accessToken)
+ }
+ }
+ }
+ )
+ }
+
+ private fun refresh(refreshToken: String, authInfo: AuthInfo) {
+ val retrofit = ServiceBuilder.buildService(RestApi::class.java)
+ retrofit.refresh(
+ authInfo.clientId,
+ "refresh_token",
+ authInfo.redirect_uri,
+ refreshToken
+ ).enqueue(
+ object : Callback {
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("Service", t.message.toString())
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val tokenData = response.body()
+ Log.d("Refresh", "${response.raw().code}")
+ if (tokenData != null) {
+ val accessToken = tokenData.access_token
+ val newRefreshToken = tokenData.refresh_token
+ val uid = tokenData.user_id
+ val timestamp = hashMapOf(
+ "date" to Date().time,
+ "access_token" to accessToken,
+ "refresh_token" to newRefreshToken,
+ "uid" to uid
+ )
+ dRef.set(timestamp)
+ getEcgInfo(accessToken)
+ }
+ }
+ }
+ )
+ }
+
+ fun getEcgInfo(accessToken: String) {
+ val headerMap = mutableMapOf()
+ headerMap["authorization"] = "Bearer $accessToken"
+
+ val retrofit = ServiceBuilder.buildService(RestApi::class.java)
+ retrofit.getEcgData(headerMap).enqueue(
+ object : Callback {
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("Service", t.message.toString())
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val ecgData = response.body()
+ Log.d("ECG Response Code", "${response.raw().code}")
+ if (response.raw().code == 200 && ecgData != null) {
+ val ecgReadings = ecgData.ecgReadings
+ if(ecgReadings.isEmpty()){
+ binding.dataTvEdf.text = ""
+ val ecg = mutableListOf(
+ EcgReading(69, "XYZ", "","", 1, 100, "","",10000, "2023-01-31 10pm",
+ mutableListOf(0))
+ )
+ val adapter = EcgAdapter(ecg)
+ binding.ecgRvEdfg.apply {
+ this.adapter = adapter
+ layoutManager = LinearLayoutManager(context)
+ }
+ adapter.onItemClick = {
+ findNavController().navigate(R.id.action_fitbitDataFragment_to_ecgGraphFragment)
+ }
+ }
+ else {
+ binding.dataTvEdf.text = "No ECG Data found!"
+ }
+ } else {
+ Log.d("ECG Response", response.raw().message)
+ }
+ }
+ }
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/EcgGraphFragment.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/EcgGraphFragment.kt
new file mode 100644
index 0000000..5b49861
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/EcgGraphFragment.kt
@@ -0,0 +1,61 @@
+package com.devsoc.hrmaa.fitbit.fragments
+
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.provider.ContactsContract.Data
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.databinding.FragmentEcgGraphBinding
+import com.jjoe64.graphview.series.DataPoint
+import com.jjoe64.graphview.series.LineGraphSeries
+
+class EcgGraphFragment : Fragment() {
+ private lateinit var binding: FragmentEcgGraphBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_ecg_graph, container, false)
+ activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val graphView = binding.ecgGvEgf
+
+ val voltages = intArrayOf(272,286,314,289,289,296,658,184,305,324,344,402,455,467,339,274,270,277,281,288,290,305,298,305,328,312,316,292,662,345,328,352,388,431,463,407,287,263,259,268,269,281,285,283,299,316,299,301,299,664,145,307,323,360,403,457,455,320,257,252,261,269,276,282,294,280,297,316,308,288,297,628,218,322,340,361,401,462,480,348,263,260,275,273,283,291,286,290,301,327,296,293,290,665,256,325,338,374,423,479,445,313,261
+ )
+ val times = intArrayOf(12800,12832,12865,12897,12930,12962,12995,13028,13060,13092,13125,13158,13190,13222,13255,13288,13321,13352,13385,13418,13451,13483,13515,13548,13581,13614,13645,13678,13711,13744,13775,13808,13841,13874,13906,13938,13971,14004,14036,14068,14101,14134,14167,14199,14231,14264,14297,14329,14361,14394,14427,14459,14492,14524,14557,14589,14622,14654,14687,14720,14752,14785,14817,14850,14882,14915,14947,14980,15012,15045,15078,15110,15142,15175,15208,15240,15272,15305,15338,15371,15403,15435,15468,15501,15533,15565,15598,15631,15664,15695,15728,15761,15794,15826,15858,15891,15924,15956,15988,16021,16054,16087,16119
+ )
+
+ val dataPoints = mutableListOf()
+ for(i in 0..voltages.size-1){
+ dataPoints.add(DataPoint((times[i]-times[0]).toDouble(), voltages[i].toDouble()))
+ }
+
+ val series: LineGraphSeries = LineGraphSeries(dataPoints.toTypedArray())
+
+ graphView.animate()
+ graphView.viewport.isScrollable = true
+ graphView.viewport.isScalable = true
+ graphView.viewport.setScalableY(true)
+ graphView.viewport.setScrollableY(true)
+ graphView.viewport.setMaxX((times[times.size-1]-times[0]).toDouble())
+ series.color = R.color.black
+ series.isDrawDataPoints = true
+ series.dataPointsRadius = 10F
+ series.setOnDataPointTapListener { series, dataPoint ->
+ binding.coordsTvEgf.text = "x = ${dataPoint.x} y = ${dataPoint.y}"
+ }
+ graphView.addSeries(series)
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/FitbitAuthFragment.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/FitbitAuthFragment.kt
new file mode 100644
index 0000000..7334b42
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/FitbitAuthFragment.kt
@@ -0,0 +1,38 @@
+package com.devsoc.hrmaa.fitbit.fragments
+
+import android.content.pm.ActivityInfo
+import android.net.Uri
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.databinding.DataBindingUtil
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.databinding.FragmentFitbitAuthBinding
+
+class FitbitAuthFragment : Fragment() {
+
+ private lateinit var binding: FragmentFitbitAuthBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_fitbit_auth, container, false)
+ activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.authBtBvFaf.setOnClickListener {
+ val url = "https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=238QCY&scope=activity+cardio_fitness+electrocardiogram+heartrate+location+nutrition+oxygen_saturation+profile+respiratory_rate+settings+sleep+social+temperature+weight"
+ val builder : CustomTabsIntent.Builder = CustomTabsIntent.Builder()
+ val customTabsIntent = builder.build()
+ customTabsIntent.launchUrl(requireContext(), Uri.parse(url))
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/FitbitRedirectFragment.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/FitbitRedirectFragment.kt
new file mode 100644
index 0000000..e37374b
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/FitbitRedirectFragment.kt
@@ -0,0 +1,73 @@
+package com.devsoc.hrmaa.fitbit.fragments
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.Fragment
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.databinding.FragmentFitbitRedirectBinding
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.firebase.firestore.ktx.firestore
+import com.google.firebase.ktx.Firebase
+import java.util.Date
+
+
+class FitbitRedirectFragment : Fragment() {
+
+ private lateinit var binding: FragmentFitbitRedirectBinding
+ private val args: FitbitRedirectFragmentArgs by navArgs()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ val sharedPreference = activity?.getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
+ if(args.code != "no_code_found"){
+ val editor = sharedPreference?.edit()
+ editor?.putString("userId",args.code)
+ editor?.commit()
+ editor?.apply()
+ }
+ val userId : String? = sharedPreference?.getString("userId", null)
+ if(userId == null){
+ val navAction = FitbitRedirectFragmentDirections.actionFitbitRedirectFragmentToFitbitAuthFragment()
+ findNavController().navigate(navAction)
+ }
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_fitbit_redirect, container, false)
+ activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ return binding.root
+
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.LoginBvFrf.setOnClickListener {
+ val navAction = FitbitRedirectFragmentDirections.actionFitbitRedirectFragmentToFitbitAuthFragment()
+ view.findNavController().navigate(navAction)
+ }
+ val sharedPreference = activity?.getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
+ val userId : String = sharedPreference?.getString("userId", null)!!
+ Log.d("Auth Code", userId)
+
+ binding.ecgCvFrf.setOnClickListener {
+ view.findNavController().navigate(R.id.action_fitbitRedirectFragment_to_fitbitDataFragment)
+ }
+
+ binding.bpmCvFrf.setOnClickListener {
+ view.findNavController().navigate(R.id.action_fitbitRedirectFragment_to_heartRateFragment)
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/HeartRateFragment.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/HeartRateFragment.kt
new file mode 100644
index 0000000..1a973b4
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/HeartRateFragment.kt
@@ -0,0 +1,199 @@
+package com.devsoc.hrmaa.fitbit.fragments
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.navigation.fragment.FragmentNavigatorExtras
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.databinding.FragmentHeartRateBinding
+import com.devsoc.hrmaa.fitbit.adapters.HeartRateAdapter
+import com.devsoc.hrmaa.fitbit.dataclasses.*
+import com.devsoc.hrmaa.fitbit.interfaces.RestApi
+import com.devsoc.hrmaa.fitbit.objects.ServiceBuilder
+import com.google.firebase.firestore.FirebaseFirestore
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import java.util.*
+
+class HeartRateFragment : Fragment() {
+ private lateinit var binding: FragmentHeartRateBinding
+ private val clientId: String = "238QCY"
+ private val redirectUri: String = "hrmaa://www.example.com/getCode"
+ private val fStore = FirebaseFirestore.getInstance()
+ private val cRef = fStore.collection("oauth")
+ private val dRef = cRef.document("test")
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_heart_rate, container, false)
+ activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val sharedPreference =
+ activity?.getSharedPreferences("PREFERENCE_NAME", Context.MODE_PRIVATE)
+ val code: String = sharedPreference?.getString("userId", null)!!
+ val authInfo = AuthInfo(clientId, "authorization_code", redirectUri, code)
+
+ dRef.get().addOnCompleteListener {task ->
+ if(task.isSuccessful){
+ val doc = task.result
+ if(doc.exists()){
+ dRef.addSnapshotListener { value, _ ->
+ val time = value!!.getLong("date")!!
+ //check if access token has expired and refresh if expired
+ if (Date().time - time < 28800000) {
+ val accToken = value.getString("access_token")!!
+ getHeartRateSeries(accToken, "2019-01-01", "2020-01-01")
+ } else {
+ val refToken = value.getString("refresh_token")!!
+ refresh(refToken, authInfo)
+ }
+ }
+ }
+ else {
+ getTokenInfo(authInfo)
+ }
+ }
+ }
+ }
+
+ private fun getTokenInfo(authInfo: AuthInfo) {
+ val retrofit = ServiceBuilder.buildService(RestApi::class.java)
+ retrofit.getTokenInfo(
+ authInfo.clientId,
+ authInfo.grant_type,
+ authInfo.redirect_uri,
+ authInfo.code
+ ).enqueue(
+ object : Callback {
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("Service", t.message + "")
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val tokenData = response.body()
+ Log.d("Access Response Code", "$response")
+ if (tokenData != null && response.raw().code == 200) {
+ val accessToken = tokenData.access_token
+ val refreshToken = tokenData.refresh_token
+ val uid = tokenData.user_id
+ val timestamp = hashMapOf(
+ "date" to Date().time,
+ "access_token" to accessToken,
+ "refresh_token" to refreshToken,
+ "uid" to uid
+ )
+ dRef.set(timestamp)
+ getHeartRateSeries(accessToken, "2019-01-01", "2020-01-01")
+ }
+ }
+ }
+ )
+ }
+
+ private fun refresh(refreshToken: String, authInfo: AuthInfo) {
+ val retrofit = ServiceBuilder.buildService(RestApi::class.java)
+ retrofit.refresh(
+ authInfo.clientId,
+ "refresh_token",
+ authInfo.redirect_uri,
+ refreshToken
+ ).enqueue(
+ object : Callback {
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("Service", t.message + "")
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val tokenData = response.body()
+ Log.d("Refresh", "${response.code()}")
+ if (tokenData != null) {
+ val accessToken = tokenData.access_token
+ val newRefreshToken = tokenData.refresh_token
+ val uid = tokenData.user_id
+ val timestamp = hashMapOf(
+ "date" to Date().time,
+ "access_token" to accessToken,
+ "refresh_token" to newRefreshToken,
+ "uid" to uid
+ )
+ dRef.set(timestamp)
+ getHeartRateSeries(accessToken, "2019-01-01", "2020-01-01")
+ }
+ }
+ }
+ )
+ }
+
+ private fun getHeartRateSeries(accessToken: String, startDate: String, endDate: String){
+ val headerMap = mutableMapOf()
+ headerMap["authorization"] = "Bearer $accessToken"
+
+ val retrofit = ServiceBuilder.buildService(RestApi::class.java)
+ retrofit.getHeartRateSeries(headerMap, startDate, endDate).enqueue(
+ object : Callback {
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("Service", t.message + "")
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ val heartRateData = response.body()
+ Log.d("Heart Rate Response Code", "".plus(response.raw().code))
+ if (response.raw().code == 200 && heartRateData != null) {
+ val activitiesHeart = heartRateData.activities_heart
+ if(activitiesHeart == null) {
+ val zones1 = mutableListOf(
+ HeartRateZone(1100.0, 120, 90, 30, "Test1"),
+ HeartRateZone(900.0, 90, 60, 15, "Test2")
+ )
+ val zones2 = mutableListOf(
+ CustomHeartRateZone(1100.0, 120, 90, 30, "Custom1"),
+ CustomHeartRateZone(1100.0, 120, 90, 30, "Custom2")
+ )
+ val activities = mutableListOf(
+ ActivitiesHeart("2023-01-31", Value(zones2, zones1, 72)),
+ ActivitiesHeart("2023-01-30", Value(zones2, zones1, 85)),
+ ActivitiesHeart("2023-01-29", Value(zones2, zones1, 65)),
+ ActivitiesHeart("2023-01-28", Value(zones2, zones1, 70))
+ )
+ val adapter = HeartRateAdapter(activities)
+ binding.noDataTvHrf.visibility = View.INVISIBLE
+ binding.heartRateRvHrf.apply {
+ visibility = View.VISIBLE
+ this.adapter = adapter
+ layoutManager = LinearLayoutManager(context)
+ }
+ adapter.onItemClick = {
+ val action = HeartRateFragmentDirections.actionHeartRateFragmentToHeartRateZonesFragment(it)
+ findNavController().navigate(action)
+ }
+ }
+ else {
+ binding.heartRateRvHrf.visibility = View.INVISIBLE
+ binding.noDataTvHrf.visibility = View.VISIBLE
+ }
+ } else {
+ binding.heartRateRvHrf.visibility = View.INVISIBLE
+ binding.noDataTvHrf.visibility = View.VISIBLE
+ Log.d("Heart Rate Response", response.raw().message)
+ }
+ }
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/HeartRateZonesFragment.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/HeartRateZonesFragment.kt
new file mode 100644
index 0000000..7660590
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/fragments/HeartRateZonesFragment.kt
@@ -0,0 +1,47 @@
+package com.devsoc.hrmaa.fitbit.fragments
+
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.databinding.DataBindingUtil
+import androidx.navigation.fragment.navArgs
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.databinding.FragmentHeartRateZonesBinding
+import com.devsoc.hrmaa.fitbit.adapters.HeartRateZoneAdapter
+import com.devsoc.hrmaa.fitbit.dataclasses.ActivitiesHeart
+import com.devsoc.hrmaa.fitbit.dataclasses.HeartRateZone
+
+class HeartRateZonesFragment : Fragment() {
+ private lateinit var binding: FragmentHeartRateZonesBinding
+ private val args: HeartRateZonesFragmentArgs by navArgs()
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ binding = DataBindingUtil.inflate(inflater, R.layout.fragment_heart_rate_zones, container, false)
+ activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val activities = args.activities
+ val customZones = mutableListOf()
+ for(c in activities.value.customHeartRateZones){
+ customZones.add(HeartRateZone(c.caloriesOut, c.max, c.min, c.minutes, c.name))
+ }
+
+ val zones = activities.value.heartRateZones.plus(customZones)
+ val adapter = HeartRateZoneAdapter(zones)
+ binding.heartRateZonesRvHrzf.apply {
+ this.adapter = adapter
+ layoutManager = LinearLayoutManager(requireContext())
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/interfaces/RestApi.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/interfaces/RestApi.kt
new file mode 100644
index 0000000..1c32369
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/interfaces/RestApi.kt
@@ -0,0 +1,48 @@
+package com.devsoc.hrmaa.fitbit.interfaces
+
+import com.devsoc.hrmaa.fitbit.dataclasses.EcgData
+import com.devsoc.hrmaa.fitbit.dataclasses.HeartRateSeries
+import com.devsoc.hrmaa.fitbit.dataclasses.TokenData
+import retrofit2.Call
+import retrofit2.http.*
+
+interface RestApi {
+
+ @Headers("Authorization: Basic MjM4UUNZOmVjNWUyZGUwYTg1YmEzNjdlYWM1NzFhNTg3MWU4MGE5")
+ @POST("oauth2/token")
+ @FormUrlEncoded
+ fun getTokenInfo(
+ @Field("clientId") clientId: String,
+ @Field("grant_type") grant_type: String,
+ @Field("redirect_uri") redirect_uri: String,
+ @Field("code") code: String
+ ): Call
+
+ @Headers("Authorization: Basic MjM4UUNZOmVjNWUyZGUwYTg1YmEzNjdlYWM1NzFhNTg3MWU4MGE5")
+ @POST("oauth2/token")
+ @FormUrlEncoded
+ fun refresh(
+ @Field("clientId") clientId: String,
+ @Field("grant_type") grant_type: String,
+ @Field("redirect_uri") redirect_uri: String,
+ @Field("refresh_token") refresh_token: String
+ ): Call
+
+ @Headers("accept: application/json")
+ @GET("1/user/-/ecg/list.json?afterDate=2022-09-28&sort=asc&limit=1&offset=0")
+ fun getEcgData(@HeaderMap headers: Map): Call
+
+ @Headers("accept: application/json")
+ @GET("1/user/-/activities/heart/date/{startDate}/{endDate}.json")
+ fun getHeartRateSeries(@HeaderMap headers: Map,
+ @Path(value = "startDate", encoded = true) startDate: String,
+ @Path(value = "endDate", encoded = true) endDate: String
+ ): Call
+
+ @Headers("accept: application/json")
+ @GET("{path}")
+ fun nextPage(@HeaderMap headers: Map,
+ @Path(value = "path", encoded = true) path: String
+ ): Call
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/fitbit/objects/ServiceBuilder.kt b/app/src/main/java/com/devsoc/hrmaa/fitbit/objects/ServiceBuilder.kt
new file mode 100644
index 0000000..722a960
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/fitbit/objects/ServiceBuilder.kt
@@ -0,0 +1,21 @@
+package com.devsoc.hrmaa.fitbit.objects
+
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+object ServiceBuilder {
+
+ private val httpClient = OkHttpClient.Builder()
+ private val client = httpClient.build()
+
+ private val retrofit = Retrofit.Builder()
+ .baseUrl("https://api.fitbit.com/")
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(client)
+ .build()
+
+ fun buildService(service: Class): T{
+ return retrofit.create(service)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/healthConnect/DateActivity.kt b/app/src/main/java/com/devsoc/hrmaa/healthConnect/DateActivity.kt
new file mode 100644
index 0000000..761b136
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/healthConnect/DateActivity.kt
@@ -0,0 +1,63 @@
+package com.devsoc.hrmaa.healthConnect
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.widget.Toast
+import androidx.core.text.isDigitsOnly
+import com.devsoc.hrmaa.databinding.ActivityDateBinding
+
+class DateActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityDateBinding
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityDateBinding.inflate(layoutInflater)
+ val view = binding.root
+ setContentView(view)
+
+ binding.getRecCvDa.setOnClickListener {
+ val startDate = binding.startDateTietDa.text.toString()
+ val endDate = binding.endDateTietDa.text.toString()
+ var startDay = 1
+ var startMonth = 1
+ var startYear = 2023
+ var endDay = 1
+ var endMonth = 1
+ var endYear = 2023
+ if(startDate!!.isNotEmpty() && startDate.substring(0,2).isDigitsOnly() && startDate.substring(3,5).isDigitsOnly() && startDate.substring(6,10).isDigitsOnly()
+ && endDate!!.isNotEmpty() && endDate.substring(0,2).isDigitsOnly() && endDate.substring(3,5).isDigitsOnly() && endDate.substring(6,10).isDigitsOnly()
+ && ((startDate[2] == '/' && startDate[5] == '/' && endDate[2] == '/' && endDate[5] == '/') || (startDate[2] == '-' && startDate[5] == '-' && endDate[2] == '-' && endDate[5] == '-'))){
+
+ startDay = startDate.substring(0,2).toInt()
+ startMonth = startDate.substring(3,5).toInt()
+ startYear = startDate.substring(6,10).toInt()
+ endDay = endDate.substring(0,2).toInt()
+ endMonth = endDate.substring(3,5).toInt()
+ endYear = endDate.substring(6,10).toInt()
+ if(startDay in 1..31 && startMonth in 1..12 && startYear in 1990..2023
+ && endDay in 1..31 && endMonth in 1..12 && endYear in 1990..2023
+ && ((endYear==startYear && endMonth==startMonth && endDay>startDay)||(endYear==startYear && endMonth>startMonth)|| endYear>startYear)){
+ val intent = Intent(this@DateActivity, ReadDataActivity::class.java)
+ intent.putExtra("start_date", startDate)
+ intent.putExtra("end_date", endDate)
+ startActivity(intent)
+
+ }
+ else {
+ Toast.makeText(
+ this@DateActivity,
+ "Enter a valid date!",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ else {
+ Toast.makeText(
+ this@DateActivity,
+ "Enter a valid date!",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/healthConnect/HealthConnectActivity.kt b/app/src/main/java/com/devsoc/hrmaa/healthConnect/HealthConnectActivity.kt
index 57104bc..7b3d82b 100644
--- a/app/src/main/java/com/devsoc/hrmaa/healthConnect/HealthConnectActivity.kt
+++ b/app/src/main/java/com/devsoc/hrmaa/healthConnect/HealthConnectActivity.kt
@@ -1,60 +1,86 @@
package com.devsoc.hrmaa.healthConnect
+import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
-import androidx.activity.result.registerForActivityResult
+import android.view.MotionEvent
+import android.view.View
+import androidx.core.content.ContextCompat
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.permission.Permission
import androidx.health.connect.client.records.HeartRateRecord
-import androidx.health.connect.client.records.StepsRecord
import androidx.lifecycle.lifecycleScope
import com.devsoc.hrmaa.R
+import com.devsoc.hrmaa.databinding.ActivityHealthConnectBinding
import kotlinx.coroutines.launch
class HealthConnectActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityHealthConnectBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_health_connect)
+ binding = ActivityHealthConnectBinding.inflate(layoutInflater)
+ val view = binding.root
+ setContentView(view)
- if (HealthConnectClient.isAvailable(this)) {
- // Health Connect is available and installed.
- val healthConnectClient = HealthConnectClient.getOrCreate(this)
+ val healthConnectClient = HealthConnectClient.getOrCreate(this)
- val requestPermissionActivityContract = healthConnectClient.permissionController.createRequestPermissionActivityContract()
+ val requestPermissionActivityContract =
+ healthConnectClient.permissionController.createRequestPermissionActivityContract()
- val requestPermissions =
- registerForActivityResult(requestPermissionActivityContract) { granted ->
- if (granted.containsAll(PERMISSIONS)) {
- // Permissions successfully granted
- } else {
- // Lack of required permissions
+ val requestPermissions =
+ registerForActivityResult(requestPermissionActivityContract) {
+
+ }
+ // Create the permissions launcher.
+
+ fun checkPermissionsAndRun(client: HealthConnectClient) {
+ lifecycleScope.launch {
+ val granted = client.permissionController.getGrantedPermissions(PERMISSIONS)
+ if (granted.containsAll(PERMISSIONS)) {
+ // Permissions already granted
+
+ binding.readCvHca.setOnTouchListener(object : View.OnTouchListener {
+ override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+ when (event?.action) {
+ MotionEvent.ACTION_DOWN -> binding.readCvHca.setCardBackgroundColor(ContextCompat.getColorStateList(applicationContext, com.google.android.material.R.color.material_grey_300)!!)
+ MotionEvent.ACTION_UP -> binding.readCvHca.setCardBackgroundColor(ContextCompat.getColorStateList(applicationContext, com.google.android.material.R.color.m3_ref_palette_white)!!)
+ }
+
+ return v?.onTouchEvent(event) ?: true
+ }
+ })
+ binding.readCvHca.setOnClickListener {
+ val intent = Intent(this@HealthConnectActivity, DateActivity::class.java)
+ startActivity(intent)
}
- }
- // Create the permissions launcher.
-
- fun checkPermissionsAndRun(client: HealthConnectClient) {
- lifecycleScope.launch {
- val granted = client.permissionController.getGrantedPermissions(PERMISSIONS)
- if (granted.containsAll(PERMISSIONS)) {
- // Permissions already granted
- } else {
- requestPermissions.launch(PERMISSIONS)
+
+ binding.manageCvHca.setOnTouchListener(object : View.OnTouchListener {
+ override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+ when (event?.action) {
+ MotionEvent.ACTION_DOWN -> binding.manageCvHca.setCardBackgroundColor(ContextCompat.getColorStateList(applicationContext, com.google.android.material.R.color.material_grey_300)!!)
+ MotionEvent.ACTION_UP -> binding.manageCvHca.setCardBackgroundColor(ContextCompat.getColorStateList(applicationContext, com.google.android.material.R.color.m3_ref_palette_white)!!)
+ }
+
+ return v?.onTouchEvent(event) ?: true
+ }
+ })
+ binding.manageCvHca.setOnClickListener {
+ val launch = packageManager.getLaunchIntentForPackage("com.google.android.apps.healthdata")
+ startActivity(launch)
}
+
+ } else {
+ requestPermissions.launch(PERMISSIONS)
}
}
- } else {
- // ...
}
-
-
+ checkPermissionsAndRun(healthConnectClient)
}
- val PERMISSIONS =
+ private val PERMISSIONS =
setOf(
Permission.createReadPermission(HeartRateRecord::class),
Permission.createWritePermission(HeartRateRecord::class),
- Permission.createReadPermission(StepsRecord::class),
- Permission.createWritePermission(StepsRecord::class)
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/healthConnect/HeartRateSeriesAdapter.kt b/app/src/main/java/com/devsoc/hrmaa/healthConnect/HeartRateSeriesAdapter.kt
new file mode 100644
index 0000000..0079900
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/healthConnect/HeartRateSeriesAdapter.kt
@@ -0,0 +1,35 @@
+package com.devsoc.hrmaa.healthConnect
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.health.connect.client.records.HeartRateRecord
+import androidx.recyclerview.widget.RecyclerView
+import com.devsoc.hrmaa.R
+import org.w3c.dom.Text
+import java.text.SimpleDateFormat
+import java.time.Instant
+import java.util.*
+
+class HeartRateSeriesAdapter(val heartRateSeries: List): RecyclerView.Adapter() {
+
+ inner class HeartRateSeriesViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeartRateSeriesViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.heart_rate_log_hc, parent, false)
+ return HeartRateSeriesViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: HeartRateSeriesViewHolder, position: Int) {
+ holder.itemView.apply {
+ findViewById(R.id.datetime_tv_hrlhc).text = SimpleDateFormat("dd-MM-yyyy hh:mm:ss a").format(Date.from(heartRateSeries[position].time))
+ findViewById(R.id.bpm_tv_hrlhc).text = "${heartRateSeries[position].beatsPerMinute} BPM"
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return heartRateSeries.size
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/devsoc/hrmaa/healthConnect/ReadDataActivity.kt b/app/src/main/java/com/devsoc/hrmaa/healthConnect/ReadDataActivity.kt
new file mode 100644
index 0000000..a3f1f48
--- /dev/null
+++ b/app/src/main/java/com/devsoc/hrmaa/healthConnect/ReadDataActivity.kt
@@ -0,0 +1,108 @@
+package com.devsoc.hrmaa.healthConnect
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.health.connect.client.HealthConnectClient
+import androidx.health.connect.client.permission.Permission
+import androidx.health.connect.client.records.HeartRateRecord
+import androidx.health.connect.client.request.ReadRecordsRequest
+import androidx.health.connect.client.time.TimeRangeFilter
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.devsoc.hrmaa.databinding.ActivityReadDataBinding
+import kotlinx.coroutines.launch
+import java.time.Instant
+import java.time.ZonedDateTime
+import java.util.*
+
+class ReadDataActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityReadDataBinding
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityReadDataBinding.inflate(layoutInflater)
+ val view = binding.root
+ setContentView(view)
+
+ val startDate = intent.getStringExtra("start_date").toString()
+ val endDate = intent.getStringExtra("end_date").toString()
+ val sd = Date(startDate.substring(6,10).toInt(), startDate.substring(3,5).toInt(), startDate.substring(0,2).toInt())
+ val ed = Date(endDate.substring(6,10).toInt(), endDate.substring(3,5).toInt(), endDate.substring(0,2).toInt())
+ val healthConnectClient = HealthConnectClient.getOrCreate(this)
+
+ val requestPermissionActivityContract =
+ healthConnectClient.permissionController.createRequestPermissionActivityContract()
+
+ val requestPermissions =
+ registerForActivityResult(requestPermissionActivityContract) {
+
+ }
+ // Create the permissions launcher.
+
+ fun checkPermissionsAndRun(client: HealthConnectClient) {
+ lifecycleScope.launch {
+ val granted = client.permissionController.getGrantedPermissions(PERMISSIONS)
+ if (granted.containsAll(PERMISSIONS)) {
+ // Permissions already granted
+ val end = ed.toInstant()
+ val start = sd.toInstant()
+ readHeartRateByTimeRange(healthConnectClient, start, end)
+
+ } else {
+ requestPermissions.launch(PERMISSIONS)
+ }
+ }
+ }
+
+ checkPermissionsAndRun(healthConnectClient)
+
+ }
+
+ private fun readHeartRateByTimeRange(
+ healthConnectClient: HealthConnectClient,
+ startTime: Instant,
+ endTime: Instant
+ ) {
+ lifecycleScope.launch {
+ val response =
+ healthConnectClient.readRecords(
+ ReadRecordsRequest(
+ HeartRateRecord::class,
+ timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
+ )
+ )
+
+ val series = mutableListOf(
+ HeartRateRecord.Sample(Instant.now().minusSeconds(259200), 101),
+ HeartRateRecord.Sample(Instant.now().minusSeconds(172800), 89),
+ HeartRateRecord.Sample(Instant.now().minusSeconds(86400), 69),
+ HeartRateRecord.Sample(Instant.now(), 75)
+ )
+ val adapter = HeartRateSeriesAdapter(series)
+ binding.heartRateSeriesRvRda.apply {
+ this.adapter = adapter
+ layoutManager = LinearLayoutManager(context)
+ }
+
+ if (response.records.isNotEmpty()) {
+ binding.dataTvRdf.text = ""
+ /*for (rec in response.records) {
+ val adapter = HeartRateSeriesAdapter(rec.samples)
+ binding.heartRateSeriesRvRda.apply {
+ this.adapter = adapter
+ layoutManager = LinearLayoutManager(context)
+ }
+ }*/
+ } else {
+ //binding.dataTvRdf.text = "No records found!"
+ }
+
+ }
+ }
+
+ private val PERMISSIONS =
+ setOf(
+ Permission.createReadPermission(HeartRateRecord::class),
+ Permission.createWritePermission(HeartRateRecord::class),
+ )
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/hc_bg.png b/app/src/main/res/drawable/hc_bg.png
new file mode 100644
index 0000000..37c8736
Binary files /dev/null and b/app/src/main/res/drawable/hc_bg.png differ
diff --git a/app/src/main/res/drawable/health_connect_bg.png b/app/src/main/res/drawable/health_connect_bg.png
new file mode 100644
index 0000000..5b5524a
Binary files /dev/null and b/app/src/main/res/drawable/health_connect_bg.png differ
diff --git a/app/src/main/res/drawable/heart_rate_icon.png b/app/src/main/res/drawable/heart_rate_icon.png
new file mode 100644
index 0000000..33b7f03
Binary files /dev/null and b/app/src/main/res/drawable/heart_rate_icon.png differ
diff --git a/app/src/main/res/drawable/settings_icon.png b/app/src/main/res/drawable/settings_icon.png
new file mode 100644
index 0000000..2f3ece9
Binary files /dev/null and b/app/src/main/res/drawable/settings_icon.png differ
diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml
index 4a29529..e3c2271 100644
--- a/app/src/main/res/layout-land/activity_main.xml
+++ b/app/src/main/res/layout-land/activity_main.xml
@@ -11,41 +11,30 @@
+ app:layout_constraintTop_toTopOf="parent">
-
-
-
-
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.5"
+ app:srcCompat="@drawable/hrmaa_logo" />
diff --git a/app/src/main/res/layout/activity_available_devices.xml b/app/src/main/res/layout/activity_available_devices.xml
index 2b958d6..056e23f 100644
--- a/app/src/main/res/layout/activity_available_devices.xml
+++ b/app/src/main/res/layout/activity_available_devices.xml
@@ -41,7 +41,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
+ android:fontFamily="sans-serif"
android:text="Available devices"
+ android:textColor="#000000"
+ android:textSize="25sp"
+ android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
@@ -51,8 +55,11 @@
android:id="@+id/tvPairedDevices"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="28dp"
+ android:layout_marginTop="40dp"
android:text="Paired Devices"
+ android:textColor="#000000"
+ android:textSize="25sp"
+ android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.496"
app:layout_constraintStart_toStartOf="parent"
@@ -83,7 +90,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
+ android:padding="10dp"
android:text="discover"
+ android:textSize="20sp"
+ android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/app/src/main/res/layout/activity_date.xml b/app/src/main/res/layout/activity_date.xml
new file mode 100644
index 0000000..cfb13b4
--- /dev/null
+++ b/app/src/main/res/layout/activity_date.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_ecghome.xml b/app/src/main/res/layout/activity_ecghome.xml
index 8ef98cd..b21d8ea 100644
--- a/app/src/main/res/layout/activity_ecghome.xml
+++ b/app/src/main/res/layout/activity_ecghome.xml
@@ -70,9 +70,10 @@
-
-
-
+ xmlns:tools="http://schemas.android.com/tools">
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_health_connect.xml b/app/src/main/res/layout/activity_health_connect.xml
index a565303..e8e4da2 100644
--- a/app/src/main/res/layout/activity_health_connect.xml
+++ b/app/src/main/res/layout/activity_health_connect.xml
@@ -1,9 +1,168 @@
-
+ xmlns:tools="http://schemas.android.com/tools">
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 73c03e6..998a40e 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -10,10 +10,10 @@
-
-
-
-
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.5"
+ app:srcCompat="@drawable/hrmaa_logo" />
diff --git a/app/src/main/res/layout/activity_read_data.xml b/app/src/main/res/layout/activity_read_data.xml
new file mode 100644
index 0000000..4067cb7
--- /dev/null
+++ b/app/src/main/res/layout/activity_read_data.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/device_card.xml b/app/src/main/res/layout/device_card.xml
index 86c7aa7..f2f3efa 100644
--- a/app/src/main/res/layout/device_card.xml
+++ b/app/src/main/res/layout/device_card.xml
@@ -1,39 +1,58 @@
-
-
-
-
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.5">
-
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_ecg_data.xml b/app/src/main/res/layout/fragment_ecg_data.xml
new file mode 100644
index 0000000..7f0a434
--- /dev/null
+++ b/app/src/main/res/layout/fragment_ecg_data.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_ecg_graph.xml b/app/src/main/res/layout/fragment_ecg_graph.xml
new file mode 100644
index 0000000..e721ab2
--- /dev/null
+++ b/app/src/main/res/layout/fragment_ecg_graph.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_fitbit_auth.xml b/app/src/main/res/layout/fragment_fitbit_auth.xml
new file mode 100644
index 0000000..5ec349f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_fitbit_auth.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_fitbit_redirect.xml b/app/src/main/res/layout/fragment_fitbit_redirect.xml
new file mode 100644
index 0000000..80f6080
--- /dev/null
+++ b/app/src/main/res/layout/fragment_fitbit_redirect.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_heart_rate.xml b/app/src/main/res/layout/fragment_heart_rate.xml
new file mode 100644
index 0000000..904b340
--- /dev/null
+++ b/app/src/main/res/layout/fragment_heart_rate.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_heart_rate_zones.xml b/app/src/main/res/layout/fragment_heart_rate_zones.xml
new file mode 100644
index 0000000..fa96ebe
--- /dev/null
+++ b/app/src/main/res/layout/fragment_heart_rate_zones.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/heart_rate_log.xml b/app/src/main/res/layout/heart_rate_log.xml
new file mode 100644
index 0000000..7378610
--- /dev/null
+++ b/app/src/main/res/layout/heart_rate_log.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/heart_rate_log_hc.xml b/app/src/main/res/layout/heart_rate_log_hc.xml
new file mode 100644
index 0000000..af3d72e
--- /dev/null
+++ b/app/src/main/res/layout/heart_rate_log_hc.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/heart_rate_zone.xml b/app/src/main/res/layout/heart_rate_zone.xml
new file mode 100644
index 0000000..73abc1f
--- /dev/null
+++ b/app/src/main/res/layout/heart_rate_zone.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/fitbit_nav_graph.xml b/app/src/main/res/navigation/fitbit_nav_graph.xml
new file mode 100644
index 0000000..0328ee4
--- /dev/null
+++ b/app/src/main/res/navigation/fitbit_nav_graph.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 4ca5d8b..1ddc5c3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -38,4 +38,9 @@
- Pulse: %2.1f (%d cycle in %2.1f seconds)
- Pulse: %2.1f (%d cycles in %2.1f seconds)
+
+ Hello blank fragment
+ Get authorized
+ No code found
+ Login again
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index f333a22..163ea77 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,19 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ mavenCentral() // Maven Central repository
+ }
+ dependencies {
+ classpath 'com.google.gms:google-services:4.3.14'
+ def nav_version = "2.4.2"
+ classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
+ }
+}
+
plugins {
- id 'com.android.application' version '7.2.2' apply false
- id 'com.android.library' version '7.2.2' apply false
+ id 'com.android.application' version '7.3.1' apply false
+ id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
// chaquopy lib
diff --git a/gradle.properties b/gradle.properties
index cd0519b..9a67673 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,4 +20,5 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
+android.nonTransitiveRClass=true
+android.enableJetifier=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 29952e3..a8902ef 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Thu Jul 07 02:01:49 IST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME