steroid-team/app

View on GitHub
app/src/main/java/com/github/steroidteam/todolist/view/ItemViewFragment.java

Summary

Maintainability
A
3 hrs
Test Coverage
B
82%
package com.github.steroidteam.todolist.view;

import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.text.Editable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.github.steroidteam.todolist.R;
import com.github.steroidteam.todolist.broadcast.ReminderDateBroadcast;
import com.github.steroidteam.todolist.broadcast.ReminderLocationBroadcast;
import com.github.steroidteam.todolist.model.todo.Task;
import com.github.steroidteam.todolist.view.adapter.ParentTaskAdapter;
import com.github.steroidteam.todolist.view.adapter.TodoAdapter;
import com.github.steroidteam.todolist.view.misc.DateHighlighterTextWatcher;
import com.github.steroidteam.todolist.view.misc.DueDateInputSpan;
import com.github.steroidteam.todolist.view.misc.TagView;
import com.github.steroidteam.todolist.viewmodel.TodoListViewModel;
import com.github.steroidteam.todolist.viewmodel.TodoViewModelFactory;
import com.github.steroidteam.todolist.viewmodel.ViewModelFactoryInjection;
import com.google.android.gms.maps.model.LatLng;
import java.util.Calendar;
import java.util.Date;
import org.jetbrains.annotations.NotNull;
import org.ocpsoft.prettytime.nlp.PrettyTimeParser;

public class ItemViewFragment extends Fragment {

    private TodoListViewModel viewModel;
    private ParentTaskAdapter adapter;
    public static final int PERMISSIONS_ACCESS_LOCATION = 2;
    private final PrettyTimeParser timeParser = new PrettyTimeParser();
    private ActivityResultLauncher<Intent> calendarExportIntentLauncher;
    private TagView tagView;

    public View onCreateView(
            @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        View root = inflater.inflate(R.layout.fragment_item_view, container, false);

        // Add a click listener to the "back" button to return to the previous view (either the
        // list selection fragment, or the item view if the update layout was open).
        root.findViewById(R.id.back_button)
                .setOnClickListener(
                        v -> {
                            ConstraintLayout updateLayout =
                                    getView().findViewById(R.id.layout_update_task);
                            if (updateLayout.getVisibility() == View.VISIBLE) {
                                closeUpdateLayout(v);
                            } else {
                                getParentFragmentManager().popBackStack();
                            }
                        });

        EditText newTaskText = root.findViewById(R.id.new_task_text);
        newTaskText.addTextChangedListener(
                new DateHighlighterTextWatcher(getContext(), timeParser));

        RecyclerView recyclerView = root.findViewById(R.id.activity_itemview_itemlist);
        // The layout manager takes care of displaying the task below each other
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        recyclerView.setHasFixedSize(true);

        adapter = new ParentTaskAdapter(createCustomListener());
        recyclerView.setAdapter(adapter);

        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());

                            adapter.setParentTodoList(todoList);
                        });

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

        ConstraintLayout updateLayout = root.findViewById(R.id.layout_update_task);
        updateLayout.setVisibility(View.GONE);

        tagView = new TagView(this, viewModel, root);
        root.findViewById(R.id.itemview_tag_button)
                .setOnClickListener(
                        v -> {
                            updateLayout.setVisibility(View.GONE);
                            tagView.tagButton();
                        });
        root.findViewById(R.id.itemview_tag_save_button)
                .setOnClickListener(v -> tagView.tagSaveButton());

        ReminderDateBroadcast.createNotificationChannel(getActivity());
        ReminderLocationBroadcast.createLocationNotificationChannel(getActivity());

        calendarExportIntentLauncher =
                registerForActivityResult(
                        new ActivityResultContracts.StartActivityForResult(), (result) -> {});

        return root;
    }

    public TodoAdapter.TaskCustomListener createCustomListener() {
        return new TodoAdapter.TaskCustomListener() {
            @Override
            public void onItemClick(final int position) {
                openUpdateLayout(position);
            }

            public void onItemDelete(final int position) {
                removeTask(position);
            }

            @Override
            public void onCheckedChangedCustom(int position, boolean isChecked) {
                checkBoxTaskListener(position, isChecked);
            }
        };
    }

    public void addTask(View view) {
        EditText newTaskET = getView().findViewById(R.id.new_task_text);

        Task task = getTaskFromEditable(newTaskET.getText());
        if (task == null) return;

        viewModel.addTask(task);

        // Clean the description text box.
        newTaskET.getText().clear();
    }

    private Task getTaskFromEditable(Editable editable) {
        String taskDescription = editable.toString();

        DueDateInputSpan[] dueDateInputSpans =
                editable.getSpans(0, editable.length(), DueDateInputSpan.class);

        Date dueDate = null;
        if (dueDateInputSpans.length > 0) {
            // There should only be one span, as we make sure that only the last date in the
            // task's body has one.
            DueDateInputSpan span = dueDateInputSpans[0];
            dueDate = span.getDate();

            // Remove one leading/trailing space, to there are no two contiguous spaces after
            // removing the date.
            int start = editable.getSpanStart(span);
            int end = editable.getSpanEnd(span);
            if (start > 0 && taskDescription.charAt(start - 1) == ' ') start--;
            else if (end < taskDescription.length() - 1 && taskDescription.charAt(end) == ' ')
                end++;

            StringBuilder sb = new StringBuilder(taskDescription);
            sb.delete(start, end);
            taskDescription = sb.toString();
        }

        // Make sure that we only return a task if the description has actual text.
        if (taskDescription.length() == 0) return null;

        Task task = new Task(taskDescription);
        if (dueDate != null) {
            task.setDueDate(dueDate);
            ReminderDateBroadcast.createNotification(dueDate, taskDescription, getActivity());
        }

        return task;
    }

    public void removeTask(final int position) {
        viewModel.removeTask(position);
        Toast.makeText(getContext(), "Successfully removed the task !", Toast.LENGTH_SHORT).show();
    }

    public void removeDoneTasks(View view) {
        viewModel.removeDoneTasks();
        Toast.makeText(
                        getContext(),
                        "Successfully removed all tasks you have done !",
                        Toast.LENGTH_LONG)
                .show();
    }

    public void closeUpdateLayout(View view) {
        ConstraintLayout updateLayout = getView().findViewById(R.id.layout_update_task);
        updateLayout.setVisibility(View.GONE);

        RecyclerView recyclerView = getView().findViewById(R.id.activity_itemview_itemlist);
        recyclerView.setVisibility(View.VISIBLE);
    }

    public void openUpdateLayout(final int position) {
        RecyclerView recyclerView = getView().findViewById(R.id.activity_itemview_itemlist);
        recyclerView.setVisibility(View.GONE);

        ConstraintLayout updateLayout = getView().findViewById(R.id.layout_update_task);
        updateLayout.setVisibility(View.VISIBLE);

        Task thisTask = viewModel.getTask(position);

        EditText userInputBody = getView().findViewById(R.id.layout_update_task_body);
        userInputBody.setText(thisTask.getBody());
        userInputBody.addTextChangedListener(
                new DateHighlighterTextWatcher(getContext(), timeParser));

        CheckBox taskCheckedBox = getView().findViewById(R.id.layout_update_task_checkbox);
        taskCheckedBox.setChecked(thisTask.isDone());

        SaveButtonSetup(userInputBody, position);
        DeleteButtonSetup(position);
        calendarExportButtonSetup(position);
        removeDueDateButtonSetup(position);
    }

    private void SaveButtonSetup(EditText userInput, final int position) {

        Button saveButton = getView().findViewById(R.id.layout_update_task_save);
        saveButton.setOnClickListener(
                (v) -> {
                    closeUpdateLayout(v);
                    Task task = getTaskFromEditable(userInput.getText());

                    if (task == null) return;

                    if (task.getBody() != null) {
                        viewModel.renameTask(position, task.getBody());
                    }
                    if (task.getDueDate() != null) {
                        viewModel.setTaskDueDate(position, task.getDueDate());
                    }
                });
    }

    private void DeleteButtonSetup(final int position) {
        Button deleteButton = getView().findViewById(R.id.layout_update_task_delete);
        deleteButton.setOnClickListener(
                (v) -> {
                    closeUpdateLayout(v);
                    removeTask(position);
                });

        Button addLocationButton = getView().findViewById(R.id.AddLocationReminderButton);
        Task currTask = viewModel.getTask(position);
        String locationName = currTask.getLocationName();
        String taskDesc = currTask.getBody();

        if (locationName != null) {
            addLocationButton.setText(locationName);
        } else {
            addLocationButton.setText(R.string.add_location);
        }

        getView()
                .findViewById(R.id.AddLocationReminderButton)
                .setOnClickListener(
                        v -> {
                            getParentFragmentManager()
                                    .setFragmentResultListener(
                                            MapFragment.LOCATION_REQ,
                                            this,
                                            (requestKey, bundle) -> {
                                                LatLng result =
                                                        bundle.getParcelable(
                                                                MapFragment.LOCATION_KEY);
                                                Location loc = new Location("");
                                                loc.setLatitude(result.latitude);
                                                loc.setLongitude(result.longitude);
                                                viewModel.setTaskLocationReminder(
                                                        position,
                                                        result,
                                                        bundle.getString(
                                                                MapFragment.LOCATION_NAME_KEY));
                                                getParentFragmentManager()
                                                        .clearFragmentResultListener(
                                                                MapFragment.LOCATION_REQ);
                                                ReminderLocationBroadcast
                                                        .createLocationNotification(
                                                                loc, taskDesc, getActivity());
                                            });

                            Navigation.findNavController(getView()).navigate(R.id.nav_map);
                        });
    }

    private void removeDueDateButtonSetup(final int position) {
        Task thisTask = viewModel.getTask(position);
        Button button = getView().findViewById(R.id.layout_update_remove_due_date);

        if (thisTask.getDueDate() == null) {
            button.setVisibility(View.GONE);
        } else {
            button.setVisibility(View.VISIBLE);
            button.setOnClickListener(
                    v -> {
                        viewModel.setTaskDueDate(position, null);
                        // Reset the text in the calendar export button.
                        Button calendarExportButton =
                                getView().findViewById(R.id.layout_update_task_export_calendar);
                        calendarExportButton.setText(R.string.export_to_calendar);
                    });
        }
    }

    private void calendarExportButtonSetup(final int position) {
        Task thisTask = viewModel.getTask(position);

        Button calendarExportButton =
                getView().findViewById(R.id.layout_update_task_export_calendar);
        if (thisTask.getDueDate() != null) {
            calendarExportButton.setText(thisTask.getDueDate().toString());
        } else {
            calendarExportButton.setText(R.string.export_to_calendar);
        }
        calendarExportButton.setOnClickListener(
                (v) -> {
                    Calendar startTime = Calendar.getInstance();
                    // If available, use the task's due date for the calendar export. If it has
                    // not been set, it will use the current date.
                    if (thisTask.getDueDate() != null) {
                        startTime.setTime(thisTask.getDueDate());
                    }

                    Intent intent =
                            new Intent(Intent.ACTION_INSERT)
                                    .setData(CalendarContract.Events.CONTENT_URI)
                                    .putExtra(
                                            CalendarContract.EXTRA_EVENT_BEGIN_TIME,
                                            startTime.getTimeInMillis())
                                    .putExtra(CalendarContract.Events.TITLE, thisTask.getBody());
                    calendarExportIntentLauncher.launch(intent);
                });
    }

    public void checkBoxTaskListener(final int position, final boolean isChecked) {
        viewModel.setTaskDone(position, isChecked);
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode,
            @NonNull @NotNull String[] permissions,
            @NonNull @NotNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        boolean isPermissionGiven = false;
        switch (requestCode) {
            case PERMISSIONS_ACCESS_LOCATION:
                isPermissionGiven = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                break;
        }
        if (!isPermissionGiven) {
            Toast.makeText(
                            getContext(),
                            "You must give access to the location to use this feature !",
                            Toast.LENGTH_SHORT)
                    .show();
        }
    }
}