From 05eaee94dd5e8eba4c16e9c376a6fcc0173f77e0 Mon Sep 17 00:00:00 2001 From: vsharika1 Date: Sat, 10 Feb 2024 16:53:56 -0800 Subject: [PATCH] Final Commit --- .gitignore | 33 ++ app/.gitignore | 1 + app/build.gradle.kts | 76 +++ app/proguard-rules.pro | 21 + .../ExampleInstrumentedTest.kt | 24 + app/src/main/AndroidManifest.xml | 81 +++ .../example/vishavjit_harika/Converters.kt | 40 ++ .../vishavjit_harika/DisplayEntryActivity.kt | 123 +++++ .../DisplayEntryMapsActivity.kt | 135 +++++ .../example/vishavjit_harika/ExerciseEntry.kt | 48 ++ .../vishavjit_harika/ExerciseEntryDatabase.kt | 30 ++ .../ExerciseEntryDatabaseDao.kt | 22 + .../ExerciseEntryRepository.kt | 29 ++ .../ExerciseEntryViewModel.kt | 31 ++ .../com/example/vishavjit_harika/FFT.java | 185 +++++++ .../vishavjit_harika/FragmentHistory.kt | 68 +++ .../vishavjit_harika/FragmentSettings.kt | 130 +++++ .../example/vishavjit_harika/FragmentStart.kt | 111 +++++ .../com/example/vishavjit_harika/GlobalKey.kt | 7 + .../com/example/vishavjit_harika/Globals.kt | 35 ++ .../HistoryListViewAdapter.kt | 91 ++++ .../example/vishavjit_harika/MainActivity.kt | 79 +++ .../vishavjit_harika/ManualDataEntry.kt | 13 + .../vishavjit_harika/ManualEntryActivity.kt | 199 ++++++++ .../example/vishavjit_harika/MapsActivity.kt | 469 ++++++++++++++++++ .../com/example/vishavjit_harika/MyDialog.kt | 165 ++++++ .../MyFragmentStateAdapter.kt | 17 + .../example/vishavjit_harika/MyViewModel.kt | 14 + .../ProfileSettingsActivity.kt | 229 +++++++++ .../vishavjit_harika/TrackingService.kt | 174 +++++++ .../java/com/example/vishavjit_harika/Util.kt | 50 ++ .../vishavjit_harika/WekaClassifier.java | 56 +++ .../res/drawable-hdpi/icons8_delete_36___.png | Bin 0 -> 359 bytes .../res/drawable-ldpi/icons8_delete_18___.png | Bin 0 -> 235 bytes .../res/drawable-mdpi/icons8_delete_24___.png | Bin 0 -> 250 bytes .../drawable-xhdpi/icons8_delete_48___.png | Bin 0 -> 423 bytes .../drawable-xxhdpi/icons8_delete_72___.png | Bin 0 -> 563 bytes .../drawable-xxxhdpi/icons8_delete_96___.png | Bin 0 -> 762 bytes app/src/main/res/drawable/cancel_24px.xml | 10 + .../res/drawable/ic_launcher_background.xml | 170 +++++++ .../res/drawable/ic_launcher_foreground.xml | 30 ++ .../main/res/drawable/location_on_24px.xml | 10 + .../res/layout/activity_display_entry.xml | 164 ++++++ .../layout/activity_display_entry_maps.xml | 65 +++ app/src/main/res/layout/activity_main.xml | 37 ++ .../main/res/layout/activity_manual_entry.xml | 76 +++ app/src/main/res/layout/activity_maps.xml | 85 ++++ .../res/layout/activity_profile_settings.xml | 294 +++++++++++ .../layout/exercise_entry_list_display.xml | 102 ++++ .../res/layout/fragment_calories_dialog.xml | 14 + .../res/layout/fragment_comment_dialog.xml | 15 + .../fragment_comments_settings_dialog.xml | 13 + .../res/layout/fragment_distance_dialog.xml | 14 + .../res/layout/fragment_duration_dialog.xml | 14 + .../res/layout/fragment_heart_rate_dialog.xml | 14 + app/src/main/res/layout/fragment_history.xml | 19 + .../fragment_pick_profile_picture_dialog.xml | 27 + app/src/main/res/layout/fragment_settings.xml | 61 +++ app/src/main/res/layout/fragment_start.xml | 72 +++ .../fragment_unit_preference_dialog.xml | 27 + app/src/main/res/menu/delete_menu.xml | 11 + app/src/main/res/menu/myruns_menu.xml | 7 + .../main/res/mipmap-anydpi/ic_launcher.xml | 6 + .../res/mipmap-anydpi/ic_launcher_round.xml | 6 + 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 app/src/main/res/values-night/themes.xml | 7 + app/src/main/res/values/colors.xml | 5 + app/src/main/res/values/strings.xml | 75 +++ app/src/main/res/values/themes.xml | 9 + app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 + app/src/main/res/xml/file_path.xml | 3 + app/src/main/res/xml/preferences.xml | 68 +++ .../vishavjit_harika/ExampleUnitTest.kt | 17 + build.gradle.kts | 6 + 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.kts | 17 + 90 files changed, 4691 insertions(+) create mode 100755 .gitignore create mode 100755 app/.gitignore create mode 100755 app/build.gradle.kts create mode 100755 app/proguard-rules.pro create mode 100755 app/src/androidTest/java/com/example/vishavjit_harika/ExampleInstrumentedTest.kt create mode 100755 app/src/main/AndroidManifest.xml create mode 100755 app/src/main/java/com/example/vishavjit_harika/Converters.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/DisplayEntryActivity.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/DisplayEntryMapsActivity.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/ExerciseEntry.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/ExerciseEntryDatabase.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/ExerciseEntryDatabaseDao.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/ExerciseEntryRepository.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/ExerciseEntryViewModel.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/FFT.java create mode 100755 app/src/main/java/com/example/vishavjit_harika/FragmentHistory.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/FragmentSettings.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/FragmentStart.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/GlobalKey.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/Globals.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/HistoryListViewAdapter.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/MainActivity.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/ManualDataEntry.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/ManualEntryActivity.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/MapsActivity.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/MyDialog.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/MyFragmentStateAdapter.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/MyViewModel.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/ProfileSettingsActivity.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/TrackingService.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/Util.kt create mode 100755 app/src/main/java/com/example/vishavjit_harika/WekaClassifier.java create mode 100755 app/src/main/res/drawable-hdpi/icons8_delete_36___.png create mode 100755 app/src/main/res/drawable-ldpi/icons8_delete_18___.png create mode 100755 app/src/main/res/drawable-mdpi/icons8_delete_24___.png create mode 100755 app/src/main/res/drawable-xhdpi/icons8_delete_48___.png create mode 100755 app/src/main/res/drawable-xxhdpi/icons8_delete_72___.png create mode 100755 app/src/main/res/drawable-xxxhdpi/icons8_delete_96___.png create mode 100755 app/src/main/res/drawable/cancel_24px.xml create mode 100755 app/src/main/res/drawable/ic_launcher_background.xml create mode 100755 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100755 app/src/main/res/drawable/location_on_24px.xml create mode 100755 app/src/main/res/layout/activity_display_entry.xml create mode 100755 app/src/main/res/layout/activity_display_entry_maps.xml create mode 100755 app/src/main/res/layout/activity_main.xml create mode 100755 app/src/main/res/layout/activity_manual_entry.xml create mode 100755 app/src/main/res/layout/activity_maps.xml create mode 100755 app/src/main/res/layout/activity_profile_settings.xml create mode 100755 app/src/main/res/layout/exercise_entry_list_display.xml create mode 100755 app/src/main/res/layout/fragment_calories_dialog.xml create mode 100755 app/src/main/res/layout/fragment_comment_dialog.xml create mode 100755 app/src/main/res/layout/fragment_comments_settings_dialog.xml create mode 100755 app/src/main/res/layout/fragment_distance_dialog.xml create mode 100755 app/src/main/res/layout/fragment_duration_dialog.xml create mode 100755 app/src/main/res/layout/fragment_heart_rate_dialog.xml create mode 100755 app/src/main/res/layout/fragment_history.xml create mode 100755 app/src/main/res/layout/fragment_pick_profile_picture_dialog.xml create mode 100755 app/src/main/res/layout/fragment_settings.xml create mode 100755 app/src/main/res/layout/fragment_start.xml create mode 100755 app/src/main/res/layout/fragment_unit_preference_dialog.xml create mode 100755 app/src/main/res/menu/delete_menu.xml create mode 100755 app/src/main/res/menu/myruns_menu.xml create mode 100755 app/src/main/res/mipmap-anydpi/ic_launcher.xml create mode 100755 app/src/main/res/mipmap-anydpi/ic_launcher_round.xml create mode 100755 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100755 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100755 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100755 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100755 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100755 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100755 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100755 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100755 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100755 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100755 app/src/main/res/values-night/themes.xml create mode 100755 app/src/main/res/values/colors.xml create mode 100755 app/src/main/res/values/strings.xml create mode 100755 app/src/main/res/values/themes.xml create mode 100755 app/src/main/res/xml/backup_rules.xml create mode 100755 app/src/main/res/xml/data_extraction_rules.xml create mode 100755 app/src/main/res/xml/file_path.xml create mode 100755 app/src/main/res/xml/preferences.xml create mode 100755 app/src/test/java/com/example/vishavjit_harika/ExampleUnitTest.kt create mode 100755 build.gradle.kts create mode 100755 gradle.properties create mode 100755 gradle/wrapper/gradle-wrapper.jar create mode 100755 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100755 gradlew.bat create mode 100755 settings.gradle.kts diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..16dcd12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Log/OS Files +*.log + +# Android Studio generated files and folders +captures/ +.externalNativeBuild/ +.cxx/ +*.apk +output.json + +# IntelliJ +*.iml +.idea/ +misc.xml +deploymentTargetDropDown.xml +render.experimental.xml + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Android Profiling +*.hprof \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100755 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100755 index 0000000..4afd466 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("kotlin-kapt") + id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") +} + +android { + namespace = "com.example.vishavjit_harika" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.vishavjit_harika" + minSdk = 34 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = 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.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.10.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.preference:preference-ktx:1.2.1") + implementation("androidx.room:room-common:2.6.0") + implementation("com.google.android.gms:play-services-maps:18.2.0") + implementation("androidx.room:room-ktx:2.6.0") + implementation("com.google.android.gms:play-services-location:21.0.1") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + + // Room components + val room_version = "2.6.0" + val lifecycle_version = "2.6.2" + implementation("androidx.room:room-ktx:$room_version") + kapt("androidx.room:room-compiler:$room_version") + implementation ("androidx.lifecycle:lifecycle-livedata-ktx: $lifecycle_version") + + implementation("com.google.code.gson:gson:2.9.0") + + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2") + + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") + + implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0") + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100755 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/example/vishavjit_harika/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/vishavjit_harika/ExampleInstrumentedTest.kt new file mode 100755 index 0000000..f3a6d18 --- /dev/null +++ b/app/src/androidTest/java/com/example/vishavjit_harika/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.vishavjit_harika + +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.example.vishavjit_harika", 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 100755 index 0000000..09ef29f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/Converters.kt b/app/src/main/java/com/example/vishavjit_harika/Converters.kt new file mode 100755 index 0000000..de84bfa --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/Converters.kt @@ -0,0 +1,40 @@ +package com.example.vishavjit_harika + +import androidx.room.TypeConverter +import com.google.android.gms.maps.model.LatLng +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class Converters { + private val format = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) + + @TypeConverter + fun fromTimestamp(value: String?): Calendar? { + return value?.let { + val cal = Calendar.getInstance() + cal.time = format.parse(value) ?: return null + cal + } + } + + @TypeConverter + fun calendarToTimestamp(calendar: Calendar?): String? { + return calendar?.let { + format.format(it.time) + } + } + + @TypeConverter + fun fromLatLngList(value: ArrayList?): String { + return Gson().toJson(value) + } + + @TypeConverter + fun toLatLngList(value: String): ArrayList { + val listType = object : TypeToken>() {}.type + return Gson().fromJson(value, listType) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/DisplayEntryActivity.kt b/app/src/main/java/com/example/vishavjit_harika/DisplayEntryActivity.kt new file mode 100755 index 0000000..d312f20 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/DisplayEntryActivity.kt @@ -0,0 +1,123 @@ +package com.example.vishavjit_harika + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.widget.Button +import android.widget.EditText +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class DisplayEntryActivity : AppCompatActivity() { + private lateinit var viewModel: ExerciseEntryViewModel + private lateinit var database: ExerciseEntryDatabase + private lateinit var databaseDao: ExerciseEntryDatabaseDao + private lateinit var repository: ExerciseEntryRepository + private var entryKey: Long = -1 + private lateinit var entry: ExerciseEntry + private lateinit var deleteButton: Button + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_display_entry) + + entryKey = GlobalKey.selectedEntryKey + + database = ExerciseEntryDatabase.getInstance(application) + databaseDao = database.exerciseEntryDatabaseDao + repository = ExerciseEntryRepository(databaseDao) + val viewModelFactory = ExerciseEntryViewModelFactory(repository) + viewModel = ViewModelProvider(this, viewModelFactory)[ExerciseEntryViewModel::class.java] + + if (entryKey != -1L) { + lifecycleScope.launch { + entry = repository.get(entryKey) + populateEntryData(entry) + } + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val inflater: MenuInflater = menuInflater + inflater.inflate(R.menu.delete_menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.delete_button -> { + entryKey = GlobalKey.selectedEntryKey + lifecycleScope.launch { + repository.delete(entryKey) + finish() + } + true + } + else -> super.onOptionsItemSelected(item) + } + } + + private fun populateEntryData(entry: ExerciseEntry) { + val inputTypeEditText = findViewById(R.id.input_type_editTextText) + val activityTypeEditText = findViewById(R.id.activity_type_editTextText) + val dateAndTimeEditText = findViewById(R.id.date_and_time_editTextText) + val durationEditText = findViewById(R.id.display_duration_editTextText) + val distanceEditText = findViewById(R.id.display_distance_editTextText) + val caloriesEditText = findViewById(R.id.editTextText6) + val heartRateEditText = findViewById(R.id.display_heart_rate_editTextText) + + val inputType: String = displayInputType(entry.inputType) + val activityType: String = displayActivityType(entry.activityType) + + inputTypeEditText.setText(inputType) + activityTypeEditText.setText(activityType) + dateAndTimeEditText.setText(formatDate(entry.dateTime)) + durationEditText.setText(entry.duration.toString()) + distanceEditText.setText(entry.distance.toString()) + caloriesEditText.setText(entry.calorie.toString()) + heartRateEditText.setText(entry.heartRate.toString()) + } + + private fun formatDate(calendar: Calendar): String { + val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) + return formatter.format(calendar.time) + } + + private fun displayInputType(entryInput: Int): String { + var selection: String = "" + when (entryInput) { + 1 -> selection = "Manual Entry" + 2 -> selection = "GPS" + 3 -> selection = "Automatic" + } + return selection + } + + private fun displayActivityType(entryActivity: Int): String { + var selection: String = "" + when (entryActivity) { + 1 -> selection = "Running" + 2 -> selection = "Walking" + 3 -> selection = "Standing" + 4 -> selection = "Cycling" + 5 -> selection = "Hiking" + 6 -> selection = "Downhill Skiing" + 7 -> selection = "Cross-Country Skiing" + 8 -> selection = "Snowboarding" + 9 -> selection = "Skating" + 10 -> selection = "Swimming" + 11 -> selection = "Mountain Biking" + 12 -> selection = "Wheelchair" + 13 -> selection = "Elliptical" + 14 -> selection = "Other" + } + return selection + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/DisplayEntryMapsActivity.kt b/app/src/main/java/com/example/vishavjit_harika/DisplayEntryMapsActivity.kt new file mode 100755 index 0000000..1aa229f --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/DisplayEntryMapsActivity.kt @@ -0,0 +1,135 @@ +package com.example.vishavjit_harika + +import android.annotation.SuppressLint +import android.graphics.Color +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.example.vishavjit_harika.databinding.ActivityDisplayEntryMapsBinding +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.PolylineOptions +import kotlinx.coroutines.launch + +class DisplayEntryMapsActivity : AppCompatActivity(), OnMapReadyCallback { + + private lateinit var mMap: GoogleMap + private lateinit var binding: ActivityDisplayEntryMapsBinding + + private lateinit var viewModel: ExerciseEntryViewModel + private lateinit var database: ExerciseEntryDatabase + private lateinit var databaseDao: ExerciseEntryDatabaseDao + private lateinit var repository: ExerciseEntryRepository + private var entryKey: Long = -1 + private lateinit var entry: ExerciseEntry + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityDisplayEntryMapsBinding.inflate(layoutInflater) + setContentView(binding.root) + + // Obtain the SupportMapFragment and get notified when the map is ready to be used. + val mapFragment = supportFragmentManager + .findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(this) + + entryKey = GlobalKey.selectedEntryKey + + database = ExerciseEntryDatabase.getInstance(application) + databaseDao = database.exerciseEntryDatabaseDao + repository = ExerciseEntryRepository(databaseDao) + val viewModelFactory = ExerciseEntryViewModelFactory(repository) + viewModel = ViewModelProvider(this, viewModelFactory)[ExerciseEntryViewModel::class.java] + + if (entryKey != -1L) { + lifecycleScope.launch { + entry = repository.get(entryKey) + populateEntryData(entry) + } + } + } + + override fun onMapReady(googleMap: GoogleMap) { + mMap = googleMap + + if (this::entry.isInitialized && entry.locationList.isNotEmpty()) { + val polylineOptions = PolylineOptions().apply { + width(5f) + color(Color.RED) + addAll(entry.locationList) + } + mMap.addPolyline(polylineOptions) + + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(entry.locationList.first(), 15f)) + + mMap.addMarker(MarkerOptions().position(entry.locationList.first()).title("Start")) + mMap.addMarker(MarkerOptions().position(entry.locationList.last()).title("End")) + } else { + val sydney = LatLng(-34.0, 151.0) + mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney")) + mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney)) + } + } + + @SuppressLint("SetTextI18n") + private fun populateEntryData(entry: ExerciseEntry) { + val activityTypeText = binding.typeTextView + val avgSpeedText = binding.avgSpeedTextView + val climbText = binding.climbTextView + val caloriesText = binding.caloriesTextView + val distanceText = binding.distanceTextView + + val activityType: String = displayActivityType(entry.activityType) + + activityTypeText.text = "Type: $activityType" + avgSpeedText.text = "Avg Speed: ${String.format("%.2f", entry.avgSpeed)} m/s" + climbText.text = "Climb: ${String.format("%.2f", entry.climb)} m" + caloriesText.text = "Calories: ${String.format("%.2f", entry.calorie)}" + distanceText.text = "Distance: ${String.format("%.2f", entry.distance)} m" + + drawPathOnMap(entry.locationList) + } + + private fun drawPathOnMap(locationList: ArrayList) { + if (locationList.isNotEmpty()) { + val polylineOptions = PolylineOptions().apply { + width(5f) + color(Color.RED) + addAll(locationList) + } + mMap.addPolyline(polylineOptions) + + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(locationList.first(), 15f)) + + mMap.addMarker(MarkerOptions().position(locationList.first()).title("Start")) + mMap.addMarker(MarkerOptions().position(locationList.last()).title("End")) + } + } + + private fun displayActivityType(entryActivity: Int): String { + var selection: String = "" + when (entryActivity) { + 1 -> selection = "Running" + 2 -> selection = "Walking" + 3 -> selection = "Standing" + 4 -> selection = "Cycling" + 5 -> selection = "Hiking" + 6 -> selection = "Downhill Skiing" + 7 -> selection = "Cross-Country Skiing" + 8 -> selection = "Snowboarding" + 9 -> selection = "Skating" + 10 -> selection = "Swimming" + 11 -> selection = "Mountain Biking" + 12 -> selection = "Wheelchair" + 13 -> selection = "Elliptical" + 14 -> selection = "Other" + } + return selection + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/ExerciseEntry.kt b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntry.kt new file mode 100755 index 0000000..9a0e1a6 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntry.kt @@ -0,0 +1,48 @@ +package com.example.vishavjit_harika + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.google.android.gms.maps.model.LatLng + +@Entity(tableName = "exercise_entry_table") +data class ExerciseEntry( + @PrimaryKey(autoGenerate = true) + var id: Long = 0L, + + @ColumnInfo(name = "input_type_column") + var inputType: Int, // Manual, GPS or automatic + + @ColumnInfo(name = "activity_type_column") + var activityType: Int, // Running, cycling etc. + + @ColumnInfo(name = "date_time_column") + var dateTime: java.util.Calendar, // When does this entry happen + + @ColumnInfo(name = "duration_column") + var duration: Double, // Exercise duration in seconds + + @ColumnInfo(name = "distance_column") + var distance: Double, // Distance traveled. Either in meters or feet. + + @ColumnInfo(name = "avg_pace_column") + var avgPace: Double, // Average pace + + @ColumnInfo(name = "avg_speed_column") + var avgSpeed: Double, // Average speed + + @ColumnInfo(name = "calorie_column") + var calorie: Double, // Calories burnt + + @ColumnInfo(name = "climb_column") + var climb: Double, // Climb. Either in meters or feet. + + @ColumnInfo(name = "heart_rate_column") + var heartRate: Double, // Heart rate + + @ColumnInfo(name = "comment_column") + var comment: String, // Comments + + @ColumnInfo(name = "location_column") + var locationList: ArrayList // Location list +) diff --git a/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryDatabase.kt b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryDatabase.kt new file mode 100755 index 0000000..cdd413f --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryDatabase.kt @@ -0,0 +1,30 @@ +package com.example.vishavjit_harika + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters + +@Database(entities = [ExerciseEntry::class], version = 1) +@TypeConverters(Converters::class) +abstract class ExerciseEntryDatabase: RoomDatabase() { + abstract val exerciseEntryDatabaseDao: ExerciseEntryDatabaseDao + + companion object{ + @Volatile + private var INSTANCE: ExerciseEntryDatabase? = null + + fun getInstance(context: Context) : ExerciseEntryDatabase{ + synchronized(this){ + var instance = INSTANCE + if(instance == null){ + instance = Room.databaseBuilder(context.applicationContext, + ExerciseEntryDatabase::class.java, "exercise_entry_table").build() + INSTANCE = instance + } + return instance + } + } + } +} diff --git a/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryDatabaseDao.kt b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryDatabaseDao.kt new file mode 100755 index 0000000..a465fef --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryDatabaseDao.kt @@ -0,0 +1,22 @@ +package com.example.vishavjit_harika + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query + +@Dao +interface ExerciseEntryDatabaseDao { + + @Insert + suspend fun insert(entry: ExerciseEntry) + + @Query("SELECT * FROM exercise_entry_table WHERE id = :key") + suspend fun get(key: Long): ExerciseEntry + + @Query("SELECT * FROM exercise_entry_table ORDER BY id DESC") + fun getAllEntries(): LiveData> + + @Query("DELETE FROM exercise_entry_table WHERE id = :key") + suspend fun delete(key: Long) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryRepository.kt b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryRepository.kt new file mode 100755 index 0000000..2450043 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryRepository.kt @@ -0,0 +1,29 @@ +package com.example.vishavjit_harika + +import androidx.lifecycle.LiveData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class ExerciseEntryRepository(private val exerciseEntryDatabaseDao: ExerciseEntryDatabaseDao) { + + val allEntries: LiveData> = exerciseEntryDatabaseDao.getAllEntries() + + suspend fun insert(entry: ExerciseEntry) { + CoroutineScope(IO).launch { + exerciseEntryDatabaseDao.insert(entry) + } + } + + suspend fun get(key: Long): ExerciseEntry = withContext(Dispatchers.IO) { + exerciseEntryDatabaseDao.get(key) + } + + fun delete(key: Long) { + CoroutineScope(IO).launch { + exerciseEntryDatabaseDao.delete(key) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryViewModel.kt b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryViewModel.kt new file mode 100755 index 0000000..00a5b6d --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/ExerciseEntryViewModel.kt @@ -0,0 +1,31 @@ +package com.example.vishavjit_harika + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch + +class ExerciseEntryViewModel(private val repository: ExerciseEntryRepository) : ViewModel() { + val allEntries: LiveData> = repository.allEntries + + fun insert(entry: ExerciseEntry) { + viewModelScope.launch { + repository.insert(entry) + } + } + + suspend fun get(id: Long): ExerciseEntry? { + return repository.get(id) + } +} + +class ExerciseEntryViewModelFactory(private val repository: ExerciseEntryRepository) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(ExerciseEntryViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return ExerciseEntryViewModel(repository) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/FFT.java b/app/src/main/java/com/example/vishavjit_harika/FFT.java new file mode 100755 index 0000000..e7d489e --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/FFT.java @@ -0,0 +1,185 @@ +package com.example.vishavjit_harika; + +public class FFT { + int n, m; + + // Lookup tables. Only need to recompute when size of FFT changes. + double[] cos; + double[] sin; + + double[] window; + + public FFT(int n) { + this.n = n; + this.m = (int) (Math.log(n) / Math.log(2)); + + // Make sure n is a power of 2 + if (n != (1 << m)) + throw new RuntimeException("FFT length must be power of 2"); + + // precompute tables + cos = new double[n / 2]; + sin = new double[n / 2]; + + // for(int i=0; i= n1) { + j = j - n1; + n1 = n1 / 2; + } + j = j + n1; + + if (i < j) { + t1 = x[i]; + x[i] = x[j]; + x[j] = t1; + t1 = y[i]; + y[i] = y[j]; + y[j] = t1; + } + } + + // FFT + n1 = 0; + n2 = 1; + + for (i = 0; i < m; i++) { + n1 = n2; + n2 = n2 + n2; + a = 0; + + for (j = 0; j < n1; j++) { + c = cos[a]; + s = sin[a]; + a += 1 << (m - i - 1); + + for (k = j; k < n; k = k + n2) { + t1 = c * x[k + n1] - s * y[k + n1]; + t2 = s * x[k + n1] + c * y[k + n1]; + x[k + n1] = x[k] - t1; + y[k + n1] = y[k] - t2; + x[k] = x[k] + t1; + y[k] = y[k] + t2; + } + } + } + } + + // Test the FFT to make sure it's working + public static void main(String[] args) { + int N = 8; + + FFT fft = new FFT(N); + + double[] re = new double[N]; + double[] im = new double[N]; + + // Impulse + re[0] = 1; + im[0] = 0; + for (int i = 1; i < N; i++) + re[i] = im[i] = 0; + beforeAfter(fft, re, im); + + // Nyquist + for (int i = 0; i < N; i++) { + re[i] = Math.pow(-1, i); + im[i] = 0; + } + beforeAfter(fft, re, im); + + // Single sin + for (int i = 0; i < N; i++) { + re[i] = Math.cos(2 * Math.PI * i / N); + im[i] = 0; + } + beforeAfter(fft, re, im); + + // Ramp + for (int i = 0; i < N; i++) { + re[i] = i; + im[i] = 0; + } + beforeAfter(fft, re, im); + + long time = System.currentTimeMillis(); + double iter = 30000; + for (int i = 0; i < iter; i++) + fft.fft(re, im); + time = System.currentTimeMillis() - time; + System.out.println("Averaged " + (time / iter) + "ms per iteration"); + } + + public static void beforeAfter(FFT fft, double[] re, double[] im) { + System.out.println("Before: "); + printReIm(re, im); + fft.fft(re, im); + System.out.println("After: "); + printReIm(re, im); + } + + public static void printReIm(double[] re, double[] im) { + System.out.print("Re: ["); + for (int i = 0; i < re.length; i++) + System.out.print(((int) (re[i] * 1000) / 1000.0) + " "); + + System.out.print("]\nIm: ["); + for (int i = 0; i < im.length; i++) + System.out.print(((int) (im[i] * 1000) / 1000.0) + " "); + + System.out.println("]"); + } +} + diff --git a/app/src/main/java/com/example/vishavjit_harika/FragmentHistory.kt b/app/src/main/java/com/example/vishavjit_harika/FragmentHistory.kt new file mode 100755 index 0000000..cc6bad2 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/FragmentHistory.kt @@ -0,0 +1,68 @@ +package com.example.vishavjit_harika + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ListView +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider + +class FragmentHistory : Fragment() { + private lateinit var adapter: HistoryListViewAdapter + private lateinit var viewModel: ExerciseEntryViewModel + private lateinit var listView: ListView + + private lateinit var database: ExerciseEntryDatabase + private lateinit var databaseDao: ExerciseEntryDatabaseDao + private lateinit var repository: ExerciseEntryRepository + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + val view: View = inflater.inflate(R.layout.fragment_history, container, false) + + database = ExerciseEntryDatabase.getInstance(requireActivity()) + databaseDao = database.exerciseEntryDatabaseDao + repository = ExerciseEntryRepository(databaseDao) + + listView = view.findViewById(R.id.exercise_entry_list) + viewModel = ViewModelProvider(requireActivity(), ExerciseEntryViewModelFactory(repository))[ExerciseEntryViewModel::class.java] + adapter = HistoryListViewAdapter(requireContext(), listOf()) + + viewModel.allEntries.observe(viewLifecycleOwner, Observer { entries -> + adapter.replace(entries) + adapter.notifyDataSetChanged() + }) + + listView.adapter = adapter + + listView.setOnItemClickListener { _, _, position, _ -> + val selectedEntry = adapter.getItem(position) as ExerciseEntry + GlobalKey.selectedEntryKey = selectedEntry.id + Log.i("key", GlobalKey.selectedEntryKey.toString()) + if (selectedEntry.inputType == 1) { + val intent = Intent(requireContext(), DisplayEntryActivity::class.java) + intent.putExtra("entry_id", selectedEntry.id) + startActivity(intent) + } else { + val intent = Intent(requireContext(), DisplayEntryMapsActivity::class.java) + intent.putExtra("entry_id", selectedEntry.id) + startActivity(intent) + } + } + + return view + } + + override fun onResume() { + super.onResume() + + viewModel.allEntries.observe(viewLifecycleOwner, Observer { entries -> + adapter.replace(entries) + adapter.notifyDataSetChanged() + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/FragmentSettings.kt b/app/src/main/java/com/example/vishavjit_harika/FragmentSettings.kt new file mode 100755 index 0000000..4191109 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/FragmentSettings.kt @@ -0,0 +1,130 @@ +package com.example.vishavjit_harika + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat + +/** + * Resources used: + * 1. For listView and its sub-item: https://stackoverflow.com/questions/7916834/adding-listview-sub-item-text-in-android + * 2. For listView and its sub-item: https://www.geeksforgeeks.org/simpleadapter-in-android-with-example/ + * 3. For fixing issue with Fragment Manager: https://stackoverflow.com/questions/60830741/kotlin-fragmentmanager-is-showing-error-when-called-from-fragment-working-with + * 4. Open URL on the click of a button: https://stackoverflow.com/questions/4930228/open-a-url-on-click-of-ok-button-in-android + * 5. List preferences: https://stackoverflow.com/questions/9880841/using-list-preference-in-android + */ + +class FragmentSettings : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences) + } +} + +// Implementation using ListView: +// +//class FragmentSettings : Fragment() { +// private val accountPreferencesArray = arrayOf("Name, Email, Class, etc", "Privacy Setting") +// private val apSubTextArray = arrayOf("User Profile", "Posting your records anonymously") +// private val additionalSettingsArray = arrayOf("Unit Preference", "Comments") +// private val asSubTextArray = arrayOf("Select the units", "Please enter your comments") +// private val miscArray = arrayOf("Webpage") +// private val miscSubTextArray = arrayOf("https://www.sfu.ca/computing.html") +// +// private val listTitle = arrayOf("Setting", "Description") +// +// private lateinit var accountPreferencesListView: ListView +// private lateinit var additionalSettingsListView: ListView +// private lateinit var miscListView: ListView +// +// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, +// savedInstanceState: Bundle?): View? { +// val inf = inflater.inflate(R.layout.fragment_settings, container, false) +// +// accountPreferencesListView = inf.findViewById(R.id.account_preferences_listView) +// additionalSettingsListView = inf.findViewById(R.id.additional_settings_Listview) +// miscListView = inf.findViewById(R.id.misc_listView) +// +// val apList = ArrayList>() +// for (i in accountPreferencesArray.indices) { +// val apMap = HashMap() +// +// apMap["Setting"] = accountPreferencesArray[i] +// apMap["Description"] = apSubTextArray[i] +// +// apList.add(apMap) +// } +// +// val apSimpleAdapter: SimpleAdapter = SimpleAdapter( +// inf.context, apList, android.R.layout.simple_list_item_2, listTitle, intArrayOf(android.R.id.text1, android.R.id.text2) +// ) +// accountPreferencesListView.adapter = apSimpleAdapter +// +// accountPreferencesListView.setOnItemClickListener() { parent: AdapterView<*>, view: View, position: Int, id: Long -> +// when(position) { +// 0 -> { +// val intent: Intent = Intent(inf.context, ProfileSettingsActivity::class.java) +// startActivity(intent) +// } +// } +// } +// +// val asList = ArrayList>() +// for (i in additionalSettingsArray.indices) { +// val asMap = HashMap() +// +// asMap["Setting"] = additionalSettingsArray[i] +// asMap["Description"] = asSubTextArray[i] +// +// asList.add(asMap) +// } +// +// val asSimpleAdapter: SimpleAdapter = SimpleAdapter( +// inf.context, asList, android.R.layout.simple_list_item_2, listTitle, intArrayOf(android.R.id.text1, android.R.id.text2) +// ) +// additionalSettingsListView.adapter = asSimpleAdapter +// +// additionalSettingsListView.setOnItemClickListener() { parent: AdapterView<*>, view: View, position: Int, id: Long -> +// when(position) { +// 0 -> { +// val myDialog = MyDialog() +// val bundle = Bundle() +// bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.UNIT_PREFERENCE_DIALOG) +// myDialog.arguments = bundle +// myDialog.show(childFragmentManager, "unit preference dialog") +// } +// 1 -> { +// val myDialog = MyDialog() +// val bundle = Bundle() +// bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.COMMENTS_SETTINGS_DIALOG) +// myDialog.arguments = bundle +// myDialog.show(childFragmentManager, "comments settings dialog") +// } +// } +// } +// +// val miscList = ArrayList>() +// for (i in miscArray.indices) { +// val miscMap = HashMap() +// +// miscMap["Setting"] = miscArray[i] +// miscMap["Description"] = miscSubTextArray[i] +// +// miscList.add(miscMap) +// } +// +// val miscSimpleAdapter: SimpleAdapter = SimpleAdapter( +// inf.context, miscList, android.R.layout.simple_list_item_2, listTitle, intArrayOf(android.R.id.text1, android.R.id.text2) +// ) +// miscListView.adapter = miscSimpleAdapter +// +// miscListView.setOnItemClickListener() { parent: AdapterView<*>, view: View, position: Int, id: Long -> +// when(position) { +// 0 -> { +// val uri: Uri? = Uri.parse("https://www.sfu.ca/computing.html") +// val intent = Intent(Intent.ACTION_VIEW, uri) +// startActivity(intent) +// } +// } +// } +// +// return inf +// } +//} diff --git a/app/src/main/java/com/example/vishavjit_harika/FragmentStart.kt b/app/src/main/java/com/example/vishavjit_harika/FragmentStart.kt new file mode 100755 index 0000000..487aa44 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/FragmentStart.kt @@ -0,0 +1,111 @@ +package com.example.vishavjit_harika + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Button +import android.widget.Spinner +import androidx.fragment.app.Fragment + +/** + * Resources used: + * 1. https://stackoverflow.com/questions/55684917/the-spinner-doesnt-work-in-my-kotlin-fragment + * 2. https://developer.android.com/develop/ui/views/components/spinner + */ + +class FragmentStart : Fragment(), AdapterView.OnItemSelectedListener { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val inf = inflater.inflate(R.layout.fragment_start, container, false) + + val inputSpinner: Spinner = inf.findViewById(R.id.input_spinner) + // Create an ArrayAdapter using the string array and a default spinner layout. + ArrayAdapter.createFromResource( + inf.context, + R.array.workout_input, + android.R.layout.simple_spinner_item + ).also { adapter -> + // Specify the layout to use when the list of choices appears. + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + // Apply the adapter to the spinner. + inputSpinner.adapter = adapter + } + + val activitySpinner: Spinner = inf.findViewById(R.id.activity_spinner) + // Create an ArrayAdapter using the string array and a default spinner layout. + ArrayAdapter.createFromResource( + inf.context, + R.array.activity_input, + android.R.layout.simple_spinner_item + ).also { adapter -> + // Specify the layout to use when the list of choices appears. + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + // Apply the adapter to the spinner. + activitySpinner.adapter = adapter + } + + val startWorkoutButton: Button = inf.findViewById(R.id.startWorkout_button) + startWorkoutButton.setOnClickListener() { + val itemSelected: String = inputSpinner.selectedItem.toString() + ManualDataEntry.inputActivityType = selectedInputActivity(itemSelected) + val activitySelected: String = activitySpinner.selectedItem.toString() + ManualDataEntry.activityType = selectedActivity(activitySelected) + + if (itemSelected == "Manual Entry") { + val intent: Intent = Intent(inf.context, ManualEntryActivity::class.java) + startActivity(intent) + } + else if (itemSelected == "GPS" || itemSelected == "Automatic") { + val intent: Intent = Intent(inf.context, MapsActivity::class.java) + startActivity(intent) + } + } + + return inf + } + + private fun selectedInputActivity(itemSelected: String): Int { + var selection: Int = -1 + when (itemSelected) { + "Manual Entry" -> selection = 1 + "GPS" -> selection = 2 + "Automatic" -> selection = 3 + } + return selection + } + + private fun selectedActivity(activitySelected: String): Int { + var selection: Int = -1 + when (activitySelected) { + "Running" -> selection = 1 + "Walking" -> selection = 2 + "Standing" -> selection = 3 + "Cycling" -> selection = 4 + "Hiking" -> selection = 5 + "Downhill Skiing" -> selection = 6 + "Cross-Country Skiing" -> selection = 7 + "Snowboarding" -> selection = 8 + "Skating" -> selection = 9 + "Swimming" -> selection = 10 + "Mountain Biking" -> selection = 11 + "Wheelchair" -> selection = 12 + "Elliptical" -> selection = 13 + "Other" -> selection = 14 + } + return selection + } + + override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) { + // An item is selected. You can retrieve the selected item using + // parent.getItemAtPosition(pos) + } + + override fun onNothingSelected(parent: AdapterView<*>) { + // Another interface callback. + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/GlobalKey.kt b/app/src/main/java/com/example/vishavjit_harika/GlobalKey.kt new file mode 100755 index 0000000..2c2aabc --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/GlobalKey.kt @@ -0,0 +1,7 @@ +package com.example.vishavjit_harika + +object GlobalKey { + var selectedEntryKey: Long = -1 + var inputTypeSelected: Long = -1 + var activityTypeSelected: Long = -1 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/Globals.kt b/app/src/main/java/com/example/vishavjit_harika/Globals.kt new file mode 100755 index 0000000..85adbb3 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/Globals.kt @@ -0,0 +1,35 @@ +package com.example.vishavjit_harika + +object Globals { + // Debugging tag + const val TAG = "MyRuns" + + const val ACCELEROMETER_BUFFER_CAPACITY = 2048 + const val ACCELEROMETER_BLOCK_CAPACITY = 64 + + const val ACTIVITY_ID_STANDING = 0 + const val ACTIVITY_ID_WALKING = 1 + const val ACTIVITY_ID_RUNNING = 2 + const val ACTIVITY_ID_OTHER = 3 + + const val SERVICE_TASK_TYPE_COLLECT = 0 + const val SERVICE_TASK_TYPE_CLASSIFY = 1 + + const val ACTION_MOTION_UPDATED = "MYRUNS_MOTION_UPDATED" + + const val CLASS_LABEL_KEY = "label" + const val CLASS_LABEL_STANDING = "standing" + const val CLASS_LABEL_WALKING = "walking" + const val CLASS_LABEL_RUNNING = "running" + const val CLASS_LABEL_OTHER = "others" + + const val FEAT_FFT_COEF_LABEL = "fft_coef_" + const val FEAT_MAX_LABEL = "max" + const val FEAT_SET_NAME = "accelerometer_features" + + const val FEATURE_FILE_NAME = "features.arff" + const val RAW_DATA_NAME = "raw_data.txt" + const val FEATURE_SET_CAPACITY = 10000 + + const val NOTIFICATION_ID = 1 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/HistoryListViewAdapter.kt b/app/src/main/java/com/example/vishavjit_harika/HistoryListViewAdapter.kt new file mode 100755 index 0000000..995887a --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/HistoryListViewAdapter.kt @@ -0,0 +1,91 @@ +package com.example.vishavjit_harika + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class HistoryListViewAdapter(private val context: Context, private var entryList: List) : BaseAdapter() { + override fun getCount(): Int = entryList.size + + override fun getItem(position: Int): Any = entryList[position] + + override fun getItemId(position: Int): Long = position.toLong() + + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + val container: ViewsContainer + val view: View + + if (convertView == null) { + view = LayoutInflater.from(context).inflate(R.layout.exercise_entry_list_display, parent, false) + container = ViewsContainer(view) + view.tag = container + } else { + view = convertView + container = convertView.tag as ViewsContainer + } + + val entry = getItem(position) as ExerciseEntry + container.entryTypeTextView.text = displayInputType(entry.inputType) + container.exerciseTextView.text = displayActivityType(entry.activityType) + container.dateTimeTextView.text = formatDate(entry.dateTime) + container.distanceTextView.text = String.format("%.2f", entry.distance) + container.durationTextView.text = String.format("%.2f", entry.duration) + + return view + } + + fun replace(newEntryList: List) { + entryList = newEntryList + notifyDataSetChanged() + } + + private class ViewsContainer(view: View) { + val entryTypeTextView: TextView = view.findViewById(R.id.entry_type_textView) + val exerciseTextView: TextView = view.findViewById(R.id.exercise_textView) + val dateTimeTextView: TextView = view.findViewById(R.id.dateTime_textView) + val distanceTextView: TextView = view.findViewById(R.id.distance_textView) + val durationTextView: TextView = view.findViewById(R.id.duration_textView) + } + + fun formatDate(calendar: Calendar): String { + val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) + return formatter.format(calendar.time) + } + + private fun displayInputType(entryInput: Int): String { + var selection: String = "" + when (entryInput) { + 1 -> selection = "Manual Entry" + 2 -> selection = "GPS" + 3 -> selection = "Automatic" + } + return selection + } + + private fun displayActivityType(entryActivity: Int): String { + var selection: String = "" + when (entryActivity) { + 1 -> selection = "Running" + 2 -> selection = "Walking" + 3 -> selection = "Standing" + 4 -> selection = "Cycling" + 5 -> selection = "Hiking" + 6 -> selection = "Downhill Skiing" + 7 -> selection = "Cross-Country Skiing" + 8 -> selection = "Snowboarding" + 9 -> selection = "Skating" + 10 -> selection = "Swimming" + 11 -> selection = "Mountain Biking" + 12 -> selection = "Wheelchair" + 13 -> selection = "Elliptical" + 14 -> selection = "Other" + } + return selection + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/MainActivity.kt b/app/src/main/java/com/example/vishavjit_harika/MainActivity.kt new file mode 100755 index 0000000..686e605 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/MainActivity.kt @@ -0,0 +1,79 @@ +package com.example.vishavjit_harika + +/** + * Resources used to complete MyRuns2: + * 1. For listView and its sub-item: https://stackoverflow.com/questions/7916834/adding-listview-sub-item-text-in-android + * 2. For listView and its sub-item: https://www.geeksforgeeks.org/simpleadapter-in-android-with-example/ + * 3. For fixing issue with Fragment Manager: https://stackoverflow.com/questions/60830741/kotlin-fragmentmanager-is-showing-error-when-called-from-fragment-working-with + * 4. Open URL on the click of a button: https://stackoverflow.com/questions/4930228/open-a-url-on-click-of-ok-button-in-android + * 5. For choosing photo from gallery: https://www.youtube.com/watch?v=poAUbNY2dEs + * 6. For drop down selector: https://stackoverflow.com/questions/55684917/the-spinner-doesnt-work-in-my-kotlin-fragment + * 7. For drop down selector: https://developer.android.com/develop/ui/views/components/spinner + * 8. LayoutKotlin Demo provided by professor: https://canvas.sfu.ca/courses/80625/files/22294116?wrap=1 + * 9. DialogFragmentKotlin Demo provided by professor: https://canvas.sfu.ca/courses/80625/files/22345741?wrap=1 + * 10. ActionTabsKotlin Demo provided by professor: https://canvas.sfu.ca/courses/80625/files/22294112?wrap=1 + * 11. Setting intent function within .xml: https://developer.android.com/guide/components/intents-filters + * 12. List preferences: https://stackoverflow.com/questions/9880841/using-list-preference-in-android + * 13. For basic queries and debugging: ChatGPT (Version 3.5) {https://chat.openai.com/auth/login} + */ + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + +class MainActivity : AppCompatActivity() { + private lateinit var fragmentStart: FragmentStart + private lateinit var fragmentHistory: FragmentHistory + private lateinit var fragmentSettings: FragmentSettings + private lateinit var viewPager2: ViewPager2 + private lateinit var tabLayout: TabLayout + private lateinit var myMyFragmentStateAdapter: MyFragmentStateAdapter + private lateinit var fragments: ArrayList + private val tabTitles = arrayOf("START", "HISTORY", "SETTINGS") //Tab titles + private lateinit var tabConfigurationStrategy: TabLayoutMediator.TabConfigurationStrategy + private lateinit var tabLayoutMediator: TabLayoutMediator + + @SuppressLint("MissingInflatedId") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + viewPager2 = findViewById(R.id.viewpager) + tabLayout = findViewById(R.id.tabContainer) + + fragmentStart = FragmentStart() + fragmentHistory = FragmentHistory() + fragmentSettings = FragmentSettings() + + fragments = java.util.ArrayList() + fragments.add(fragmentStart) + fragments.add(fragmentHistory) + fragments.add(fragmentSettings) + + myMyFragmentStateAdapter = MyFragmentStateAdapter(this, fragments) + viewPager2.adapter = myMyFragmentStateAdapter + + tabConfigurationStrategy = + TabLayoutMediator.TabConfigurationStrategy { tab: TabLayout.Tab, position: Int -> + tab.text = tabTitles[position] + } + tabLayoutMediator = TabLayoutMediator(tabLayout, viewPager2, tabConfigurationStrategy) + tabLayoutMediator.attach() + } + + override fun onDestroy() { + super.onDestroy() + tabLayoutMediator.detach() + } + + fun openManualEntryActivity(v: View?) { + val intent = Intent(this, ManualEntryActivity::class.java) + startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/ManualDataEntry.kt b/app/src/main/java/com/example/vishavjit_harika/ManualDataEntry.kt new file mode 100755 index 0000000..e590837 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/ManualDataEntry.kt @@ -0,0 +1,13 @@ +package com.example.vishavjit_harika + +object ManualDataEntry { + var inputActivityType: Int = 0 + var activityType: Int = 0 + var date: String = "" + var time: String = "" + var duration: Double = 0.0 + var distance: Double = 0.0 + var calories: Double = 0.0 + var heartRate: Double = 0.0 + var comment: String = "" +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/ManualEntryActivity.kt b/app/src/main/java/com/example/vishavjit_harika/ManualEntryActivity.kt new file mode 100755 index 0000000..ca3477d --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/ManualEntryActivity.kt @@ -0,0 +1,199 @@ +package com.example.vishavjit_harika + +import android.app.DatePickerDialog +import android.app.TimePickerDialog +import android.content.Intent +import android.icu.text.SimpleDateFormat +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Button +import android.widget.DatePicker +import android.widget.ListView +import android.widget.TimePicker +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.maps.model.LatLng +import kotlinx.coroutines.launch +import java.text.ParseException +import java.util.Calendar +import java.util.Locale + +/** + * Resources used: + * 1. LayoutKotlin Demo provided by professor + * 2. Dialog Demo provided by professor + */ + +class ManualEntryActivity : AppCompatActivity(), DatePickerDialog.OnDateSetListener, + TimePickerDialog.OnTimeSetListener { + private val workoutStatistics = arrayOf( + "Date", "Time", "Duration", "Distance", "Calories", "Heart Rate", "Comment" + ) + private lateinit var myListView: ListView + private val calendar = Calendar.getInstance() + + val inputData = mutableMapOf() + + private lateinit var database: ExerciseEntryDatabase + private lateinit var databaseDao: ExerciseEntryDatabaseDao + private lateinit var repository: ExerciseEntryRepository + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_manual_entry) + + myListView = this.findViewById(R.id.myListView) + + database = ExerciseEntryDatabase.getInstance(this) + databaseDao = database.exerciseEntryDatabaseDao + repository = ExerciseEntryRepository(databaseDao) + + val arrayAdapter: ArrayAdapter = ArrayAdapter( + this, + android.R.layout.simple_list_item_1, workoutStatistics + ) + myListView.adapter = arrayAdapter + myListView.setOnItemClickListener() { parent: AdapterView<*>, view: View, position: Int, id: Long -> + when (position) { + 0 -> { + val datePickerDialog = DatePickerDialog( + this, + { _, year, monthOfYear, dayOfMonth -> + val selectedDate = Calendar.getInstance() + selectedDate.set(year, monthOfYear, dayOfMonth) + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val dateString = dateFormat.format(selectedDate.time) + ManualDataEntry.date = dateString + Log.i("Date", ManualDataEntry.date) + }, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ) + datePickerDialog.show() + } + + 1 -> { + val timePickerDialog = TimePickerDialog( + this, + { _, hourOfDay, minute -> + val selectedTime = Calendar.getInstance() + selectedTime.set(Calendar.HOUR_OF_DAY, hourOfDay) + selectedTime.set(Calendar.MINUTE, minute) + val timeFormat = SimpleDateFormat("HH:mm", Locale.getDefault()) + val timeString = timeFormat.format(selectedTime.time) + ManualDataEntry.time = timeString + Log.i("Time", ManualDataEntry.time) + }, + calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE), + true + ) + timePickerDialog.show() + } + + 2 -> { + val myDialog = MyDialog() + val bundle = Bundle() + bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.DURATION_DIALOG) + myDialog.arguments = bundle + myDialog.show(supportFragmentManager, "duration dialog") + } + + 3 -> { + val myDialog = MyDialog() + val bundle = Bundle() + bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.DISTANCE_DIALOG) + myDialog.arguments = bundle + myDialog.show(supportFragmentManager, "distance dialog") + } + + 4 -> { + val myDialog = MyDialog() + val bundle = Bundle() + bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.CALORIES_DIALOG) + myDialog.arguments = bundle + myDialog.show(supportFragmentManager, "calories dialog") + } + + 5 -> { + val myDialog = MyDialog() + val bundle = Bundle() + bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.HEART_RATE_DIALOG) + myDialog.arguments = bundle + myDialog.show(supportFragmentManager, "heart rate dialog") + } + + 6 -> { + val myDialog = MyDialog() + val bundle = Bundle() + bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.COMMENT_DIALOG) + myDialog.arguments = bundle + myDialog.show(supportFragmentManager, "comment dialog") + } + } + } + + val saveButton: Button = findViewById(R.id.save_button) + val cancelButton: Button = findViewById(R.id.cancel_button) + + saveButton.setOnClickListener() { + lifecycleScope.launch { + val dateTime: Calendar = + dateTimeConvertor(ManualDataEntry.date, ManualDataEntry.time) + val entry = ExerciseEntry( + inputType = ManualDataEntry.inputActivityType, + activityType = ManualDataEntry.activityType, + dateTime = dateTime, + duration = ManualDataEntry.duration, + distance = ManualDataEntry.distance, + avgPace = 0.0, + avgSpeed = 0.0, + calorie = ManualDataEntry.calories, + climb = 0.0, + heartRate = ManualDataEntry.heartRate, + comment = ManualDataEntry.comment, + locationList = ArrayList() + ) + repository.insert(entry) + finish() + Log.i("Entry", entry.toString()) + } + } + cancelButton.setOnClickListener() { + val intent: Intent = Intent(this, MainActivity::class.java) + startActivity(intent) + Toast.makeText(this, "Entry discarded.", Toast.LENGTH_SHORT).show() + } + } + + override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { + val inputDate = "${year.toString()} / ${month + 1} / $dayOfMonth" + Log.i("date", inputDate); + } + + override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { + val inputTime = "$hourOfDay : $minute" + Log.i("time", inputTime) + } + + private fun dateTimeConvertor(date: String, time: String): Calendar { + val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()) + val dateTime = Calendar.getInstance() + + try { + val parsedDate = dateFormat.parse("$date $time") + parsedDate?.let { + dateTime.time = it + } + } catch (e: ParseException) { + Log.e("ManualEntryActivity", "Parsing datetime error", e) + } + + return dateTime + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/MapsActivity.kt b/app/src/main/java/com/example/vishavjit_harika/MapsActivity.kt new file mode 100755 index 0000000..bd5a102 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/MapsActivity.kt @@ -0,0 +1,469 @@ +package com.example.vishavjit_harika + +import android.Manifest +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.graphics.Color +import android.location.Location +import android.os.Bundle +import android.os.Looper +import android.util.Log +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.widget.Button +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.example.vishavjit_harika.databinding.ActivityMapsBinding +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.LocationServices +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.PolylineOptions +import kotlinx.coroutines.launch +import java.util.Calendar + +class MapsActivity : AppCompatActivity(), OnMapReadyCallback { + + private val binding by lazy { ActivityMapsBinding.inflate(layoutInflater) } + private lateinit var mMap: GoogleMap + private val fusedLocationProviderClient by lazy { + LocationServices.getFusedLocationProviderClient(this) + } + private lateinit var locationRequest: LocationRequest + private val locationList = ArrayList() + + private lateinit var database: ExerciseEntryDatabase + private lateinit var databaseDao: ExerciseEntryDatabaseDao + private lateinit var repository: ExerciseEntryRepository + + private lateinit var saveButton: Button + private lateinit var cancelButton: Button + + private lateinit var startTime: Calendar + + private lateinit var typeTextView: TextView + private lateinit var avgSpeedTextView: TextView + private lateinit var currentSpeedTextView: TextView + private lateinit var climbTextView: TextView + private lateinit var calorieTextView: TextView + private lateinit var distanceTextView: TextView + + private var lastUpdateTime: Long = 0 + + private var totalDistance = 0.0 + private var totalDuration = 0.0 + + private val userWeightInKg = 70 + + private var lastLocation: Location? = null + private var entryKey: Long = -1 + + private lateinit var activityTypeReceiver: BroadcastReceiver + + @SuppressLint("UnspecifiedRegisterReceiverFlag") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + + startTime = Calendar.getInstance() + + (supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment).getMapAsync(this) + + database = ExerciseEntryDatabase.getInstance(this) + databaseDao = database.exerciseEntryDatabaseDao + repository = ExerciseEntryRepository(databaseDao) + + if (hasLocationPermission()) { + startTrackingService() + } else { + requestLocationPermission() + } + createLocationRequest() + + typeTextView = binding.typeTextView + avgSpeedTextView = binding.avgSpeedTextView + currentSpeedTextView = binding.currentSpeedTextView + climbTextView = binding.climbTextView + calorieTextView = binding.calorieTextView + distanceTextView = binding.distanceTextView + + if (ManualDataEntry.inputActivityType == 3) { + activityTypeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val activityType = intent.getIntExtra("activityType", -1) + ManualDataEntry.activityType = activityType + } + } + val filter = IntentFilter("com.example.ACTION_ACTIVITY_TYPE") + LocalBroadcastManager.getInstance(this).registerReceiver(activityTypeReceiver, filter) + } + + saveButton = findViewById(R.id.saveButton) + + saveButton.setOnClickListener { + val endTime = Calendar.getInstance() + val durationInMinutes = (endTime.timeInMillis - startTime.timeInMillis) / 1000 / 60.0 + lifecycleScope.launch { + val entry = ExerciseEntry( + inputType = ManualDataEntry.inputActivityType, + activityType = ManualDataEntry.activityType, + dateTime = startTime, + duration = durationInMinutes, + distance = calculateDistance(), + avgPace = calculateAveragePace(durationInMinutes, calculateDistance()), + avgSpeed = calculateAverageSpeed(durationInMinutes, calculateDistance()), + calorie = calculateCalories(calculateDistance()), + climb = calculateClimb(), + heartRate = 0.0, + comment = "", + locationList = locationList + ) + repository.insert(entry) + finish() + Log.i("Entry", entry.toString()) + } + } + + cancelButton = findViewById(R.id.cancelButton) + + cancelButton.setOnClickListener { + finish() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val inflater: MenuInflater = menuInflater + inflater.inflate(R.menu.delete_menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.delete_button -> { + entryKey = GlobalKey.selectedEntryKey + Log.i("Key", entryKey.toString()) + lifecycleScope.launch { + repository.delete(entryKey) + finish() + } + true + } + + else -> super.onOptionsItemSelected(item) + } + } + + override fun onPause() { + super.onPause() + stopLocationUpdates() + } + + override fun onResume() { + super.onResume() + startLocationUpdates() + } + + override fun onDestroy() { + super.onDestroy() + stopTrackingService() + if (ManualDataEntry.inputActivityType == 3) { + LocalBroadcastManager.getInstance(this).unregisterReceiver(activityTypeReceiver) + } + } + + override fun onMapReady(googleMap: GoogleMap) { + mMap = googleMap + updateLocationUI() + getDeviceLocation() + startLocationUpdates() + } + + private val locationCallback = object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult) { + locationResult ?: return + for (location in locationResult.locations) { + val latLng = LatLng(location.latitude, location.longitude) + locationList.add(latLng) + drawPolyline() + + if (lastLocation != null) { + totalDistance += lastLocation!!.distanceTo(location) // distanceTo gives meters + } + lastLocation = location + + totalDuration = + (Calendar.getInstance().timeInMillis - startTime.timeInMillis) / 1000.0 + + updateRealTimeData(location) + mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng)) + } + } + } + + private fun updateRealTimeData(location: Location) { + val currentSpeed = calculateCurrentSpeed(location) + val avgSpeed = calculateCurrentAverageSpeed() + val distance = calculateDistance() + val calories = calculateCurrentCalories() + val climb = calculateClimb() + + val activityType: String = displayActivityType(ManualDataEntry.activityType) + + typeTextView.text = "Type: ${activityType}" + currentSpeedTextView.text = "Current Speed: ${format(currentSpeed)} m/s" + avgSpeedTextView.text = "Avg Speed: ${format(avgSpeed)} m/s" + distanceTextView.text = "Distance: ${format(distance)} m" + calorieTextView.text = "Calories: ${format(calories)}" + climbTextView.text = "Climb: ${format(climb)} m" + } + + private fun displayActivityType(entryActivity: Int): String { + var selection: String = "" + when (entryActivity) { + 1 -> selection = "Running" + 2 -> selection = "Walking" + 3 -> selection = "Standing" + 4 -> selection = "Cycling" + 5 -> selection = "Hiking" + 6 -> selection = "Downhill Skiing" + 7 -> selection = "Cross-Country Skiing" + 8 -> selection = "Snowboarding" + 9 -> selection = "Skating" + 10 -> selection = "Swimming" + 11 -> selection = "Mountain Biking" + 12 -> selection = "Wheelchair" + 13 -> selection = "Elliptical" + 14 -> selection = "Other" + } + return selection + } + + + private fun drawPolyline() { + val polylineOptions = PolylineOptions() + .addAll(locationList) + .width(5f) + .color(Color.RED) + + mMap.addPolyline(polylineOptions) + } + + private fun updateLocationUI() { + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + mMap.isMyLocationEnabled = true + mMap.uiSettings.isMyLocationButtonEnabled = true + } else { + requestLocationPermission() + } + } + + private fun getDeviceLocation() { + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + fusedLocationProviderClient.lastLocation.addOnCompleteListener(this) { task -> + if (task.isSuccessful && task.result != null) { + val lastKnownLocation = task.result + + val currentLocation = + LatLng(lastKnownLocation.latitude, lastKnownLocation.longitude) + + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLocation, 15f)) + + mMap.addMarker( + MarkerOptions() + .position(currentLocation) + .title("Current Location") + .snippet("This is your last known location") + ) + + } else { + showLocationNotFoundDialog() + } + } + } else { + requestLocationPermission() + } + } + + private fun hasLocationPermission(): Boolean { + return ActivityCompat.checkSelfPermission( + this, Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + + private fun requestLocationPermission() { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + REQUEST_LOCATION_PERMISSION + ) + } + + private fun showLocationNotFoundDialog() { + AlertDialog.Builder(this) + .setTitle("Location Not Found") + .setMessage("Unable to find the current location. Please ensure your GPS is turned on and try again.") + .setPositiveButton("OK", null) + .show() + } + + companion object { + private const val REQUEST_LOCATION_PERMISSION = 1 + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == REQUEST_LOCATION_PERMISSION) { + if ((grantResults.isNotEmpty() && + grantResults[0] == PackageManager.PERMISSION_GRANTED) + ) { + startTrackingService() + updateLocationUI() + getDeviceLocation() + } else { + showPermissionDeniedDialog() + } + } + } + + private fun showPermissionDeniedDialog() { + AlertDialog.Builder(this) + .setTitle("Permission Denied") + .setMessage("Location permission is necessary to access your current location.") + .setPositiveButton("Retry") { _, _ -> requestLocationPermission() } + .setNegativeButton("Cancel", null) + .show() + } + + private fun createLocationRequest() { + locationRequest = LocationRequest.create().apply { + interval = 10000 + fastestInterval = 5000 + priority = LocationRequest.PRIORITY_HIGH_ACCURACY + } + } + + private fun startLocationUpdates() { + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + fusedLocationProviderClient.requestLocationUpdates( + locationRequest, + locationCallback, + Looper.getMainLooper() + ) + } + } + + private fun stopLocationUpdates() { + fusedLocationProviderClient.removeLocationUpdates(locationCallback) + } + + private fun calculateDistance(): Double { + var totalDistance = 0.0 + for (i in 0 until locationList.size - 1) { + val startPoint = locationList[i] + val endPoint = locationList[i + 1] + totalDistance += distanceBetween(startPoint, endPoint) + } + return totalDistance + } + + private fun distanceBetween(startPoint: LatLng, endPoint: LatLng): Double { + val results = FloatArray(1) + Location.distanceBetween( + startPoint.latitude, + startPoint.longitude, + endPoint.latitude, + endPoint.longitude, + results + ) + return results[0].toDouble() + } + + private fun calculateAveragePace(durationInMinutes: Double, distanceInMeters: Double): Double { + return if (distanceInMeters > 0) durationInMinutes / (distanceInMeters / 1000) else 0.0 + } + + private fun calculateAverageSpeed(durationInMinutes: Double, distanceInMeters: Double): Double { + return if (durationInMinutes > 0) (distanceInMeters / 1000) / (durationInMinutes / 60) else 0.0 + } + + private fun calculateCalories(distance: Double): Double { + val distanceInKm = distance / 1000 + return userWeightInKg * distanceInKm + } + + private fun calculateClimb(): Double { + return 0.0 + } + + private fun startTrackingService() { + val serviceIntent = Intent(this, TrackingService::class.java) + ContextCompat.startForegroundService(this, serviceIntent) + } + + private fun stopTrackingService() { + val serviceIntent = Intent(this, TrackingService::class.java) + stopService(serviceIntent) + } + + private fun calculateCurrentSpeed(location: Location): Double { + if (locationList.size < 2) { + lastUpdateTime = System.currentTimeMillis() + return 0.0 + } + + val lastLocation = locationList[locationList.size - 2] + val newLocation = LatLng(location.latitude, location.longitude) + + val distance = distanceBetween(lastLocation, newLocation) // in meters + val currentTime = System.currentTimeMillis() + val timeElapsed = (currentTime - lastUpdateTime) / 1000.0 // in seconds + + lastUpdateTime = currentTime + return if (timeElapsed > 0) distance / timeElapsed else 0.0 // speed in m/s + } + + private fun calculateCurrentAverageSpeed(): Double { + return if (totalDuration > 0) totalDistance / totalDuration else 0.0 + } + + private fun calculateCurrentCalories(): Double { + val distanceInKm = totalDistance / 1000 + return userWeightInKg * distanceInKm + } + + private fun format(value: Double): String { + return String.format("%.2f", value) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/MyDialog.kt b/app/src/main/java/com/example/vishavjit_harika/MyDialog.kt new file mode 100755 index 0000000..d19da18 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/MyDialog.kt @@ -0,0 +1,165 @@ +package com.example.vishavjit_harika + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.Button +import android.widget.EditText +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment + +class MyDialog : DialogFragment(), DialogInterface.OnClickListener{ + companion object{ + const val DIALOG_KEY = "dialog" + const val DURATION_DIALOG = 1 + const val DISTANCE_DIALOG = 2 + const val CALORIES_DIALOG = 3 + const val HEART_RATE_DIALOG = 4 + const val COMMENT_DIALOG = 5 + const val UNIT_PREFERENCE_DIALOG = 6 + const val COMMENTS_SETTINGS_DIALOG = 7 + const val PICK_PICTURE_DIALOG = 8 + } + + lateinit var openCameraButton: Button + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + lateinit var ret: Dialog + val bundle = arguments + when (bundle?.getInt(DIALOG_KEY)) { + DURATION_DIALOG -> { + val builder = AlertDialog.Builder(requireActivity()) + val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_duration_dialog, + null) + builder.setView(view) + builder.setTitle("Duration") + builder.setPositiveButton("OK") {_, _ -> + val durationEditText: EditText = view.findViewById(R.id.duration_editTextNumberDecimal2) + try { + val duration = durationEditText.text.toString().toDouble() + ManualDataEntry.duration = duration + Log.i("Duration", ManualDataEntry.duration.toString()) + } catch (_: NumberFormatException) {} + } + builder.setNegativeButton("CANCEL") {dialog, _ -> + dialog.cancel() + } + ret = builder.create() + } + DISTANCE_DIALOG -> { + val builder = AlertDialog.Builder(requireActivity()) + val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_distance_dialog, + null) + builder.setView(view) + builder.setTitle("Distance") + builder.setPositiveButton("OK") {_, _ -> + val distanceEditText: EditText = view.findViewById(R.id.distance_editTextNumberDecimal2) + try { + val distance = distanceEditText.text.toString().toDouble() + ManualDataEntry.distance = distance + Log.i("Distance", ManualDataEntry.distance.toString()) + } catch (_: NumberFormatException) {} + } + builder.setNegativeButton("CANCEL") {dialog, _ -> + dialog.cancel() + } + ret = builder.create() + } + CALORIES_DIALOG -> { + val builder = AlertDialog.Builder(requireActivity()) + val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_calories_dialog, + null) + builder.setView(view) + builder.setTitle("Calories") + builder.setPositiveButton("OK") {_, _ -> + val caloriesEditText: EditText = view.findViewById(R.id.calories_editTextNumberDecimal2) + try { + val calories = caloriesEditText.text.toString().toDouble() + ManualDataEntry.calories = calories + Log.i("Calories", ManualDataEntry.calories.toString()) + } catch (_: NumberFormatException) {} + } + builder.setNegativeButton("CANCEL") {dialog, _ -> + dialog.cancel() + } + ret = builder.create() + } + HEART_RATE_DIALOG -> { + val builder = AlertDialog.Builder(requireActivity()) + val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_heart_rate_dialog, + null) + builder.setView(view) + builder.setTitle("Heart Rate") + builder.setPositiveButton("OK") {_, _ -> + val heartRateEditText: EditText = view.findViewById(R.id.heart_rate_editTextNumberDecimal2) + try { + val heartRate = heartRateEditText.text.toString().toDouble() + ManualDataEntry.heartRate = heartRate + Log.i("Heart Rate", ManualDataEntry.heartRate.toString()) + } catch (_: NumberFormatException) {} + } + builder.setNegativeButton("CANCEL") {dialog, _ -> + dialog.cancel() + } + ret = builder.create() + } + COMMENT_DIALOG -> { + val builder = AlertDialog.Builder(requireActivity()) + val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_comment_dialog, + null) + builder.setView(view) + builder.setTitle("Comment") + builder.setPositiveButton("OK") {_, _ -> + val commentEditText: EditText = view.findViewById(R.id.comment_editTextText) + val comment = commentEditText.text.toString() + ManualDataEntry.comment = comment + Log.i("Comment", ManualDataEntry.comment) + } + builder.setNegativeButton("CANCEL") {dialog, _ -> + dialog.cancel() + } + ret = builder.create() + } + UNIT_PREFERENCE_DIALOG -> { + val builder = AlertDialog.Builder(requireActivity()) + val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_unit_preference_dialog, + null) + builder.setView(view) + builder.setTitle("Unit Preference") + builder.setNegativeButton("CANCEL", this) + ret = builder.create() + } + COMMENTS_SETTINGS_DIALOG -> { + val builder = AlertDialog.Builder(requireActivity()) + val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_comments_settings_dialog, + null) + builder.setView(view) + builder.setTitle("Comments") + builder.setPositiveButton("OK", this) + builder.setNegativeButton("CANCEL", this) + ret = builder.create() + } + PICK_PICTURE_DIALOG -> { + val builder = AlertDialog.Builder(requireActivity()) + val view: View = requireActivity().layoutInflater.inflate(R.layout.fragment_pick_profile_picture_dialog, + null) + builder.setView(view) + builder.setTitle("Pick Profile Picture") + ret = builder.create() + openCameraButton = view.findViewById(R.id.open_camera_button) + } + } + return ret + } + + override fun onClick(dialog: DialogInterface, item: Int) { + if (item == DialogInterface.BUTTON_POSITIVE) { + Toast.makeText(activity, "ok clicked", Toast.LENGTH_LONG).show() + } else if (item == DialogInterface.BUTTON_NEGATIVE) { + Toast.makeText(activity, "cancel clicked", Toast.LENGTH_LONG).show() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/MyFragmentStateAdapter.kt b/app/src/main/java/com/example/vishavjit_harika/MyFragmentStateAdapter.kt new file mode 100755 index 0000000..206a014 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/MyFragmentStateAdapter.kt @@ -0,0 +1,17 @@ +package com.example.vishavjit_harika + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter + +class MyFragmentStateAdapter(activity: FragmentActivity, var list: ArrayList) + : FragmentStateAdapter(activity){ + + override fun createFragment(position: Int): Fragment { + return list[position] + } + + override fun getItemCount(): Int { + return list.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/MyViewModel.kt b/app/src/main/java/com/example/vishavjit_harika/MyViewModel.kt new file mode 100755 index 0000000..7f6463a --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/MyViewModel.kt @@ -0,0 +1,14 @@ +/** + * Resource used: + * CameraDemoKotlin provided by the professor {https://canvas.sfu.ca/courses/80625/files/22243351?wrap=1} + */ + +package com.example.vishavjit_harika + +import android.graphics.Bitmap +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class MyViewModel: ViewModel() { + val userImage = MutableLiveData() +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/ProfileSettingsActivity.kt b/app/src/main/java/com/example/vishavjit_harika/ProfileSettingsActivity.kt new file mode 100755 index 0000000..9c05a9c --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/ProfileSettingsActivity.kt @@ -0,0 +1,229 @@ +/** + * Resources used: + * 1. For creating menu items: https://developer.android.com/develop/ui/views/components/menus + * 2. For accessing camera and saving the profile photo: CameraDemoKotlin provided by the professor {https://canvas.sfu.ca/courses/80625/pages/the-phone-camera-and-data-storage} and {https://canvas.sfu.ca/courses/80625/files/22243351?wrap=1} + * 3. For saving data: https://developer.android.com/training/data-storage/shared-preferences + * 4. For saving data using shared preferences: https://www.geeksforgeeks.org/shared-preferences-in-android-with-examples/ + * 5. For implementing form validation: https://www.geeksforgeeks.org/implement-form-validation-error-to-edittext-in-android/ + * 6. For implementing email validation: https://www.geeksforgeeks.org/implement-email-validator-in-android/ + * 7. For basic queries and debugging: ChatGPT (Version 3.5) {https://chat.openai.com/auth/login} + * 8. Limit text length of EditText: https://stackoverflow.com/questions/3285412/whats-the-best-way-to-limit-text-length-of-edittext-in-android + */ + +package com.example.vishavjit_harika + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.net.Uri +import android.os.Bundle +import android.provider.MediaStore +import android.util.Patterns +import android.widget.Button +import android.widget.EditText +import android.widget.ImageView +import android.widget.RadioGroup +import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultCallback +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import java.io.File + +/** + * For choosing photo from gallery: https://www.youtube.com/watch?v=poAUbNY2dEs + */ + +@Suppress("NAME_SHADOWING") +class ProfileSettingsActivity : AppCompatActivity() { + // Profile photo + private lateinit var profilePhotoImageView: ImageView + private lateinit var changePhotoButton: Button + private lateinit var galleryButton: Button + private lateinit var currentPhotoUri: Uri + private lateinit var myViewModel: MyViewModel + private lateinit var cameraResult: ActivityResultLauncher + + private val tempImgFileName = "profile_photo.jpg" + + // Input + private lateinit var editTextArray: Array + private lateinit var radioGroup: RadioGroup + private lateinit var saveButton: Button + private lateinit var cancelButton: Button + private lateinit var sharedPreferences: SharedPreferences + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_profile_settings) + + // Profile photo + profilePhotoImageView = findViewById(R.id.profilePhoto_imageView) + changePhotoButton = findViewById(R.id.changePhoto_Button) + galleryButton = findViewById(R.id.gallery_button) + + // Text input + // Initialize EditText fields + editTextArray = arrayOf( + findViewById(R.id.name_editTextText), + findViewById(R.id.email_editTextTextEmailAddress), + findViewById(R.id.editTextPhone), + findViewById(R.id.class_editTextNumber), + findViewById(R.id.major_editTextText) + ) + + radioGroup = findViewById(R.id.gender_radioGroup) + saveButton = findViewById(R.id.save_button) + cancelButton = findViewById(R.id.cancel_button) + sharedPreferences = getSharedPreferences("MyPreferences", Context.MODE_PRIVATE) + + // Load saved text for each editText + for (i in editTextArray.indices) { + val savedText = sharedPreferences.getString("userText$i", "") + editTextArray[i].setText(savedText) + } + + // Load saved radio button selection + val savedRadioSelection = sharedPreferences.getInt("radioSelection", -1) + if (savedRadioSelection != -1) { + radioGroup.check(savedRadioSelection) + } + + saveButton.setOnClickListener { + // Save text from each EditText + val editor = sharedPreferences.edit() + for (i in editTextArray.indices) { + val inputText = editTextArray[i].text.toString() + editor.putString("userText$i", inputText) + } + + // Save radio button selection + val selectedRadioButtonId = radioGroup.checkedRadioButtonId + editor.putInt("radioSelection", selectedRadioButtonId) + + editor.apply() + + if (checkInput()) { + // Show a toast message + Toast.makeText(this, "Data saved", Toast.LENGTH_SHORT).show() + + val intent: Intent = Intent(this, MainActivity::class.java) + startActivity(intent) + } + } + + cancelButton.setOnClickListener { + // Reset input fields to saved data + for (i in editTextArray.indices) { + val savedText = sharedPreferences.getString("userText$i", "") + editTextArray[i].setText(savedText) + } + val savedRadioSelection = sharedPreferences.getInt("radioSelection", -1) + if (savedRadioSelection != -1) { + radioGroup.check(savedRadioSelection) + } + + finish() + } + + // Ask for permission to access camera + Util.checkPermissions(this) + + // Profile Photo + val tempImgFile = File(getExternalFilesDir(null), tempImgFileName) + currentPhotoUri = + FileProvider.getUriForFile(this, "com.example.vishavjit_harika", tempImgFile) + + val galleryImage = registerForActivityResult(ActivityResultContracts.GetContent(), + ActivityResultCallback { + profilePhotoImageView.setImageURI(it) + }) + + galleryButton.setOnClickListener { + galleryImage.launch("image/*") + } + + changePhotoButton.setOnClickListener { +// val myDialog = MyDialog() +// val bundle = Bundle() +// bundle.putInt(MyDialog.DIALOG_KEY, MyDialog.PICK_PICTURE_DIALOG) +// myDialog.arguments = bundle +// myDialog.show(supportFragmentManager, "pick picture dialog") + val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + intent.putExtra(MediaStore.EXTRA_OUTPUT, currentPhotoUri) + cameraResult.launch(intent) + } + + cameraResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) + { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + val bitmap = Util.getBitmap(this, currentPhotoUri) + myViewModel.userImage.value = bitmap + } + } + + myViewModel = ViewModelProvider(this)[MyViewModel::class.java] + myViewModel.userImage.observe(this) { + profilePhotoImageView.setImageBitmap(it) + } + + if (tempImgFile.exists()) { + val bitmap = Util.getBitmap(this, currentPhotoUri) + profilePhotoImageView.setImageBitmap(bitmap) + } + } + + override fun onPause() { + super.onPause() + + // Save data when the app goes into background + val editor = sharedPreferences.edit() + for (i in editTextArray.indices) { + val inputText = editTextArray[i].text.toString() + editor.putString("userText$i", inputText) + } + + val selectedRadioButtonId = radioGroup.checkedRadioButtonId + editor.putInt("radioSelection", selectedRadioButtonId) + + editor.apply() + } + + private fun checkInput(): Boolean { + if (editTextArray[0].length() == 0) { + editTextArray[0].error = "Name is required" + return false + } + if (editTextArray[1].length() == 0) { + editTextArray[1].error = "Email is required" + return false + } + if (!checkEmail()){ + editTextArray[1].error = "Email format incorrect" + return false + } + if (editTextArray[2].length() == 0) { + editTextArray[2].error = "Phone Number is required" + return false + } + if (editTextArray[3].length() == 0) { + editTextArray[3].error = "Class is required" + return false + } + if (editTextArray[4].length() == 0) { + editTextArray[4].error = "Major is required" + return false + } + + return true + } + + private fun checkEmail(): Boolean { + val emailToString = editTextArray[1].text.toString() + return Patterns.EMAIL_ADDRESS.matcher(emailToString).matches() + } +} diff --git a/app/src/main/java/com/example/vishavjit_harika/TrackingService.kt b/app/src/main/java/com/example/vishavjit_harika/TrackingService.kt new file mode 100755 index 0000000..a8b82cf --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/TrackingService.kt @@ -0,0 +1,174 @@ +package com.example.vishavjit_harika + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.os.AsyncTask +import android.os.Build +import android.os.IBinder +import androidx.core.app.NotificationCompat +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import java.util.concurrent.ArrayBlockingQueue + +class TrackingService : Service(), SensorEventListener { + private val NOTIFY_ID = 1 + private val CHANNEL_ID = "tracking_channel" + private lateinit var notificationManager: NotificationManager + private lateinit var stopReceiver: BroadcastReceiver + + private val mFeatLen = Globals.ACCELEROMETER_BLOCK_CAPACITY + 2 + private lateinit var mLabel: String + private lateinit var mAccBuffer: ArrayBlockingQueue + + companion object { + const val STOP_SERVICE_ACTION = "stop service action" + } + + override fun onCreate() { + super.onCreate() + notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + createNotificationChannel() + startForeground(NOTIFY_ID, getNotification()) + + stopReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + stopSelf() + notificationManager.cancel(NOTIFY_ID) + } + } + + val filter = IntentFilter().apply { + addAction(STOP_SERVICE_ACTION) + } + registerReceiver(stopReceiver, filter) + } + + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(stopReceiver) + notificationManager.cancel(NOTIFY_ID) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + return START_STICKY + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val name = getString(R.string.channel_name) + val descriptionText = getString(R.string.channel_description) + val importance = NotificationManager.IMPORTANCE_DEFAULT + val channel = NotificationChannel(CHANNEL_ID, name, importance).apply { + description = descriptionText + } + notificationManager.createNotificationChannel(channel) + } + } + + private fun getNotification(): Notification { + val stopIntent = Intent(this, TrackingService::class.java).apply { + action = STOP_SERVICE_ACTION + } + val stopPendingIntent = PendingIntent.getService( + this, 0, stopIntent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + return NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("Tracking Service") + .setContentText("The app is tracking your location.") + .setSmallIcon(R.drawable.location_on_24px) // Make sure this resource exists + .addAction( + R.drawable.cancel_24px, + "Stop", + stopPendingIntent + ) + .build() + } + + inner class OnSensorChangedTask : AsyncTask() { + override fun doInBackground(vararg arg0: Void?): Void? { + var blockSize = 0 + val fft = FFT(Globals.ACCELEROMETER_BLOCK_CAPACITY) + val accBlock = DoubleArray(Globals.ACCELEROMETER_BLOCK_CAPACITY) + val im = DoubleArray(Globals.ACCELEROMETER_BLOCK_CAPACITY) + var max = Double.MIN_VALUE + var featuresArrayList = ArrayList(Globals.ACCELEROMETER_BLOCK_CAPACITY) + + while (true) { + try { + if (isCancelled() == true) { + return null + } + + accBlock[blockSize++] = mAccBuffer.take().toDouble() + if (blockSize == Globals.ACCELEROMETER_BLOCK_CAPACITY) { + blockSize = 0 + + max = .0 + for (`val` in accBlock) { + if (max < `val`) { + max = `val` + } + } + fft.fft(accBlock, im) + for (i in accBlock.indices) { + val mag = Math.sqrt( + accBlock[i] * accBlock[i] + im[i] + * im[i] + ) + im[i] = .0 + featuresArrayList.add(mag) + } + + val inputType = WekaClassifier.classify(featuresArrayList.toArray()).toInt() + + when (inputType) { + Globals.ACTIVITY_ID_STANDING -> ManualDataEntry.activityType = 3 + Globals.ACTIVITY_ID_WALKING -> ManualDataEntry.activityType = 2 + Globals.ACTIVITY_ID_RUNNING -> ManualDataEntry.activityType = 1 + Globals.ACTIVITY_ID_OTHER -> ManualDataEntry.activityType = 14 + } + val intent = Intent("com.example.ACTION_ACTIVITY_TYPE") + intent.putExtra("activityType", ManualDataEntry.activityType) + LocalBroadcastManager.getInstance(this@TrackingService).sendBroadcast(intent) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } + + override fun onSensorChanged(event: SensorEvent) { + if (event.sensor.type == Sensor.TYPE_LINEAR_ACCELERATION) { + val m = Math.sqrt( + (event.values[0] * event.values[0] + event.values[1] * event.values[1] + (event.values[2] + * event.values[2])).toDouble() + ) + try { + mAccBuffer.add(m) + } catch (e: IllegalStateException) { + + val newBuf = ArrayBlockingQueue(mAccBuffer.size * 2) + mAccBuffer.drainTo(newBuf) + mAccBuffer = newBuf + mAccBuffer.add(m) + } + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} +} diff --git a/app/src/main/java/com/example/vishavjit_harika/Util.kt b/app/src/main/java/com/example/vishavjit_harika/Util.kt new file mode 100755 index 0000000..992e64b --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/Util.kt @@ -0,0 +1,50 @@ +/** + * Resource used: + * CameraDemoKotlin provided by the professor {https://canvas.sfu.ca/courses/80625/files/22243351?wrap=1} + */ + +package com.example.vishavjit_harika + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Matrix +import android.net.Uri +import android.os.Build +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + +object Util { + fun checkPermissions(activity: Activity?) { + if (Build.VERSION.SDK_INT < 23) return + if (ContextCompat.checkSelfPermission( + activity!!, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) != PackageManager.PERMISSION_GRANTED + || ContextCompat.checkSelfPermission( + activity, + Manifest.permission.CAMERA + ) != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + activity, + arrayOf( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.CAMERA + ), + 0 + ) + } + } + + fun getBitmap(context: Context, imgUri: Uri): Bitmap { + var bitmap = BitmapFactory.decodeStream(context.contentResolver.openInputStream(imgUri)) + val matrix = Matrix() + matrix.setRotate(0f) + var ret = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + return ret + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/vishavjit_harika/WekaClassifier.java b/app/src/main/java/com/example/vishavjit_harika/WekaClassifier.java new file mode 100755 index 0000000..09d6615 --- /dev/null +++ b/app/src/main/java/com/example/vishavjit_harika/WekaClassifier.java @@ -0,0 +1,56 @@ +package com.example.vishavjit_harika; + +class WekaClassifier { + public static double classify(Object[] i) + throws Exception { + + double p = Double.NaN; + p = WekaClassifier.N259f42000(i); + return p; + } + static double N259f42000(Object []i) { + double p = Double.NaN; + if (i[0] == null) { + p = 0; + } else if (((Double) i[0]).doubleValue() <= 13.390311) { + p = 0; + } else if (((Double) i[0]).doubleValue() > 13.390311) { + p = WekaClassifier.N6cfb97a81(i); + } + return p; + } + static double N6cfb97a81(Object []i) { + double p = Double.NaN; + if (i[64] == null) { + p = 1; + } else if (((Double) i[64]).doubleValue() <= 14.534508) { + p = WekaClassifier.N1f770cf2(i); + } else if (((Double) i[64]).doubleValue() > 14.534508) { + p = 2; + } + return p; + } + static double N1f770cf2(Object []i) { + double p = Double.NaN; + if (i[4] == null) { + p = 1; + } else if (((Double) i[4]).doubleValue() <= 14.034383) { + p = WekaClassifier.N32ff42d93(i); + } else if (((Double) i[4]).doubleValue() > 14.034383) { + p = 1; + } + return p; + } + static double N32ff42d93(Object []i) { + double p = Double.NaN; + if (i[7] == null) { + p = 1; + } else if (((Double) i[7]).doubleValue() <= 4.804712) { + p = 1; + } else if (((Double) i[7]).doubleValue() > 4.804712) { + p = 2; + } + return p; + } +} + diff --git a/app/src/main/res/drawable-hdpi/icons8_delete_36___.png b/app/src/main/res/drawable-hdpi/icons8_delete_36___.png new file mode 100755 index 0000000000000000000000000000000000000000..6835f881247686c6f0b6f70256b83d38026b9a4f GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBI14-?iy0WWg+Z8+Vb&Z81_nk+ zPZ!6KiaBqu+j}uPO0<3C+}WTq_n@{$j%al5)(``Ml+#a`!lG8C7g+z7aXb?AK0&Ix zg1NOs^HWPBt9AL@_kWK}s7;uqn^yPcjgAk8?`xq6%t2%7=@qb{Fe$@WGCZrS;dF5s+3`Ggabd{f@sd~4CL zO?N>bbB3~{2y>d#8J!4bHKz-+nZD?-*lIeo3Y08nh~;p6zKVO{s$kZP_+yQ>tQp=d zo!8&YlT@+E>~|Es!1}IXJs2Mel(v93Qbkhvl6+*5py1C6wtj(;s z!V3;HnQ;cBIlgAeNOkm15SVcAyxYQtuS{2FdNf2aR%u>vZg|I4BD7#J)6drTjowSF e6Fv(5GE)wjR4x_uJtqR_PzFy|KbLh*2~7Z^`%@FSPcPW=F*-`Lf7IO4x8a?`X<3bzF20*t(-%DDn{&8GFnW@PXW8U*fDZOJ~m-xMB^i2ZHJ~q^^u#NK_U_K|Iz4q);pZ{H{9WzB#FghVgbv*B~asQweW2z z>BN9MKQC`FXdWozUl~aM0(hcxdg)3)3Osl(r?p}3*ML|o)(Xfu{=gGSy8(lafB^|W z4^Vd)U(e2N8xRop4uCt6@d0q*063Qf6dV9Yl7L+Yz>Xwf-2t#BeK)W!l?04B07fJM z{SJUWF+f06498bQ#|OZzA>c*>aAF8J)&LwD0uD3)+lGKG4Zx})U_}EkV+fem01O)f zhBN@ZK+|A+GdoKh5D;$$fLE3A0dQ>qxY7ia3;;z_DIQ~)WG3y(|EYMIG|DN5wRmXoqOaW&=RbY4(I0Yt6#>Zm+tPijohfocn RUm5@a002ovPDHLkV1f%{nlu0a literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/icons8_delete_72___.png b/app/src/main/res/drawable-xxhdpi/icons8_delete_72___.png new file mode 100755 index 0000000000000000000000000000000000000000..ceb2539810c2a2de88f9cb0d28f09b4f09fc337a GIT binary patch literal 563 zcmV-30?hr1P)8y$|c>bHQSWN!bPPmgB%K)vLZA{$|nweYlHN8-v4`hfAhWEdmipRTn^`&rIb=i zX&- z&uq8i90I=6JUeA<8R(z`HTV^KWZeSv0W+NmF1pXa{jN^ZDW#NBa{wE}^QLB?W#U}*9JOdp=-%-y%J>uK*%Dwft1uB5I=-M!E zzzcLOD+HQH*PKG2+vsX31iFr{357rx(KVtF=rpdd5rzn%_bvO$o4yjule_oC9{f%K$%sv!)2R0&F|R z`377zWx$Ef=Bmv~PgJ?ayUs781K#6LDW#N5#&1$(r75^emM#DQ002ovPDHLkV1h$s B^`ig) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/icons8_delete_96___.png b/app/src/main/res/drawable-xxxhdpi/icons8_delete_96___.png new file mode 100755 index 0000000000000000000000000000000000000000..41eab240dd2a62e2b77ef0442eff382274a513a0 GIT binary patch literal 762 zcmVi zJo}xzPy#W+=Ry_Y{&b!u+GU$$pyTJJLeL9DW#NB zN-3ob#`m^UbEui_W54Oy7p=f<)3YmDfo-Pe_GksJ!8)}Z0;JoShIa-4=rsL~q z4NjVlccL{|HXW}-YjDtXJR7aSUeobtv<5p&$Gy=SY&9J>M{95mR^_)k)EX_qS^51G zt-@*feIBjCarwO!t->++9gSAufc&10R$-6)9*S0tJEya|J6eTJ^4l0KOaA5LO0*8& z%j?@{9Zt#X<7ge;kk@O`I=mpS!_hiCA+P<>I@~X>-O)PSA+K%GI$V!S^0_qB7Cpc( z@;M*9z&G+a6TQH@@;Mp3z^n3Ej$Yt7`5cU1;4%5^jb7kB`Rs^Z;1>C8jb4nqkr(Hq<&kEQ4hZji^O z=uwg}_bbsW{Gfl|MX!c=`ucY&dWARj@Ac@_jQ)l+Z_h`sl8i5SBzlD{_*>tPp*Kg* z@V0)Ph~DA0@$VA7xrkdM13Z8~^|kao7@1(_FlRyge(dT6{Qnql`PA^K&p2gqZP97t spYdEi!jrgm(K(e;N-3q3QX;i~0gy&S9`tr&*Z=?k07*qoM6N<$g3K>@%K!iX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/cancel_24px.xml b/app/src/main/res/drawable/cancel_24px.xml new file mode 100755 index 0000000..ea3a291 --- /dev/null +++ b/app/src/main/res/drawable/cancel_24px.xml @@ -0,0 +1,10 @@ + + + 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 100755 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_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100755 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/location_on_24px.xml b/app/src/main/res/drawable/location_on_24px.xml new file mode 100755 index 0000000..871f7ce --- /dev/null +++ b/app/src/main/res/drawable/location_on_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_display_entry.xml b/app/src/main/res/layout/activity_display_entry.xml new file mode 100755 index 0000000..d492f04 --- /dev/null +++ b/app/src/main/res/layout/activity_display_entry.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_display_entry_maps.xml b/app/src/main/res/layout/activity_display_entry_maps.xml new file mode 100755 index 0000000..cb64bab --- /dev/null +++ b/app/src/main/res/layout/activity_display_entry_maps.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100755 index 0000000..b24b15c --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_manual_entry.xml b/app/src/main/res/layout/activity_manual_entry.xml new file mode 100755 index 0000000..568c94d --- /dev/null +++ b/app/src/main/res/layout/activity_manual_entry.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + +