Repository: Futsch1/medTimer (language: kotlin)

Issue:
Show taken timestamp in export
The export of dosing history currently only shows if a medicine was taken or not. Instead, the actual taken timestamp should be displayed.


Discussion hints:
nan

Pull request: Taken date in export
fixes #124 

Test command: ./gradlew :app:testDebugUnitTest --tests "com.futsch1.medtimer.CSVExportUnitTest"

Fix patch:
diff --git a/README.md b/README.md
index 9be429fb2..c77462108 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,7 @@
 
 <img src="app/src/main/play/listings/en-US/graphics/feature-graphic/1.png" width="150" />
 
-The app is built targeting SDK 34 and requiring SDK 33 (>= Android 13) making use of an Android Room
-database and Material 3 theming.
+The app is built targeting SDK 34 and requiring SDK 33 (>= Android 13).
 
 [<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
 alt="Get it on F-Droid"
@@ -55,6 +54,7 @@ Flexible & Personalized Reminders:
 Simple & Secure Data Management:
 
 - Confirm or dismiss reminders to record medication adherence accurately.
+- Get an overview of past doses in a chart and a tabular view.
 - Export your medication history as a CSV file for easy record-keeping or sharing with healthcare
   professionals.
 - Backup and restore your medication list as JSON file.
@@ -67,3 +67,15 @@ Privacy & Offline Accessibility:
 MedTimer is a free app without adds.
 
 Keywords: Medication reminder, pill reminder, medication adherence, health app, privacy, offline
+
+## Dependencies
+
+MedTimer uses the following great open source libraries and dependencies:
+
+- [Bootstrap icon](https://icons.getbootstrap.com/)
+- [TableView](https://github.com/evrencoskun/TableView)
+- [androidplot](https://github.com/halfhp/androidplot)
+- [SimplyPDF](https://github.com/wwdablu/SimplyPDF)
+- [AndroidX Preference eXtended](https://github.com/takisoft/preferencex-android)
+- [Mockito](https://github.com/mockito/mockito)
+- [Robolectric](https://github.com/robolectric/robolectric)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 19cdd2dee..ed8d12662 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -17,8 +17,8 @@ android {
         applicationId = "com.futsch1.medtimer"
         minSdk = 33
         targetSdk = 34
-        versionCode = 46
-        versionName = "1.8.2"
+        versionCode = 47
+        versionName = "1.8.3"
         setProperty("archivesBaseName", "MedTimer")
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -74,7 +74,7 @@ dependencies {
     implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
     implementation("androidx.work:work-runtime:2.9.0")
     implementation("androidx.core:core-ktx:1.13.1")
-    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
+    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
     implementation("androidx.room:room-runtime:$roomVersion")
     implementation("androidx.tracing:tracing:1.2.0")
     implementation("org.mockito:mockito-core:5.12.0")
@@ -83,22 +83,19 @@ dependencies {
     implementation("com.takisoft.preferencex:preferencex-ringtone:$preferenceXVersion")
     implementation("com.github.wwdablu:SimplyPDF:2.1.1")
     implementation("com.google.code.gson:gson:2.10.1")
+    implementation("com.github.evrencoskun:TableView:v0.8.9.4")
+    implementation("com.androidplot:androidplot-core:1.5.10")
 
     testImplementation("junit:junit:4.13.2")
     testImplementation("org.mockito:mockito-inline:5.2.0")
-    testImplementation("org.robolectric:robolectric:4.12.1")
+    testImplementation("org.robolectric:robolectric:4.12.2")
 
-    androidTestImplementation("androidx.test.ext:junit:1.2.0-alpha04")
-    androidTestImplementation("androidx.test.espresso:espresso-core:3.6.0-alpha04")
-    androidTestImplementation("androidx.test.espresso:espresso-contrib:3.6.0-alpha04")
-    androidTestImplementation("androidx.test:rules:1.6.0-alpha04")
+    androidTestImplementation("androidx.test.ext:junit:1.2.0-beta01")
+    androidTestImplementation("androidx.test.espresso:espresso-core:3.6.0-beta01")
+    androidTestImplementation("androidx.test.espresso:espresso-contrib:3.6.0-beta01")
+    androidTestImplementation("androidx.test:rules:1.6.0-beta01")
 
     annotationProcessor("androidx.room:room-compiler:$roomVersion")
-    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1")
-
-    implementation("com.github.evrencoskun:TableView:v0.8.9.4")
-
-    implementation("com.androidplot:androidplot-core:1.5.10")
 }
 
 play {
diff --git a/app/src/main/java/com/futsch1/medtimer/exporters/CSVExport.java b/app/src/main/java/com/futsch1/medtimer/exporters/CSVExport.java
index cddb8c2ab..7aff74f6d 100644
--- a/app/src/main/java/com/futsch1/medtimer/exporters/CSVExport.java
+++ b/app/src/main/java/com/futsch1/medtimer/exporters/CSVExport.java
@@ -33,7 +33,8 @@ public void export(File file) throws ExporterException {
                         TimeHelper.toLocalizedDatetimeString(reminderEvent.remindedTimestamp, defaultZoneId),
                         reminderEvent.medicineName,
                         reminderEvent.amount,
-                        reminderEvent.status == ReminderEvent.ReminderStatus.TAKEN ? "x" : "");
+                        reminderEvent.status == ReminderEvent.ReminderStatus.TAKEN ?
+                                TimeHelper.toLocalizedDatetimeString(reminderEvent.processedTimestamp, defaultZoneId) : "");
                 csvFile.write(line);
             }
         } catch (IOException e) {
diff --git a/app/src/main/java/com/futsch1/medtimer/exporters/PDFExport.java b/app/src/main/java/com/futsch1/medtimer/exporters/PDFExport.java
index aa445ad79..53d056212 100644
--- a/app/src/main/java/com/futsch1/medtimer/exporters/PDFExport.java
+++ b/app/src/main/java/com/futsch1/medtimer/exporters/PDFExport.java
@@ -48,7 +48,7 @@ public void export(File file) {
         LinkedList<LinkedList<Cell>> rows = new LinkedList<>();
 
         final int pageWidth = simplyPdfDocument.getUsablePageWidth();
-        int[] columnWidths = {pageWidth / 4, pageWidth / 3, pageWidth / 4, pageWidth / 6};
+        int[] columnWidths = {pageWidth / 4, pageWidth / 3, pageWidth / 6, pageWidth / 4};
 
         LinkedList<Cell> header = getHeader(columnWidths);
         rows.add(header);
@@ -104,7 +104,8 @@ private LinkedList<Cell> getCells(ReminderEvent reminderEvent, TextProperties te
         row.add(new TextCell(TimeHelper.toLocalizedDatetimeString(reminderEvent.remindedTimestamp, defaultZoneId), textProperties, columnWidths[0]));
         row.add(new TextCell(reminderEvent.medicineName, textProperties, columnWidths[1]));
         row.add(new TextCell(reminderEvent.amount, textProperties, columnWidths[2]));
-        row.add(new TextCell(reminderEvent.status == ReminderEvent.ReminderStatus.TAKEN ? "x" : "", textProperties, columnWidths[3]));
+        row.add(new TextCell(reminderEvent.status == ReminderEvent.ReminderStatus.TAKEN ?
+                TimeHelper.toLocalizedDatetimeString(reminderEvent.processedTimestamp, defaultZoneId) : "", textProperties, columnWidths[3]));
         return row;
     }
 
diff --git a/app/src/main/java/com/futsch1/medtimer/overview/ManualDose.java b/app/src/main/java/com/futsch1/medtimer/overview/ManualDose.java
index 3494117bd..d1415b458 100644
--- a/app/src/main/java/com/futsch1/medtimer/overview/ManualDose.java
+++ b/app/src/main/java/com/futsch1/medtimer/overview/ManualDose.java
@@ -95,8 +95,8 @@ private void getTimeAndLog(ReminderEvent reminderEvent) {
         LocalDateTime localDateTime = LocalDateTime.now();
         TimeHelper.TimePickerWrapper timePicker = new TimeHelper.TimePickerWrapper(activity);
         timePicker.show(localDateTime.getHour(), localDateTime.getMinute(), minutes -> {
-            reminderEvent.remindedTimestamp = TimeHelper.instantFromTodayMinutes(minutes).toEpochMilli() / 1000;
-            reminderEvent.processedTimestamp = Instant.now().toEpochMilli() / 1000;
+            reminderEvent.remindedTimestamp = TimeHelper.instantFromTodayMinutes(minutes).getEpochSecond();
+            reminderEvent.processedTimestamp = Instant.now().getEpochSecond();
 
             medicineRepository.insertReminderEvent(reminderEvent);
         });
diff --git a/app/src/main/java/com/futsch1/medtimer/overview/NextReminderListener.java b/app/src/main/java/com/futsch1/medtimer/overview/NextReminderListener.java
index 110e1eea8..d83deced5 100644
--- a/app/src/main/java/com/futsch1/medtimer/overview/NextReminderListener.java
+++ b/app/src/main/java/com/futsch1/medtimer/overview/NextReminderListener.java
@@ -94,6 +94,7 @@ public void processFutureReminder(boolean taken) {
             ReminderEvent reminderEvent = ReminderWork.buildReminderEvent(medicine, reminder);
             if (reminderEvent != null) {
                 reminderEvent.status = taken ? ReminderEvent.ReminderStatus.TAKEN : ReminderEvent.ReminderStatus.SKIPPED;
+                reminderEvent.processedTimestamp = Instant.now().getEpochSecond();
                 medicineViewModel.medicineRepository.insertReminderEvent(reminderEvent);
                 ReminderProcessor.requestReschedule(nextReminder.getContext());
             }
diff --git a/app/src/main/java/com/futsch1/medtimer/remindertable/ReminderTableAdapter.java b/app/src/main/java/com/futsch1/medtimer/remindertable/ReminderTableAdapter.java
index 5e939d88c..99be97a45 100644
--- a/app/src/main/java/com/futsch1/medtimer/remindertable/ReminderTableAdapter.java
+++ b/app/src/main/java/com/futsch1/medtimer/remindertable/ReminderTableAdapter.java
@@ -91,12 +91,13 @@ public void submitList(List<ReminderEvent> reminderEvents) {
 
         for (ReminderEvent reminderEvent : reminderEvents) {
             List<ReminderTableCellModel> cell = new ArrayList<>();
-            cell.add(new ReminderTableCellModel(reminderEvent.remindedTimestamp, TimeHelper.toLocalizedDatetimeString(reminderEvent.remindedTimestamp, defaultZoneId), reminderEvent.reminderId, null));
-            cell.add(new ReminderTableCellModel(reminderEvent.medicineName, reminderEvent.medicineName, reminderEvent.reminderId, "medicineName"));
-            cell.add(new ReminderTableCellModel(reminderEvent.amount, reminderEvent.amount, reminderEvent.reminderId, null));
+            cell.add(new ReminderTableCellModel(reminderEvent.remindedTimestamp, TimeHelper.toLocalizedDatetimeString(reminderEvent.remindedTimestamp, defaultZoneId), reminderEvent.reminderEventId, null));
+            cell.add(new ReminderTableCellModel(reminderEvent.medicineName, reminderEvent.medicineName, reminderEvent.reminderEventId, "medicineName"));
+            cell.add(new ReminderTableCellModel(reminderEvent.amount, reminderEvent.amount, reminderEvent.reminderEventId, null));
             cell.add(new ReminderTableCellModel(reminderEvent.status,
-                    reminderEvent.status == ReminderEvent.ReminderStatus.TAKEN ? "✔" : " ",
-                    reminderEvent.reminderId, null));
+                    reminderEvent.status == ReminderEvent.ReminderStatus.TAKEN ?
+                            TimeHelper.toLocalizedDatetimeString(reminderEvent.processedTimestamp, defaultZoneId) : " ",
+                    reminderEvent.reminderEventId, null));
             cells.add(cell);
             rows.add(new ReminderTableCellModel(reminderEvent.reminderEventId, Integer.toString(reminderEvent.reminderEventId), reminderEvent.reminderEventId, null));
         }


Test patch:
diff --git a/app/src/test/java/com/futsch1/medtimer/CSVExportUnitTest.java b/app/src/test/java/com/futsch1/medtimer/CSVExportUnitTest.java
index 78c647fa5..0cd2a4850 100644
--- a/app/src/test/java/com/futsch1/medtimer/CSVExportUnitTest.java
+++ b/app/src/test/java/com/futsch1/medtimer/CSVExportUnitTest.java
@@ -34,6 +34,7 @@ public void test_create_csv_file_with_correct_headers_and_data() {
         List<ReminderEvent> reminderEvents = new ArrayList<>();
         ReminderEvent reminderEvent1 = new ReminderEvent();
         reminderEvent1.remindedTimestamp = 1620000000; // Set remindedTimestamp to a specific value
+        reminderEvent1.processedTimestamp = 1620000120;
         reminderEvent1.medicineName = "Medicine 1";
         reminderEvent1.amount = "10mg";
         reminderEvent1.status = ReminderEvent.ReminderStatus.TAKEN;
@@ -41,6 +42,7 @@ public void test_create_csv_file_with_correct_headers_and_data() {
 
         ReminderEvent reminderEvent2 = new ReminderEvent();
         reminderEvent2.remindedTimestamp = 1620001800; // Set remindedTimestamp to a specific value
+        reminderEvent2.processedTimestamp = 1620001980;
         reminderEvent2.medicineName = "Medicine 2";
         reminderEvent2.amount = "20mg";
         reminderEvent2.status = ReminderEvent.ReminderStatus.SKIPPED;
@@ -68,7 +70,7 @@ public void test_create_csv_file_with_correct_headers_and_data() {
 
                 // Verify that the FileWriter wrote the correct data to the file
                 verify(fileWriter).write("Time;Name;Amount;Taken\n");
-                verify(fileWriter).write("5/3/21 12:00 AM;Medicine 1;10mg;x\n");
+                verify(fileWriter).write("5/3/21 12:00 AM;Medicine 1;10mg;5/3/21 12:02 AM\n");
                 verify(fileWriter).write("5/3/21 12:30 AM;Medicine 2;20mg;\n");
             } catch (Exporter.ExporterException | IOException e) {
                 fail("Exception occurred");