Commit 47da2420 authored by sivl0509's avatar sivl0509
Browse files

StayFitApplication full impl

parent 8ed0f85b
*.iml
.gradle
.idea
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/build
\ No newline at end of file
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.example.stayfitapplication"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments += [
"room.schemaLocation":"$projectDir/schemas".toString(),
"room.incremental":"true",
"room.expandProjection":"true"]
}
}
}
buildFeatures {
viewBinding true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
repositories {
maven { url "https://jitpack.io" }
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.recyclerview:recyclerview:1.2.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$rootProject.navigation_version"
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
}
\ No newline at end of file
# 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
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "dd9358e903cb0055d6ae480fd9f3a74b",
"entities": [
{
"tableName": "session_table",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `start_time` TEXT NOT NULL, `end_time` TEXT NOT NULL, `bpm` TEXT NOT NULL, `step_count` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "startTime",
"columnName": "start_time",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "endTime",
"columnName": "end_time",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "bpm",
"columnName": "bpm",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "stepCount",
"columnName": "step_count",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'dd9358e903cb0055d6ae480fd9f3a74b')"
]
}
}
\ No newline at end of file
package com.example.stayfitapplication
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.stayfitapplication", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.stayfitapplication">
<uses-permission android:name="android.permission.BODY_SENSORS" />
<application
android:name=".app.StayFitApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:screenOrientation="portrait"
android:supportsRtl="true"
android:theme="@style/Theme.StayFitApplication">
<activity android:name=".view.SessionDetailActivity">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".view.SessionActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".view.SessionListActivity">
<intent-filter>
<action android:name="android.intent.action.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
package com.example.stayfitapplication.app
import android.app.Application
import com.example.stayfitapplication.model.repository.SessionRepositoryImpl
import com.example.stayfitapplication.model.repository.SessionDatabase
import com.example.stayfitapplication.model.repository.SessionRepository
class StayFitApplication : Application() {
companion object {
private lateinit var instance: StayFitApplication
private val database: SessionDatabase by lazy {
SessionDatabase.buildDatabase(instance)
}
val repository: SessionRepository by lazy {
SessionRepositoryImpl(database.sessionDao())
}
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
\ No newline at end of file
package com.example.stayfitapplication.app
import androidx.room.TypeConverter
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
fun Date.toDateTimeStringFormatter(pattern: String = "dd.MM.yyyy HH:mm:ss"): String {
return SimpleDateFormat(pattern).format(this)
}
class BpmConverter {
@TypeConverter
fun bpmArrToStr(bpmArr: ArrayList<Int>): String {
var bpmStr = ""
for (bpm in bpmArr) {
if (bpmStr.isNotEmpty()) {
bpmStr += ","
}
bpmStr += bpm.toString()
}
return bpmStr
}
@TypeConverter
fun bpmStrToArr(bpmStr: String): ArrayList<Int> {
val bpmArr: ArrayList<Int> = ArrayList()
val bpmStrArr = bpmStr.split(",").toTypedArray()
for (bpm in bpmStrArr) {
bpmArr.add(if (bpm.isNotEmpty()) bpm.toInt() else 0 )
}
return bpmArr
}
}
package com.example.stayfitapplication.model
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.formatter.IValueFormatter
import com.github.mikephil.charting.utils.ViewPortHandler
class LineChartValueFormatter : IValueFormatter {
override fun getFormattedValue(
value: Float,
entry: Entry?,
dataSetIndex: Int,
viewPortHandler: ViewPortHandler?
): String {
return ""
}
}
\ No newline at end of file
package com.example.stayfitapplication.model
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
class Sensors : SensorEventListener {
private lateinit var mSensorManager: SensorManager
private var mHeartRateSensor: Sensor? = null
private var mStepCounterSensor: Sensor? = null
private var mStepDetectorSensor: Sensor? = null
private lateinit var mSensorDataListener: SensorDataListener
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// do nothing
}
override fun onSensorChanged(event: SensorEvent?) {
if (event == null) {
return
}
if (event.sensor.type == Sensor.TYPE_HEART_RATE) {
if (event.accuracy == SensorManager.SENSOR_STATUS_NO_CONTACT) {
mSensorDataListener.sensorHeartRateError("Heart rate sensor not in contact with user.")
} else if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
mSensorDataListener.sensorHeartRateError("Heart rate sensor can not read values.")
} else {
mSensorDataListener.heartRateValueChanged(event.values[0].toInt())
}
} else if (event.sensor.type == Sensor.TYPE_STEP_COUNTER) {
mSensorDataListener.stepCounterValueChanged(event.values[0].toInt())
} else if (event.sensor.type == Sensor.TYPE_STEP_DETECTOR) {
mSensorDataListener.stepDetected()
}
}
fun createSensor(context: Context, sensorDataListener: SensorDataListener) {
mSensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
mHeartRateSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE)
mStepCounterSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
mStepDetectorSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)
mSensorDataListener = sensorDataListener
}
fun start() {
mHeartRateSensor?.also { heartRate ->
mSensorManager.registerListener(
this,
heartRate,
SensorManager.SENSOR_DELAY_NORMAL,
SensorManager.SENSOR_DELAY_FASTEST
)
}
mStepCounterSensor?.also { stepCounter ->
mSensorManager.registerListener(
this,
stepCounter,
SensorManager.SENSOR_DELAY_NORMAL,
SensorManager.SENSOR_DELAY_FASTEST
)
}
mStepDetectorSensor?.also { stepDetector ->
mSensorManager.registerListener(
this,
stepDetector,
SensorManager.SENSOR_DELAY_NORMAL,
SensorManager.SENSOR_DELAY_FASTEST
)
}
}
fun stop() {
mSensorManager.unregisterListener(this, mHeartRateSensor)
mSensorManager.unregisterListener(this, mStepCounterSensor)
mSensorManager.unregisterListener(this, mStepDetectorSensor)
}
interface SensorDataListener {
fun heartRateValueChanged(bpm: Int)
fun stepCounterValueChanged(stepCount: Int)
fun stepDetected();
fun sensorHeartRateError(msg: String)
}
}
\ No newline at end of file
package com.example.stayfitapplication.model
import androidx.annotation.NonNull
import androidx.room.*
@Entity(tableName = "session_table")
data class Session(
@PrimaryKey(autoGenerate = true) @NonNull val uid: Int,
@ColumnInfo(name = "start_time") val startTime: String,
@ColumnInfo(name = "end_time") val endTime: String,
@ColumnInfo(name = "bpm") val bpm: ArrayList<Int>,
@ColumnInfo(name = "step_count") val stepCount: Int
) {
fun getAvgBpm(): Int {
if (bpm.size < 1) {
return 0
}
var sumBpm: Int = 0
for (item in bpm) {
sumBpm += item
}
return sumBpm / bpm.size
}
}
package com.example.stayfitapplication.model.repository
import androidx.room.*
import com.example.stayfitapplication.model.Session
@Dao
interface SessionDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(session: Session)
@Delete
suspend fun delete(vararg session: Session)
@Query("SELECT * FROM session_table WHERE uid IN (:sessionId)")
fun getSessionById(sessionId: Int): Session
@Query("SELECT * FROM session_table")
suspend fun getAllSessions(): List<Session>
}
\ No newline at end of file
package com.example.stayfitapplication.model.repository
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.example.stayfitapplication.app.BpmConverter
import com.example.stayfitapplication.model.Session
@Database(entities = [Session::class], version = 1)
@TypeConverters(BpmConverter::class)
abstract class SessionDatabase : RoomDatabase() {
companion object {
private const val DATABASE_NAME = "session_database"
fun buildDatabase(context: Context): SessionDatabase {
return Room.databaseBuilder(
context,
SessionDatabase::class.java,
DATABASE_NAME
).allowMainThreadQueries().build()
}
}
abstract fun sessionDao(): SessionDao
}
\ No newline at end of file
package com.example.stayfitapplication.model.repository
import com.example.stayfitapplication.model.Session
interface SessionRepository {
suspend fun insert(session: Session)
suspend fun delete(vararg session: Session)
fun getSessionById(sessionId: Int): Session?
suspend fun getAllSessions(): List<Session>
}
\ No newline at end of file
package com.example.stayfitapplication.model.repository
import com.example.stayfitapplication.model.Session
class SessionRepositoryImpl(private val sessionDao: SessionDao) : SessionRepository {
override suspend fun insert(session: Session) {
sessionDao.insert(session)
}
override suspend fun delete(vararg session: Session) {
sessionDao.delete(*session)
}
override fun getSessionById(sessionId: Int): Session? {
return sessionDao.getSessionById(sessionId)
}
override suspend fun getAllSessions(): List<Session> = sessionDao.getAllSessions()
}
\ No newline at end of file
package com.example.stayfitapplication.view
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.example.stayfitapplication.R
import com.example.stayfitapplication.databinding.SessionActivityBinding
import com.example.stayfitapplication.viewmodel.SessionViewModel
import com.example.stayfitapplication.viewmodel.SessionViewModelFactory
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.launch
class SessionActivity : AppCompatActivity() {
private lateinit var mBinding: SessionActivityBinding
private lateinit var mViewModel: SessionViewModel
private val mSensorPermissionRequestCode = 0
private val mBtnStartSessionId = "Start"
private val mBtnStopSessionId = "Stop"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = SessionActivityBinding.inflate(layoutInflater)
setContentView(mBinding.root)
mViewModel =
ViewModelProvider(this, SessionViewModelFactory(this)).get(SessionViewModel::class.java)
setupBottomNavigation()
checkSensorPermissions()
initUiElements()
}
override fun onDestroy() {
mViewModel.stopSensors()
super.onDestroy()
}
private fun setupBottomNavigation() {
val bottomNavigationView: BottomNavigationView = findViewById(R.id.bottom_navigation_menu)