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

Commit 88df88a

Browse files
committed
added data export dialog
1 parent f6375e6 commit 88df88a

File tree

12 files changed

+513
-32
lines changed

12 files changed

+513
-32
lines changed

app/build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
apply plugin: "com.android.application"
22
apply plugin: "kotlin-android"
3+
apply plugin: 'kotlin-android-extensions'
34
apply plugin: "realm-android"
45

56
android {
@@ -9,8 +10,8 @@ android {
910
applicationId "de.dorianscholz.openlibre"
1011
minSdkVersion 16
1112
targetSdkVersion 25
12-
versionCode 4
13-
versionName "0.2.2"
13+
versionCode 5
14+
versionName "0.2.3"
1415
vectorDrawables.useSupportLibrary = true
1516
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
1617
project.ext.set("archivesBaseName", defaultConfig.applicationId + "-" + defaultConfig.versionName);

app/src/main/java/de/dorianscholz/openlibre/model/RawTagData.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,8 @@ public String getId() {
107107
public String getTagId() {
108108
return tagId;
109109
}
110+
111+
public byte[] getData() {
112+
return data;
113+
}
110114
}

app/src/main/java/de/dorianscholz/openlibre/model/ReadingData.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ private static boolean listsStartEqual(List<Integer> l1, List<GlucoseData> l2) {
220220
return true;
221221
}
222222

223+
public String getId() {
224+
return id;
225+
}
226+
223227
public long getDate() {
224228
return date;
225229
}
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
package de.dorianscholz.openlibre.service
2+
3+
import android.util.Log
4+
import com.google.gson.Gson
5+
import com.google.gson.stream.JsonWriter
6+
import de.dorianscholz.openlibre.OpenLibre.*
7+
import de.dorianscholz.openlibre.model.GlucoseData
8+
import de.dorianscholz.openlibre.model.RawTagData
9+
import de.dorianscholz.openlibre.model.ReadingData
10+
import de.dorianscholz.openlibre.model.ReadingData.numHistoryValues
11+
import de.dorianscholz.openlibre.model.ReadingData.numTrendValues
12+
import de.dorianscholz.openlibre.ui.ExportFragment
13+
import io.realm.Realm
14+
import io.realm.RealmObject
15+
import io.realm.RealmResults
16+
import io.realm.Sort
17+
import kotlinx.coroutines.experimental.CommonPool
18+
import kotlinx.coroutines.experimental.android.UI
19+
import kotlinx.coroutines.experimental.async
20+
import java.io.File
21+
import java.io.FileWriter
22+
import java.io.IOException
23+
import java.lang.Math.abs
24+
import java.text.SimpleDateFormat
25+
import java.util.*
26+
import java.util.concurrent.TimeUnit
27+
28+
object ExportTask {
29+
30+
private val LOG_ID = "OpenLibre::" + ExportTask::class.java.simpleName
31+
32+
private val finishedCallbacks = ArrayList<() -> Unit>()
33+
private val progressCallbacks = ArrayList<(Double, Date?) -> Unit>()
34+
35+
var isRunning = false
36+
var isCancelled = false
37+
38+
fun registerCallbacks(finished: () -> Unit, progress: (Double, Date?) -> Unit) {
39+
finishedCallbacks += finished
40+
progressCallbacks += progress
41+
}
42+
43+
fun unregisterCallbacks(finished: () -> Unit, progress: (Double, Date?) -> Unit) {
44+
finishedCallbacks -= finished
45+
progressCallbacks -= progress
46+
}
47+
48+
private fun notifyProgress(progress: Double, date: Date?) {
49+
progressCallbacks.forEach { it(progress, date) }
50+
}
51+
52+
private fun notifyFinished() {
53+
finishedCallbacks.forEach { it() }
54+
}
55+
56+
fun exportDataAsync(dataType: ExportFragment.DataTypes, outputFormat: ExportFragment.OutputFormats) = async(UI) {
57+
try {
58+
isRunning = true
59+
isCancelled = false
60+
val job = async(CommonPool) {
61+
when (dataType) {
62+
ExportFragment.DataTypes.RAW ->
63+
Realm.getInstance(realmConfigRawData).use { realm ->
64+
exportEntries<RawTagData>("raw-data", outputFormat, realm,
65+
realm.where(RawTagData::class.java)
66+
.findAllSorted(RawTagData.DATE, Sort.ASCENDING))
67+
}
68+
69+
ExportFragment.DataTypes.READING ->
70+
Realm.getInstance(realmConfigProcessedData).use { realm ->
71+
exportEntries<ReadingData>("decoded-data", outputFormat, realm,
72+
realm.where(ReadingData::class.java)
73+
.findAllSorted(ReadingData.DATE, Sort.ASCENDING))
74+
}
75+
76+
ExportFragment.DataTypes.GLUCOSE ->
77+
Realm.getInstance(realmConfigProcessedData).use { realm ->
78+
exportEntries<GlucoseData>("glucose-data", outputFormat, realm,
79+
realm.where(GlucoseData::class.java)
80+
.equalTo(GlucoseData.IS_TREND_DATA, false)
81+
.findAllSorted(GlucoseData.DATE, Sort.ASCENDING))
82+
}
83+
}
84+
}
85+
job.await()
86+
}
87+
catch (e: Exception) {
88+
Log.e(LOG_ID, "Error in exportDataAsync: " + e.toString())
89+
}
90+
finally {
91+
isRunning = false
92+
notifyFinished()
93+
}
94+
}
95+
96+
inline private suspend fun <reified T: RealmObject> exportEntries(
97+
dataType: String, outputFormat: ExportFragment.OutputFormats, realm: Realm, realmResults: RealmResults<T>) {
98+
when (outputFormat) {
99+
ExportFragment.OutputFormats.JSON -> exportEntriesJson(dataType, realm, realmResults)
100+
ExportFragment.OutputFormats.CSV -> exportEntriesCsv(dataType, realmResults)
101+
}
102+
}
103+
104+
inline private suspend fun <reified T: RealmObject> exportEntriesJson(dataType: String, realm: Realm, realmResults: RealmResults<T>) {
105+
notifyProgress(0.0, null)
106+
try {
107+
val jsonFile = File(openLibreDataPath, "openlibre-export-%s.json".format(dataType))
108+
val writer = JsonWriter(FileWriter(jsonFile))
109+
writer.setIndent(" ")
110+
writer.beginObject()
111+
writer.name(T::class.java.simpleName)
112+
writer.beginArray()
113+
114+
val gson = Gson()
115+
var count = 0
116+
for (data in realmResults) {
117+
gson.toJson(realm.copyFromRealm(data), T::class.java, writer)
118+
119+
count++
120+
if (count % 10 == 0) {
121+
val progress = count.toDouble() / realmResults.size
122+
// this is an ugly way to resolve date, but there is no common base class, due to missing support in Realm
123+
var date: Date? = null
124+
if (data is GlucoseData) {
125+
date = Date(data.date)
126+
} else if (data is ReadingData) {
127+
date = Date(data.date)
128+
} else if (data is RawTagData) {
129+
date = Date(data.date)
130+
}
131+
notifyProgress(progress, date)
132+
}
133+
if (isCancelled) break
134+
}
135+
136+
writer.endArray()
137+
writer.endObject()
138+
writer.flush()
139+
writer.close()
140+
141+
} catch (e: IOException) {
142+
Log.e(LOG_ID, "exportEntriesJson: error: " + e.toString())
143+
e.printStackTrace()
144+
}
145+
}
146+
147+
inline private suspend fun <reified T: RealmObject> exportEntriesCsv(dataType: String, realmResults: RealmResults<T>) {
148+
notifyProgress(0.0, null)
149+
if (realmResults.size == 0) {
150+
return
151+
}
152+
var csvSeparator = ','
153+
if (java.text.DecimalFormatSymbols.getInstance().decimalSeparator == csvSeparator) {
154+
csvSeparator = ';'
155+
}
156+
try {
157+
File(openLibreDataPath, "openlibre-export-%s.csv".format(dataType)).printWriter().use { csvFile ->
158+
var headerList = listOf("id", "timezone", "date")
159+
if (realmResults[0] is GlucoseData) {
160+
headerList += listOf("glucose [%s]".format(GlucoseData.getDisplayUnit()))
161+
162+
} else if (realmResults[0] is ReadingData) {
163+
headerList += listOf("ageInSensorMinutes")
164+
headerList += (1..numTrendValues).map{ "trend %d [%s]".format(it, GlucoseData.getDisplayUnit()) }
165+
headerList += (1..numHistoryValues).map{ "history %d [%s]".format(it, GlucoseData.getDisplayUnit()) }
166+
167+
} else if (realmResults[0] is RawTagData) {
168+
headerList += listOf("rawDataHex")
169+
}
170+
csvFile.println(headerList.joinToString(csvSeparator.toString()))
171+
172+
var count = 0
173+
for (data in realmResults) {
174+
175+
var date: Date? = null
176+
var dataList = emptyList<Any>()
177+
if (data is GlucoseData) {
178+
date = Date(data.date)
179+
180+
dataList += data.id
181+
dataList += getTimezoneName(data.timezoneOffsetInMinutes)
182+
dataList += formatDateTimeWithoutTimezone(date, data.timezoneOffsetInMinutes)
183+
184+
dataList += data.glucose()
185+
186+
} else if (data is ReadingData) {
187+
date = Date(data.date)
188+
189+
dataList += data.id
190+
dataList += getTimezoneName(data.timezoneOffsetInMinutes)
191+
dataList += formatDateTimeWithoutTimezone(date, data.timezoneOffsetInMinutes)
192+
193+
dataList += data.sensorAgeInMinutes
194+
dataList += data.trend.map { it.glucose() }
195+
dataList += DoubleArray(numTrendValues - data.trend.size).asList()
196+
dataList += data.history.map { it.glucose() }
197+
dataList += DoubleArray(numHistoryValues - data.history.size).asList()
198+
199+
} else if (data is RawTagData) {
200+
date = Date(data.date)
201+
202+
dataList += data.id
203+
dataList += getTimezoneName(data.timezoneOffsetInMinutes)
204+
dataList += formatDateTimeWithoutTimezone(date, data.timezoneOffsetInMinutes)
205+
206+
dataList += data.data.map{ "%02x".format(it) }.joinToString("")
207+
}
208+
csvFile.println(dataList.joinToString(csvSeparator.toString()))
209+
210+
count++
211+
if (count % 10 == 0) {
212+
val progress = count.toDouble() / realmResults.size
213+
notifyProgress(progress, date)
214+
}
215+
if (isCancelled) break
216+
}
217+
}
218+
} catch (e: IOException) {
219+
Log.e(LOG_ID, "exportEntriesJson: error: " + e.toString())
220+
e.printStackTrace()
221+
}
222+
}
223+
224+
private fun formatDateTimeWithoutTimezone(date: Date, timezoneOffsetInMinutes: Int): String {
225+
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
226+
df.timeZone = TimeZone.getTimeZone(getTimezoneName(timezoneOffsetInMinutes))
227+
return df.format(date)
228+
}
229+
230+
private fun getTimezoneName(timezoneOffsetInMinutes: Int): String {
231+
val offsetSign = "%+d".format(timezoneOffsetInMinutes)[0]
232+
233+
val of = SimpleDateFormat("HH:mm", Locale.US)
234+
of.timeZone = TimeZone.getTimeZone("UTC")
235+
val timezoneOffsetString = of.format(Date(TimeUnit.MINUTES.toMillis(abs(timezoneOffsetInMinutes).toLong())))
236+
237+
return "GMT" + offsetSign + timezoneOffsetString
238+
}
239+
240+
}

app/src/main/java/de/dorianscholz/openlibre/ui/DataPlotFragment.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,7 @@ public String getFormattedValue(float value, AxisBase axis) {
221221
updateTargetArea();
222222

223223
try {
224-
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
225-
mPlot.setHardwareAccelerationEnabled(false);
226-
} else {
227-
mPlot.setHardwareAccelerationEnabled(true);
228-
}
224+
mPlot.setHardwareAccelerationEnabled(true);
229225
} catch (Exception e) {
230226
Log.d(LOG_ID, "Hardware acceleration for data plot failed: " + e.toString());
231227
}
@@ -269,13 +265,13 @@ public void showMultipleScans(List<ReadingData> readingDataList) {
269265
((TextView) mDataPlotView.findViewById(R.id.tv_plot_date)).setText("");
270266
}
271267

272-
void showHistory(List<GlucoseData> history, List<GlucoseData> trend) {
268+
void showHistory(List<GlucoseData> history) {
273269
updateTargetArea();
274270
mPlot.clear();
275271
mDataPlotView.findViewById(R.id.scan_progress).setVisibility(View.INVISIBLE);
276272
mDataPlotView.findViewById(R.id.scan_view).setVisibility(View.VISIBLE);
277273

278-
updatePlot(history, trend);
274+
updatePlot(history, null);
279275
}
280276

281277
void showScan(ReadingData readData) {
@@ -299,11 +295,9 @@ private void updateScanData(List<GlucoseData> trend) {
299295
GlucoseData currentGlucose = trend.get(trend.size() - 1);
300296
TextView tv_currentGlucose = (TextView) mDataPlotView.findViewById(R.id.tv_glucose_current_value);
301297
tv_currentGlucose.setText(
302-
getResources().getString(R.string.glucose_current_value) +
303-
": " +
304-
String.valueOf(currentGlucose.glucoseString()) +
305-
" " +
306-
getDisplayUnit()
298+
String.format(getResources().getString(R.string.glucose_current_value),
299+
currentGlucose.glucoseString(),
300+
getDisplayUnit())
307301
);
308302

309303
PredictionData predictedGlucose = new PredictionData(trend);

0 commit comments

Comments
 (0)