Skip to content
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies {
implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation 'com.google.android.gms:play-services-location:18.0.0'
implementation 'jp.wasabeef:richeditor-android:2.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:3.9.0'
androidTestImplementation 'org.mockito:mockito-android:3.9.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.github.steroidteam.todolist;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;

import android.content.Context;
import androidx.fragment.app.testing.FragmentScenario;
import androidx.test.espresso.intent.Intents;
import com.github.steroidteam.todolist.database.Database;
import com.github.steroidteam.todolist.database.DatabaseFactory;
import com.github.steroidteam.todolist.model.todo.Tag;
import com.github.steroidteam.todolist.model.todo.Task;
import com.github.steroidteam.todolist.model.todo.TodoList;
import com.github.steroidteam.todolist.model.todo.TodoListCollection;
import com.github.steroidteam.todolist.view.DailyPlanningFragment;
import com.github.steroidteam.todolist.viewmodel.ViewModelFactoryInjection;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class DailyPlanningFragmentTest {

private FragmentScenario<DailyPlanningFragment> scenario;
@Mock Database databaseMock;

@Mock Context context;

@Before
public void initIntents() {
Intents.init();
}

@After
public void releaseIntents() {
Intents.release();
}

@Before
public void setUp() {
// Mock context for Broadcast Reminder
doReturn(context).when(context).getApplicationContext();

TodoList todoList = new TodoList("Some random title");
CompletableFuture<TodoList> todoListFuture = new CompletableFuture<>();
todoListFuture.complete(todoList);

Task task = new Task("Random task title");
Task task2 = new Task("task2");
todoList.addTask(task);
todoList.addTask(task2);
doReturn(todoListFuture).when(databaseMock).getTodoList(any());
CompletableFuture<Task> taskFuture = new CompletableFuture<>();
taskFuture.complete(task);
doReturn(taskFuture).when(databaseMock).putTask(any(), any(Task.class));
doReturn(taskFuture).when(databaseMock).updateTask(any(), anyInt(), any(Task.class));
doReturn(taskFuture).when(databaseMock).removeTask(any(), anyInt());
doReturn(taskFuture).when(databaseMock).setTaskDone(any(), anyInt(), anyBoolean());
doReturn(taskFuture).when(databaseMock).removeDoneTasks(any());

TodoListCollection collection = new TodoListCollection();
collection.addUUID(UUID.randomUUID());
CompletableFuture<TodoListCollection> todoListCollectionFuture = new CompletableFuture<>();
todoListCollectionFuture.complete(collection);
doReturn(todoListCollectionFuture).when(databaseMock).getTodoListCollection();

CompletableFuture<List<Tag>> tagsFuture = new CompletableFuture<>();
tagsFuture.complete(new ArrayList<>());
doReturn(tagsFuture).when(databaseMock).getAllTags();
doReturn(tagsFuture).when(databaseMock).getTagsFromIds(any());
doReturn(tagsFuture).when(databaseMock).getAllTagsIds();
doReturn(tagsFuture).when(databaseMock).getTagsFromList(any());

DatabaseFactory.setCustomDatabase(databaseMock);

File fakeFile = new File("Fake pathname");
doReturn(fakeFile).when(context).getCacheDir();
ViewModelFactoryInjection.setCustomTodoListRepo(context, UUID.randomUUID());
scenario =
FragmentScenario.launchInContainer(
DailyPlanningFragment.class, null, R.style.AsteroidTheme);
}

@Test
public void setTaskForTodayWorks() {
final String TASK_DESCRIPTION = "Buy bananas";

TodoList todoList = new TodoList("Some random title");
CompletableFuture<TodoList> todoListFuture = new CompletableFuture<>();
todoListFuture.complete(todoList);
Task task = new Task(TASK_DESCRIPTION);
todoList.addTask(task);
doReturn(todoListFuture).when(databaseMock).getTodoList(any());

CompletableFuture<Task> taskFuture = new CompletableFuture<>();
taskFuture.complete(task);
doReturn(taskFuture).when(databaseMock).getTask(any(), anyInt());

onView(withId(R.id.today_plan_button)).perform(click());
onView(withId(R.id.today_none_button)).perform(click());
}

@Test
public void tasksProperlyCycle() {
onView(withId(R.id.today_plan_button)).perform(click());
onView(withId(R.id.afternoon_button)).perform(click());
onView(withId(R.id.task_description)).check(matches(withText("task2")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package com.github.steroidteam.todolist.view;

import android.os.Bundle;
import android.text.format.DateUtils;
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.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.github.steroidteam.todolist.R;
import com.github.steroidteam.todolist.model.todo.Task;
import com.github.steroidteam.todolist.model.todo.TodoList;
import com.github.steroidteam.todolist.viewmodel.TodoListViewModel;
import com.github.steroidteam.todolist.viewmodel.TodoViewModelFactory;
import com.github.steroidteam.todolist.viewmodel.ViewModelFactoryInjection;
import java.util.Calendar;
import java.util.Date;

public class DailyPlanningFragment extends Fragment {

private TodoListViewModel viewModel;
private int currentTaskIndex = -1;

@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View root = inflater.inflate(R.layout.fragment_daily_planning, container, false);

// Add a click listener to the "back" button to return to the previous activity.
root.findViewById(R.id.back_button)
.setOnClickListener(v -> getParentFragmentManager().popBackStack());

TodoViewModelFactory todoViewModelFactory =
ViewModelFactoryInjection.getTodoViewModelFactory(getContext());
viewModel =
new ViewModelProvider(requireActivity(), todoViewModelFactory)
.get(TodoListViewModel.class);

viewModel
.getTodoList()
.observe(
getViewLifecycleOwner(),
(todoList) -> {
TextView activityTitle = root.findViewById(R.id.activity_title);
activityTitle.setText(todoList.getTitle());
});

setListeners(root);

return root;
}

@Override
public void onStart() {
super.onStart();
findNextUnplannedTask();
}

public void setListeners(View root) {
root.findViewById(R.id.set_done_button).setOnClickListener(this::setDoneButtonListener);
root.findViewById(R.id.today_plan_button).setOnClickListener(this::switchToToday);
root.findViewById(R.id.other_day_plan_button).setOnClickListener(this::switchToOtherDay);
root.findViewById(R.id.delete_task_button).setOnClickListener(this::skipTaskInPlan);

root.findViewById(R.id.today_none_button)
.setOnClickListener(new SetDateListener(Plan.TODAY));
root.findViewById(R.id.midday_button).setOnClickListener(new SetDateListener(Plan.MIDDAY));
root.findViewById(R.id.afternoon_button)
.setOnClickListener(new SetDateListener(Plan.AFTERNOON));
root.findViewById(R.id.evening_button)
.setOnClickListener(new SetDateListener(Plan.EVENING));
root.findViewById(R.id.night_button).setOnClickListener(new SetDateListener(Plan.NIGHT));
root.findViewById(R.id.tomorrow_button)
.setOnClickListener(new SetDateListener(Plan.TOMORROW));
root.findViewById(R.id.twodays_plan_button)
.setOnClickListener(new SetDateListener(Plan.TWODAYS));
root.findViewById(R.id.week_plan_button).setOnClickListener(new SetDateListener(Plan.WEEK));
root.findViewById(R.id.oneday_button).setOnClickListener(new SetDateListener(Plan.ONEDAY));
}

public void setDoneButtonListener(View view) {
viewModel.setTaskDone(currentTaskIndex, true);
findNextUnplannedTask();
}

public void switchToToday(View view) {
ConstraintLayout mainPlan = getView().findViewById(R.id.main_plan);
mainPlan.setVisibility(View.GONE);
ConstraintLayout todayPlan = getView().findViewById(R.id.today_plan);
todayPlan.setVisibility(View.VISIBLE);
}

public void switchToOtherDay(View view) {
ConstraintLayout mainPlan = getView().findViewById(R.id.main_plan);
mainPlan.setVisibility(View.GONE);
ConstraintLayout otherDayPlan = getView().findViewById(R.id.other_day_plan);
otherDayPlan.setVisibility(View.VISIBLE);
}

/**
* Used as placeholder waiting for more features (selecting day and hour / integrate viewmodel
*/
public void skipTaskInPlan(View view) {
findNextUnplannedTask();
}

/**
* Finds the next task to be planned, if there isn't anymore it goes back to ItemViewActivity
*/
private void findNextUnplannedTask() {
TodoList list = viewModel.getTodoList().getValue();
while (currentTaskIndex + 1 < list.getSize()) {
currentTaskIndex++;
Task task = list.getTask(currentTaskIndex);
Date date = task.getDueDate();
if (!task.isDone()
&& (date == null
|| DateUtils.isToday(date.getTime())
|| date.before(new Date()))) {
TextView text = getView().findViewById(R.id.task_description);
text.setText(task.getBody());
return;
}
}
getParentFragmentManager().popBackStack();
}

private enum Plan {
TODAY,
TOMORROW,
TWODAYS,
WEEK,
ONEDAY,
MIDDAY,
AFTERNOON,
EVENING,
NIGHT
}

private class SetDateListener implements View.OnClickListener {
private Plan plan;

public SetDateListener(Plan plan) {
this.plan = plan;
}

@Override
public void onClick(View v) {
Date date = new Date();
Calendar c = Calendar.getInstance();
c.setTime(date);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);

switch (plan) {
case TODAY:
// Nothing more to do
break;
case TOMORROW:
c.add(Calendar.DATE, 1);
date = c.getTime();
break;
case TWODAYS:
c.add(Calendar.DATE, 2);
date = c.getTime();
break;
case WEEK:
c.add(Calendar.DATE, 7);
date = c.getTime();
break;
case ONEDAY:
date = null;
break;
case MIDDAY:
c.set(Calendar.HOUR_OF_DAY, 12);
date = c.getTime();
break;
case AFTERNOON:
c.set(Calendar.HOUR_OF_DAY, 15);
date = c.getTime();
break;
case EVENING:
c.set(Calendar.HOUR_OF_DAY, 19);
date = c.getTime();
break;
case NIGHT:
c.set(Calendar.HOUR_OF_DAY, 22);
date = c.getTime();
break;
}

viewModel.setTaskDueDate(currentTaskIndex, date);
findNextUnplannedTask();
ConstraintLayout mainPlan = getView().findViewById(R.id.main_plan);
mainPlan.setVisibility(View.VISIBLE);
ConstraintLayout todayPlan = getView().findViewById(R.id.today_plan);
todayPlan.setVisibility(View.GONE);
ConstraintLayout otherDayPlan = getView().findViewById(R.id.other_day_plan);
otherDayPlan.setVisibility(View.GONE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public View onCreateView(

root.findViewById(R.id.new_task_btn).setOnClickListener(this::addTask);
root.findViewById(R.id.remove_done_tasks_btn).setOnClickListener(this::removeDoneTasks);
root.findViewById(R.id.daily_planning_btn).setOnClickListener(this::goToDailyPlanning);

ConstraintLayout updateLayout = root.findViewById(R.id.layout_update_task);
updateLayout.setVisibility(View.GONE);
Expand Down Expand Up @@ -361,6 +362,10 @@ public void checkBoxTaskListener(final int position, final boolean isChecked) {
viewModel.setTaskDone(position, isChecked);
}

public void goToDailyPlanning(View view) {
Navigation.findNavController(getView()).navigate(R.id.nav_daily_planning, new Bundle());
}

@Override
public void onRequestPermissionsResult(
int requestCode,
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/res/drawable/fab.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="0dp"
android:shape="ring"
android:thicknessRatio="2"
android:useLevel="false" >
<solid android:color="@android:color/transparent" />

<stroke
android:width="3dp"
android:color="@android:color/black" />
</shape>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_baseline_arrow_downward_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"/>
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
</vector>
Loading