From a164e23b2d0a95b05aff6c90c72bdd4c779028fc Mon Sep 17 00:00:00 2001 From: Avior Date: Fri, 10 Dec 2021 09:44:20 +0100 Subject: [PATCH] Initial Commit Signed-off-by: Avior --- .gitignore | 15 + .idea/.gitignore | 3 + .idea/compiler.xml | 6 + .idea/gradle.xml | 19 ++ .idea/misc.xml | 17 ++ app/.gitignore | 1 + app/build.gradle | 54 ++++ app/proguard-rules.pro | 21 ++ .../openhealth/ExampleInstrumentedTest.kt | 24 ++ app/src/main/AndroidManifest.xml | 29 ++ .../java/com/dzeio/openhealth/MainActivity.kt | 73 +++++ .../dzeio/openhealth/connectors/GoogleFit.kt | 264 ++++++++++++++++++ .../openhealth/ui/gallery/GalleryFragment.kt | 42 +++ .../openhealth/ui/gallery/GalleryViewModel.kt | 13 + .../dzeio/openhealth/ui/home/HomeFragment.kt | 120 ++++++++ .../dzeio/openhealth/ui/home/HomeViewModel.kt | 13 + .../ui/slideshow/SlideshowFragment.kt | 42 +++ .../ui/slideshow/SlideshowViewModel.kt | 13 + .../drawable-v24/ic_launcher_foreground.xml | 30 ++ .../res/drawable/ic_launcher_background.xml | 170 +++++++++++ app/src/main/res/drawable/ic_menu_camera.xml | 12 + app/src/main/res/drawable/ic_menu_gallery.xml | 9 + .../main/res/drawable/ic_menu_slideshow.xml | 9 + app/src/main/res/drawable/side_nav_bar.xml | 9 + app/src/main/res/layout/activity_main.xml | 25 ++ app/src/main/res/layout/app_bar_main.xml | 34 +++ app/src/main/res/layout/content_main.xml | 20 ++ app/src/main/res/layout/fragment_gallery.xml | 22 ++ app/src/main/res/layout/fragment_home.xml | 16 ++ .../main/res/layout/fragment_slideshow.xml | 22 ++ app/src/main/res/layout/nav_header_main.xml | 35 +++ .../main/res/menu/activity_main_drawer.xml | 20 ++ app/src/main/res/menu/main.xml | 9 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../main/res/navigation/mobile_navigation.xml | 25 ++ app/src/main/res/values-land/dimens.xml | 3 + app/src/main/res/values-night/themes.xml | 16 ++ app/src/main/res/values-w1240dp/dimens.xml | 3 + app/src/main/res/values-w600dp/dimens.xml | 3 + app/src/main/res/values/colors.xml | 10 + app/src/main/res/values/dimens.xml | 8 + app/src/main/res/values/strings.xml | 13 + app/src/main/res/values/themes.xml | 25 ++ .../com/dzeio/openhealth/ExampleUnitTest.kt | 17 ++ build.gradle | 10 + gradle.properties | 23 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 ++++++++++++ gradlew.bat | 89 ++++++ settings.gradle | 16 ++ 62 files changed, 1673 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/dzeio/openhealth/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/dzeio/openhealth/MainActivity.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryFragment.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryViewModel.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/slideshow/SlideshowFragment.kt create mode 100644 app/src/main/java/com/dzeio/openhealth/ui/slideshow/SlideshowViewModel.kt create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_menu_camera.xml create mode 100644 app/src/main/res/drawable/ic_menu_gallery.xml create mode 100644 app/src/main/res/drawable/ic_menu_slideshow.xml create mode 100644 app/src/main/res/drawable/side_nav_bar.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/app_bar_main.xml create mode 100644 app/src/main/res/layout/content_main.xml create mode 100644 app/src/main/res/layout/fragment_gallery.xml create mode 100644 app/src/main/res/layout/fragment_home.xml create mode 100644 app/src/main/res/layout/fragment_slideshow.xml create mode 100644 app/src/main/res/layout/nav_header_main.xml create mode 100644 app/src/main/res/menu/activity_main_drawer.xml create mode 100644 app/src/main/res/menu/main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/navigation/mobile_navigation.xml create mode 100644 app/src/main/res/values-land/dimens.xml create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values-w1240dp/dimens.xml create mode 100644 app/src/main/res/values-w600dp/dimens.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/test/java/com/dzeio/openhealth/ExampleUnitTest.kt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..eaf91e2 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..7d7ec2e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..e4e5c48 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2ea30c1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f641b26 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "com.dzeio.openhealth" + minSdk 21 + targetSdk 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + viewBinding true + } +} + +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 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + // Google Fit + implementation "com.google.android.gms:play-services-fitness:21.0.0" + implementation "com.google.android.gms:play-services-auth:20.0.0" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/dzeio/openhealth/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/dzeio/openhealth/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d4bfffb --- /dev/null +++ b/app/src/androidTest/java/com/dzeio/openhealth/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.dzeio.openhealth + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.dzeio.openhealth", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5f3852 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/MainActivity.kt b/app/src/main/java/com/dzeio/openhealth/MainActivity.kt new file mode 100644 index 0000000..1fa158f --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/MainActivity.kt @@ -0,0 +1,73 @@ +package com.dzeio.openhealth + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.Menu +import androidx.appcompat.app.AppCompatActivity +import androidx.drawerlayout.widget.DrawerLayout +import androidx.navigation.findNavController +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.setupActionBarWithNavController +import androidx.navigation.ui.setupWithNavController +import com.dzeio.openhealth.databinding.ActivityMainBinding +import com.google.android.material.navigation.NavigationView +import com.google.android.material.snackbar.Snackbar + +class MainActivity : AppCompatActivity() { + + private lateinit var appBarConfiguration: AppBarConfiguration + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.appBarMain.toolbar) + + 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_slideshow + ), 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) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + Log.d("MainActivity", "Result $requestCode") + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + for (fragment in supportFragmentManager.primaryNavigationFragment!!.childFragmentManager.fragments) { + fragment.onActivityResult(requestCode, resultCode, data) + } + } +} \ 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 new file mode 100644 index 0000000..2721272 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/connectors/GoogleFit.kt @@ -0,0 +1,264 @@ +package com.dzeio.openhealth.connectors + +import android.Manifest +import android.app.Activity +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.app.ActivityCompat +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.fitness.Fitness +import com.google.android.gms.fitness.FitnessOptions +import com.google.android.gms.fitness.data.DataPoint +import com.google.android.gms.fitness.data.DataSet +import com.google.android.gms.fitness.data.DataSource +import com.google.android.gms.fitness.data.DataType +import com.google.android.gms.fitness.request.DataReadRequest +import com.google.android.gms.fitness.request.DataSourcesRequest +import com.google.android.gms.fitness.request.OnDataPointListener +import com.google.android.gms.fitness.request.SensorRequest +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.util.concurrent.TimeUnit + +enum class ActionRequestCode { + FIND_DATA_SOURCES +} + +class GoogleFit( + private val activity: Activity, +) { + companion object { + const val TAG = "GoogleFitConnector" + } + private val fitnessOptions = FitnessOptions.builder() + .addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE) + .addDataType(DataType.TYPE_ACTIVITY_SEGMENT) + .addDataType(DataType.TYPE_SLEEP_SEGMENT) + .addDataType(DataType.TYPE_CALORIES_EXPENDED) + .addDataType(DataType.TYPE_BASAL_METABOLIC_RATE) + .addDataType(DataType.TYPE_POWER_SAMPLE) + .addDataType(DataType.TYPE_HEART_RATE_BPM) + .addDataType(DataType.TYPE_LOCATION_SAMPLE) + .addDataType(DataType.TYPE_WEIGHT) + .build() + + private val runningQOrLater = + android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q + + // [START dataPointListener_variable_reference] + // Need to hold a reference to this listener, as it's passed into the "unregister" + // method in order to stop all sensors from sending data to this listener. + private var dataPointListener: OnDataPointListener? = null + + fun import() { + checkPermissionsAndRun(ActionRequestCode.FIND_DATA_SOURCES) + } + + private fun checkPermissionsAndRun(actionRequestCode: ActionRequestCode) { + if (permissionApproved()) { + signIn(actionRequestCode) + } else { + requestRuntimePermissions(actionRequestCode) + } + } + + private fun requestRuntimePermissions(requestCode: ActionRequestCode) { + val shouldProvideRationale = + ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION) + + // Provide an additional rationale to the user. This would happen if the user denied the + // request previously, but didn't check the "Don't ask again" checkbox. + requestCode.let { + if (shouldProvideRationale) { + Log.i(TAG, "Displaying permission rationale to provide additional context.") +// ProgressDialog.show(activity, "Waiting for authorization...", "") + ActivityCompat.requestPermissions(activity, + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + requestCode.ordinal) + } else { + Log.i(TAG, "Requesting permission") + // Request permission. It's possible this can be auto answered if device policy + // sets the permission in a given state or the user denied the permission + // previously and checked "Never ask again". + ActivityCompat.requestPermissions(activity, + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + requestCode.ordinal) + } + } + } + + private fun permissionApproved(): Boolean { + val approved = if (runningQOrLater) { + PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission( + activity, + Manifest.permission.ACCESS_FINE_LOCATION) + } else { + true + } + return approved + } + + fun signIn(requestCode: ActionRequestCode) { + if (oAuthPermissionsApproved()) { + performActionForRequestCode(requestCode) + } else { + requestCode.let { + GoogleSignIn.requestPermissions( + activity, + it.ordinal, + getGoogleAccount(), fitnessOptions) + } + } + } + + private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(getGoogleAccount(), fitnessOptions) + + /** + * Gets a Google account for use in creating the Fitness client. This is achieved by either + * using the last signed-in account, or if necessary, prompting the user to sign in. + * `getAccountForExtension` is recommended over `getLastSignedInAccount` as the latter can + * return `null` if there has been no sign in before. + */ + private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(activity, fitnessOptions) + + /** + * Runs the desired method, based on the specified request code. The request code is typically + * passed to the Fit sign-in flow, and returned with the success callback. This allows the + * caller to specify which method, post-sign-in, should be called. + * + * @param requestCode The code corresponding to the action to perform. + */ + fun performActionForRequestCode(requestCode: ActionRequestCode) = when (requestCode) { + ActionRequestCode.FIND_DATA_SOURCES -> findFitnessDataSources() + } + + /** Finds available data sources and attempts to register on a specific [DataType]. */ + private fun findFitnessDataSources() { // [START find_data_sources] + // Note: Fitness.SensorsApi.findDataSources() requires the ACCESS_FINE_LOCATION permission. + Fitness.getSensorsClient(activity, getGoogleAccount()) + .findDataSources( + DataSourcesRequest.Builder() + .setDataTypes(DataType.TYPE_LOCATION_SAMPLE) + .setDataSourceTypes(DataSource.TYPE_RAW) + .build()) + .addOnSuccessListener { dataSources -> + for (dataSource in dataSources) { + Log.i(TAG, "Data source found: $dataSource") + Log.i(TAG, "Data Source type: " + dataSource.dataType.name) + // Let's register a listener to receive Activity data! + if (dataSource.dataType == DataType.TYPE_LOCATION_SAMPLE && dataPointListener == null) { + Log.i(TAG, "Data source for LOCATION_SAMPLE found! Registering.") + registerFitnessDataListener(dataSource, DataType.TYPE_LOCATION_SAMPLE) + } + } + } + .addOnFailureListener { e -> Log.e(TAG, "failed", e) } + // [END find_data_sources] + } + + /** + * Registers a listener with the Sensors API for the provided [DataSource] and [DataType] combo. + */ + private fun registerFitnessDataListener(dataSource: DataSource, dataType: DataType) { + // [START register_data_listener] + dataPointListener = OnDataPointListener { dataPoint -> + for (field in dataPoint.dataType.fields) { + val value = dataPoint.getValue(field) + Log.i(TAG, "Detected DataPoint field: ${field.name}") + Log.i(TAG, "Detected DataPoint value: $value") + } + } + Fitness.getSensorsClient(activity, getGoogleAccount()) + .add( + SensorRequest.Builder() + .setDataSource(dataSource) // Optional but recommended for custom data sets. + .setDataType(dataType) // Can't be omitted. + .setSamplingRate(10, TimeUnit.SECONDS) + .build(), + dataPointListener!! + ) + .addOnCompleteListener { task -> + if (task.isSuccessful) { + Log.i(TAG, "Listener registered!") + } else { + Log.e(TAG, "Listener not registered.", task.exception) + } + } + // [END register_data_listener] + } + + @RequiresApi(Build.VERSION_CODES.O) + fun getHistory() { + + val endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()) + val readRequest = DataReadRequest.Builder() + .aggregate(DataType.AGGREGATE_CALORIES_EXPENDED) + .bucketByActivityType(1, TimeUnit.SECONDS) + .setTimeRange(endTime.minusWeeks(1).toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS) + .build() + + Fitness.getHistoryClient(activity, GoogleSignIn.getAccountForExtension(activity, fitnessOptions)) + .readData(readRequest) + .addOnSuccessListener { response -> + for (dataSet in response.buckets.flatMap { it.dataSets }) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + dumpDataSet(dataSet) + } else { + Log.e(TAG, "DUMB SHIT") + } + } + + } + } + + @RequiresApi(Build.VERSION_CODES.O) + fun dumpDataSet(dataSet: DataSet) { + Log.i(TAG, "Data returned for Data type: ${dataSet.dataType.name}") + for (dp in dataSet.dataPoints) { + Log.i(TAG,"Data point:") + Log.i(TAG,"\tType: ${dp.dataType.name}") + Log.i(TAG,"\tStart: ${dp.getStartTimeString()}") + Log.i(TAG,"\tEnd: ${dp.getEndTimeString()}") + for (field in dp.dataType.fields) { + Log.i(TAG,"\tField: ${field.name.toString()} Value: ${dp.getValue(field)}") + } + } + } + + @RequiresApi(Build.VERSION_CODES.O) + fun DataPoint.getStartTimeString() = Instant.ofEpochSecond(this.getStartTime(TimeUnit.SECONDS)) + .atZone(ZoneId.systemDefault()) + .toLocalDateTime().toString() + + @RequiresApi(Build.VERSION_CODES.O) + fun DataPoint.getEndTimeString() = Instant.ofEpochSecond(this.getEndTime(TimeUnit.SECONDS)) + .atZone(ZoneId.systemDefault()) + .toLocalDateTime().toString() + + + /** Unregisters the listener with the Sensors API. */ + private fun unregisterFitnessDataListener() { + if (dataPointListener == null) { + // This code only activates one listener at a time. If there's no listener, there's + // nothing to unregister. + return + } + // [START unregister_data_listener] + // Waiting isn't actually necessary as the unregister call will complete regardless, + // even if called from within onStop, but a callback can still be added in order to + // inspect the results. + Fitness.getSensorsClient(activity, getGoogleAccount()) + .remove(dataPointListener!!) + .addOnCompleteListener { task -> + if (task.isSuccessful && task.result!!) { + Log.i(TAG, "Listener was removed!") + } else { + Log.i(TAG, "Listener was not removed.") + } + } + // [END unregister_data_listener] + } +} \ 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 new file mode 100644 index 0000000..ece59f6 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryFragment.kt @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000..251bce1 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/gallery/GalleryViewModel.kt @@ -0,0 +1,13 @@ +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/HomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt new file mode 100644 index 0000000..5c30c96 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt @@ -0,0 +1,120 @@ +package com.dzeio.openhealth.ui.home + +import android.app.Activity.RESULT_OK +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +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.databinding.FragmentHomeBinding + +class HomeFragment : Fragment() { + + companion object { + const val TAG = "HomeFragment" + } + + private var _binding: FragmentHomeBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + + private lateinit var fit: GoogleFit + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val homeViewModel = + ViewModelProvider(this).get(HomeViewModel::class.java) + + _binding = FragmentHomeBinding.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() + } + } + return root + } + + override fun onDestroyView() { + super.onDestroyView() + _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") + } + } + } + + 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/home/HomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt new file mode 100644 index 0000000..902c24b9 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt @@ -0,0 +1,13 @@ +package com.dzeio.openhealth.ui.home + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class HomeViewModel : ViewModel() { + + private val _text = MutableLiveData().apply { + value = "This is home Fragment" + } + val text: LiveData = _text +} \ No newline at end of file diff --git a/app/src/main/java/com/dzeio/openhealth/ui/slideshow/SlideshowFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/slideshow/SlideshowFragment.kt new file mode 100644 index 0000000..2229da9 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/slideshow/SlideshowFragment.kt @@ -0,0 +1,42 @@ +package com.dzeio.openhealth.ui.slideshow + +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.FragmentSlideshowBinding + +class SlideshowFragment : Fragment() { + + private var _binding: FragmentSlideshowBinding? = 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 slideshowViewModel = + ViewModelProvider(this).get(SlideshowViewModel::class.java) + + _binding = FragmentSlideshowBinding.inflate(inflater, container, false) + val root: View = binding.root + + val textView: TextView = binding.textSlideshow + slideshowViewModel.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/slideshow/SlideshowViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/slideshow/SlideshowViewModel.kt new file mode 100644 index 0000000..0eb90e5 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/slideshow/SlideshowViewModel.kt @@ -0,0 +1,13 @@ +package com.dzeio.openhealth.ui.slideshow + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class SlideshowViewModel : ViewModel() { + + private val _text = MutableLiveData().apply { + value = "This is slideshow Fragment" + } + val text: LiveData = _text +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_menu_camera.xml b/app/src/main/res/drawable/ic_menu_camera.xml new file mode 100644 index 0000000..634fe92 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_camera.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_gallery.xml b/app/src/main/res/drawable/ic_menu_gallery.xml new file mode 100644 index 0000000..03c7709 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_gallery.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_slideshow.xml b/app/src/main/res/drawable/ic_menu_slideshow.xml new file mode 100644 index 0000000..5e9e163 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_slideshow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 0000000..6d81870 --- /dev/null +++ b/app/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,9 @@ + + + \ 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 new file mode 100644 index 0000000..6c7dd7c --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,25 @@ + + + + + + + \ 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 new file mode 100644 index 0000000..c0dbb85 --- /dev/null +++ b/app/src/main/res/layout/app_bar_main.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + \ 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 new file mode 100644 index 0000000..6e0ea39 --- /dev/null +++ b/app/src/main/res/layout/content_main.xml @@ -0,0 +1,20 @@ + + + + + \ 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 new file mode 100644 index 0000000..643fe25 --- /dev/null +++ b/app/src/main/res/layout/fragment_gallery.xml @@ -0,0 +1,22 @@ + + + + + \ 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 new file mode 100644 index 0000000..5c1eb28 --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,16 @@ + + + +