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 " + tasks[i].getTitle();
+ text += i != tasks.length-1?"\n":"";
+ }
+ if(text.equals("")) text = "No Appointments";
+
+
+
+ // Create the NotificationChannel, but only on API 26+
+ if (Build.VERSION.SDK_INT >= 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 0000000..d64c22e
Binary files /dev/null and b/app/src/main/res/drawable/ic_add.png differ
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 0000000..835b58a
Binary files /dev/null and b/app/src/main/res/drawable/ic_alarm.png differ
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 0000000..42d4a89
Binary files /dev/null and b/app/src/main/res/drawable/ic_back_arrow.png differ
diff --git a/app/src/main/res/drawable/ic_calendar.png b/app/src/main/res/drawable/ic_calendar.png
new file mode 100644
index 0000000..cd6323d
Binary files /dev/null and b/app/src/main/res/drawable/ic_calendar.png differ
diff --git a/app/src/main/res/drawable/ic_calendar_2.png b/app/src/main/res/drawable/ic_calendar_2.png
new file mode 100644
index 0000000..df11b1c
Binary files /dev/null and b/app/src/main/res/drawable/ic_calendar_2.png differ
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 0000000..b67d9fb
Binary files /dev/null and b/app/src/main/res/drawable/ic_nav_back.png differ
diff --git a/app/src/main/res/drawable/ic_nav_next.png b/app/src/main/res/drawable/ic_nav_next.png
new file mode 100644
index 0000000..aa421db
Binary files /dev/null and b/app/src/main/res/drawable/ic_nav_next.png differ
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml
new file mode 100644
index 0000000..6b6a1d5
--- /dev/null
+++ b/app/src/main/res/layout/list_item.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_edit.xml b/app/src/main/res/menu/menu_edit.xml
new file mode 100644
index 0000000..12aa3e6
--- /dev/null
+++ b/app/src/main/res/menu/menu_edit.xml
@@ -0,0 +1,20 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..65323c0
--- /dev/null
+++ b/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,21 @@
+
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..473b6e6
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3d278c8
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..59c3b3c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..36e5fb3
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..5ad82ee
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..3caf179
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d13d7b
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9030618
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..8a73efa
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..781ff62
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..69b2233
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..abb4c29
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Calendar
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..41e5cc1
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/src/test/java/be/bitscuit/calendar/ExampleUnitTest.java b/app/src/test/java/be/bitscuit/calendar/ExampleUnitTest.java
new file mode 100644
index 0000000..0f38dd0
--- /dev/null
+++ b/app/src/test/java/be/bitscuit/calendar/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package be.bitscuit.calendar;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..a5bb815
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.3'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..199d16e
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d1faa7c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Feb 02 09:11:50 CET 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..ac772d1
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name='Calendar'