steroid-team/app

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

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package com.github.steroidteam.todolist.view.adapter;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.format.DateFormat;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.RelativeSizeSpan;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.github.steroidteam.todolist.R;
import com.github.steroidteam.todolist.model.todo.Task;
import com.github.steroidteam.todolist.model.todo.TodoList;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

public class TodoAdapter extends RecyclerView.Adapter<TodoAdapter.TaskHolder> {

    private TodoList todoList;
    private TaskCustomListener listener;
    private Map<Task, Integer> taskIntegerMap;

    public TodoAdapter(TaskCustomListener listener, Map<Task, Integer> taskIntegerMap) {
        this.listener = listener;
        this.taskIntegerMap = taskIntegerMap;
    }

    @NonNull
    @Override
    public TaskHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View layoutNotDone =
                LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.layout_task_item, parent, false);
        return new TaskHolder(layoutNotDone);
    }

    @Override
    public void onBindViewHolder(@NonNull TaskHolder holder, int position) {
        Task currentTask = todoList.getTask(position);

        holder.setPosition(taskIntegerMap.get(currentTask));

        Context context = holder.itemView.getContext();
        SpannableString taskBodySS = new SpannableString(currentTask.getBody());
        taskBodySS.setSpan(
                new ForegroundColorSpan(fetchTextPrimaryColour(context)),
                0,
                taskBodySS.length(),
                Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        holder.taskBody.setText(taskBodySS);

        if (currentTask.getDueDate() != null) {
            SpannableString dueDate = createDateSpan(context, currentTask.getDueDate());

            // Add some margin between the task's body and the due date.
            holder.taskBody.append("   ");
            // Insert the span with the due date.
            holder.taskBody.append(dueDate);
        }
        holder.taskBox.setChecked(currentTask.isDone());

        if (currentTask.isDone()) {
            holder.taskBody.setTextColor(Color.LTGRAY);
            holder.taskBody.setPaintFlags(
                    holder.taskBody.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
            holder.displayDeleteButton();
        } else {
            holder.taskBody.setTextColor(Color.DKGRAY);
            holder.taskBody.setPaintFlags(0);
            holder.hideDeleteButton();
        }
    }

    private SpannableString createDateSpan(Context context, Date date) {
        int lightGrey = ContextCompat.getColor(context, R.color.light_grey);
        // The ImageSpan works by replacing a substring by the image (in this case, the clock
        // icon). We add two leading spaces here to:
        //  1) Keep useful text from being overwritten by the image.
        //  2) Add a little margin between the icon and the date.
        SpannableString dueDate = new SpannableString("  " + formatDate(date));
        // Make the due date 75% the size of the task's body.
        dueDate.setSpan(
                new RelativeSizeSpan(0.75f), 0, dueDate.length(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        // Tint the clock icon with the same colour as the text (light grey).
        Drawable clockIcon =
                DrawableCompat.wrap(
                        Objects.requireNonNull(
                                ContextCompat.getDrawable(context, R.drawable.clock_icon)));
        DrawableCompat.setTint(clockIcon, lightGrey);
        clockIcon.setBounds(0, 0, clockIcon.getIntrinsicWidth(), clockIcon.getIntrinsicHeight());
        // Add the clock icon to the start of the span.
        dueDate.setSpan(
                new ImageSpan(clockIcon, DynamicDrawableSpan.ALIGN_BASELINE),
                0,
                1,
                Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        // Make the text's colour light grey as well.
        dueDate.setSpan(
                new ForegroundColorSpan(lightGrey),
                0,
                dueDate.length(),
                Spanned.SPAN_EXCLUSIVE_INCLUSIVE);

        return dueDate;
    }

    @Override
    public int getItemCount() {
        return todoList.getSize();
    }

    private static CharSequence formatDate(Date date) {
        Calendar currentDate = Calendar.getInstance();
        Calendar inputDate = Calendar.getInstance();
        inputDate.setTime(date);

        String format = "yyyy-MM-dd HH:mm";

        // Remove the year if it matches the current one.
        if (currentDate.get(Calendar.YEAR) == inputDate.get(Calendar.YEAR)) {
            format = format.replace("yyyy-", "");

            // Just display the time if the date is today.
            if (currentDate.get(Calendar.DAY_OF_YEAR) == inputDate.get(Calendar.DAY_OF_YEAR)) {
                format = format.replace("MM-dd ", "");
            }
        }

        return DateFormat.format(format, date);
    }

    public void setTodoList(TodoList todoList) {
        // Updates the adapter with the new todoList (the observable one)
        // this.todoList = todoList;
        this.todoList = todoList.sortByDate();
        // Check notifyDataSetChanged() might not be the best function
        // considering performance
        notifyDataSetChanged();
    }

    public interface TaskCustomListener {
        void onItemClick(final int position);

        void onItemDelete(final int position);

        void onCheckedChangedCustom(int position, boolean isChecked);
    }

    public class TaskHolder extends RecyclerView.ViewHolder {

        private final TextView taskBody;
        private final CheckBox taskBox;
        private final ImageButton taskDelete;
        private int position;

        public TaskHolder(@NonNull View itemView) {
            super(itemView);
            taskBody = itemView.findViewById(R.id.layout_task_body);
            taskBox = itemView.findViewById(R.id.layout_task_checkbox);
            taskDelete = itemView.findViewById(R.id.layout_task_delete_button);

            taskBox.setOnClickListener(
                    (v) -> {
                        if (position != RecyclerView.NO_POSITION) {
                            listener.onCheckedChangedCustom(position, taskBox.isChecked());
                        }
                    });

            taskDelete.setOnClickListener(v -> listener.onItemDelete(position));

            itemView.setOnClickListener(
                    (v) -> {
                        if (position != RecyclerView.NO_POSITION) {
                            listener.onItemClick(position);
                        }
                    });
        }

        public void setPosition(int position) {
            this.position = position;
        }

        public void displayDeleteButton() {
            taskDelete.setVisibility(View.VISIBLE);
        }

        public void hideDeleteButton() {
            taskDelete.setVisibility(View.GONE);
        }
    }

    static int fetchTextPrimaryColour(Context context) {
        TypedValue typedValue = new TypedValue();
        Resources.Theme theme = context.getTheme();
        theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
        TypedArray arr =
                context.obtainStyledAttributes(
                        typedValue.data, new int[] {android.R.attr.textColorPrimary});
        int primaryColor = arr.getColor(0, -1);
        arr.recycle();
        return primaryColor;
    }
}