diff --git a/README.md b/README.md index 86066b90..c26c2906 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ MediTrak is an Android application designed to make it easier for users to keep + Android 8.0 Oreo or newer + 15 MB storage +## Languages + + - English (default) + - German by [uDEV2019](https://github.com/uDEV2019) + ## Building the App It is strongly recommended to use [Android Studio](https://developer.android.com/studio) to build and test this application. diff --git a/app/build.gradle b/app/build.gradle index 978abcf1..557d8bda 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,14 +3,24 @@ plugins { } android { + dependenciesInfo { + // Disables dependency metadata when building APKs. + includeInApk = false + // Disables dependency metadata when building Android App Bundles. + includeInBundle = false + } + defaultConfig { applicationId "projects.medicationtracker" minSdkVersion 26 targetSdkVersion 33 - compileSdk 33 - versionCode 15 - versionName "0.11.4" - + compileSdk 34 + + versionCode 16 + versionName "0.11.5" + + resConfigs('en', 'de') + ndk { abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } @@ -45,7 +55,7 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.9.0' + implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.preference:preference:1.2.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ca02332b..f5e12b14 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,18 +7,15 @@ - - + + + android:theme="@style/Theme.MedicationTracker" + android:localeConfig="@xml/locales_config" > + + + - \ No newline at end of file diff --git a/app/src/main/cpp/DatabaseController/DatabaseController.cpp b/app/src/main/cpp/DatabaseController/DatabaseController.cpp index 11136aca..26d0ce1f 100644 --- a/app/src/main/cpp/DatabaseController/DatabaseController.cpp +++ b/app/src/main/cpp/DatabaseController/DatabaseController.cpp @@ -166,9 +166,16 @@ void DatabaseController::exportJSON( manager.exportData(exportFilePath, ignoreTables); } -void DatabaseController::importJSON( +void DatabaseController::importJSONFile( const string &importFilePath, const vector &ignoreTables ) { - manager.importData(importFilePath, ignoreTables); + manager.importDataFromFile(importFilePath, ignoreTables); +} + +void DatabaseController::importJSONString( + string &data, + const vector &ignoreTables +) { + manager.importData(data, ignoreTables); } diff --git a/app/src/main/cpp/DatabaseController/DatabaseController.h b/app/src/main/cpp/DatabaseController/DatabaseController.h index fbc8350b..a71680c0 100644 --- a/app/src/main/cpp/DatabaseController/DatabaseController.h +++ b/app/src/main/cpp/DatabaseController/DatabaseController.h @@ -14,12 +14,12 @@ using namespace std; namespace TimeFormats { const string _12_HOUR = "hh:mm a"; const string _24_HOUR = "HH:mm"; -}; +} namespace DateFormats { const string MM_DD_YYYY = "MM/dd/yyyy"; const string DD_MM_YYYY = "dd/MM/yyyy"; -}; +} class DatabaseController { private: @@ -148,7 +148,15 @@ class DatabaseController { * column that does not exist in the provided database/table. * @param importFilePath Path to JSON file storing data to import. */ - void importJSON(const string& importFilePath, const vector& ignoreTables = {}); + void importJSONFile(const string& importFilePath, const vector& ignoreTables = {}); + + /** + * Imports data from JSON file and writes it to database, + * throws an error if attempting to write to a table or + * column that does not exist in the provided database/table. + * @param importData JSON formatted string containing data to import + */ + void importJSONString(string& importData, const vector& ignoreTables = {}); }; diff --git a/app/src/main/cpp/DbManager/DbManager.cpp b/app/src/main/cpp/DbManager/DbManager.cpp index 0247d7fc..49265fbd 100644 --- a/app/src/main/cpp/DbManager/DbManager.cpp +++ b/app/src/main/cpp/DbManager/DbManager.cpp @@ -345,16 +345,22 @@ void DbManager::exportData(const string& exportFilePath, const vector& i map>> data = getAllRowFromAllTables(ignoreTables); string outData; + outFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + try { outFile.open(exportFilePath, fstream::trunc); + } catch (system_error& error) { + const string errMessage = "File failed to open at '" + exportFilePath + + "' with error '" + error.code().message() + "'" + + " error number: " + to_string(errno); + + cerr << errMessage << endl; - if (!outFile.is_open()) { - throw runtime_error("Could not open file: " + exportFilePath); + if (outFile.is_open()) { + outFile.close(); } - } catch (exception& e) { - cerr << e.what() << endl; - throw runtime_error("Could not open file: " + exportFilePath); + throw runtime_error(errMessage); } outFile << "{"; @@ -393,32 +399,49 @@ void DbManager::exportData(const string& exportFilePath, const vector& i outFile.close(); } -void DbManager::importData(const std::string &importFilePath, const vector& ignoreTables) { - fstream fin; +void DbManager::importDataFromFile(const std::string &importFilePath, const vector& ignoreTables) { string inData; - map>> data; - vector tables = getTables(ignoreTables); - stringstream importQuery; - char* err; + ifstream fin; + + fin.exceptions(std::ifstream::failbit | std::ifstream::badbit); try { if (importFilePath.substr(importFilePath.find_last_of('.') + 1) != "json") { throw runtime_error("Provided file is not a JSON file"); } - fin.open(importFilePath); - - if (!fin.is_open()) { throw runtime_error("Import file failed to open"); } + fin.open(importFilePath, ios::in); inData = string(istreambuf_iterator{fin}, {}); fin.close(); + } catch (system_error& error) { + const string errMessage = "File failed to open at '" + importFilePath + + "' with error '" + error.code().message() + "'" + + " error number: " + to_string(errno); + + cerr << errMessage << endl; + + if (fin.is_open()) { + fin.close(); + } + + throw runtime_error(errMessage); } catch (runtime_error& error) { cerr << error.what() << ": " << importFilePath << endl; throw error; } + importData(inData, ignoreTables); +} + +void DbManager::importData(string& inData, const vector& ignoreTables) { + map>> data; + vector tables = getTables(ignoreTables); + stringstream importQuery; + char* err; + // Remove unneeded chars inData.erase( remove_if(inData.begin(), inData.end(), [](unsigned char x) { return std::isspace(x); }), diff --git a/app/src/main/cpp/DbManager/DbManager.h b/app/src/main/cpp/DbManager/DbManager.h index 183726e8..c7dd46b3 100644 --- a/app/src/main/cpp/DbManager/DbManager.h +++ b/app/src/main/cpp/DbManager/DbManager.h @@ -160,7 +160,14 @@ class DbManager { * column that does not exist in the provided database/table. * @param importFilePath Path to JSON file storing data to import. */ - void importData(const string& importFilePath, const vector& ignoreTables = {}); + void importDataFromFile(const string &importFilePath, const vector &ignoreTables); + + /** + * Imports data from a string + * @param inData Data to feed to database, must be in JSON format + * @param ignoreTables Tables not to ignore that may be referenced indata + */ + void importData(string& inData, const vector& ignoreTables = {}); }; diff --git a/app/src/main/cpp/medicationtracker.cpp b/app/src/main/cpp/medicationtracker.cpp index 8ad40616..c3458f4c 100644 --- a/app/src/main/cpp/medicationtracker.cpp +++ b/app/src/main/cpp/medicationtracker.cpp @@ -5,6 +5,7 @@ #include #include #include +#include std::map getValues(jobjectArray arr, JNIEnv *env) { const jclass pair = env->FindClass("android/util/Pair"); @@ -67,13 +68,14 @@ Java_projects_medicationtracker_Helpers_NativeDbHelper_dbImporter( JNIEnv *env, jobject thiz, jstring db_path, - jstring import_path, + jstring file_contents, jobjectArray ignored_tables ) { std::string db = env->GetStringUTFChars(db_path, new jboolean(true)); - std::string importPath = env->GetStringUTFChars(import_path, new jboolean(true)); + std::string fileContents = env->GetStringUTFChars(file_contents, new jboolean(true)); std::vector ignoredTbls; int len = env->GetArrayLength(ignored_tables); + bool success = true; for (int i = 0; i < len; i++) { auto str = (jstring) (env->GetObjectArrayElement(ignored_tables, i)); @@ -85,14 +87,16 @@ Java_projects_medicationtracker_Helpers_NativeDbHelper_dbImporter( DatabaseController controller(db); try { - controller.importJSON(importPath, ignoredTbls); + controller.importJSONString(fileContents, ignoredTbls); } catch (exception &e) { __android_log_write(ANDROID_LOG_ERROR, nullptr, e.what()); - return false; + success = false; } - return true; +// env->ReleaseStringUTFChars(file_contents, fileContents.c_str()); + + return success; } extern "C" diff --git a/app/src/main/java/projects/medicationtracker/Dialogs/BackupDestinationPicker.java b/app/src/main/java/projects/medicationtracker/Dialogs/BackupDestinationPicker.java index 0119e788..aedbb5b8 100644 --- a/app/src/main/java/projects/medicationtracker/Dialogs/BackupDestinationPicker.java +++ b/app/src/main/java/projects/medicationtracker/Dialogs/BackupDestinationPicker.java @@ -1,7 +1,10 @@ package projects.medicationtracker.Dialogs; +import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.text.Editable; @@ -12,6 +15,9 @@ import android.widget.ArrayAdapter; import android.widget.Toast; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; @@ -31,9 +37,7 @@ public class BackupDestinationPicker extends DialogFragment { private String exportDir; private String exportFile; - private MaterialAutoCompleteTextView dirSelector; private TextInputLayout fileNameInputLayout; - private TextInputEditText fileName; private NativeDbHelper nativeDb; @Override @@ -62,6 +66,10 @@ public Dialog onCreateDialog(Bundle savedInstances) { dialog = builder.create(); dialog.show(); + MaterialAutoCompleteTextView dirSelector = dialog.findViewById(R.id.export_dir); + fileNameInputLayout = dialog.findViewById(R.id.export_file_layout); + TextInputEditText fileName = dialog.findViewById(R.id.export_file); + dirs.add(getString(R.string.downloads)); dirs.add(getString(R.string.documents)); @@ -72,24 +80,12 @@ public Dialog onCreateDialog(Bundle savedInstances) { adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line, dirs); - dirSelector = dialog.findViewById(R.id.export_dir); - fileNameInputLayout = dialog.findViewById(R.id.export_file_layout); - fileName = dialog.findViewById(R.id.export_file); - dirSelector.setAdapter(adapter); - dirSelector.setText(adapter.getItem(0)); + dirSelector.setText(dirSelector.getAdapter().getItem(0).toString(), false); exportDir = directories[0]; - dirSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - exportDir = directories[position]; - } - - @Override - public void onNothingSelected(AdapterView parent) {} - }); + dirSelector.setOnItemClickListener((parent, view, position, id) -> exportDir = directories[position]); exportFile = "meditrak_" + now.getYear() + "_" @@ -99,11 +95,8 @@ public void onNothingSelected(AdapterView parent) {} fileName.setText(exportFile); fileName.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} @Override public void afterTextChanged(Editable editable) { @@ -134,40 +127,6 @@ public void afterTextChanged(Editable editable) { return dialog; } - @Override - public void onStart() { - final String[] directories; - ArrayAdapter adapter; - ArrayList dirs = new ArrayList<>(); - - super.onStart(); - - dirs.add(getString(R.string.downloads)); - dirs.add(getString(R.string.documents)); - - directories = new String[] { - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(), - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath() - }; - - adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line, dirs); - - dirSelector = getDialog().findViewById(R.id.export_dir); - fileName = getDialog().findViewById(R.id.export_file); - - dirSelector.setAdapter(adapter); - - dirSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - exportDir = directories[position]; - } - - @Override - public void onNothingSelected(AdapterView parent) {} - }); - } - private void onExportClick() { String resMessage; boolean res = nativeDb.dbExport( diff --git a/app/src/main/java/projects/medicationtracker/Helpers/NativeDbHelper.java b/app/src/main/java/projects/medicationtracker/Helpers/NativeDbHelper.java index 044940d3..eed994b9 100644 --- a/app/src/main/java/projects/medicationtracker/Helpers/NativeDbHelper.java +++ b/app/src/main/java/projects/medicationtracker/Helpers/NativeDbHelper.java @@ -79,12 +79,12 @@ public boolean dbExport(String exportPath, String[] ignoredTables) { /** * Imports a database - * @param importPath Where to find import file + * @param fileContents Contents of import file * @param ignoredTables Tables to ignore while importing * @return true if import succeeded */ - public boolean dbImport(String importPath, String[] ignoredTables) { - return dbImporter(dbPath, importPath, ignoredTables); + public boolean dbImport(String fileContents, String[] ignoredTables) { + return dbImporter(dbPath, fileContents, ignoredTables); } private native void dbCreate(String dbPath); @@ -93,5 +93,5 @@ public boolean dbImport(String importPath, String[] ignoredTables) { private native boolean update(String dbPath, String table, Pair[] values, Pair[] where); private native long delete(String dbPath, String table, Pair[] values); private native boolean dbExporter(String databaseName, String exportDirectory, String[] ignoredTables); - private native boolean dbImporter(String dbPath, String importPath, String[] ignoredTables); + private native boolean dbImporter(String dbPath, String fileContents, String[] ignoredTables); } diff --git a/app/src/main/java/projects/medicationtracker/MainActivity.java b/app/src/main/java/projects/medicationtracker/MainActivity.java index 31879686..b4976a4a 100644 --- a/app/src/main/java/projects/medicationtracker/MainActivity.java +++ b/app/src/main/java/projects/medicationtracker/MainActivity.java @@ -16,15 +16,15 @@ import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Pair; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.ScrollView; -import android.widget.Spinner; import android.widget.TextView; import androidx.activity.result.ActivityResultLauncher; @@ -33,6 +33,9 @@ import androidx.appcompat.app.AppCompatDelegate; import androidx.fragment.app.FragmentContainerView; +import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.google.android.material.textfield.TextInputLayout; + import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -53,9 +56,10 @@ public class MainActivity extends AppCompatActivity { public static String dbDir; public static Bundle preferences; private final DBHelper db = new DBHelper(this); - private NativeDbHelper nativeDb; private LinearLayout scheduleLayout; private LocalDate aDayThisWeek; + private NativeDbHelper nativeDb; + private TextInputLayout namesLayout; private final ActivityResultLauncher notificationPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> db.seenPermissionRequest(SEEN_NOTIFICATION_REQUEST) @@ -108,6 +112,8 @@ protected void onCreate(Bundle savedInstanceState) { notificationPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS); } + namesLayout = findViewById(R.id.names_layout_main); + createMainActivityViews(); } @@ -166,14 +172,14 @@ public void onSettingsClick(MenuItem item) { public void createMainActivityViews() { TextView noMeds = findViewById(R.id.noMeds); ScrollView scheduleScrollView = findViewById(R.id.scheduleScrollView); - Spinner patientNames = findViewById(R.id.patientSpinner); + MaterialAutoCompleteTextView patientNames = findViewById(R.id.patientSpinner); final String you = getString(R.string.you); // Exit if there are no patients in DB if (db.numberOfRows() == 0) { noMeds.setVisibility(View.VISIBLE); scheduleScrollView.setVisibility(View.GONE); - patientNames.setVisibility(View.GONE); + namesLayout.setVisibility(View.GONE); this.findViewById(R.id.navButtonLayout).setVisibility(View.GONE); return; } @@ -183,14 +189,15 @@ public void createMainActivityViews() { // Load contents into spinner, or print results for only patient if (db.getPatients().size() == 1) { - patientNames.setVisibility(View.GONE); + namesLayout.setVisibility(View.GONE); createMedicationSchedule(medications, names.get(0)); } else { patientNames.setVisibility(View.VISIBLE); - if (names.contains("ME!")) + if (names.contains("ME!")) { names.set(names.indexOf("ME!"), you); + } ArrayAdapter patientAdapter = new ArrayAdapter<>( this, @@ -199,31 +206,30 @@ public void createMainActivityViews() { ); patientNames.setAdapter(patientAdapter); - // Select "You" by default - if (names.contains(you)) { - for (int i = 0; i < names.size(); i++) { - if (names.get(i).equals(you)) - patientNames.setSelection(i); - } - } + patientNames.addTextChangedListener(new TextWatcher() { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} - patientNames.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + public void afterTextChanged(Editable s) { scheduleLayout.removeAllViews(); - String name = adapterView.getSelectedItem().toString(); + String name = s.toString(); - if (name.equals(you)) + if (name.equals(you)) { name = "ME!"; + } createMedicationSchedule(medications, name); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { + patientNames.clearFocus(); } }); + + if (names.contains(you)) { + patientNames.setText(you, false); + } else { + patientNames.setText(names.get((0)), false); + } } } diff --git a/app/src/main/java/projects/medicationtracker/MyMedications.java b/app/src/main/java/projects/medicationtracker/MyMedications.java index c9c4f86c..21b37f75 100644 --- a/app/src/main/java/projects/medicationtracker/MyMedications.java +++ b/app/src/main/java/projects/medicationtracker/MyMedications.java @@ -2,19 +2,22 @@ import android.content.Intent; import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; import android.view.MenuItem; import android.view.View; -import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.ScrollView; -import android.widget.Spinner; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.FragmentContainerView; +import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.google.android.material.textfield.TextInputLayout; + import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; @@ -53,7 +56,8 @@ protected void onCreate(Bundle savedInstanceState) { scrollMyMeds.setVisibility(View.VISIBLE); } - final Spinner nameSpinner = findViewById(R.id.nameSpinner); + final TextInputLayout namesLayout = findViewById(R.id.names_layout); + final MaterialAutoCompleteTextView namesSelector = findViewById(R.id.nameSpinner); final LinearLayout myMedsLayout = findViewById(R.id.medLayout); ArrayList patientNames = db.getPatients(); @@ -78,7 +82,9 @@ protected void onCreate(Bundle savedInstanceState) { createMyMedCards(medication, myMedsLayout); } } else if (allMeds.size() > 1) { - String[] patients = allMeds.stream().map(Pair::getFirst).map(p -> Objects.equals(p, "ME!") ? getString(R.string.you) : p).toArray(String[]::new); + String[] patients = allMeds.stream().map(Pair::getFirst).map(p -> + Objects.equals(p, "ME!") ? getString(R.string.you) : p).toArray(String[]::new + ); if (allMeds.stream().allMatch(m -> m.getFirst().equals("ME!"))) { allMeds = allMeds.stream().map(m -> { @@ -91,20 +97,21 @@ protected void onCreate(Bundle savedInstanceState) { } ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, patients); - nameSpinner.setAdapter(adapter); - - nameSpinner.setVisibility(View.VISIBLE); + namesSelector.setAdapter(adapter); - if (Arrays.asList(patients).contains(you)) - nameSpinner.setSelection(adapter.getPosition(you)); + namesLayout.setVisibility(View.VISIBLE); final ArrayList>> allMedsClone = (ArrayList>>) allMeds.clone(); - nameSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + + namesSelector.addTextChangedListener(new TextWatcher() { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} + @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + public void afterTextChanged(Editable s) { myMedsLayout.removeAllViews(); - String selected = adapterView.getSelectedItem().toString(); + String selected = s.toString(); final String patient = selected.equals(you) ? "ME!" : selected; ArrayList patientMeds = allMedsClone.stream().filter( @@ -116,12 +123,16 @@ public void onItemSelected(AdapterView adapterView, View view, int i, long l) createMyMedCards(medication, myMedsLayout); } - } - @Override - public void onNothingSelected(AdapterView adapterView) { + namesSelector.clearFocus(); } }); + + if (Arrays.asList(patients).contains(you)) { + namesSelector.setText(you, false); + } else { + namesSelector.setText(adapter.getItem(0).toString(), false); + } } } @@ -170,7 +181,8 @@ private void createMyMedCards(Medication medication, LinearLayout baseLayout) { bundle.putParcelable("Medication", medication); - getSupportFragmentManager().beginTransaction() + getSupportFragmentManager() + .beginTransaction() .setReorderingAllowed(true) .add((int) medication.getId(), MyMedicationsFragment.class, bundle) .commit(); diff --git a/app/src/main/java/projects/medicationtracker/Settings.java b/app/src/main/java/projects/medicationtracker/Settings.java index e867b6ab..b9d45480 100644 --- a/app/src/main/java/projects/medicationtracker/Settings.java +++ b/app/src/main/java/projects/medicationtracker/Settings.java @@ -9,16 +9,16 @@ import static projects.medicationtracker.MainActivity.preferences; import android.Manifest; +import android.content.ContentResolver; import android.content.Intent; import android.content.pm.PackageManager; -import android.database.Cursor; import android.graphics.Color; +import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Environment; -import android.provider.OpenableColumns; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; @@ -36,6 +36,10 @@ import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import java.io.DataInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Objects; @@ -46,7 +50,7 @@ public class Settings extends AppCompatActivity { private final DBHelper db = new DBHelper(this); - private ActivityResultLauncher chooseFileLauncher; + private ActivityResultLauncher chooseFileLauncher; private final ActivityResultLauncher permissionRequester = registerForActivityResult( new ActivityResultContracts.RequestPermission(), isGranted -> {} @@ -89,39 +93,66 @@ protected void onCreate(Bundle savedInstanceState) { setTimeFormatMenu(); chooseFileLauncher = registerForActivityResult( - new ActivityResultContracts.GetContent(), + new ActivityResultContracts.StartActivityForResult(), result -> { - if (result != null && result.getPath() != null) { - String absPath = ""; - String name; - Cursor cursor = getContentResolver().query(result, null, null, null, null); - - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - - name = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); - } else { - return; - } - - switch (Objects.requireNonNull(result.getAuthority())) { - case "com.android.providers.downloads.documents": - absPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath() + "/" + name; - break; - case "com.android.providers.documents.documents": - absPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath() + "/" + name; - break; - } - - if (nativeDb.dbImport(absPath, new String[]{DBHelper.ANDROID_METADATA, DBHelper.SETTINGS_TABLE})) { - Toast.makeText(this, getString(R.string.import_success), Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(this, getString(R.string.failed_import), Toast.LENGTH_SHORT).show(); + Uri uri = result.getData().getData(); + + if (uri != null && uri.getPath() != null) { + ContentResolver contentResolver = getContentResolver(); + InputStream inputStream; + int size; + String contents; + boolean success = false; + int length; + byte[] bytes; + + try { + try { + inputStream = contentResolver.openInputStream(uri); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + + try { + size = inputStream.available(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + DataInputStream dis = new DataInputStream(inputStream); + bytes = new byte[size]; + + try { + length = dis.read(bytes, 0, size); + } catch (IOException e) { + throw new RuntimeException(e); + } + + inputStream.markSupported(); + + contents = new String(bytes, 0, length); + + try { + inputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + success = nativeDb.dbImport(contents, new String[]{DBHelper.ANDROID_METADATA, DBHelper.SETTINGS_TABLE}); + } catch (Exception e) { + Log.e("Import Error", "Error occurred when reading file"); + } finally { + if (success) { + Toast.makeText(this, getString(R.string.import_success), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, getString(R.string.failed_import), Toast.LENGTH_SHORT).show(); + } } } else { Toast.makeText(this, getString(R.string.could_not_retrieve_file), Toast.LENGTH_SHORT).show(); } - }); + } + ); } /** @@ -144,7 +175,8 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { */ @Override public void onBackPressed() { - Intent intent = new Intent(this,MainActivity.class); + super.onBackPressed(); + Intent intent = new Intent(this, MainActivity.class); finish(); startActivity(intent); } @@ -393,7 +425,7 @@ private ArrayAdapter createTimeFormatMenuAdapter() { * Listener for export data button */ public void onExportClick(View view) { - if (Build.VERSION.SDK_INT <= 32 && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissionRequester.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); } @@ -403,12 +435,15 @@ public void onExportClick(View view) { public void onImportClick(View view) { String type= Build.VERSION.SDK_INT >= 30 ? "application/json" : "*/*"; + Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT); + i.addCategory(Intent.CATEGORY_OPENABLE); + i.setType(type); - if (Build.VERSION.SDK_INT <= 32 && checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissionRequester.launch(Manifest.permission.READ_EXTERNAL_STORAGE); } - chooseFileLauncher.launch(type); + chooseFileLauncher.launch(i); } /** diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5c8b8b80..cb15109a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -24,10 +24,21 @@ android:layout_height="match_parent" android:orientation="vertical"> - + android:layout_height="wrap_content" + android:layout_marginStart="10dp" + android:layout_marginEnd="10dp" + android:layout_marginBottom="15dp" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu"> + + + - + android:layout_height="wrap_content" + android:layout_marginStart="10dp" + android:layout_marginEnd="10dp" + android:layout_marginBottom="15dp" + android:visibility="gone" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu"> + + + + + Ja + Nein + Schließen + Abbrechen + MediTrak + Bearbeiten + Übernehmen + Änderungen speichern + Speichern + Ich + Dein + OK + Löschen + Ich stimme zu + + + Ungültiger Wert angegeben + Der Wert muss eine positive ganze Zahl sein + Der angegebene Wert ist zu groß + Der angegebene Wert darf 50 nicht überschreiten + Der angegebene Wert muss größer als 0 sein + Der angegebene Name ist ungültig + Bitte gib einen Namen an + Bitte gib die Dosierung ein + Bitte gib die Einheiten für dieses Medikament ein + Bitte wähle die Häufigkeit aus + Bitte wähle eine Uhrzeit aus + Bitte wähle ein Startdatum aus. + Bitte gib an, wie oft dieses Medikament täglich eingenommen wird + Bitte gibt an, wie oft das Medikament eingenommen wird. + Bitte wähle eine Zeiteinheit aus. + Bitte gib einen Namen für dieses Medikament ein + Fehlender Dateiname + Die Exportdatei muss die Erweiterung .json haben + + + Mmm dd, yyyy + hh:mm aa + + + Sonntag + Montag + Dienstag + Mittwoch + Donnerstag + Freitag + Samstag + Minuten + Stunden + Tage + Wochen + + + Keine Medikamente gefunden + Medikamentenplan + Keine Medikamente für %1$s + Medikamente können nicht mehr als %1$d Stunden im Voraus eingenommen werden + Nach Bedarf dosieren + Willkommen zu MediTrak! + + Diese Anwendung soll dir dabei helfen, den Überblick über deine Medikamente zu behalten. Es ist + nicht als medizinischer Rat gedacht und die in dieser Anwendung bereitgestellten Informationen + sollten nicht als solche missverstanden werden. Die Ersteller dieser Anwendung haften nicht für + etwaige versäumte Dosen Ihrer Medikamente, aus welchem Grund auch immer. Wenn Du medizinische Hilfe benötigst, + wende dich bitte an einen Arzt. + Durch die Nutzung dieser Anwendung stimmst Du den oben genannten Bedingungen sowie denen der GPL2-Lizenz zu, + unter der die App veröffentlicht wird. \n\n + + Diese Anwendung wird unter der GNU Public License Version 2 veröffentlicht. + Weitere Einzelheiten findest Du weiter unten: + + + + Medikamente hinzufügen + Medikamente bearbeiten + Medikamente löschen + am: %1$s um: %2$s + Bearbeitet: %1$s um: %2$s + Patient geändert von %1$s zu %2$s\n + Name geändert von %1$s zu %2$s\n + Alias %1$s entfernt\n + Alias %1$s hinzugefügt\n + Alias geändert von %1$s zu %2$s\n + Dosierung geändert von %1$s auf %2$s\n + Häufigkeit geändert von %1$s auf %2$s\n + Zeiten pro + Ich + Patient + Andere + Name + Medikamentennamee + Dosierung + Häufigkeit + Eingenommen jeden + Einheiten + Eingenommen + Häufigkeit + Mehrmals pro Tag + Wie benötigt + Täglich + Wöchentlich + Monatlich + + Wer nimmt dieses Medikament?? + Jemand anderes + + Alias für Benachrichtigungen + Wie oft wird das Medikament eingenommen? + Startdatum + Eingenommen um + Zeiteinheit + Benutzerdefinierte Häufigkeit + Hier tippen, um die Uhrzeit einzustellen + Zeiten pro Tag + Hier tippen, um die Uhrzeit einzustellen + Beginnend: + Alias für Medikamente + Alias für Medikamentenbenachrichtigung + Medikamentenalias wird in der Benachrichtigung angezeigt + PPlatzhalter, tippen Sie erneut und klicken Sie dann auf Abbrechen + Deine Medikamente: + Für dieses Medikament liegen keine Hinweise vor + z.B. mg, g, Tropfen + Patientenname: + Keine Aliase + Tippen, um das Datum einzustellen + Um: + Tippen, um die Zeit einzustellen + Änderungen rückwirkend anwenden + Warnung: Änderungen der Häufigkeit können dazu führen, dass zuvor eingenommene Dosen nicht mehr im Zeitplan sichtbar sind. + Änderungen der Dosierung und Häufigkeit können rückwirkend ab der vorherigen Änderung angewendet werden. Möchten Sie diese Änderungen rückwirkend anwenden? + Änderungen übernehmen ab: + Letzte Bearbeitung + Beginn der Medikamenteneinnahme + + + Meine Medikamente + Name des Medikaments: %1$s + Dosierung: %1$s %2$s + Täglich eingenommen um: + Eingenommen, alle: + Alias: %1$s + Eingenommen, seit: %1$s + Nach Bedarf eingenommen. + Alle vorherigen Änderungen wurden überschrieben. + + + Notizen + Notiz hinzufügen + Notiz bearbeiten + + + Einstellungen + Optionen + Lizenz + MediTrak ist eine kostenlose Open-Source-Anwendung, die dir dabei helfen soll, den Überblick über deine Medikamente zu behalten, damit Du die Frage \"Habe ich gestern meine Medikamente eingenommen?\" einfacher beantworten kannst + Copyright © 2022 Adam Guidarini + MediTrak ist freie Software und darf unter den Bedingungen der GNU General Public License, wie von der Free Software Foundation veröffentlicht, entweder Version 2 oder (nach deiner Wahl) später, weitergegeben und/oder geändert werden. + + Dieses Programm ist freie Software; Du kannst es weiterverbreiten und/oder + unter den Bedingungen der GNU General Public License, wie von der + Free Software Foundation veröffentlicht, ändern; entweder Version 2 + der Lizenz oder (nach deiner Wahl) einer späteren Version. + + Dieses Programm wird in der Hoffnung verbreitet, dass es nützlich sein wird, + aber OHNE JEGLICHE GARANTIE; ohne die stillschweigende Garantie von + + MARKTGÄNGIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. Siehe die + weiteren Informationen zur GNU General Public License + + + Sie sollten eine Kopie der GNU General Public License erhalten haben + zusammen mit diesem Programm; Wenn nicht, schreiben Sie an die Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Kann Medikamente nicht mehr einnehmen als + Stunden im Voraus + Einstellungen + Alles löschen + Medikation pausieren + Medikation fortsetzen + Bist Du sicher, dass Du %1$s pausieren möchtest? Es erscheint nicht mehr in deinem Zeitplan, kann aber jederzeit wieder aufgenommen werden. + Wenn Du %1$s fortsetzt, wird dein Zeitplan wiederhergestellt. Bist Du sicher, dass Du fortfahren möchtest? + Benachrichtigungen aktivieren + Zeitlimit im Voraus deaktivieren + Thema + Hell + Dunkel + System + Grenze, bis zu der Medikamente nicht eingenommen werden dürfen: + Exportieren + Importieren + Daten + Export-Verzeichnis + Export-Dateiname + Downloads + Dokumente + Daten exportieren + Daten erfolgreich nach %1$s exportiert + Daten erfolgreich importiert + Daten konnten nicht exportiert werden + Daten konnten nicht importiert werden + Die Importdatei konnte nicht abgerufen werden + Benachrichtigungen sind bereits aktiviert + Datumsformat + MM/TT/JJJJ + TT/MM/JJJJ + Zeitformat + 12 Stunden + 24 Stunden + Sprache + + + Noch nicht eingenommen + Nicht eingenommen + Als eingenommen markiert + Dosisinformationen + Eingenommen um + Eingenommen am + Diese Dosis + + + Alle gespeicherten Daten löschen + Alle gespeicherten Daten löschen? Diese Aktion kann nicht rückgängig gemacht werden. + Alle Daten wurden gelöscht + + + Eingenommen + Schlummern + Es ist Zeit, dein %1$s zu nehmen + Es ist Zeit für die %2$s von %1$s + Dosis hinzufügen + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87018a19..7ed8ff07 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -208,6 +208,7 @@ Time Format 12 Hour 24 Hour + Language Not yet taken diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml new file mode 100644 index 00000000..e04caba1 --- /dev/null +++ b/app/src/main/res/xml/locales_config.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt new file mode 100644 index 00000000..d9c3262e --- /dev/null +++ b/fastlane/metadata/android/de-DE/full_description.txt @@ -0,0 +1 @@ +

MediTrak ist eine Android-App, die es Benutzern erleichtern soll, den Überblick über ihre Medikamente zu behalten und sich an deren Einnahme zu erinnern. Die App bietet die Möglichkeit, Medikamente für mehrere Patienten hinzuzufügen, Medikamentenerinnerungen in verschiedenen Intervallen festzulegen und Notizen zu einem Medikament zu machen, um eventuelle Nebenwirkungen aufzuzeichnen. Alle Benutzerdaten werden lokal gespeichert und weder an den Entwickler noch an andere Dritte weitergegeben.

diff --git a/fastlane/metadata/android/de-DE/short_description.txt b/fastlane/metadata/android/de-DE/short_description.txt new file mode 100644 index 00000000..c94d52db --- /dev/null +++ b/fastlane/metadata/android/de-DE/short_description.txt @@ -0,0 +1 @@ +Behalten Sie den Überblick über Ihre Medikamente