Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding chart to GDH #202

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ release/
dev_release/
debug/
second/
/.kotlin/
11 changes: 6 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.6.1' apply false
id 'com.android.library' version '8.6.1' apply false
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
id 'com.android.application' version '8.8.0' apply false
id 'com.android.library' version '8.8.0' apply false
id 'org.jetbrains.kotlin.android' version '2.1.0' apply false
id 'com.google.devtools.ksp' version "2.1.0-1.0.29" apply false
}

Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())

project.ext.set("versionCode", 98)
project.ext.set("versionName", "1.3")
project.ext.set("versionCode", 99)
project.ext.set("versionName", "1.3.1")
project.ext.set("compileSdk", 34)
project.ext.set("targetSdk", 34)
project.ext.set("minSdk", 26)
Expand Down
9 changes: 9 additions & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp'
}

android {
Expand Down Expand Up @@ -48,10 +49,18 @@ dependencies {
implementation 'com.google.android.gms:play-services-wearable:19.0.0'
implementation 'androidx.work:work-runtime:2.9.1'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.13.12'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
testImplementation 'org.mockito:mockito-core:4.11.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
implementation 'com.google.code.gson:gson:2.10.1'

//Room
def roomVersion = "2.6.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ object Constants {
const val REQUEST_DATA_MESSAGE_PATH = "/request_data_intent"
const val REQUEST_LOGCAT_MESSAGE_PATH = "/request_logcat_intent"
const val LOGCAT_CHANNEL_PATH = "/logcat_intent"
const val DB_SYNC_CHANNEL_PATH = "/db_sync_intent"
const val COMMAND_PATH = "/command_intent"
const val GLUCODATA_BROADCAST_ACTION = "glucodata.Minute"
const val SETTINGS_BUNDLE = "settings_bundle"
Expand Down Expand Up @@ -284,4 +285,9 @@ object Constants {
const val AA_MEDIA_PLAYER_DURATION = "aa_media_player_duration"

const val PATIENT_NAME = "patient_name"

// graph
const val GRAPH_ID = "graph_id"
const val SHARED_PREF_GRAPH_DURATION_WEAR_COMPLICATION = "graph_duration_wear_complication"

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import de.michelinside.glucodatahandler.common.database.dbAccess
import de.michelinside.glucodatahandler.common.notification.AlarmHandler
import de.michelinside.glucodatahandler.common.notification.AlarmNotificationBase
import de.michelinside.glucodatahandler.common.notification.AlarmType
Expand Down Expand Up @@ -64,6 +65,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
var cob: Float = Float.NaN
var iobCobTime: Long = 0
private var lowValue: Float = 70F
val lowRaw: Float get() = lowValue
val low: Float get() {
if(isMmol && lowValue > 0F) // mmol/l
{
Expand All @@ -72,6 +74,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
return lowValue
}
private var highValue: Float = 240F
val highRaw: Float get() = highValue
val high: Float get() {
if(isMmol && highValue > 0F) // mmol/l
{
Expand All @@ -80,6 +83,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
return highValue
}
private var targetMinValue = 90F
val targetMinRaw: Float get() = targetMinValue
val targetMin: Float get() {
if(isMmol) // mmol/l
{
Expand All @@ -88,6 +92,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
return targetMinValue
}
private var targetMaxValue = 165F
val targetMaxRaw: Float get() = targetMaxValue
val targetMax: Float get() {
if(isMmol) // mmol/l
{
Expand Down Expand Up @@ -139,6 +144,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
initialized = true
unitMgDl = context.resources.getString(R.string.unit_mgdl)
unitMmol = context.resources.getString(R.string.unit_mmol)
dbAccess.init(context)
AlarmHandler.initData(context)
readTargets(context)
loadExtras(context)
Expand Down Expand Up @@ -319,6 +325,19 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
return AlarmType.OK
}

fun getValueColor(rawValue: Int): Int {
val customValue = if(isMmol) GlucoDataUtils.mgToMmol(rawValue.toFloat()) else rawValue.toFloat()
if(high>0F && customValue >= high)
return getAlarmTypeColor(AlarmType.VERY_HIGH)
if(low>0F && customValue <= low)
return getAlarmTypeColor(AlarmType.VERY_LOW)
if(targetMin>0F && customValue < targetMin)
return getAlarmTypeColor(AlarmType.LOW)
if(targetMax>0F && customValue > targetMax)
return getAlarmTypeColor(AlarmType.HIGH)
return getAlarmTypeColor(AlarmType.OK)
}

private fun calculateAlarm(): Int {
alarm = 0 // reset to calculate again
val curAlarmType = getAlarmType()
Expand Down Expand Up @@ -541,6 +560,8 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
}
}

dbAccess.addGlucoseValue(time, rawValue)

val notifySource = if(interApp) NotifySource.MESSAGECLIENT else NotifySource.BROADCAST

InternalNotifier.notify(context, notifySource, createExtras()) // re-create extras to have all changed value inside...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.os.Bundle
import android.util.Log
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.*
import de.michelinside.glucodatahandler.common.database.dbSync
import de.michelinside.glucodatahandler.common.notification.AlarmHandler
import de.michelinside.glucodatahandler.common.notification.AlarmNotificationBase
import de.michelinside.glucodatahandler.common.notification.AlarmType
Expand All @@ -29,7 +30,8 @@ enum class Command {
DISABLE_INACTIVE_TIME,
PAUSE_NODE,
RESUME_NODE,
FORCE_UPDATE
FORCE_UPDATE,
DB_SYNC
}

class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityClient.OnCapabilityChangedListener, NotifierInterface {
Expand Down Expand Up @@ -79,7 +81,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
}
else if (addMissing) {
batterLevels.add(-1)
})
} else {})
}
return batterLevels
}
Expand All @@ -102,7 +104,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
}
else if (addMissing) {
connectionStates[getDisplayName(node.value)] = context.getString(R.string.state_await_data)
})
} else {})
}
return connectionStates

Expand Down Expand Up @@ -384,9 +386,10 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
LOG_ID,
dataSource.toString() + " data send to node " + node.toString()
)
noDataSend.remove(node.id)
if(notConnectedNodes.isEmpty())
removeTimer()
if(noDataSend.contains(node.id)) {
noDataSend.remove(node.id)
checkNodeConnect(node.id)
}
}
addOnFailureListener { error ->
if (retryCount < 2) {
Expand All @@ -412,6 +415,20 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
}
}

private fun checkNodeConnect(nodeId: String) {
Log.d(LOG_ID, "check node connect $nodeId")
if(!noDataReceived.contains(nodeId) && !noDataSend.contains(nodeId)) {
Log.i(LOG_ID, "Node with id " + nodeId + " connected!")
if(notConnectedNodes.isEmpty())
removeTimer()
dbSync.requestDbSync(context)
} else if(noDataReceived.contains(nodeId)) {
Log.i(LOG_ID, "Node with id " + nodeId + " still waiting for receiving data!")
} else if(noDataSend.contains(nodeId)) {
Log.i(LOG_ID, "Node with id " + nodeId + " still sending data!")
}
}

fun sendCommand(command: Command, extras: Bundle?) {
// Send command to all nodes in parallel
Log.d(LOG_ID, "sendCommand called for $command with extras: ${Utils.dumpBundle(extras)}")
Expand Down Expand Up @@ -444,9 +461,10 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
try {
Log.i(LOG_ID, "onMessageReceived from " + p0.sourceNodeId + " with path " + p0.path)
checkConnectedNode(p0.sourceNodeId)
noDataReceived.remove(p0.sourceNodeId)
if (notConnectedNodes.isEmpty())
removeTimer()
if(noDataReceived.contains(p0.sourceNodeId)) {
noDataReceived.remove(p0.sourceNodeId)
checkNodeConnect(p0.sourceNodeId)
}
val extras = Utils.bytesToBundle(p0.data)
//Log.v(LOG_ID, "Received extras for path ${p0.path}: ${Utils.dumpBundle(extras)}")
if(extras!= null) {
Expand Down Expand Up @@ -562,6 +580,10 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC

if(p0.path == Constants.REQUEST_DATA_MESSAGE_PATH || forceSend) {
Log.d(LOG_ID, "Data request received from " + p0.sourceNodeId)
if (p0.path == Constants.REQUEST_DATA_MESSAGE_PATH) {
// new data request -> new connection on other side -> reset connection
noDataSend.add(p0.sourceNodeId) // add to trigger db sync after connection established
}
var bundle = ReceiveData.createExtras()
var source = NotifySource.BROADCAST
if( bundle == null && BatteryReceiver.batteryPercentage >= 0) {
Expand All @@ -583,7 +605,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
if(p0.path == Constants.REQUEST_LOGCAT_MESSAGE_PATH) {
sendLogcat(p0.sourceNodeId)
}
if (noDataSend.contains(p0.sourceNodeId)) {
if (p0.path != Constants.REQUEST_DATA_MESSAGE_PATH && noDataSend.contains(p0.sourceNodeId)) {
sendDataRequest()
}
} catch (exc: Exception) {
Expand Down Expand Up @@ -621,6 +643,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
InternalNotifier.notify(context, NotifySource.MESSAGECLIENT, bundle)
}
}
Command.DB_SYNC -> dbSync.sendData(context, nodeId)
}

} catch (exc: Exception) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.michelinside.glucodatahandler.common.chart

import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import android.view.View

class ChartBitmap(val context: Context, durationPref: String = "", width: Int = 1000, height: Int = 0, forComplication: Boolean = false) {

private val LOG_ID = "GDH.Chart.Bitmap"

private var chartViewer: ChartBitmapCreator
private var chart: GlucoseChart = GlucoseChart(context)
init {
val viewHeight = if(height > 0) height else width/3
Log.v(LOG_ID, "init - width: $width - durationPref: $durationPref")
chart.measure (View.MeasureSpec.makeMeasureSpec (width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec (viewHeight, View.MeasureSpec.EXACTLY))
chart.layout (0, 0, chart.getMeasuredWidth(), chart.getMeasuredHeight())

chartViewer = ChartBitmapCreator(chart, context, durationPref, forComplication)
chartViewer.create()
}

fun close() {
Log.v(LOG_ID, "close")
chartViewer.close()
}

fun getBitmap(): Bitmap? {
return chartViewer.getBitmap()
}

val chartId: Int get() = chart.id

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package de.michelinside.glucodatahandler.common.chart

import android.content.Context
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.database.dbAccess
import de.michelinside.glucodatahandler.common.notifier.InternalNotifier
import de.michelinside.glucodatahandler.common.notifier.NotifySource

class ChartBitmapCreator(chart: GlucoseChart, context: Context, durationPref: String = "", val forComplication: Boolean = false): ChartCreator(chart, context, durationPref) {
private val LOG_ID = "GDH.Chart.BitmapCreator"
private var bitmap: Bitmap? = null
override val resetChart = true
override val circleRadius = 3F

override fun initXaxis() {
Log.v(LOG_ID, "initXaxis")
chart.xAxis.isEnabled = false
}

override fun initYaxis() {
Log.v(LOG_ID, "initYaxis")
chart.axisRight.setDrawAxisLine(false)
chart.axisRight.setDrawLabels(false)
chart.axisRight.setDrawZeroLine(false)
chart.axisRight.setDrawGridLines(false)
chart.axisLeft.isEnabled = false
}

override fun initChart(touchEnabled: Boolean) {
super.initChart(false)
chart.isDrawingCacheEnabled = false
}

override fun getDefaultRange(): Long {
if(durationPref.isNotEmpty())
return super.getDefaultRange()
return 120L
}

override fun getMaxRange(): Long {
return getDefaultRange()
}

override fun getDefaultMaxValue(): Float {
if(forComplication)
return maxOf(super.getDefaultMaxValue(), 310F)
return super.getDefaultMaxValue()
}

override fun updateChart(dataSet: LineDataSet) {
Log.v(LOG_ID, "Update chart for ${dataSet.values.size} entries and ${dataSet.circleColors.size} colors")
if(dataSet.values.isNotEmpty())
chart.data = LineData(dataSet)
else
chart.data = LineData()
addEmptyTimeData()
chart.notifyDataSetChanged()
bitmap = null // reset
chart.postInvalidate()
InternalNotifier.notify(context, NotifySource.GRAPH_CHANGED, Bundle().apply { putInt(Constants.GRAPH_ID, chart.id) })
}

override fun updateTimeElapsed() {
update(dbAccess.getGlucoseValues(getMinTime()))
}

fun getBitmap(): Bitmap? {
if(bitmap == null)
bitmap = createBitmap()
return bitmap
}
}
Loading