Skip to content
This repository has been archived by the owner on Apr 20, 2020. It is now read-only.

Commit

Permalink
added data export dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
DorianScholz committed Jun 2, 2017
1 parent f6375e6 commit 88df88a
Show file tree
Hide file tree
Showing 12 changed files with 513 additions and 32 deletions.
5 changes: 3 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
apply plugin: "com.android.application"
apply plugin: "kotlin-android"
apply plugin: 'kotlin-android-extensions'
apply plugin: "realm-android"

android {
Expand All @@ -9,8 +10,8 @@ android {
applicationId "de.dorianscholz.openlibre"
minSdkVersion 16
targetSdkVersion 25
versionCode 4
versionName "0.2.2"
versionCode 5
versionName "0.2.3"
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
project.ext.set("archivesBaseName", defaultConfig.applicationId + "-" + defaultConfig.versionName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,8 @@ public String getId() {
public String getTagId() {
return tagId;
}

public byte[] getData() {
return data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ private static boolean listsStartEqual(List<Integer> l1, List<GlucoseData> l2) {
return true;
}

public String getId() {
return id;
}

public long getDate() {
return date;
}
Expand Down
240 changes: 240 additions & 0 deletions app/src/main/java/de/dorianscholz/openlibre/service/ExportTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
package de.dorianscholz.openlibre.service

import android.util.Log
import com.google.gson.Gson
import com.google.gson.stream.JsonWriter
import de.dorianscholz.openlibre.OpenLibre.*
import de.dorianscholz.openlibre.model.GlucoseData
import de.dorianscholz.openlibre.model.RawTagData
import de.dorianscholz.openlibre.model.ReadingData
import de.dorianscholz.openlibre.model.ReadingData.numHistoryValues
import de.dorianscholz.openlibre.model.ReadingData.numTrendValues
import de.dorianscholz.openlibre.ui.ExportFragment
import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.Sort
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.lang.Math.abs
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit

object ExportTask {

private val LOG_ID = "OpenLibre::" + ExportTask::class.java.simpleName

private val finishedCallbacks = ArrayList<() -> Unit>()
private val progressCallbacks = ArrayList<(Double, Date?) -> Unit>()

var isRunning = false
var isCancelled = false

fun registerCallbacks(finished: () -> Unit, progress: (Double, Date?) -> Unit) {
finishedCallbacks += finished
progressCallbacks += progress
}

fun unregisterCallbacks(finished: () -> Unit, progress: (Double, Date?) -> Unit) {
finishedCallbacks -= finished
progressCallbacks -= progress
}

private fun notifyProgress(progress: Double, date: Date?) {
progressCallbacks.forEach { it(progress, date) }
}

private fun notifyFinished() {
finishedCallbacks.forEach { it() }
}

fun exportDataAsync(dataType: ExportFragment.DataTypes, outputFormat: ExportFragment.OutputFormats) = async(UI) {
try {
isRunning = true
isCancelled = false
val job = async(CommonPool) {
when (dataType) {
ExportFragment.DataTypes.RAW ->
Realm.getInstance(realmConfigRawData).use { realm ->
exportEntries<RawTagData>("raw-data", outputFormat, realm,
realm.where(RawTagData::class.java)
.findAllSorted(RawTagData.DATE, Sort.ASCENDING))
}

ExportFragment.DataTypes.READING ->
Realm.getInstance(realmConfigProcessedData).use { realm ->
exportEntries<ReadingData>("decoded-data", outputFormat, realm,
realm.where(ReadingData::class.java)
.findAllSorted(ReadingData.DATE, Sort.ASCENDING))
}

ExportFragment.DataTypes.GLUCOSE ->
Realm.getInstance(realmConfigProcessedData).use { realm ->
exportEntries<GlucoseData>("glucose-data", outputFormat, realm,
realm.where(GlucoseData::class.java)
.equalTo(GlucoseData.IS_TREND_DATA, false)
.findAllSorted(GlucoseData.DATE, Sort.ASCENDING))
}
}
}
job.await()
}
catch (e: Exception) {
Log.e(LOG_ID, "Error in exportDataAsync: " + e.toString())
}
finally {
isRunning = false
notifyFinished()
}
}

inline private suspend fun <reified T: RealmObject> exportEntries(
dataType: String, outputFormat: ExportFragment.OutputFormats, realm: Realm, realmResults: RealmResults<T>) {
when (outputFormat) {
ExportFragment.OutputFormats.JSON -> exportEntriesJson(dataType, realm, realmResults)
ExportFragment.OutputFormats.CSV -> exportEntriesCsv(dataType, realmResults)
}
}

inline private suspend fun <reified T: RealmObject> exportEntriesJson(dataType: String, realm: Realm, realmResults: RealmResults<T>) {
notifyProgress(0.0, null)
try {
val jsonFile = File(openLibreDataPath, "openlibre-export-%s.json".format(dataType))
val writer = JsonWriter(FileWriter(jsonFile))
writer.setIndent(" ")
writer.beginObject()
writer.name(T::class.java.simpleName)
writer.beginArray()

val gson = Gson()
var count = 0
for (data in realmResults) {
gson.toJson(realm.copyFromRealm(data), T::class.java, writer)

count++
if (count % 10 == 0) {
val progress = count.toDouble() / realmResults.size
// this is an ugly way to resolve date, but there is no common base class, due to missing support in Realm
var date: Date? = null
if (data is GlucoseData) {
date = Date(data.date)
} else if (data is ReadingData) {
date = Date(data.date)
} else if (data is RawTagData) {
date = Date(data.date)
}
notifyProgress(progress, date)
}
if (isCancelled) break
}

writer.endArray()
writer.endObject()
writer.flush()
writer.close()

} catch (e: IOException) {
Log.e(LOG_ID, "exportEntriesJson: error: " + e.toString())
e.printStackTrace()
}
}

inline private suspend fun <reified T: RealmObject> exportEntriesCsv(dataType: String, realmResults: RealmResults<T>) {
notifyProgress(0.0, null)
if (realmResults.size == 0) {
return
}
var csvSeparator = ','
if (java.text.DecimalFormatSymbols.getInstance().decimalSeparator == csvSeparator) {
csvSeparator = ';'
}
try {
File(openLibreDataPath, "openlibre-export-%s.csv".format(dataType)).printWriter().use { csvFile ->
var headerList = listOf("id", "timezone", "date")
if (realmResults[0] is GlucoseData) {
headerList += listOf("glucose [%s]".format(GlucoseData.getDisplayUnit()))

} else if (realmResults[0] is ReadingData) {
headerList += listOf("ageInSensorMinutes")
headerList += (1..numTrendValues).map{ "trend %d [%s]".format(it, GlucoseData.getDisplayUnit()) }
headerList += (1..numHistoryValues).map{ "history %d [%s]".format(it, GlucoseData.getDisplayUnit()) }

} else if (realmResults[0] is RawTagData) {
headerList += listOf("rawDataHex")
}
csvFile.println(headerList.joinToString(csvSeparator.toString()))

var count = 0
for (data in realmResults) {

var date: Date? = null
var dataList = emptyList<Any>()
if (data is GlucoseData) {
date = Date(data.date)

dataList += data.id
dataList += getTimezoneName(data.timezoneOffsetInMinutes)
dataList += formatDateTimeWithoutTimezone(date, data.timezoneOffsetInMinutes)

dataList += data.glucose()

} else if (data is ReadingData) {
date = Date(data.date)

dataList += data.id
dataList += getTimezoneName(data.timezoneOffsetInMinutes)
dataList += formatDateTimeWithoutTimezone(date, data.timezoneOffsetInMinutes)

dataList += data.sensorAgeInMinutes
dataList += data.trend.map { it.glucose() }
dataList += DoubleArray(numTrendValues - data.trend.size).asList()
dataList += data.history.map { it.glucose() }
dataList += DoubleArray(numHistoryValues - data.history.size).asList()

} else if (data is RawTagData) {
date = Date(data.date)

dataList += data.id
dataList += getTimezoneName(data.timezoneOffsetInMinutes)
dataList += formatDateTimeWithoutTimezone(date, data.timezoneOffsetInMinutes)

dataList += data.data.map{ "%02x".format(it) }.joinToString("")
}
csvFile.println(dataList.joinToString(csvSeparator.toString()))

count++
if (count % 10 == 0) {
val progress = count.toDouble() / realmResults.size
notifyProgress(progress, date)
}
if (isCancelled) break
}
}
} catch (e: IOException) {
Log.e(LOG_ID, "exportEntriesJson: error: " + e.toString())
e.printStackTrace()
}
}

private fun formatDateTimeWithoutTimezone(date: Date, timezoneOffsetInMinutes: Int): String {
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
df.timeZone = TimeZone.getTimeZone(getTimezoneName(timezoneOffsetInMinutes))
return df.format(date)
}

private fun getTimezoneName(timezoneOffsetInMinutes: Int): String {
val offsetSign = "%+d".format(timezoneOffsetInMinutes)[0]

val of = SimpleDateFormat("HH:mm", Locale.US)
of.timeZone = TimeZone.getTimeZone("UTC")
val timezoneOffsetString = of.format(Date(TimeUnit.MINUTES.toMillis(abs(timezoneOffsetInMinutes).toLong())))

return "GMT" + offsetSign + timezoneOffsetString
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,7 @@ public String getFormattedValue(float value, AxisBase axis) {
updateTargetArea();

try {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
mPlot.setHardwareAccelerationEnabled(false);
} else {
mPlot.setHardwareAccelerationEnabled(true);
}
mPlot.setHardwareAccelerationEnabled(true);
} catch (Exception e) {
Log.d(LOG_ID, "Hardware acceleration for data plot failed: " + e.toString());
}
Expand Down Expand Up @@ -269,13 +265,13 @@ public void showMultipleScans(List<ReadingData> readingDataList) {
((TextView) mDataPlotView.findViewById(R.id.tv_plot_date)).setText("");
}

void showHistory(List<GlucoseData> history, List<GlucoseData> trend) {
void showHistory(List<GlucoseData> history) {
updateTargetArea();
mPlot.clear();
mDataPlotView.findViewById(R.id.scan_progress).setVisibility(View.INVISIBLE);
mDataPlotView.findViewById(R.id.scan_view).setVisibility(View.VISIBLE);

updatePlot(history, trend);
updatePlot(history, null);
}

void showScan(ReadingData readData) {
Expand All @@ -299,11 +295,9 @@ private void updateScanData(List<GlucoseData> trend) {
GlucoseData currentGlucose = trend.get(trend.size() - 1);
TextView tv_currentGlucose = (TextView) mDataPlotView.findViewById(R.id.tv_glucose_current_value);
tv_currentGlucose.setText(
getResources().getString(R.string.glucose_current_value) +
": " +
String.valueOf(currentGlucose.glucoseString()) +
" " +
getDisplayUnit()
String.format(getResources().getString(R.string.glucose_current_value),
currentGlucose.glucoseString(),
getDisplayUnit())
);

PredictionData predictedGlucose = new PredictionData(trend);
Expand Down
Loading

0 comments on commit 88df88a

Please sign in to comment.