Repository: Futsch1/medTimer (language: kotlin)

Issue:
Restore events
OK, it may well be that I'm getting a bit old and don't realise things so quickly. ;) Saving and restoring the medication schedule now works. Exporting the events is quite simple. But how and where can I restore previously saved events in the app, i.e. restore the events?


Discussion hints:
This is not implemented yet, so it is indeed hard to find ;-)

I wouldn't consider this a high prio feature since events can already be exported and you usually wouldn't need them imported back into the app as a part of the backup from my opinion. But maybe I'm overlooking some use case?

So I'll keep it open and as To-Do, but not very high prio as mentioned.
OK, that of course explains why the option cannot be found.

After the app could not be updated via the Google App Store or F-Droid, I had to delete the app and reinstall it. The history of the last month was then also lost. :/ Hence my endeavour to restore the event history ... 

Pull request: Event import
fixes #62 

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

Fix patch:
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index e6526c2c3..1f82003df 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 = 41
-        versionName = "1.8.0"
+        versionCode = 42
+        versionName = "1.8.1-beta.1"
         setProperty("archivesBaseName", "MedTimer")
 
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
diff --git a/app/src/main/java/com/futsch1/medtimer/BackupManager.java b/app/src/main/java/com/futsch1/medtimer/BackupManager.java
new file mode 100644
index 000000000..21bff0a3f
--- /dev/null
+++ b/app/src/main/java/com/futsch1/medtimer/BackupManager.java
@@ -0,0 +1,153 @@
+package com.futsch1.medtimer;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultLauncher;
+
+import com.futsch1.medtimer.database.JSONBackup;
+import com.futsch1.medtimer.database.JSONMedicineBackup;
+import com.futsch1.medtimer.database.JSONReminderEventBackup;
+import com.futsch1.medtimer.helpers.FileHelper;
+import com.futsch1.medtimer.helpers.PathHelper;
+
+import java.io.File;
+import java.util.List;
+
+public class BackupManager {
+    private final Context context;
+    private final HandlerThread backgroundThread;
+    private final Menu menu;
+    private final MedicineViewModel medicineViewModel;
+    private final ActivityResultLauncher<Intent> openFileLauncher;
+    private PendingFileOperation pendingFileOperation = PendingFileOperation.NONE;
+
+
+    public BackupManager(Context context, Menu menu, MedicineViewModel medicineViewModel, ActivityResultLauncher<Intent> openFileLauncher) {
+        this.context = context;
+        this.menu = menu;
+        this.medicineViewModel = medicineViewModel;
+        this.openFileLauncher = openFileLauncher;
+        backgroundThread = new HandlerThread("Backup");
+        backgroundThread.start();
+
+        setupBackup();
+        setupEventsBackup();
+    }
+
+    private void setupBackup() {
+        Handler handler = new Handler(backgroundThread.getLooper());
+
+        MenuItem item = menu.findItem(R.id.medicine_backup);
+        item.setOnMenuItemClickListener(menuItem -> {
+            handler.post(() ->
+                    createBackup(new JSONMedicineBackup(),
+                            medicineViewModel.medicineRepository.getMedicines(),
+                            "Medicine"));
+            return true;
+        });
+
+        item = menu.findItem(R.id.restore_medicine_backup);
+        item.setOnMenuItemClickListener(menuItem -> {
+            new AlertDialog.Builder(context)
+                    .setTitle(R.string.restore)
+                    .setMessage(R.string.restore_start)
+                    .setCancelable(true)
+                    .setPositiveButton(R.string.ok, (dialog, which) -> openBackup(PendingFileOperation.MEDICINE))
+                    .setNegativeButton(R.string.cancel, (dialog, which) -> { // Intentionally left empty
+                    }).show();
+            return true;
+        });
+    }
+
+    private void setupEventsBackup() {
+        Handler handler = new Handler(backgroundThread.getLooper());
+
+        MenuItem item = menu.findItem(R.id.backup_events);
+        item.setOnMenuItemClickListener(menuItem -> {
+            handler.post(() ->
+                    createBackup(new JSONReminderEventBackup(),
+                            medicineViewModel.medicineRepository.getAllReminderEvents(),
+                            "ReminderEvents"));
+            return true;
+        });
+
+        item = menu.findItem(R.id.restore_events);
+        item.setOnMenuItemClickListener(menuItem -> {
+            new AlertDialog.Builder(context)
+                    .setTitle(R.string.restore)
+                    .setMessage(R.string.restore_events_start)
+                    .setCancelable(true)
+                    .setPositiveButton(R.string.ok, (dialog, which) -> openBackup(PendingFileOperation.REMINDER_EVENTS))
+                    .setNegativeButton(R.string.cancel, (dialog, which) -> { // Intentionally left empty
+                    }).show();
+            return true;
+        });
+    }
+
+    private <T> void createBackup(JSONBackup<T> jsonBackup, List<T> backupData, String backupType) {
+        String jsonContent = jsonBackup.createBackup(medicineViewModel.medicineRepository.getVersion(),
+                backupData);
+        createAndSave(jsonContent, backupType);
+    }
+
+    public void fileSelected(Uri data) {
+        String json = FileHelper.readFromUri(data, context.getContentResolver());
+        boolean restoreSuccessful = false;
+        if (json != null) {
+            if (pendingFileOperation == PendingFileOperation.MEDICINE) {
+                restoreSuccessful = restoreBackup(json, new JSONMedicineBackup());
+            } else if (pendingFileOperation == PendingFileOperation.REMINDER_EVENTS) {
+                restoreSuccessful = restoreBackup(json, new JSONReminderEventBackup());
+            }
+
+            pendingFileOperation = PendingFileOperation.NONE;
+        }
+
+        new AlertDialog.Builder(context)
+                .setMessage(restoreSuccessful ? R.string.restore_successful : R.string.restore_failed)
+                .setPositiveButton(R.string.ok, (dialog, which) -> {
+                    // Intentionally empty
+                })
+                .show();
+    }
+
+    private <T> boolean restoreBackup(String json, JSONBackup<T> backup) {
+        List<T> backupData = backup.parseBackup(json);
+        if (backupData != null) {
+            backup.applyBackup(backupData, medicineViewModel.medicineRepository);
+            return true;
+        }
+        return false;
+    }
+
+    private void createAndSave(String fileContent, String backupType) {
+        File file = new File(context.getCacheDir(), PathHelper.getBackupFilename(backupType));
+        if (FileHelper.saveToFile(file, fileContent)) {
+            FileHelper.shareFile(context, file);
+        } else {
+            Toast.makeText(context, R.string.backup_failed, Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void openBackup(PendingFileOperation pendingFileOperation) {
+        this.pendingFileOperation = pendingFileOperation;
+        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType("application/json");
+        openFileLauncher.launch(intent);
+    }
+
+    public void onDestroy() {
+        backgroundThread.quitSafely();
+    }
+
+    private enum PendingFileOperation {NONE, MEDICINE, REMINDER_EVENTS}
+}
diff --git a/app/src/main/java/com/futsch1/medtimer/NextReminderListener.java b/app/src/main/java/com/futsch1/medtimer/NextReminderListener.java
index 98630869a..e54130069 100644
--- a/app/src/main/java/com/futsch1/medtimer/NextReminderListener.java
+++ b/app/src/main/java/com/futsch1/medtimer/NextReminderListener.java
@@ -46,20 +46,28 @@ public static void sendNextReminder(@NonNull Context context, int reminderId, In
     public void onReceive(Context context, Intent intent) {
         Log.d("ReminderListener", "Next reminder received");
         final Handler handler = new Handler(thread.getLooper());
-        handler.post(() -> {
-            int reminderId = intent.getIntExtra(EXTRA_REMINDER_ID, 0);
-            Instant timestamp = intent.getSerializableExtra(EXTRA_REMINDER_TIME, Instant.class);
-            if (reminderId > 0 && timestamp != null) {
-                Reminder reminder = medicineViewModel.getReminder(reminderId);
+        handler.post(() -> receiveNextReminderIntent(context, intent));
+    }
+
+    private void receiveNextReminderIntent(Context context, Intent intent) {
+        int reminderId = intent.getIntExtra(EXTRA_REMINDER_ID, 0);
+        Instant timestamp = intent.getSerializableExtra(EXTRA_REMINDER_TIME, Instant.class);
+        if (reminderId > 0 && timestamp != null) {
+            Reminder reminder = medicineViewModel.getReminder(reminderId);
+            if (reminder != null) {
                 Medicine medicine = medicineViewModel.getMedicine(reminder.medicineRelId);
                 String nextTime = timestamp.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT));
                 final Handler mainHandler = new Handler(Looper.getMainLooper());
-                mainHandler.post(() -> {
-                    nextReminder.setText(context.getString(R.string.reminder_event, reminder.amount, medicine.name, nextTime));
-                    nextReminder.setCompoundDrawables(null, null, null, null);
-                });
+                mainHandler.post(() -> setNextReminderText(context, reminder, medicine, nextTime));
             }
-        });
+        }
+    }
+
+    private void setNextReminderText(Context context, Reminder reminder, Medicine medicine, String nextTime) {
+        if (reminder != null && medicine != null) {
+            nextReminder.setText(context.getString(R.string.reminder_event, reminder.amount, medicine.name, nextTime));
+            nextReminder.setCompoundDrawables(null, null, null, null);
+        }
     }
 
     public void stop() {
diff --git a/app/src/main/java/com/futsch1/medtimer/OptionsMenu.java b/app/src/main/java/com/futsch1/medtimer/OptionsMenu.java
index fdf5fdb09..6cd592379 100644
--- a/app/src/main/java/com/futsch1/medtimer/OptionsMenu.java
+++ b/app/src/main/java/com/futsch1/medtimer/OptionsMenu.java
@@ -1,11 +1,13 @@
 package com.futsch1.medtimer;
 
+import android.annotation.SuppressLint;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -13,12 +15,10 @@
 
 import androidx.activity.result.ActivityResultLauncher;
 import androidx.annotation.NonNull;
-import androidx.core.content.FileProvider;
+import androidx.appcompat.view.menu.MenuBuilder;
 import androidx.core.view.MenuProvider;
 import androidx.navigation.NavController;
 
-import com.futsch1.medtimer.database.JSONBackup;
-import com.futsch1.medtimer.database.MedicineWithReminders;
 import com.futsch1.medtimer.exporters.CSVExport;
 import com.futsch1.medtimer.exporters.Exporter;
 import com.futsch1.medtimer.exporters.PDFExport;
@@ -27,17 +27,18 @@
 import com.futsch1.medtimer.reminders.ReminderProcessor;
 
 import java.io.File;
-import java.net.URLConnection;
-import java.util.List;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.TimeZone;
 
 public class OptionsMenu implements MenuProvider {
     private final Context context;
     private final MedicineViewModel medicineViewModel;
-    private final HandlerThread backgroundThread;
     private final ActivityResultLauncher<Intent> openFileLauncher;
     private final NavController navController;
+    private final HandlerThread backgroundThread;
     private Menu menu;
+    private BackupManager backupManager;
 
     public OptionsMenu(Context context, MedicineViewModel medicineViewModel, ActivityResultLauncher<Intent> openFileLauncher, NavController navController) {
         this.context = context;
@@ -49,34 +50,17 @@ public OptionsMenu(Context context, MedicineViewModel medicineViewModel, Activit
     }
 
     public void onDestroy() {
+        backupManager.onDestroy();
         backgroundThread.quitSafely();
     }
 
-    public void fileSelected(Uri data) {
-        String json = FileHelper.readFromUri(data, context.getContentResolver());
-        boolean restoreSuccessful = false;
-        if (json != null) {
-            JSONBackup jsonBackup = new JSONBackup();
-            List<MedicineWithReminders> backupData = jsonBackup.parseBackup(json);
-            if (backupData != null) {
-                jsonBackup.applyBackup(backupData, medicineViewModel.medicineRepository);
-                restoreSuccessful = true;
-            }
-        }
-
-        new AlertDialog.Builder(context)
-                .setMessage(restoreSuccessful ? R.string.restore_successful : R.string.restore_failed)
-                .setPositiveButton(R.string.ok, (dialog, which) -> {
-                    // Intentionally empty
-                })
-                .show();
-    }
-
     @Override
     public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
         menuInflater.inflate(R.menu.main, menu);
         menu.setGroupDividerEnabled(true);
+        enableOptionalIcons(menu);
 
+        this.backupManager = new BackupManager(context, menu, medicineViewModel, openFileLauncher);
         this.menu = menu;
         setupSettings();
         setupVersion();
@@ -84,7 +68,19 @@ public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater)
         setupClearEvents();
         setupExport();
         setupGenerateTestData();
-        setupBackup();
+    }
+
+    @SuppressLint("RestrictedApi")
+    private static void enableOptionalIcons(@NonNull Menu menu) {
+        if (menu instanceof MenuBuilder) {
+            try {
+                Method m = menu.getClass().getDeclaredMethod(
+                        "setOptionalIconsVisible", Boolean.TYPE);
+                m.invoke(menu, true);
+            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+                Log.e("Menu", "onMenuOpened", e);
+            }
+        }
     }
 
     private void setupSettings() {
@@ -158,73 +154,22 @@ void setupGenerateTestData() {
         }
     }
 
-    private void setupBackup() {
-        Handler handler = new Handler(backgroundThread.getLooper());
-
-        MenuItem item = menu.findItem(R.id.perform_backup);
-        item.setOnMenuItemClickListener(menuItem -> {
-            JSONBackup jsonBackup = new JSONBackup();
-            handler.post(() -> {
-                String jsonContent = jsonBackup.createBackup(medicineViewModel.medicineRepository.getVersion(), medicineViewModel.medicineRepository.getMedicines());
-                createAndSave(jsonContent);
-            });
-            return true;
-        });
-
-        item = menu.findItem(R.id.restore_backup);
-        item.setOnMenuItemClickListener(menuItem -> {
-            new AlertDialog.Builder(context)
-                    .setTitle(R.string.restore)
-                    .setMessage(R.string.restore_start)
-                    .setCancelable(true)
-                    .setPositiveButton(R.string.ok, (dialog, which) -> openBackup())
-                    .setNegativeButton(R.string.cancel, (dialog, which) -> { // Intentionally left empty
-                    }).show();
-            return true;
-        });
-    }
-
     private void export(Exporter exporter) {
         File csvFile = new File(context.getCacheDir(), PathHelper.getExportFilename(exporter));
         try {
             exporter.export(csvFile);
-            shareFile(csvFile);
+            FileHelper.shareFile(context, csvFile);
         } catch (Exporter.ExporterException e) {
             Toast.makeText(context, R.string.export_failed, Toast.LENGTH_LONG).show();
         }
     }
 
-    private void createAndSave(String fileContent) {
-        File file = new File(context.getCacheDir(), PathHelper.getBackupFilename());
-        if (FileHelper.saveToFile(file, fileContent)) {
-            shareFile(file);
-        } else {
-            Toast.makeText(context, R.string.backup_failed, Toast.LENGTH_LONG).show();
-        }
-    }
-
-    private void openBackup() {
-        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
-        intent.addCategory(Intent.CATEGORY_OPENABLE);
-        intent.setType("application/json");
-        openFileLauncher.launch(intent);
-    }
-
-    private void shareFile(File file) {
-        Uri uri = FileProvider.getUriForFile(context, "com.futsch1.medtimer.fileprovider", file);
-        Intent intentShareFile = new Intent(Intent.ACTION_SEND);
-
-        intentShareFile.setDataAndType(uri, URLConnection.guessContentTypeFromName(file.getName()));
-        //Allow sharing apps to read the file Uri
-        intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        //Pass the file Uri instead of the path
-        intentShareFile.putExtra(Intent.EXTRA_STREAM,
-                uri);
-        context.startActivity(Intent.createChooser(intentShareFile, "Share File"));
-    }
-
     @Override
     public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
         return false;
     }
+
+    public void fileSelected(Uri data) {
+        backupManager.fileSelected(data);
+    }
 }
diff --git a/app/src/main/java/com/futsch1/medtimer/database/JSONBackup.java b/app/src/main/java/com/futsch1/medtimer/database/JSONBackup.java
index 296a1ee50..65273d793 100644
--- a/app/src/main/java/com/futsch1/medtimer/database/JSONBackup.java
+++ b/app/src/main/java/com/futsch1/medtimer/database/JSONBackup.java
@@ -11,56 +11,61 @@
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParseException;
 import com.google.gson.annotations.Expose;
+import com.google.gson.reflect.TypeToken;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Type;
-import java.time.Instant;
 import java.util.List;
 
-public class JSONBackup {
-    public String createBackup(int databaseVersion, List<MedicineWithReminders> medicinesWithReminders) {
-        DatabaseContentWithVersion content = new DatabaseContentWithVersion(databaseVersion, medicinesWithReminders);
+public abstract class JSONBackup<T> {
+
+    private final Class<T> contentClass;
+
+    protected JSONBackup(Class<T> contentClass) {
+        this.contentClass = contentClass;
+    }
+
+    public String createBackup(int databaseVersion, List<T> list) {
+        JSONBackup.DatabaseContentWithVersion<T> content = new JSONBackup.DatabaseContentWithVersion<>(databaseVersion, list);
         Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create();
         return gson.toJson(content);
     }
 
-    public @Nullable List<MedicineWithReminders> parseBackup(String jsonFile) {
+    public @Nullable List<T> parseBackup(String jsonFile) {
         // In a first step, parse with the version set to 0
-        Gson gson = new GsonBuilder()
-                .excludeFieldsWithoutExposeAnnotation()
-                .setVersion(0.0)
-                .registerTypeAdapter(Medicine.class, new FullDeserialize<Medicine>())
-                .registerTypeAdapter(Reminder.class, new FullDeserialize<Reminder>())
-                .create();
+        Gson gson = registerTypeAdapters(new GsonBuilder()).setVersion(0.0).create();
         try {
-            DatabaseContentWithVersion content = gson.fromJson(jsonFile, DatabaseContentWithVersion.class);
+            Type type = TypeToken.getParameterized(DatabaseContentWithVersion.class, contentClass).getType();
+            DatabaseContentWithVersion<T> content = gson.fromJson(jsonFile, type);
             gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setVersion(content.version).create();
-            return gson.fromJson(jsonFile, DatabaseContentWithVersion.class).medicinesWithReminders;
+            content = gson.fromJson(jsonFile, type);
+            return checkBackup(content.list);
         } catch (JsonParseException e) {
             Log.e("JSONBackup", e.getMessage());
             return null;
         }
     }
 
-    public void applyBackup(List<MedicineWithReminders> listOfMedicineWithReminders, MedicineRepository medicineRepository) {
-        medicineRepository.deleteReminders();
-        medicineRepository.deleteMedicines();
+    protected abstract GsonBuilder registerTypeAdapters(GsonBuilder builder);
 
-        for (MedicineWithReminders medicineWithReminders : listOfMedicineWithReminders) {
-            long medicineId = medicineRepository.insertMedicine(medicineWithReminders.medicine);
-            for (Reminder reminder : medicineWithReminders.reminders) {
-                reminder.medicineRelId = (int) medicineId;
-                reminder.createdTimestamp = Instant.now().toEpochMilli() / 1000;
-                medicineRepository.insertReminder(reminder);
+    private @Nullable List<T> checkBackup(List<T> list) {
+        if (list != null) {
+            for (T item : list) {
+                if (isInvalid(item)) return null;
             }
         }
+        return list;
     }
 
-    private record DatabaseContentWithVersion(@Expose int version,
-                                              @Expose List<MedicineWithReminders> medicinesWithReminders) {
+    protected abstract boolean isInvalid(T item);
+
+    public abstract void applyBackup(List<T> list, MedicineRepository medicineRepository);
+
+    protected record DatabaseContentWithVersion<T>(@Expose int version,
+                                                   @Expose List<T> list) {
     }
 
-    private static class FullDeserialize<T> implements JsonDeserializer<T> {
+    protected static class FullDeserialize<T> implements JsonDeserializer<T> {
 
         public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
             T pojo = new Gson().fromJson(je, type);
diff --git a/app/src/main/java/com/futsch1/medtimer/database/JSONMedicineBackup.java b/app/src/main/java/com/futsch1/medtimer/database/JSONMedicineBackup.java
new file mode 100644
index 000000000..8f1bf0504
--- /dev/null
+++ b/app/src/main/java/com/futsch1/medtimer/database/JSONMedicineBackup.java
@@ -0,0 +1,38 @@
+package com.futsch1.medtimer.database;
+
+import com.google.gson.GsonBuilder;
+
+import java.time.Instant;
+import java.util.List;
+
+public class JSONMedicineBackup extends JSONBackup<MedicineWithReminders> {
+
+    public JSONMedicineBackup() {
+        super(MedicineWithReminders.class);
+    }
+
+    @Override
+    protected GsonBuilder registerTypeAdapters(GsonBuilder builder) {
+        return builder
+                .registerTypeAdapter(Medicine.class, new FullDeserialize<Medicine>())
+                .registerTypeAdapter(Reminder.class, new FullDeserialize<Reminder>());
+    }
+
+    protected boolean isInvalid(MedicineWithReminders medicineWithReminders) {
+        return medicineWithReminders == null || medicineWithReminders.medicine == null || medicineWithReminders.reminders == null;
+    }
+
+    public void applyBackup(List<MedicineWithReminders> listOfMedicineWithReminders, MedicineRepository medicineRepository) {
+        medicineRepository.deleteReminders();
+        medicineRepository.deleteMedicines();
+
+        for (MedicineWithReminders medicineWithReminders : listOfMedicineWithReminders) {
+            long medicineId = medicineRepository.insertMedicine(medicineWithReminders.medicine);
+            for (Reminder reminder : medicineWithReminders.reminders) {
+                reminder.medicineRelId = (int) medicineId;
+                reminder.createdTimestamp = Instant.now().toEpochMilli() / 1000;
+                medicineRepository.insertReminder(reminder);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/com/futsch1/medtimer/database/JSONReminderEventBackup.java b/app/src/main/java/com/futsch1/medtimer/database/JSONReminderEventBackup.java
new file mode 100644
index 000000000..37a10e613
--- /dev/null
+++ b/app/src/main/java/com/futsch1/medtimer/database/JSONReminderEventBackup.java
@@ -0,0 +1,28 @@
+package com.futsch1.medtimer.database;
+
+import com.google.gson.GsonBuilder;
+
+import java.util.List;
+
+public class JSONReminderEventBackup extends JSONBackup<ReminderEvent> {
+
+    public JSONReminderEventBackup() {
+        super(ReminderEvent.class);
+    }
+
+    protected boolean isInvalid(ReminderEvent reminderEvent) {
+        return reminderEvent == null || reminderEvent.medicineName == null;
+    }
+
+    @Override
+    protected GsonBuilder registerTypeAdapters(GsonBuilder builder) {
+        return builder
+                .registerTypeAdapter(ReminderEvent.class, new FullDeserialize<ReminderEvent>());
+    }
+
+    public void applyBackup(List<ReminderEvent> listOfReminderEvents, MedicineRepository medicineRepository) {
+        medicineRepository.deleteReminderEvents();
+
+        listOfReminderEvents.forEach(medicineRepository::insertReminderEvent);
+    }
+}
diff --git a/app/src/main/java/com/futsch1/medtimer/database/ReminderEvent.java b/app/src/main/java/com/futsch1/medtimer/database/ReminderEvent.java
index 92aaac763..82c73406c 100644
--- a/app/src/main/java/com/futsch1/medtimer/database/ReminderEvent.java
+++ b/app/src/main/java/com/futsch1/medtimer/database/ReminderEvent.java
@@ -4,19 +4,29 @@
 import androidx.room.Entity;
 import androidx.room.PrimaryKey;
 
+import com.google.gson.annotations.Expose;
+
 @Entity
 public class ReminderEvent {
     @PrimaryKey(autoGenerate = true)
     public int reminderEventId;
+    @Expose
     public String medicineName;
+    @Expose
     public String amount;
     @ColumnInfo(defaultValue = "0")
+    @Expose
     public int color;
     @ColumnInfo(defaultValue = "false")
+    @Expose
     public boolean useColor;
+    @Expose
     public ReminderStatus status;
+    @Expose
     public long remindedTimestamp;
+    @Expose
     public long processedTimestamp;
+    @Expose
     public int reminderId;
     @ColumnInfo(defaultValue = "0")
     public int notificationId;
diff --git a/app/src/main/java/com/futsch1/medtimer/helpers/FileHelper.java b/app/src/main/java/com/futsch1/medtimer/helpers/FileHelper.java
index f65c497d2..633472f94 100644
--- a/app/src/main/java/com/futsch1/medtimer/helpers/FileHelper.java
+++ b/app/src/main/java/com/futsch1/medtimer/helpers/FileHelper.java
@@ -1,11 +1,14 @@
 package com.futsch1.medtimer.helpers;
 
 import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
 import android.net.Uri;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.content.FileProvider;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -13,6 +16,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.net.URLConnection;
 import java.nio.charset.StandardCharsets;
 
 public class FileHelper {
@@ -59,4 +63,18 @@ private static StringBuilder getStringBuilder(InputStreamReader inputStreamReade
         }
         return stringBuilder;
     }
+
+
+    public static void shareFile(Context context, File file) {
+        Uri uri = FileProvider.getUriForFile(context, "com.futsch1.medtimer.fileprovider", file);
+        Intent intentShareFile = new Intent(Intent.ACTION_SEND);
+
+        intentShareFile.setDataAndType(uri, URLConnection.guessContentTypeFromName(file.getName()));
+        //Allow sharing apps to read the file Uri
+        intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        //Pass the file Uri instead of the path
+        intentShareFile.putExtra(Intent.EXTRA_STREAM,
+                uri);
+        context.startActivity(Intent.createChooser(intentShareFile, "Share File"));
+    }
 }
diff --git a/app/src/main/java/com/futsch1/medtimer/helpers/PathHelper.java b/app/src/main/java/com/futsch1/medtimer/helpers/PathHelper.java
index caca377c5..5033b3a6b 100644
--- a/app/src/main/java/com/futsch1/medtimer/helpers/PathHelper.java
+++ b/app/src/main/java/com/futsch1/medtimer/helpers/PathHelper.java
@@ -18,8 +18,8 @@ public static String getExportFilename(Exporter exporter) {
         return fileName.replaceAll(RESERVED_CHARS, "_");
     }
 
-    public static String getBackupFilename() {
-        String fileName = String.format("MedTimer Backup %s.json", ZonedDateTime.now().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
+    public static String getBackupFilename(String backupType) {
+        String fileName = String.format("MedTimer %s Backup %s.json", backupType, ZonedDateTime.now().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
         return fileName.replaceAll(RESERVED_CHARS, "_");
     }
 }
diff --git a/app/src/main/res/drawable/box_arrow_down.xml b/app/src/main/res/drawable/box_arrow_down.xml
new file mode 100644
index 000000000..eed41cd80
--- /dev/null
+++ b/app/src/main/res/drawable/box_arrow_down.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+  <path
+      android:pathData="M3.5,10a0.5,0.5 0,0 1,-0.5 -0.5v-8a0.5,0.5 0,0 1,0.5 -0.5h9a0.5,0.5 0,0 1,0.5 0.5v8a0.5,0.5 0,0 1,-0.5 0.5h-2a0.5,0.5 0,0 0,0 1h2A1.5,1.5 0,0 0,14 9.5v-8A1.5,1.5 0,0 0,12.5 0h-9A1.5,1.5 0,0 0,2 1.5v8A1.5,1.5 0,0 0,3.5 11h2a0.5,0.5 0,0 0,0 -1z"
+      android:fillColor="?android:colorForeground"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M7.646,15.854a0.5,0.5 0,0 0,0.708 0l3,-3a0.5,0.5 0,0 0,-0.708 -0.708L8.5,14.293V5.5a0.5,0.5 0,0 0,-1 0v8.793l-2.146,-2.147a0.5,0.5 0,0 0,-0.708 0.708z"
+      android:fillColor="?android:colorForeground"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/app/src/main/res/drawable/box_arrow_in_down.xml b/app/src/main/res/drawable/box_arrow_in_down.xml
new file mode 100644
index 000000000..58c7624a6
--- /dev/null
+++ b/app/src/main/res/drawable/box_arrow_in_down.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+  <path
+      android:pathData="M3.5,6a0.5,0.5 0,0 0,-0.5 0.5v8a0.5,0.5 0,0 0,0.5 0.5h9a0.5,0.5 0,0 0,0.5 -0.5v-8a0.5,0.5 0,0 0,-0.5 -0.5h-2a0.5,0.5 0,0 1,0 -1h2A1.5,1.5 0,0 1,14 6.5v8a1.5,1.5 0,0 1,-1.5 1.5h-9A1.5,1.5 0,0 1,2 14.5v-8A1.5,1.5 0,0 1,3.5 5h2a0.5,0.5 0,0 1,0 1z"
+      android:fillColor="?android:colorForeground"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M7.646,11.854a0.5,0.5 0,0 0,0.708 0l3,-3a0.5,0.5 0,0 0,-0.708 -0.708L8.5,10.293V1.5a0.5,0.5 0,0 0,-1 0v8.793L5.354,8.146a0.5,0.5 0,1 0,-0.708 0.708z"
+      android:fillColor="?android:colorForeground"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/app/src/main/res/drawable/calendar2_event.xml b/app/src/main/res/drawable/calendar2_event.xml
new file mode 100644
index 000000000..b7308d0e2
--- /dev/null
+++ b/app/src/main/res/drawable/calendar2_event.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+  <path
+      android:pathData="M11,7.5a0.5,0.5 0,0 1,0.5 -0.5h1a0.5,0.5 0,0 1,0.5 0.5v1a0.5,0.5 0,0 1,-0.5 0.5h-1a0.5,0.5 0,0 1,-0.5 -0.5z"
+      android:fillColor="?android:colorForeground"/>
+  <path
+      android:pathData="M3.5,0a0.5,0.5 0,0 1,0.5 0.5V1h8V0.5a0.5,0.5 0,0 1,1 0V1h1a2,2 0,0 1,2 2v11a2,2 0,0 1,-2 2H2a2,2 0,0 1,-2 -2V3a2,2 0,0 1,2 -2h1V0.5a0.5,0.5 0,0 1,0.5 -0.5M2,2a1,1 0,0 0,-1 1v11a1,1 0,0 0,1 1h12a1,1 0,0 0,1 -1V3a1,1 0,0 0,-1 -1z"
+      android:fillColor="?android:colorForeground"/>
+  <path
+      android:pathData="M2.5,4a0.5,0.5 0,0 1,0.5 -0.5h10a0.5,0.5 0,0 1,0.5 0.5v1a0.5,0.5 0,0 1,-0.5 0.5H3a0.5,0.5 0,0 1,-0.5 -0.5z"
+      android:fillColor="?android:colorForeground"/>
+</vector>
diff --git a/app/src/main/res/drawable/filetype_csv.xml b/app/src/main/res/drawable/filetype_csv.xml
new file mode 100644
index 000000000..ae237dbaa
--- /dev/null
+++ b/app/src/main/res/drawable/filetype_csv.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+  <path
+      android:pathData="M14,4.5L14,14a2,2 0,0 1,-2 2h-1v-1h1a1,1 0,0 0,1 -1L13,4.5h-2A1.5,1.5 0,0 1,9.5 3L9.5,1L4,1a1,1 0,0 0,-1 1v9L2,11L2,2a2,2 0,0 1,2 -2h5.5zM3.517,14.841a1.13,1.13 0,0 0,0.401 0.823q0.195,0.162 0.478,0.252 0.284,0.091 0.665,0.091 0.507,0 0.859,-0.158 0.354,-0.158 0.539,-0.44 0.187,-0.284 0.187,-0.656 0,-0.336 -0.134,-0.56a1,1 0,0 0,-0.375 -0.357,2 2,0 0,0 -0.566,-0.21l-0.621,-0.144a1,1 0,0 1,-0.404 -0.176,0.37 0.37,0 0,1 -0.144,-0.299q0,-0.234 0.185,-0.384 0.188,-0.152 0.512,-0.152 0.214,0 0.37,0.068a0.6,0.6 0,0 1,0.246 0.181,0.56 0.56,0 0,1 0.12,0.258h0.75a1.1,1.1 0,0 0,-0.2 -0.566,1.2 1.2,0 0,0 -0.5,-0.41 1.8,1.8 0,0 0,-0.78 -0.152q-0.439,0 -0.776,0.15 -0.337,0.149 -0.527,0.421 -0.19,0.273 -0.19,0.639 0,0.302 0.122,0.524 0.124,0.223 0.352,0.367 0.228,0.143 0.539,0.213l0.618,0.144q0.31,0.073 0.463,0.193a0.39,0.39 0,0 1,0.152 0.326,0.5 0.5,0 0,1 -0.085,0.29 0.56,0.56 0,0 1,-0.255 0.193q-0.167,0.07 -0.413,0.07 -0.175,0 -0.32,-0.04a0.8,0.8 0,0 1,-0.248 -0.115,0.58 0.58,0 0,1 -0.255,-0.384zM0.806,13.693q0,-0.373 0.102,-0.633a0.87,0.87 0,0 1,0.302 -0.399,0.8 0.8,0 0,1 0.475,-0.137q0.225,0 0.398,0.097a0.7,0.7 0,0 1,0.272 0.26,0.85 0.85,0 0,1 0.12,0.381h0.765v-0.072a1.33,1.33 0,0 0,-0.466 -0.964,1.4 1.4,0 0,0 -0.489,-0.272 1.8,1.8 0,0 0,-0.606 -0.097q-0.534,0 -0.911,0.223 -0.375,0.222 -0.572,0.632 -0.195,0.41 -0.196,0.979v0.498q0,0.568 0.193,0.976 0.197,0.407 0.572,0.626 0.375,0.217 0.914,0.217 0.439,0 0.785,-0.164t0.55,-0.454a1.27,1.27 0,0 0,0.226 -0.674v-0.076h-0.764a0.8,0.8 0,0 1,-0.118 0.363,0.7 0.7,0 0,1 -0.272,0.25 0.9,0.9 0,0 1,-0.401 0.087,0.85 0.85,0 0,1 -0.478,-0.132 0.83,0.83 0,0 1,-0.299 -0.392,1.7 1.7,0 0,1 -0.102,-0.627zM9.045,15.931h-0.953l-1.338,-3.999h0.917l0.896,3.138h0.038l0.888,-3.138h0.879z"
+      android:fillColor="?android:colorForeground"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/app/src/main/res/drawable/filetype_pdf.xml b/app/src/main/res/drawable/filetype_pdf.xml
new file mode 100644
index 000000000..30922bc2a
--- /dev/null
+++ b/app/src/main/res/drawable/filetype_pdf.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+  <path
+      android:pathData="M14,4.5L14,14a2,2 0,0 1,-2 2h-1v-1h1a1,1 0,0 0,1 -1L13,4.5h-2A1.5,1.5 0,0 1,9.5 3L9.5,1L4,1a1,1 0,0 0,-1 1v9L2,11L2,2a2,2 0,0 1,2 -2h5.5zM1.6,11.85L0,11.85v3.999h0.791v-1.342h0.803q0.43,0 0.732,-0.173 0.305,-0.175 0.463,-0.474a1.4,1.4 0,0 0,0.161 -0.677q0,-0.375 -0.158,-0.677a1.2,1.2 0,0 0,-0.46 -0.477q-0.3,-0.18 -0.732,-0.179m0.545,1.333a0.8,0.8 0,0 1,-0.085 0.38,0.57 0.57,0 0,1 -0.238,0.241 0.8,0.8 0,0 1,-0.375 0.082L0.788,13.886L0.788,12.48h0.66q0.327,0 0.512,0.181 0.185,0.183 0.185,0.522m1.217,-1.333v3.999h1.46q0.602,0 0.998,-0.237a1.45,1.45 0,0 0,0.595 -0.689q0.196,-0.45 0.196,-1.084 0,-0.63 -0.196,-1.075a1.43,1.43 0,0 0,-0.589 -0.68q-0.396,-0.234 -1.005,-0.234zM4.153,12.495h0.563q0.371,0 0.609,0.152a0.9,0.9 0,0 1,0.354 0.454q0.118,0.302 0.118,0.753a2.3,2.3 0,0 1,-0.068 0.592,1.1 1.1,0 0,1 -0.196,0.422 0.8,0.8 0,0 1,-0.334 0.252,1.3 1.3,0 0,1 -0.483,0.082h-0.563zM7.896,14.258v1.591h-0.79L7.106,11.85h2.548v0.653L7.896,12.503v1.117h1.606v0.638z"
+      android:fillColor="?android:colorForeground"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/app/src/main/res/drawable/trash.xml b/app/src/main/res/drawable/trash.xml
new file mode 100644
index 000000000..6fe10748d
--- /dev/null
+++ b/app/src/main/res/drawable/trash.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="16dp"
+    android:height="16dp"
+    android:viewportWidth="16"
+    android:viewportHeight="16">
+  <path
+      android:pathData="M5.5,5.5A0.5,0.5 0,0 1,6 6v6a0.5,0.5 0,0 1,-1 0V6a0.5,0.5 0,0 1,0.5 -0.5m2.5,0a0.5,0.5 0,0 1,0.5 0.5v6a0.5,0.5 0,0 1,-1 0V6a0.5,0.5 0,0 1,0.5 -0.5m3,0.5a0.5,0.5 0,0 0,-1 0v6a0.5,0.5 0,0 0,1 0z"
+      android:fillColor="?android:colorForeground"/>
+  <path
+      android:pathData="M14.5,3a1,1 0,0 1,-1 1H13v9a2,2 0,0 1,-2 2H5a2,2 0,0 1,-2 -2V4h-0.5a1,1 0,0 1,-1 -1V2a1,1 0,0 1,1 -1H6a1,1 0,0 1,1 -1h2a1,1 0,0 1,1 1h3.5a1,1 0,0 1,1 1zM4.118,4 L4,4.059V13a1,1 0,0 0,1 1h6a1,1 0,0 0,1 -1V4.059L11.882,4zM2.5,3h11V2h-11z"
+      android:fillColor="?android:colorForeground"/>
+</vector>
diff --git a/app/src/main/res/layout/preferences_activity.xml b/app/src/main/res/layout/preferences_activity.xml
deleted file mode 100644
index de6591a20..000000000
--- a/app/src/main/res/layout/preferences_activity.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <FrameLayout
-        android:id="@+id/settings"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index 520716a01..24f73426a 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -3,35 +3,52 @@
     <group android:id="@+id/settings_group">
         <item
             android:id="@+id/settings"
-            android:icon="@android:drawable/ic_menu_preferences"
+            android:icon="@drawable/gear"
             android:title="@string/tab_settings" />
     </group>
     <item
-        android:id="@+id/export"
-        android:icon="@drawable/floppy2"
-        android:title="@string/export">
+        android:id="@+id/event_data"
+        android:icon="@drawable/calendar2_event"
+        android:title="@string/event_data">
         <menu>
-            <item
-                android:id="@+id/export_csv"
-                android:title="@string/csv" />
             <item
                 android:id="@+id/export_pdf"
-                android:title="@string/pdf" />
+                android:icon="@drawable/filetype_pdf"
+                android:title="@string/export_pdf" />
+            <item
+                android:id="@+id/export_csv"
+                android:icon="@drawable/filetype_csv"
+                android:title="@string/export_csv" />
+            <group android:id="@+id/event_backup">
+                <item
+                    android:id="@+id/backup_events"
+                    android:icon="@drawable/box_arrow_down"
+                    android:title="@string/backup" />
+                <item
+                    android:id="@+id/restore_events"
+                    android:icon="@drawable/box_arrow_in_down"
+                    android:title="@string/restore" />
+            </group>
+            <group android:id="@+id/clear">
+                <item
+                    android:id="@+id/clear_events"
+                    android:icon="@drawable/trash"
+                    android:title="@string/clear_events" />
+            </group>
         </menu>
     </item>
     <item
-        android:id="@+id/clear_events"
-        android:icon="@drawable/x_circle"
-        android:title="@string/clear_events" />
-    <item
-        android:id="@+id/backup"
-        android:title="@string/backup_medicine">
+        android:id="@+id/medicine_data"
+        android:icon="@drawable/capsule"
+        android:title="@string/medicine_data">
         <menu>
             <item
-                android:id="@+id/perform_backup"
-                android:title="@string/create" />
+                android:id="@+id/medicine_backup"
+                android:icon="@drawable/box_arrow_down"
+                android:title="@string/backup" />
             <item
-                android:id="@+id/restore_backup"
+                android:id="@+id/restore_medicine_backup"
+                android:icon="@drawable/box_arrow_in_down"
                 android:title="@string/restore" />
         </menu>
     </item>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index a3ec83eb0..e6377aec1 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -34,12 +34,9 @@
     <string name="clear_events">Ereignisse zurücksetzen</string>
     <string name="app_version">App-Version</string>
     <string name="are_you_sure_delete_events">Ereignisse wirklich zurücksetzen?</string>
-    <string name="export">Ereignisse exportieren</string>
     <string name="swipe_left">Wischen nach links</string>
     <string name="long_press">Lange drücken</string>
     <string name="delete_items">Einträge löschen</string>
-    <string name="actions">Aktionen</string>
-    <string name="information">Information</string>
     <string name="show_notifications">Benachrichtigungen anzeigen</string>
     <string name="overview_events">Übersicht Erinnerungen anzeigen</string>
     <string name="four_hours">4 Stunden</string>
@@ -58,12 +55,10 @@
     <string name="generate_test_data">Testdaten generieren</string>
     <string name="create_reminder_dosage_hint">Dosierung für neue Erinnerung</string>
     <string name="notification_tone_title">Benachrichtigungston</string>
-    <string name="select_format">Format auswählen</string>
     <string name="days">Tage</string>
     <string name="theme">Theme</string>
     <string name="default_theme">Standard</string>
     <string name="alternative">Alternativ</string>
-    <string name="instructions">Hinweis</string>
     <string name="no_instructions">kein Hinweis</string>
     <string name="before_meal">vor der Mahlzeit</string>
     <string name="after_meal">nach der Mahlzeit</string>
@@ -71,8 +66,7 @@
     <string name="time_after">x Stunden nach Medikament y</string>
     <string name="instruction_templates">Hinweisvorlagen</string>
     <string name="instructions_label">Zusätzliche Hinweise (z.B. vor einer Mahlzeit)</string>
-    <string name="backup_medicine">Backup der Medikamente</string>
-    <string name="create">Erstellen</string>
+    <string name="medicine_data">Medikamentendaten</string>
     <string name="restore">Wiederherstellen</string>
     <string name="restore_failed">Wiederherstellen fehlgeschlagen</string>
     <string name="backup_failed">Erstellen fehlgeschlagen</string>
@@ -120,4 +114,9 @@
     <string name="total">Gesamt</string>
     <string name="last_n_days">Letzte %1$d Tage</string>
     <string name="name">Name</string>
+    <string name="event_data">Ereignisdaten</string>
+    <string name="backup">Backup</string>
+    <string name="export_csv">Export als CSV</string>
+    <string name="export_pdf">Export als PDF</string>
+    <string name="restore_events_start">Alle Ereignisse werden überschrieben.\n\nFortfahren?</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 576c73849..4ce966b63 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -33,12 +33,9 @@
     <string name="clear_events">Restablecer eventos</string>
     <string name="app_version">Versión de la aplicación</string>
     <string name="are_you_sure_delete_events">¿Quieres eliminar todos los recordatorios?</string>
-    <string name="export">Exportar eventos</string>
     <string name="swipe_left">Deslizar a la izquierda</string>
     <string name="long_press">Mantener pulsado</string>
     <string name="delete_items">Eliminar ítems</string>
-    <string name="actions">Acciones</string>
-    <string name="information">Información</string>
     <string name="show_notifications">Enviar notificaciones</string>
     <string name="overview_events">Mostrar eventos en el resumen</string>
     <string name="four_hours">4 horas</string>
@@ -57,13 +54,11 @@
     <string name="generate_test_data">Generar datos para test</string>
     <string name="create_reminder_dosage_hint">Dosis para el nuevo recordatorio</string>
     <string name="notification_tone_title">Sonido de notificación</string>
-    <string name="select_format">Seleccionar formato</string>
     <string name="cycle_consecutive_days">Días activos</string>
     <string name="days">días</string>
     <string name="theme">Tema</string>
     <string name="default_theme">Estándar</string>
     <string name="alternative">Alternativo</string>
-    <string name="instructions">Instrucciones</string>
     <string name="no_instructions">no hay instrucciones</string>
     <string name="before_meal">antes de la comida</string>
     <string name="after_meal">después de la comida</string>
@@ -71,8 +66,7 @@
     <string name="time_after">x horas después del medicamento y</string>
     <string name="instruction_templates">Plantillas de instrucciones</string>
     <string name="instructions_label">Instrucciones adicionales (por ejemplo, antes de las comidas)</string>
-    <string name="backup_medicine">Backup de los medicamentos</string>
-    <string name="create">Crear</string>
+    <string name="medicine_data">Datos de medicamentos</string>
     <string name="restore">Restaurar</string>
     <string name="restore_failed">Error al restaurar</string>
     <string name="backup_failed">Error al crear</string>
@@ -119,4 +113,9 @@
     <string name="total">Total</string>
     <string name="last_n_days">Últimos %1$d días</string>
     <string name="name">Nombre</string>
+    <string name="event_data">Datos del evento</string>
+    <string name="backup">Backup</string>
+    <string name="export_csv">Exportar en CSV</string>
+    <string name="restore_events_start">Esto reemplazará todos los eventos de recordatorio.\n\n¿Continuar?</string>
+    <string name="export_pdf">Exportar en PDF</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index f2e0591eb..d674d4837 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -67,18 +67,12 @@
 
     <string name="are_you_sure_delete_events">Êtes-vous sûr de supprimer tous les événements ?</string>
 
-    <string name="export">Exporter des événements</string>
-
     <string name="swipe_left">Balayez vers la gauche</string>
 
     <string name="long_press">Appui long</string>
 
     <string name="delete_items">Supprimer des éléments</string>
 
-    <string name="actions">Actions</string>
-
-    <string name="information">Information</string>
-
     <string name="show_notifications">Afficher les notifications</string>
 
     <string name="overview_events">Événements d’affichage d’aperçu</string>
@@ -115,8 +109,6 @@
 
     <string name="notification_tone_title">Tonalité de notification</string>
 
-    <string name="select_format">Sélectionnez le format</string>
-
     <string name="cycle_consecutive_days">Jours actifs</string>
 
     <string name="days">jours</string>
@@ -127,8 +119,6 @@
 
     <string name="alternative">Alternative</string>
 
-    <string name="instructions">Instructions</string>
-
     <string name="no_instructions">aucune instruction</string>
 
     <string name="before_meal">avant le repas</string>
@@ -143,9 +133,7 @@
 
     <string name="instructions_label">Instructions supplémentaires (par exemple avant les repas)</string>
 
-    <string name="backup_medicine">Paramètres de sauvegarde des médicaments</string>
-
-    <string name="create">Créer</string>
+    <string name="medicine_data">Données sur les médicaments</string>
 
     <string name="restore">Restaurer</string>
 
@@ -221,5 +209,10 @@
     <string name="total">Total</string>
     <string name="last_n_days">Dernier %1$d jours</string>
     <string name="name">Nom</string>
+    <string name="event_data">Données sur les événements</string>
+    <string name="backup">Sauvegarde</string>
+    <string name="export_csv">Exporter en CSV</string>
+    <string name="export_pdf">Exporter en PDF</string>
+    <string name="restore_events_start">Cela remplacera tous les événements de rappel.\n\nPoursuivre ?</string>
 
 </resources>
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 3719407fe..862b0be9a 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -34,12 +34,9 @@
     <string name="clear_events">Herinneringen verwijderen</string>
     <string name="app_version">App versie</string>
     <string name="are_you_sure_delete_events">Weet je zeker dat je alle herinneringen wilt verwijderen?</string>
-    <string name="export">Herinneringen exporteren</string>
     <string name="swipe_left">Veeg naar links</string>
     <string name="long_press">Lang indrukken</string>
     <string name="delete_items">Verwijderen</string>
-    <string name="actions">Acties</string>
-    <string name="information">Informatie</string>
     <string name="show_notifications">Meldingen weergeven</string>
     <string name="overview_events">Overzicht herinneringen tonen</string>
     <string name="four_hours">4 uur</string>
@@ -58,13 +55,11 @@
     <string name="generate_test_data">Testgegevens genereren</string>
     <string name="create_reminder_dosage_hint">Dosering voor nieuwe herinnering</string>
     <string name="notification_tone_title">Waarschuwingstoon</string>
-    <string name="select_format">Formaat selecteren</string>
     <string name="cycle_consecutive_days">Dagen</string>
     <string name="days">dagen</string>
     <string name="theme">Thema</string>
     <string name="default_theme">Standaard</string>
     <string name="alternative">Alternatief</string>
-    <string name="instructions">Instructies</string>
     <string name="no_instructions">geen instructies</string>
     <string name="before_meal">vóór de maaltijd</string>
     <string name="after_meal">na het eten</string>
@@ -72,8 +67,7 @@
     <string name="time_after">x uur na medicijn y</string>
     <string name="instruction_templates">Referentiesjablonen</string>
     <string name="instructions_label">Aanvullende instructies (bijvoorbeeld vóór de maaltijd)</string>
-    <string name="backup_medicine">Back-up van de medicatie</string>
-    <string name="create">Creëren</string>
+    <string name="medicine_data">Medicatie gegevens</string>
     <string name="restore">Herstellen</string>
     <string name="restore_failed">Herstel mislukt</string>
     <string name="backup_failed">Back-up mislukt</string>
@@ -120,4 +114,9 @@
     <string name="total">Totaal</string>
     <string name="last_n_days">Laatste %1$d dagen</string>
     <string name="name">Naam</string>
+    <string name="event_data">Herinneringen gegevens</string>
+    <string name="backup">Backup</string>
+    <string name="export_csv">Exporteren als CSV</string>
+    <string name="export_pdf">Exporteren als PDF</string>
+    <string name="restore_events_start">Dit vervangt alle herinneringsgebeurtenissen.\n\nDoorgaan?</string>
 </resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b0a8ff2f4..83f01e52e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -35,12 +35,9 @@
     <string name="clear_events">Clear events</string>
     <string name="app_version">App version</string>
     <string name="are_you_sure_delete_events">Are you sure to delete all events?</string>
-    <string name="export">Export events</string>
     <string name="swipe_left">Swipe left</string>
     <string name="long_press">Long press</string>
     <string name="delete_items">Delete items</string>
-    <string name="actions">Actions</string>
-    <string name="information">Information</string>
     <string name="show_notifications">Show notifications</string>
     <string name="overview_events">Overview display events</string>
     <string name="four_hours">4 hours</string>
@@ -59,13 +56,11 @@
     <string name="generate_test_data">Generate test data</string>
     <string name="create_reminder_dosage_hint">Dosage for new reminder</string>
     <string name="notification_tone_title">Notification tone</string>
-    <string name="select_format">Select format</string>
     <string name="cycle_consecutive_days">Active days</string>
     <string name="days">days</string>
     <string name="theme">Theme</string>
     <string name="default_theme">Default</string>
     <string name="alternative">Alternative</string>
-    <string name="instructions">Instructions</string>
     <string name="no_instructions">no instructions</string>
     <string name="before_meal">before the meal</string>
     <string name="after_meal">after the meal</string>
@@ -73,10 +68,9 @@
     <string name="time_after">x hours after medicine y</string>
     <string name="instruction_templates">Instruction templates</string>
     <string name="instructions_label">Additional instructions (e.g. before meals)</string>
-    <string name="backup_medicine">Backup medicine settings</string>
-    <string name="csv" translatable="false">CSV</string>
-    <string name="pdf" translatable="false">PDF</string>
-    <string name="create">Create</string>
+    <string name="medicine_data">Medicine data</string>
+    <string name="export_csv">Export as CSV</string>
+    <string name="export_pdf">Export as PDF</string>
     <string name="restore">Restore</string>
     <string name="restore_failed">Restore failed</string>
     <string name="backup_failed">Backup failed</string>
@@ -124,4 +118,7 @@
     <string name="total">Total</string>
     <string name="last_n_days">Last %1$d days</string>
     <string name="name">Name</string>
+    <string name="event_data">Event data</string>
+    <string name="backup">Backup</string>
+    <string name="restore_events_start">This will replace all reminder events.\n\nContinue?</string>
 </resources>
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 7a8058975..83eea01e4 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,6 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 plugins {
-    id("com.android.application") version "8.3.2" apply false
+    id("com.android.application") version "8.4.0" apply false
     id("androidx.room") version "2.6.1" apply false
     id("androidx.navigation.safeargs") version "2.7.7" apply false
 }


Test patch:
diff --git a/app/src/androidTest/java/com/futsch1/medtimer/StatisticsTest.java b/app/src/androidTest/java/com/futsch1/medtimer/StatisticsTest.java
index 11bbc4147..3e6f98c92 100644
--- a/app/src/androidTest/java/com/futsch1/medtimer/StatisticsTest.java
+++ b/app/src/androidTest/java/com/futsch1/medtimer/StatisticsTest.java
@@ -77,7 +77,7 @@ public void statisticsTest() {
                         childAtPosition(
                                 childAtPosition(
                                         withId(androidx.appcompat.R.id.content),
-                                        0),
+                                        1),
                                 0),
                         isDisplayed()));
         materialTextView.perform(click());
diff --git a/app/src/androidTest/java/com/futsch1/medtimer/TestDataAndDeleteAndManualDoseTest.java b/app/src/androidTest/java/com/futsch1/medtimer/TestDataAndDeleteAndManualDoseTest.java
index 23749895a..2b2cec23a 100644
--- a/app/src/androidTest/java/com/futsch1/medtimer/TestDataAndDeleteAndManualDoseTest.java
+++ b/app/src/androidTest/java/com/futsch1/medtimer/TestDataAndDeleteAndManualDoseTest.java
@@ -69,7 +69,7 @@ public void testDataAndDeleteAndManualDoseTest() {
                         childAtPosition(
                                 childAtPosition(
                                         withId(androidx.appcompat.R.id.content),
-                                        0),
+                                        1),
                                 0),
                         isDisplayed()));
         materialTextView.perform(click());
diff --git a/app/src/test/java/com/futsch1/medtimer/JSONBackupUnitTest.java b/app/src/test/java/com/futsch1/medtimer/JSONMedicineBackupUnitTest.java
similarity index 86%
rename from app/src/test/java/com/futsch1/medtimer/JSONBackupUnitTest.java
rename to app/src/test/java/com/futsch1/medtimer/JSONMedicineBackupUnitTest.java
index 5d51cb800..45a9a9edc 100644
--- a/app/src/test/java/com/futsch1/medtimer/JSONBackupUnitTest.java
+++ b/app/src/test/java/com/futsch1/medtimer/JSONMedicineBackupUnitTest.java
@@ -6,7 +6,7 @@
 
 import android.graphics.Color;
 
-import com.futsch1.medtimer.database.JSONBackup;
+import com.futsch1.medtimer.database.JSONMedicineBackup;
 import com.futsch1.medtimer.database.Medicine;
 import com.futsch1.medtimer.database.MedicineWithReminders;
 import com.futsch1.medtimer.database.Reminder;
@@ -20,14 +20,14 @@
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
-public class JSONBackupUnitTest {
+public class JSONMedicineBackupUnitTest {
 
     // Generated by CodiumAI
 
     // creates a backup object with a version number and a medicines array
     @Test
     public void test_creates_backup_with_version_and_medicines_array() {
-        JSONBackup jsonBackup = new JSONBackup();
+        JSONMedicineBackup jsonMedicineBackup = new JSONMedicineBackup();
         List<MedicineWithReminders> medicinesWithReminders = new ArrayList<>();
         MedicineWithReminders medicineWithReminders = new MedicineWithReminders();
         Reminder reminder = new Reminder(0);
@@ -44,7 +44,7 @@ public void test_creates_backup_with_version_and_medicines_array() {
         medicineWithReminders.medicine.color = Color.RED;
         medicinesWithReminders.add(medicineWithReminders);
 
-        String result = jsonBackup.createBackup(5, medicinesWithReminders);
+        String result = jsonMedicineBackup.createBackup(5, medicinesWithReminders);
 
         assertNotNull(result);
 
@@ -52,7 +52,7 @@ public void test_creates_backup_with_version_and_medicines_array() {
         assertEquals("""
 {
   "version": 5,
-  "medicinesWithReminders": [
+  "list": [
     {
       "reminders": [
         {
@@ -83,13 +83,13 @@ public void test_creates_backup_with_version_and_medicines_array() {
 }""", result);
         // @formatter:on
 
-        List<MedicineWithReminders> parsedReminders = jsonBackup.parseBackup(result);
+        List<MedicineWithReminders> parsedReminders = jsonMedicineBackup.parseBackup(result);
         assertNotNull(parsedReminders);
         compareListMedicineWithReminders(parsedReminders, medicinesWithReminders);
     }
 
     private void compareListMedicineWithReminders(List<MedicineWithReminders> actual, List<MedicineWithReminders> expected) {
-        assertEquals(actual.size(), expected.size());
+        assertEquals(expected.size(), actual.size());
         for (int i = 0; i < actual.size(); i++) {
             compareMedicineWithReminders(actual.get(i), expected.get(i));
         }
@@ -115,7 +115,7 @@ private void compareReminder(Reminder reminder, Reminder reminder1) {
     // iterates over the list of MedicineWithReminders and adds each one to the medicines array as a JSONObject
     @Test
     public void test_iterates_over_medicinesWithReminders_and_adds_to_medicines_array() {
-        JSONBackup jsonBackup = new JSONBackup();
+        JSONMedicineBackup jsonMedicineBackup = new JSONMedicineBackup();
         List<MedicineWithReminders> medicinesWithReminders = new ArrayList<>();
         MedicineWithReminders medicineWithReminders1 = new MedicineWithReminders();
         Reminder reminder1 = new Reminder(0);
@@ -149,7 +149,7 @@ public void test_iterates_over_medicinesWithReminders_and_adds_to_medicines_arra
         medicineWithReminders2.medicine.color = Color.BLUE;
         medicinesWithReminders.add(medicineWithReminders2);
 
-        String result = jsonBackup.createBackup(4, medicinesWithReminders);
+        String result = jsonMedicineBackup.createBackup(4, medicinesWithReminders);
 
         assertNotNull(result);
 
@@ -157,7 +157,7 @@ public void test_iterates_over_medicinesWithReminders_and_adds_to_medicines_arra
         assertEquals("""
 {
   "version": 4,
-  "medicinesWithReminders": [
+  "list": [
     {
       "reminders": [
         {
@@ -231,7 +231,7 @@ public void test_iterates_over_medicinesWithReminders_and_adds_to_medicines_arra
 }""", result);
         // @formatter:on
 
-        List<MedicineWithReminders> parsedReminders = jsonBackup.parseBackup(result);
+        List<MedicineWithReminders> parsedReminders = jsonMedicineBackup.parseBackup(result);
         assertNotNull(parsedReminders);
         compareListMedicineWithReminders(parsedReminders, medicinesWithReminders);
     }
@@ -239,11 +239,11 @@ public void test_iterates_over_medicinesWithReminders_and_adds_to_medicines_arra
     @Test
     public void test_parse_json_backup_missing_fields() {
         // Arrange
-        JSONBackup jsonBackup = new JSONBackup();
+        JSONMedicineBackup jsonMedicineBackup = new JSONMedicineBackup();
         String validJsonBackup = "{\"version\": 1, \"medicinesWithReminders\": [{\"reminders\": [], \"medicine\": {}}]}";
 
         // Act
-        List<MedicineWithReminders> result = jsonBackup.parseBackup(validJsonBackup);
+        List<MedicineWithReminders> result = jsonMedicineBackup.parseBackup(validJsonBackup);
 
         // Assert
         assertNull(result);
@@ -252,11 +252,11 @@ public void test_parse_json_backup_missing_fields() {
     @Test
     public void test_parse_invalid_json_backup() {
         // Arrange
-        JSONBackup jsonBackup = new JSONBackup();
+        JSONMedicineBackup jsonMedicineBackup = new JSONMedicineBackup();
         String invalidJsonBackup = "invalid_json";
 
         // Act
-        List<MedicineWithReminders> result = jsonBackup.parseBackup(invalidJsonBackup);
+        List<MedicineWithReminders> result = jsonMedicineBackup.parseBackup(invalidJsonBackup);
 
         // Assert
         assertNull(result);
@@ -265,11 +265,11 @@ public void test_parse_invalid_json_backup() {
     @Test
     public void test_parse_json_backup_with_missing_fields() {
         // Arrange
-        JSONBackup jsonBackup = new JSONBackup();
+        JSONMedicineBackup jsonMedicineBackup = new JSONMedicineBackup();
         String jsonBackupWithMissingFields = "{\"version\": 1}";
 
         // Act
-        List<MedicineWithReminders> result = jsonBackup.parseBackup(jsonBackupWithMissingFields);
+        List<MedicineWithReminders> result = jsonMedicineBackup.parseBackup(jsonBackupWithMissingFields);
 
         // Assert
         assertNull(result);
diff --git a/app/src/test/java/com/futsch1/medtimer/JSONReminderEventBackupUnitTest.java b/app/src/test/java/com/futsch1/medtimer/JSONReminderEventBackupUnitTest.java
new file mode 100644
index 000000000..e38d43f2b
--- /dev/null
+++ b/app/src/test/java/com/futsch1/medtimer/JSONReminderEventBackupUnitTest.java
@@ -0,0 +1,83 @@
+package com.futsch1.medtimer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.Color;
+
+import com.futsch1.medtimer.database.JSONReminderEventBackup;
+import com.futsch1.medtimer.database.ReminderEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class JSONReminderEventBackupUnitTest {
+
+    // creates a backup object with a version number and a medicines array
+    @Test
+    public void test_backup() {
+        JSONReminderEventBackup jsonReminderEventBackup = new JSONReminderEventBackup();
+        List<ReminderEvent> reminderEvents = new ArrayList<>();
+        ReminderEvent reminderEvent = new ReminderEvent();
+        reminderEvent.medicineName = "Medicine A";
+        reminderEvent.color = Color.RED;
+        reminderEvent.useColor = true;
+        reminderEvent.amount = "1";
+        reminderEvent.status = ReminderEvent.ReminderStatus.TAKEN;
+        reminderEvent.remindedTimestamp = 1;
+        reminderEvent.processedTimestamp = 2;
+        reminderEvent.reminderId = 3;
+        reminderEvents.add(reminderEvent);
+
+        String result = jsonReminderEventBackup.createBackup(1, reminderEvents);
+
+        assertNotNull(result);
+
+        // @formatter:off
+        assertEquals("""
+{
+  "version": 1,
+  "list": [
+    {
+      "medicineName": "Medicine A",
+      "amount": "1",
+      "color": -65536,
+      "useColor": true,
+      "status": "TAKEN",
+      "remindedTimestamp": 1,
+      "processedTimestamp": 2,
+      "reminderId": 3
+    }
+  ]
+}""", result);
+        // @formatter:on
+
+        List<ReminderEvent> parsedReminders = jsonReminderEventBackup.parseBackup(result);
+        assertNotNull(parsedReminders);
+        compareListReminderEvents(parsedReminders, reminderEvents);
+    }
+
+    private void compareListReminderEvents(List<ReminderEvent> actual, List<ReminderEvent> expected) {
+        assertEquals(expected.size(), actual.size());
+        for (int i = 0; i < actual.size(); i++) {
+            compareReminderEvent(actual.get(i), expected.get(i));
+        }
+    }
+
+    private void compareReminderEvent(ReminderEvent reminderEvent1, ReminderEvent reminderEvent2) {
+        assertEquals(reminderEvent1.medicineName, reminderEvent2.medicineName);
+        assertEquals(reminderEvent1.color, reminderEvent2.color);
+        assertEquals(reminderEvent1.useColor, reminderEvent2.useColor);
+        assertEquals(reminderEvent1.amount, reminderEvent2.amount);
+        assertEquals(reminderEvent1.status, reminderEvent2.status);
+        assertEquals(reminderEvent1.remindedTimestamp, reminderEvent2.remindedTimestamp);
+        assertEquals(reminderEvent1.processedTimestamp, reminderEvent2.processedTimestamp);
+        assertEquals(reminderEvent1.reminderId, reminderEvent2.reminderId);
+    }
+}
+