From 4042320106b3768a9cf266229f51a43764528346 Mon Sep 17 00:00:00 2001 From: bitscuit Date: Sat, 6 Mar 2021 13:13:51 +0100 Subject: [PATCH] Initial code upload --- .gitignore | 15 + .idea/.gitignore | 3 + .idea/codeStyles/Project.xml | 116 +++++ .idea/compiler.xml | 6 + .idea/jarRepositories.xml | 25 + .idea/misc.xml | 9 + .idea/runConfigurations.xml | 12 + app/.gitignore | 1 + app/build.gradle | 31 ++ app/proguard-rules.pro | 21 + .../calendar/ExampleInstrumentedTest.java | 27 + app/src/main/AndroidManifest.xml | 56 ++ .../calendar/Activities/EditActivity.java | 490 ++++++++++++++++++ .../calendar/Activities/MainActivity.java | 223 ++++++++ .../calendar/Activities/SettingsActivity.java | 383 ++++++++++++++ .../BroadcastReceivers/AlarmReceiver.java | 72 +++ .../BroadcastReceivers/BootReceiver.java | 55 ++ .../BroadcastReceivers/OverviewReceiver.java | 79 +++ .../BroadcastReceivers/SleepReceiver.java | 63 +++ .../bitscuit/calendar/Util/ListAdapter.java | 85 +++ .../be/bitscuit/calendar/Util/TaskData.java | 84 +++ .../be/bitscuit/calendar/Util/TaskUtil.java | 326 ++++++++++++ .../be/bitscuit/calendar/Util/UserPrefs.java | 73 +++ .../java/be/bitscuit/calendar/Util/Util.java | 39 ++ .../drawable-v24/ic_launcher_foreground.xml | 34 ++ app/src/main/res/drawable/ic_add.png | Bin 0 -> 102 bytes app/src/main/res/drawable/ic_alarm.png | Bin 0 -> 1383 bytes app/src/main/res/drawable/ic_back_arrow.png | Bin 0 -> 155 bytes app/src/main/res/drawable/ic_calendar.png | Bin 0 -> 360 bytes app/src/main/res/drawable/ic_calendar_2.png | Bin 0 -> 336 bytes .../res/drawable/ic_launcher_background.xml | 170 ++++++ app/src/main/res/drawable/ic_nav_back.png | Bin 0 -> 219 bytes app/src/main/res/drawable/ic_nav_next.png | Bin 0 -> 220 bytes app/src/main/res/layout/activity_edit.xml | 232 +++++++++ app/src/main/res/layout/activity_main.xml | 69 +++ app/src/main/res/layout/activity_settings.xml | 198 +++++++ app/src/main/res/layout/list_item.xml | 39 ++ app/src/main/res/menu/menu_edit.xml | 20 + app/src/main/res/menu/menu_main.xml | 21 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2471 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4360 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1395 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2430 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3130 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5607 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 5438 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10348 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 6959 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 13084 bytes app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 12 + .../be/bitscuit/calendar/ExampleUnitTest.java | 17 + build.gradle | 27 + gradle.properties | 20 + gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++++ gradlew.bat | 84 +++ settings.gradle | 2 + 59 files changed, 3426 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/be/bitscuit/calendar/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/be/bitscuit/calendar/Activities/EditActivity.java create mode 100644 app/src/main/java/be/bitscuit/calendar/Activities/MainActivity.java create mode 100644 app/src/main/java/be/bitscuit/calendar/Activities/SettingsActivity.java create mode 100644 app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/AlarmReceiver.java create mode 100644 app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/BootReceiver.java create mode 100644 app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/OverviewReceiver.java create mode 100644 app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/SleepReceiver.java create mode 100644 app/src/main/java/be/bitscuit/calendar/Util/ListAdapter.java create mode 100644 app/src/main/java/be/bitscuit/calendar/Util/TaskData.java create mode 100644 app/src/main/java/be/bitscuit/calendar/Util/TaskUtil.java create mode 100644 app/src/main/java/be/bitscuit/calendar/Util/UserPrefs.java create mode 100644 app/src/main/java/be/bitscuit/calendar/Util/Util.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_add.png create mode 100644 app/src/main/res/drawable/ic_alarm.png create mode 100644 app/src/main/res/drawable/ic_back_arrow.png create mode 100644 app/src/main/res/drawable/ic_calendar.png create mode 100644 app/src/main/res/drawable/ic_calendar_2.png create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_nav_back.png create mode 100644 app/src/main/res/drawable/ic_nav_next.png create mode 100644 app/src/main/res/layout/activity_edit.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_settings.xml create mode 100644 app/src/main/res/layout/list_item.xml create mode 100644 app/src/main/res/menu/menu_edit.xml create mode 100644 app/src/main/res/menu/menu_main.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/be/bitscuit/calendar/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore index ddbbaa3..e721e41 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,18 @@ lint/tmp/ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +# ------> Pregenerated Android Studio rules +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..61a9130 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d5d35ec --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..cc6e871 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "be.bitscuit.calendar" + minSdkVersion 15 + targetSdkVersion 29 + versionCode 4 + versionName "1.1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation 'androidx.recyclerview:recyclerview:1.1.0' + +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/be/bitscuit/calendar/ExampleInstrumentedTest.java b/app/src/androidTest/java/be/bitscuit/calendar/ExampleInstrumentedTest.java new file mode 100644 index 0000000..297becb --- /dev/null +++ b/app/src/androidTest/java/be/bitscuit/calendar/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package be.bitscuit.calendar; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("be.bitscuit.calendar", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cb239c1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/be/bitscuit/calendar/Activities/EditActivity.java b/app/src/main/java/be/bitscuit/calendar/Activities/EditActivity.java new file mode 100644 index 0000000..571fd8f --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/Activities/EditActivity.java @@ -0,0 +1,490 @@ +package be.bitscuit.calendar.Activities; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import android.app.AlertDialog; +import android.app.DatePickerDialog; +import android.app.TimePickerDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.CompoundButton; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.TimePicker; + +import java.util.Calendar; + +import be.bitscuit.calendar.R; +import be.bitscuit.calendar.Util.TaskData; +import be.bitscuit.calendar.Util.TaskUtil; +import be.bitscuit.calendar.Util.Util; + +public class EditActivity extends AppCompatActivity { + + private EditText editTitle, editDateStart, editDateEnd, editTimeStart, editTimeEnd, editNotify, + editRepeatEvery, editRepeatTimes, editDescription; + private Switch switchRepeat; + private TextView labelRepeatEvery, labelRepeatTimes, labelRepeat; + + private TaskData taskData; + private TaskUtil taskUtil; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit); + + int taskID = getIntent().getIntExtra("TASK_ID", 0); + + //Init vars + taskUtil = new TaskUtil(this); + taskData = taskID == 0 ? new TaskData() : taskUtil.getByTaskID(taskID); + + if(taskID == 0) {//Only set time when creating new task + long taskTimeStart = getIntent().getLongExtra("TASK_TIME_START", System.currentTimeMillis()); + taskData.setTimeStart(taskTimeStart); + taskData.setTimeEnd(taskTimeStart + 3600000); + taskData.setTimeNotify(0);//Default no reminder + } + + //Set toolbar + Toolbar toolbar = findViewById(R.id.edit_toolbar); + setSupportActionBar(toolbar); + setTitle( (taskID == 0?"Add":"Edit") + " Appointment"); + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + + //Init views + editTitle = findViewById(R.id.edit_edit_title); + editDescription = findViewById(R.id.edit_edit_description); + editDateStart = findViewById(R.id.edit_edit_date_start); + editDateEnd = findViewById(R.id.edit_edit_date_end); + editTimeStart = findViewById(R.id.edit_edit_time_start); + editTimeEnd = findViewById(R.id.edit_edit_time_end); + editNotify = findViewById(R.id.edit_edit_notify); + editRepeatEvery = findViewById(R.id.edit_edit_repeat_every); + editRepeatTimes = findViewById(R.id.edit_edit_repeat_times); + switchRepeat = findViewById(R.id.edit_switch_batch); + labelRepeatEvery = findViewById(R.id.edit_label_repeat_every); + labelRepeatTimes = findViewById(R.id.edit_label_repeat_times); + labelRepeat = findViewById(R.id.edit_label_batch); + + //Edit view properties + editTitle.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if(i == EditorInfo.IME_ACTION_DONE) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(editTitle.getWindowToken(), 0); + editTitle.clearFocus(); + return true; + } + return false; + } + }); + editDescription.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if(i == EditorInfo.IME_ACTION_DONE) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(editDescription.getWindowToken(), 0); + editDescription.clearFocus(); + return true; + } + return false; + } + }); + editDateStart.setFocusable(false); + editDateStart.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeStart()); + new DatePickerDialog(view.getContext(), new DatePickerDialog.OnDateSetListener() { + @Override + public void onDateSet(DatePicker datePicker, int i, int i1, int i2) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeStart()); + int h = calendar.get(Calendar.HOUR_OF_DAY); + int m = calendar.get(Calendar.MINUTE); + calendar.clear(); + calendar.set(i, i1, i2, h, m); + taskData.setTimeStart(calendar.getTimeInMillis()); + + //Update text + editDateStart.setText(Util.formatMillis(taskData.getTimeStart(), "dd/MM/yyyy")); + + //Update timeDateEnd if necessary + if(taskData.getTimeEnd() < taskData.getTimeStart()){ + taskData.setTimeEnd(taskData.getTimeStart() + 3600000); + + //Update text + editDateEnd.setText(Util.formatMillis(taskData.getTimeEnd(), "dd/MM/yyyy")); + editTimeEnd.setText(Util.formatMillis(taskData.getTimeEnd(), "HH:mm")); + } + } + }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)) + .show(); + } + }); + editDateEnd.setFocusable(false); + editDateEnd.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeEnd()); + new DatePickerDialog(view.getContext(), new DatePickerDialog.OnDateSetListener() { + @Override + public void onDateSet(DatePicker datePicker, int i, int i1, int i2) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeEnd()); + int h = calendar.get(Calendar.HOUR_OF_DAY); + int m = calendar.get(Calendar.MINUTE); + calendar.clear(); + calendar.set(i, i1, i2, h, m); + taskData.setTimeEnd(calendar.getTimeInMillis()); + + //Update text + editDateEnd.setText(Util.formatMillis(taskData.getTimeEnd(), "dd/MM/yyyy")); + } + }, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)) + .show(); + } + }); + editTimeStart.setFocusable(false); + editTimeStart.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeStart()); + new TimePickerDialog(view.getContext(), new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker timePicker, int i, int i1) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeStart()); + int y = calendar.get(Calendar.YEAR); + int m = calendar.get(Calendar.MONTH); + int d = calendar.get(Calendar.DAY_OF_MONTH); + calendar.clear(); + calendar.set(y, m, d, i, i1); + taskData.setTimeStart(calendar.getTimeInMillis()); + + //Update text + editTimeStart.setText(Util.formatMillis(taskData.getTimeStart(), "HH:mm")); + + //Check if needs to update timeEnd as well + if(taskData.getTimeEnd() < taskData.getTimeStart()){ + taskData.setTimeEnd(taskData.getTimeStart() + 3600000); + + //Update text + editDateEnd.setText(Util.formatMillis(taskData.getTimeEnd(), "dd/MM/yyyy")); + editTimeEnd.setText(Util.formatMillis(taskData.getTimeEnd(), "HH:mm")); + } + } + }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true) + .show(); + } + }); + editTimeEnd.setFocusable(false); + editTimeEnd.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeEnd()); + new TimePickerDialog(view.getContext(), new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker timePicker, int i, int i1) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeEnd()); + int y = calendar.get(Calendar.YEAR); + int m = calendar.get(Calendar.MONTH); + int d = calendar.get(Calendar.DAY_OF_MONTH); + calendar.clear(); + calendar.set(y, m, d, i, i1); + taskData.setTimeEnd(calendar.getTimeInMillis()); + + //Update text + editTimeEnd.setText(Util.formatMillis(taskData.getTimeEnd(), "HH:mm")); + } + }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), true) + .show(); + } + }); + editNotify.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if(i == EditorInfo.IME_ACTION_DONE) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(editNotify.getWindowToken(), 0); + editNotify.clearFocus(); + return true; + } + return false; + } + }); + switchRepeat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + editRepeatEvery.setEnabled(b); + editRepeatTimes.setEnabled(b); + labelRepeatEvery.setEnabled(b); + labelRepeatTimes.setEnabled(b); + } + }); + + + //Set data + editTitle.setText(taskData.getTitle()); + editDescription.setText(taskData.getDescription()); + editDateStart.setText(Util.formatMillis(taskData.getTimeStart(), "dd/MM/yyyy")); + editDateEnd.setText(Util.formatMillis(taskData.getTimeEnd(), "dd/MM/yyyy")); + editTimeStart.setText(Util.formatMillis(taskData.getTimeStart(), "HH:mm")); + editTimeEnd.setText(Util.formatMillis(taskData.getTimeEnd(), "HH:mm")); + editNotify.setText(taskData.getTimeNotify() == 0?"":""+(taskData.getTimeStart()-taskData.getTimeNotify())/60000); + if(taskData.getTaskID() == 0) {//Can only set repeat if creating new task! + switchRepeat.setChecked(false); + editRepeatEvery.setEnabled(false); + editRepeatTimes.setEnabled(false); + labelRepeatEvery.setEnabled(false); + labelRepeatTimes.setEnabled(false); + } + else{ + switchRepeat.setVisibility(View.GONE); + editRepeatEvery.setVisibility(View.GONE); + editRepeatTimes.setVisibility(View.GONE); + labelRepeatEvery.setVisibility(View.GONE); + labelRepeatTimes.setVisibility(View.GONE); + labelRepeat.setVisibility(View.GONE); + } + + } + + + private void checkAndSave(){ + System.out.println("Check and save!"); + //Reset all error in the views + editTitle.setError(null); + editDescription.setError(null); + editDateEnd.setError(null); + editTimeEnd.setError(null); + editNotify.setError(null); + editRepeatEvery.setError(null); + editRepeatTimes.setError(null); + + //Check if all entries are valid + boolean valid = true; + + //Check title + taskData.setTitle(""+editTitle.getText()); + if(taskData.getTitle().equals("") || taskData.getTitle().equals(" ")){ + editTitle.setError("Required!"); + valid = false; + } + else if(taskData.getTitle().contains(";")){ + editTitle.setError("Can't contain ';'!"); + valid = false; + } + + //Check description + taskData.setDescription(""+editDescription.getText()); + if(taskData.getDescription().contains(";")){ + editDescription.setError("Can't contain ';'!"); + valid = false; + } + + //Check date and time + if(taskData.getTimeStart() >= taskData.getTimeEnd()){ + editDateEnd.setError("Invalid!"); + editTimeEnd.setError("Invalid!"); + valid = false; + } + + //Check notify time + if((""+editNotify.getText()).equals("")) taskData.setTimeNotify(0); + else { + try { + int minutes = Integer.parseInt("" + editNotify.getText()); + + if(minutes < 0){ + editNotify.setError("Must be positive or empty!"); + valid = false; + } + else{ + // When valid minutes -> calculate and save notifyTime + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(taskData.getTimeStart()); + calendar.add(Calendar.MINUTE, -1*minutes); + taskData.setTimeNotify(calendar.getTimeInMillis()); + } + + } catch (Exception e) { + editNotify.setError("Enter a number or leave empty!"); + valid = false; + } + } + + //Check repeat stuff + if(switchRepeat.isChecked()){ + try{ + int repeatEvery = Integer.parseInt(""+editRepeatEvery.getText()); + if(repeatEvery <= 0){ + editRepeatEvery.setError("Must be > zero!"); + valid = false; + } + }catch (Exception e){ + editRepeatEvery.setError("Enter a number!"); + valid = false; + } + try{ + int repeatTimes = Integer.parseInt(""+editRepeatTimes.getText()); + if(repeatTimes <= 1){ + editRepeatTimes.setError("Must be > one!"); + valid = false; + } + }catch (Exception e){ + editRepeatTimes.setError("Enter a number!"); + valid = false; + } + } + + + //Return if not valid + if(!valid) return; + + + + //Save task + if(!switchRepeat.isChecked()) {//When no repeat + boolean newTask = taskData.getTaskID() == 0; + if (newTask) taskData.setTaskID(taskUtil.getNextTaskID()); + + taskUtil.saveTask(taskData); + } + else{ + //When repeat + int repeatEvery = Integer.parseInt(""+editRepeatEvery.getText()); + int repeatTimes = Integer.parseInt(""+editRepeatTimes.getText()); + + long firstTimeStart = taskData.getTimeStart(); + long firstTimeEnd = taskData.getTimeEnd(); + long firstTimeNotify = taskData.getTimeNotify(); + boolean notify = firstTimeNotify != 0; + + Calendar calendarStart = Calendar.getInstance(); + Calendar calendarEnd = Calendar.getInstance(); + Calendar calendarNotify = Calendar.getInstance(); + calendarStart.setTimeInMillis(firstTimeStart); + calendarEnd.setTimeInMillis(firstTimeEnd); + calendarNotify.setTimeInMillis(firstTimeNotify); + + taskData.setBatchID(taskUtil.getNextBatchID()); + for(int i=0; i can't remove it yet + if(taskData.getTaskID() == 0) { + menu.removeItem(R.id.edit_menu_remove); + } + + //When creating new task or editing task not from batch -> can't remove batch + if(taskData.getTaskID() == 0 || taskData.getBatchID() == -1){ + menu.removeItem(R.id.edit_menu_remove_batch); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.edit_menu_remove: + //Show alert dialog + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Are you sure?"); + builder.setMessage("You're about to remove this appointment."); + builder.setPositiveButton("Remove", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + taskUtil.removeTask(taskData.getTaskID()); + finish(); + } + }); + builder.setNegativeButton("Cancel", null); + AlertDialog dialog = builder.create(); + dialog.show(); + return true; + + case R.id.edit_menu_remove_batch: + //Show alert dialog + builder = new AlertDialog.Builder(this); + builder.setTitle("Are you sure?"); + builder.setMessage("You're about to remove this entire batch of appointments."); + builder.setPositiveButton("Remove", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + //Remove batch + TaskData[] rem = taskUtil.getByBatchID(taskData.getBatchID()); + for(int j=0; j let superclass handle it + return super.onOptionsItemSelected(item); + + } + } + + + public static class DatePickerFragment extends DialogFragment { + + private MainActivity mainActivity; + + public DatePickerFragment(MainActivity mainActivity){ + this.mainActivity = mainActivity; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the shown date as the default date in the picker + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(mainActivity.showDateMillis); + int year = c.get(Calendar.YEAR); + int month = c.get(Calendar.MONTH); + int day = c.get(Calendar.DAY_OF_MONTH); + + // Create a new instance of DatePickerDialog and return it + return new DatePickerDialog(getActivity(), mainActivity, year, month, day); + } + } + + + + public void onDateSet(DatePicker view, int year, int month, int day) { + // Do something with the date chosen by the user + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month); + calendar.set(Calendar.DAY_OF_MONTH, day); + long millis = calendar.getTimeInMillis(); + + //Change list data + showDateMillis = Util.millisToDateMillis(millis); + + updateList(); + } + + + @Override + protected void onResume() { + + //Update list + taskUtil.reload(); + updateList(); + + super.onResume(); + } +} diff --git a/app/src/main/java/be/bitscuit/calendar/Activities/SettingsActivity.java b/app/src/main/java/be/bitscuit/calendar/Activities/SettingsActivity.java new file mode 100644 index 0000000..ccfd01b --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/Activities/SettingsActivity.java @@ -0,0 +1,383 @@ +package be.bitscuit.calendar.Activities; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.app.TimePickerDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.TimePicker; +import android.widget.Toast; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Calendar; +import java.util.Objects; + +import be.bitscuit.calendar.BroadcastReceivers.OverviewReceiver; +import be.bitscuit.calendar.BroadcastReceivers.SleepReceiver; +import be.bitscuit.calendar.R; +import be.bitscuit.calendar.Util.TaskData; +import be.bitscuit.calendar.Util.TaskUtil; +import be.bitscuit.calendar.Util.UserPrefs; +import be.bitscuit.calendar.Util.Util; + +public class SettingsActivity extends AppCompatActivity { + + private static final int RC_CREATE_FILE = 1; + private static final int RC_OPEN_FILE = 2; + + private TextView labelDeleteAfter, labelOverviewTime, labelSleepTime; + private Switch switchDelete, switchOverview, switchSleep; + private EditText editDeleteAfter, editOverviewTime, editSleepTime; + private Button buttonBackupCreate, buttonBackupLoad; + + private UserPrefs userPrefs; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings); + + //Init vars + userPrefs = new UserPrefs(this); + + //Set toolbar + Toolbar toolbar = findViewById(R.id.setting_toolbar); + setSupportActionBar(toolbar); + setTitle("Settings"); + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + + //Set views + switchDelete = findViewById(R.id.settings_switch_delete); + labelDeleteAfter = findViewById(R.id.settings_label_delete_after); + editDeleteAfter = findViewById(R.id.settings_edit_delete_after); + switchOverview = findViewById(R.id.settings_switch_overview); + labelOverviewTime = findViewById(R.id.settings_label_overview_time); + editOverviewTime = findViewById(R.id.settings_edit_overview_time); + switchSleep = findViewById(R.id.settings_switch_sleep); + labelSleepTime = findViewById(R.id.settings_label_sleep_time); + editSleepTime = findViewById(R.id.settings_edit_sleep_time); + buttonBackupCreate = findViewById(R.id.settings_button_backup_create); + buttonBackupLoad = findViewById(R.id.settings_button_backup_load); + + //Edit view properties + switchDelete.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + userPrefs.setAutoDeleteTasks(b); + updateViews(); + } + }); + editDeleteAfter.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if(i == EditorInfo.IME_ACTION_DONE) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(editDeleteAfter.getWindowToken(), 0); + editDeleteAfter.clearFocus(); + return true; + } + return false; + } + }); + editDeleteAfter.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 afterTextChanged(Editable editable) { + //When text changed -> check if valid + try{ + int days = Integer.parseInt(editDeleteAfter.getText()+""); + if(days < 0){ + editDeleteAfter.setError("Invalid!"); + return; + } + userPrefs.setAutoDeleteTasksAfter(days * 86400000); + }catch (Exception e){ + editDeleteAfter.setError("Enter a number!"); + } + } + }); + switchOverview.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + userPrefs.setSendDailyOverview(b); + updateOverviewAlarm(); + updateViews(); + } + }); + editOverviewTime.setFocusable(false); + editOverviewTime.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + long t = userPrefs.getDailyOverviewTime(); + new TimePickerDialog(view.getContext(), new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker timePicker, int i, int i1) { + userPrefs.setDailyOverviewTime(i*3600000 + i1*60000); + + //Set alarm + updateOverviewAlarm(); + + //Update text + updateViews(); + } + }, (int)t/3600000, (int)(t%3600000)/60000, true) + .show(); + } + }); + switchSleep.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + userPrefs.setSendSleepReminder(b); + updateSleepAlarm(); + updateViews(); + } + }); + editSleepTime.setFocusable(false); + editSleepTime.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + long t = userPrefs.getSleepTime(); + new TimePickerDialog(view.getContext(), new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker timePicker, int i, int i1) { + userPrefs.setSleepTime(i*3600000 + i1*60000); + + //Set alarm + updateSleepAlarm(); + + //Update text + updateViews(); + } + }, (int)t/3600000, (int)(t%3600000)/60000, true) + .show(); + } + }); + buttonBackupCreate.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //Open file selector + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { + Toast.makeText(view.getContext(), "Action not supported on API < 19", Toast.LENGTH_SHORT).show(); + return; + } + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/clndr"); + intent.putExtra(Intent.EXTRA_TITLE, "calendar_backup_"+Util.formatMillis(System.currentTimeMillis(), "yyyyMMddHHmmss")+".clndr"); + + startActivityForResult(intent, RC_CREATE_FILE);//Result handled in onActivityResult() + } + }); + buttonBackupLoad.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //Open file selector + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { + Toast.makeText(view.getContext(), "Action not supported on API < 19", Toast.LENGTH_SHORT).show(); + return; + } + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/clndr"); + + startActivityForResult(intent, RC_OPEN_FILE); + } + }); + + //Set view data + updateViews(); + } + + + public void updateViews(){ + switchDelete.setChecked(userPrefs.getAutoDeleteTasks()); + editDeleteAfter.setText(""+userPrefs.getAutoDeleteTasksAfter()/86400000); + labelDeleteAfter.setEnabled(switchDelete.isChecked()); + editDeleteAfter.setEnabled(switchDelete.isChecked()); + switchOverview.setChecked(userPrefs.getSendDailyOverview()); + editOverviewTime.setText(Util.formatMillis(Util.millisToDateMillis(System.currentTimeMillis()) + userPrefs.getDailyOverviewTime(), "HH:mm")); + labelOverviewTime.setEnabled(switchOverview.isChecked()); + editOverviewTime.setEnabled(switchOverview.isChecked()); + switchSleep.setChecked(userPrefs.getSendSleepReminder()); + editSleepTime.setText(Util.formatMillis(Util.millisToDateMillis(System.currentTimeMillis()) + userPrefs.getSleepTime(), "HH:mm")); + labelSleepTime.setEnabled(switchSleep.isChecked()); + editSleepTime.setEnabled(switchSleep.isChecked()); + } + + + public void updateOverviewAlarm(){//Also change in BootReceiver!!! + //Delete old alarm + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + Intent intent = new Intent(getApplicationContext(), OverviewReceiver.class); + PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntent.cancel(); + alarmManager.cancel(pendingIntent); + + //Set new alarm + if(userPrefs.getSendDailyOverview()) { + intent = new Intent(getApplicationContext(), OverviewReceiver.class); + pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0); + long time = Util.millisToDateMillis(System.currentTimeMillis()) + userPrefs.getDailyOverviewTime(); + if (time < System.currentTimeMillis()) + time += 86400000; //When next overview is tomorrow + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, time, 86400000, pendingIntent); + } + } + + public void updateSleepAlarm(){//Also change in BootReceiver!!! + //Delete old alarm + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + Intent intent = new Intent(getApplicationContext(), SleepReceiver.class); + PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntent.cancel(); + alarmManager.cancel(pendingIntent); + + //Set new alarm + if(userPrefs.getSendSleepReminder()) { + intent = new Intent(getApplicationContext(), SleepReceiver.class); + pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0); + long time = Util.millisToDateMillis(System.currentTimeMillis()) + userPrefs.getSleepTime(); + if (time < System.currentTimeMillis()) + time += 86400000; //When next overview is tomorrow + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, time, 86400000, pendingIntent); + } + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (requestCode == RC_CREATE_FILE && resultCode == Activity.RESULT_OK) { + Uri uri = null; + if (data != null) { + uri = data.getData(); + + //Open file and write to it + try{ + TaskUtil taskUtil = new TaskUtil(this); + String text = taskUtil.getBackup(); + writeDocument(uri, text); + Toast.makeText(this, "Created backup file!", Toast.LENGTH_SHORT).show(); + }catch (Exception e){ + Toast.makeText(this, "Error writing file!", Toast.LENGTH_SHORT).show(); + } + } + } + if (requestCode == RC_OPEN_FILE && resultCode == Activity.RESULT_OK) { + Uri uri = null; + if (data != null) { + uri = data.getData(); + + //Open file and read from it + try { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + String text = readTextFromUri(uri); + + TaskUtil taskUtil = new TaskUtil(this); + taskUtil.loadBackup(text); + + Toast.makeText(this, "Backup file loaded!", Toast.LENGTH_SHORT).show(); + } + }catch (Exception e){ + Toast.makeText(this, "Error reading file!", Toast.LENGTH_SHORT).show(); + } + } + } + + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void onBackPressed() { + //Close activity + this.finish(); + } + + + + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + private String readTextFromUri(Uri uri) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + try (InputStream inputStream = + getContentResolver().openInputStream(uri); + BufferedReader reader = new BufferedReader( + new InputStreamReader(Objects.requireNonNull(inputStream)))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line + "\n"); + } + } + return stringBuilder.toString(); + } + + private void writeDocument(Uri uri, String text) { + try { + ParcelFileDescriptor pfd = getContentResolver(). + openFileDescriptor(uri, "w"); + FileOutputStream fileOutputStream = + new FileOutputStream(pfd.getFileDescriptor()); + fileOutputStream.write(text.getBytes()); + fileOutputStream.close(); + pfd.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + +} diff --git a/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/AlarmReceiver.java b/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/AlarmReceiver.java new file mode 100644 index 0000000..660a568 --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/AlarmReceiver.java @@ -0,0 +1,72 @@ +package be.bitscuit.calendar.BroadcastReceivers; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.media.RingtoneManager; +import android.os.Build; + +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import be.bitscuit.calendar.Activities.MainActivity; +import be.bitscuit.calendar.R; +import be.bitscuit.calendar.Util.TaskData; +import be.bitscuit.calendar.Util.TaskUtil; +import be.bitscuit.calendar.Util.Util; + +public class AlarmReceiver extends BroadcastReceiver { + + private final static String CHANNEL_ID = "be.bitscuit.calendar.NOTIFICATIONS_REMINDER"; + + @Override + public void onReceive(Context context, Intent intent) { + + int taskID = intent.getIntExtra("TASK_ID", 0); + if(taskID == 0) return;//Something went wrong while setting the alarm + + TaskUtil taskUtil = new TaskUtil(context); + TaskData taskData = taskUtil.getByTaskID(taskID); + if(taskData == null) return;//Task doesn't exist + + + // Create the NotificationChannel, but only on API 26+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String name = "Appointment Reminders"; + String description = "Receive reminders for important appointments."; + int importance = NotificationManager.IMPORTANCE_HIGH; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); + channel.setDescription(description); + + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + + + //Create notification + Intent i = new Intent(context, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, i, 0); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_alarm) + .setContentTitle(taskData.getTitle()) + .setContentText("at "+ Util.formatMillis(taskData.getTimeStart(), "HH:mm")) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .setVibrate(new long[]{0, 250, 250, 250, 250, 250, 250}) + .setLights(Color.RED, 500, 1500) + .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); + + + NotificationManagerCompat nmc = NotificationManagerCompat.from(context); + nmc.notify(taskData.getTaskID(), builder.build()); + + + } +} diff --git a/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/BootReceiver.java b/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/BootReceiver.java new file mode 100644 index 0000000..1cd0ee7 --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/BootReceiver.java @@ -0,0 +1,55 @@ +package be.bitscuit.calendar.BroadcastReceivers; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import be.bitscuit.calendar.Util.TaskData; +import be.bitscuit.calendar.Util.TaskUtil; +import be.bitscuit.calendar.Util.UserPrefs; +import be.bitscuit.calendar.Util.Util; + +import static android.content.Context.ALARM_SERVICE; + +public class BootReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + + //Set all the alarms, they were removed due to reboot + TaskUtil taskUtil = new TaskUtil(context); + TaskData[] tasks = taskUtil.getAllTasks(); + + for(int i=0; i System.currentTimeMillis()){//Only set alarm if in future + taskUtil.setAlarmForTask(tasks[i]); + } + } + + + //Set new alarm for overview + UserPrefs userPrefs = new UserPrefs(context); + if(userPrefs.getSendDailyOverview()) { + AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); + Intent i = new Intent(context.getApplicationContext(), OverviewReceiver.class); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, i, 0); + long time = Util.millisToDateMillis(System.currentTimeMillis()) + userPrefs.getDailyOverviewTime(); + if (time < System.currentTimeMillis()) time += 86400000; //When next overview is tomorrow + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, time, 86400000, pendingIntent); + } + + //Set new alarm for sleep reminder + if(userPrefs.getSendSleepReminder()) { + AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); + Intent i = new Intent(context.getApplicationContext(), SleepReceiver.class); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, i, 0); + long time = Util.millisToDateMillis(System.currentTimeMillis()) + userPrefs.getSleepTime(); + if (time < System.currentTimeMillis()) time += 86400000; //When next overview is tomorrow + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, time, 86400000, pendingIntent); + } + + + } +} diff --git a/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/OverviewReceiver.java b/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/OverviewReceiver.java new file mode 100644 index 0000000..acfee52 --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/OverviewReceiver.java @@ -0,0 +1,79 @@ +package be.bitscuit.calendar.BroadcastReceivers; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.media.RingtoneManager; +import android.os.Build; +import android.widget.Toast; + +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import be.bitscuit.calendar.Activities.MainActivity; +import be.bitscuit.calendar.R; +import be.bitscuit.calendar.Util.TaskData; +import be.bitscuit.calendar.Util.TaskUtil; +import be.bitscuit.calendar.Util.Util; + +public class OverviewReceiver extends BroadcastReceiver { + + private final static String CHANNEL_ID = "be.bitscuit.calendar.NOTIFICATIONS_OVERVIEW"; + + @Override + public void onReceive(Context context, Intent intent) { + + //Load tasks + TaskUtil taskUtil = new TaskUtil(context); + TaskData[] tasks = taskUtil.getTaskDataForDate(Util.millisToDateMillis(System.currentTimeMillis())); + + //Create notification text + String text = ""; + for(int i=0; i= Build.VERSION_CODES.O) { + String name = "Daily Appointment Overview"; + String description = "Receive a daily notification with the appointments of that day."; + int importance = NotificationManager.IMPORTANCE_HIGH; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); + channel.setDescription(description); + + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + + + //Create notification + Intent i = new Intent(context, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, i, 0); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_calendar_2) + .setContentTitle("Today's Overview") + .setContentText(text) + .setStyle(new NotificationCompat.BigTextStyle() + .bigText(text)) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); + + + NotificationManagerCompat nmc = NotificationManagerCompat.from(context); + nmc.notify(999, builder.build()); + + } +} diff --git a/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/SleepReceiver.java b/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/SleepReceiver.java new file mode 100644 index 0000000..5a9d70a --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/BroadcastReceivers/SleepReceiver.java @@ -0,0 +1,63 @@ +package be.bitscuit.calendar.BroadcastReceivers; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.RingtoneManager; +import android.os.Build; + +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import be.bitscuit.calendar.Activities.MainActivity; +import be.bitscuit.calendar.R; +import be.bitscuit.calendar.Util.TaskData; +import be.bitscuit.calendar.Util.TaskUtil; +import be.bitscuit.calendar.Util.Util; + +public class SleepReceiver extends BroadcastReceiver { + + private final static String CHANNEL_ID = "be.bitscuit.calendar.NOTIFICATIONS_SLEEP"; + + @Override + public void onReceive(Context context, Intent intent) { + + //Create notification text + String title = "It's getting late..."; + String text = "Don't forget to sleep."; + + + + // Create the NotificationChannel, but only on API 26+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String name = "Sleep Reminder"; + String description = "Receive a reminder notification when it's getting late."; + int importance = NotificationManager.IMPORTANCE_HIGH; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); + channel.setDescription(description); + + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + + + //Create notification + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_calendar_2) + .setContentTitle(title) + .setContentText(text) + .setStyle(new NotificationCompat.BigTextStyle() + .bigText(text)) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); + + + NotificationManagerCompat nmc = NotificationManagerCompat.from(context); + nmc.notify(998, builder.build()); + + } +} diff --git a/app/src/main/java/be/bitscuit/calendar/Util/ListAdapter.java b/app/src/main/java/be/bitscuit/calendar/Util/ListAdapter.java new file mode 100644 index 0000000..3da93c2 --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/Util/ListAdapter.java @@ -0,0 +1,85 @@ +package be.bitscuit.calendar.Util; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; + +import be.bitscuit.calendar.R; + +public class ListAdapter extends RecyclerView.Adapter { + + private TaskData[] taskData; + private OnTaskClickListener onTaskClickListener; + + public ListAdapter(TaskData[] data){ + this.taskData = data; + } + + public void changeData(TaskData[] data){ + this.taskData = data; + notifyDataSetChanged(); + } + + public void setOnTaskClickListener(OnTaskClickListener listener){ + onTaskClickListener = listener; + } + + + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + //Create a new ViewHolder + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item, parent, false); + + ViewHolder holder = new ViewHolder(view); + return holder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + //Set data of viewHolder + TaskData data = taskData[position]; + holder.titleText.setText(data.getTitle()); + holder.timeText.setText(Util.formatMillis(data.getTimeStart(), "HH:mm") + " - "+ Util.formatMillis(data.getTimeEnd(), "HH:mm")); + + final int taskID = data.getTaskID(); + holder.layout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onTaskClickListener.onClick(view, taskID); + } + }); + } + + @Override + public int getItemCount() { + return taskData.length; + } + + + public static class ViewHolder extends RecyclerView.ViewHolder{ + + public TextView titleText, timeText; + public ConstraintLayout layout; + + public ViewHolder(View view){ + super(view); + + layout = view.findViewById(R.id.list_item_layout); + titleText = view.findViewById(R.id.list_item_text_title); + timeText = view.findViewById(R.id.list_item_text_time); + } + } + + + public interface OnTaskClickListener{ + void onClick(View view, int taskID); + } +} diff --git a/app/src/main/java/be/bitscuit/calendar/Util/TaskData.java b/app/src/main/java/be/bitscuit/calendar/Util/TaskData.java new file mode 100644 index 0000000..54d0c69 --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/Util/TaskData.java @@ -0,0 +1,84 @@ +package be.bitscuit.calendar.Util; + +public class TaskData { + + private int taskID; + private String title, description; + private long timeStart, timeEnd, timeNotify; + private int groupID, batchID; + + public TaskData(){ + taskID = 0; + title = "New Appointment"; + description = ""; + timeStart = System.currentTimeMillis() + 3600000; + timeEnd = timeStart + 3600000; + timeNotify = timeStart - 1800000; + groupID = 0; + batchID = -1; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public long getTimeStart() { + return timeStart; + } + + public void setTimeStart(long timeStart) { + this.timeStart = timeStart; + } + + public long getTimeEnd() { + return timeEnd; + } + + public void setTimeEnd(long timeEnd) { + this.timeEnd = timeEnd; + } + + public int getGroupID() { + return groupID; + } + + public void setGroupID(int groupID) { + this.groupID = groupID; + } + + public int getTaskID() { + return taskID; + } + + public void setTaskID(int taskID) { + this.taskID = taskID; + } + + public int getBatchID() { + return batchID; + } + + public void setBatchID(int batchID) { + this.batchID = batchID; + } + + public long getTimeNotify() { + return timeNotify; + } + + public void setTimeNotify(long timeNotify) { + this.timeNotify = timeNotify; + } +} diff --git a/app/src/main/java/be/bitscuit/calendar/Util/TaskUtil.java b/app/src/main/java/be/bitscuit/calendar/Util/TaskUtil.java new file mode 100644 index 0000000..4ea7b38 --- /dev/null +++ b/app/src/main/java/be/bitscuit/calendar/Util/TaskUtil.java @@ -0,0 +1,326 @@ +package be.bitscuit.calendar.Util; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; + +import java.util.ArrayList; +import java.util.Arrays; + +import be.bitscuit.calendar.BroadcastReceivers.AlarmReceiver; + +import static android.content.Context.ALARM_SERVICE; + + +public class TaskUtil { + + private Context context; + private UserPrefs userPrefs; + + private String taskDataRaw; + private TaskData[] taskData; + private int nextTaskID; + private int nextBatchID; + + public TaskUtil(Context context){ + this.context = context; + userPrefs = new UserPrefs(context); + reload(); + } + + + public void reload(){ + System.out.println("Reload"); + SharedPreferences prefs = context.getSharedPreferences("TASK_DATA", Context.MODE_PRIVATE); + taskDataRaw = prefs.getString("TASK_DATA_RAW", ""); + nextTaskID = prefs.getInt("NEXT_TASK_ID", 1000); + nextBatchID = prefs.getInt("NEXT_BATCH_ID", 1000); + + //Parse data + String[] lines = taskDataRaw.split("\n"); + ArrayList tasks = new ArrayList<>(); + boolean saveAfterParsing = false;//Whether to save the parsed array of tasks (used when removed old task) + boolean deleteOldTasks = userPrefs.getAutoDeleteTasks(); + long deleteOldTasksAfter = userPrefs.getAutoDeleteTasksAfter(); + for(int i=0; i tasks = new ArrayList<>(); + for(int i=0; i= dateMillis && taskData[i].getTimeStart() < dateMillis + 86400000){ + tasks.add(taskData[i]); + } + } + + TaskData[] ret = new TaskData[tasks.size()]; + ret = tasks.toArray(ret); + //return sortTaskData(ret);//Don't need to sort + return ret; + } + + public TaskData getByTaskID(int taskID){ + for(int i=0; i t = new ArrayList<>(); + for(int i=0; i td1.getTimeEnd()); + } + + + public static TaskData fromString(String text){ + TaskData taskData = new TaskData(); + String[] slices = text.split(";"); + taskData.setTaskID(Integer.parseInt(slices[0])); + taskData.setTimeStart(Long.parseLong(slices[1])); + taskData.setTimeEnd(Long.parseLong(slices[2])); + taskData.setTitle(slices[3]); + taskData.setGroupID(Integer.parseInt(slices[4])); + taskData.setBatchID(Integer.parseInt(slices[5])); + taskData.setTimeNotify(Long.parseLong(slices[6])); + taskData.setDescription(slices[7].equals("EMPTY")?"":slices[7]); + return taskData; + } + + public static String toString(TaskData task){ + String r = ""; + r += task.getTaskID()+";"; + r += task.getTimeStart()+";"; + r += task.getTimeEnd()+";"; + r += task.getTitle()+";"; + r += task.getGroupID()+";"; + r += task.getBatchID()+";"; + r += task.getTimeNotify()+";"; + r += (task.getDescription().equals("")?"EMPTY":task.getDescription())+";"; + return r; + } + + + public int getNextTaskID() { + int ret = Integer.valueOf(nextTaskID); + nextTaskID++; + + //Save new taskID + SharedPreferences prefs = context.getSharedPreferences("TASK_DATA", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt("NEXT_TASK_ID", nextTaskID); + editor.apply(); + + return ret; + } + + public int getNextBatchID() { + int ret = Integer.valueOf(nextBatchID); + nextBatchID++; + + //Save new batchID + SharedPreferences prefs = context.getSharedPreferences("TASK_DATA", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt("NEXT_BATCH_ID", nextBatchID); + editor.apply(); + + return ret; + } + + + public void saveTask(TaskData task){ + //Adds task if new taskID or updates existing one + for(int i=0; i special data stuff + String[] slices = lines[0].split(";"); + nextTaskID = Math.max(Integer.parseInt(slices[0]), nextTaskID); + nextBatchID = Math.max(Integer.parseInt(slices[1]), nextBatchID); + getNextTaskID();//Save it + getNextBatchID();//Save it + + //Set tasks + for(int i=1; i + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_add.png b/app/src/main/res/drawable/ic_add.png new file mode 100644 index 0000000000000000000000000000000000000000..d64c22e9edfdcf9babea9681c44e2dee53a6d2f6 GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^2_Vb}Bp6OT_L>T$m`Z~Df*BafCZDwc^5i{T978G? xlN*`>5BzUTIj7P7-(I3-fns}2dtZ?oBSS_(`pg9u0+k>GJYD@<);T3K0RZru9Krwq literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_alarm.png b/app/src/main/res/drawable/ic_alarm.png new file mode 100644 index 0000000000000000000000000000000000000000..835b58af96c9aa76d1beec90cc62183d2dce38ef GIT binary patch literal 1383 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy!M1o(uw0_hFkmPv3lh_2g=28Hd+4 z?bii%&+d=juJGOe{pI{Ei$sMP7+AzST^vIy7~fvM9`)Emfc1egcjC>0IgWA94>mrT z^Lfwz;M@hGJq4?yTYL}Kzkly>HT0_3ztj6_jfE~ubEtXNTFU9`X+71=C;WQKv6g9T zT%Y+InY7(w(}cFRq)rv%=^YP`J?oBMVVSybLtV4pawmULFVU*33&CrQCp0)UH!fr3 zy(v^%#;tXZpH0t4Lsj?J8Woc}H^0PgUwC2TOYKy_7i!g8f43icoc#0U&im3lf)c$i zH-GAQbjLVA?~w7#X%ay%;#nUW|9T^R;;cANh>W?({xcueT*^}VpE%=-<26;c6TMH? z>uGpdo(fF7JWYPyCdpgUSu1k3-pZHCOJ9HZYT4TD%vT(5)K(~5md=W{dMH}ZY>@68 zu3PiyQ1a$$H`TY@_FNpgm-l<<-XB$+p&!Dk%65scv(Gzq^_GCjU(|MfqyQb0@-b*w#1}Oq}8~$8_UN>0RXt%v1GLZt?R(gx?FzNL9S(z4Wj3h0Pht zhgLY;UtE#5c}~lr8n?IJMFbLsw>=CtFZMchHKiWo&A2TR){*c=McPTWfva_rU;n#jADK5<&D}M{ zY_=7HYGr-(9K+Ug)lADoxTLZ(R&WITiLy);ukdFu5@DOLGH}K|{Rh*W{40-@zvo_| z<^QwM>0d3Q?n|)`A{sIK^cOfjzwHUJ%SU0*JyLK_X+RR3Z8H?z(dRH#A6o0@c*_cT3vx}WL{*>`s5lAysk@2rOUf& zjWoNW@6%N;0#-O{1ujSzi_7Zo;$mjzDiaJk=Q%ehdQQgWtF1kWt?@yD%VY0XL_d&x zGV7EZ_w|>B|CgOyJs~=H15d@2(p4?bW(c2Hn#*PXFz{NY?5{OD+@`R$ew1n2r7Now1LE8AXFHAKyu7b6vQ#(mct?Tj6~LTwicLKiLPl6@&- zJCkL~Cox-ozAZe)U*1#&?AZ9ya@j)7#LL@F1Q*U;lz*pnn;i4*TQWk=Hv7)Eb>cCd zC-L8;;MktBgadKP)8?HO4qFw@GdDPW~UNkc2nl~lhTKYk-KL*+}@sl{~tq(>#DU+lr8Uqic3#dKbLh*2~7aB C#U%>> literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_back_arrow.png b/app/src/main/res/drawable/ic_back_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..42d4a89242b7403443adb992f417a3357ab676b9 GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBl001;Ln;{Gp4-UVV8Fu~DE?-) zV30urd!hkb0>k5wFCQk(dEvL~-%TFDK$k6rvS)%0uJ-%aX>?*OM?7@ z8UCv@AHTo9T7sqf_xIxMizn;!Uw{1EVKdKc11?^ke4w(`o-U3d6^w7sv+^+;^0-_q ztepAr-*+8u?evpRD;IqTU!bhCbFXtRLfd)yG1o;Is z{8wo{et&S z|Cb)0nY^~|>dJ+0jDstV+!z1wv!Ox5FGxn`?59JAriSu*`AYwr-xwy&R3(so*0doq z*dl>t>m5eT6{&m-+o$mEVBxTs#K2f)z{teHA)w&Uz<|t{z>qhYIW53j=0aoCn+Dc1 zuTI2hcZ5E>U3TG=7dNN2$JeE;(Yqwuy^kNX(<)_~@{%ugyYZwVP`G%y`njxgN@xNA D;RawG literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_nav_back.png b/app/src/main/res/drawable/ic_nav_back.png new file mode 100644 index 0000000000000000000000000000000000000000..b67d9fbb2a364617a50f191666d9b58813c97110 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeE3?v1%WpM*3xd5LKS0IfCY}d%o0!nk01o;Is zQTPw=n=luD@ui|cv`?)y> zN^;Aagg?z`5XOq^a`tSqwOGJ*L|p#kQTLjUg7GylX0;$tC|%Hg{gLz9A3uBj*&_sG d!Y%ywj753={fH?^j8j1l@O1TaS?83{1OP6qP1yhd literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_edit.xml b/app/src/main/res/layout/activity_edit.xml new file mode 100644 index 0000000..cd2a995 --- /dev/null +++ b/app/src/main/res/layout/activity_edit.xml @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4008156 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000..4f4fcf6 --- /dev/null +++ b/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + +