My Android app is crashing after I defer the last item in the list. Here is the log:

FATAL EXCEPTION: main
                                                                                                    Process: com.markrai.waqt, PID: 29626
                                                                                                    java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 4
                                                                                                    	at jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
                                                                                                    	at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
                                                                                                    	at jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
                                                                                                    	at java.util.Objects.checkIndex(Objects.java:359)
                                                                                                    	at java.util.ArrayList.get(ArrayList.java:434)
                                                                                                    	at com.markrai.waqt.adapter.TaskAdapter$CustomTextWatcher.onTextChanged(TaskAdapter.java:503)
                                                                                                    	at android.widget.TextView.sendOnTextChanged(TextView.java:12733)
                                                                                                    	at android.widget.TextView.setText(TextView.java:7470)
                                                                                                    	at android.widget.TextView.setText(TextView.java:7282)
                                                                                                    	at android.widget.EditText.setText(EditText.java:184)
                                                                                                    	at android.widget.TextView.setText(TextView.java:7220)
                                                                                                    	at com.markrai.waqt.adapter.TaskViewBinder.bindTaskViewHolder

package com.markrai.waqt.adapter;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;

import com.markrai.waqt.R;
import com.markrai.waqt.activity.MainActivity;
import com.markrai.waqt.model.Task;
import com.markrai.waqt.storage.JsonStorage;
import com.markrai.waqt.util.TaskUtils;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

public class TaskAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static MainActivity.OnTimerStateChangeListener timerStateChangeListener;
    private static final int VIEW_TYPE_TASK = 0;
    private static final int VIEW_TYPE_SEPARATOR = 1;
    private static final int VIEW_TYPE_DEFERRED_TASK = 2;
    Task currentActiveTask = null;
    private final List<Task> taskList;
    final List<Task> deferredTaskList;
    private final OnStartDragListener dragStartListener;
    int totalTimeBurden;
    final TotalTimeBurdenListener totalTimeBurdenListener;
    final String[] timeOptions;
    private static final long SEPARATOR_ID = Long.MAX_VALUE;


    long remainingTime;
    final Context context;

    public TaskAdapter(Context context, List<Task> taskList, List<Task> deferredTaskList, OnStartDragListener dragStartListener, TotalTimeBurdenListener totalTimeBurdenListener) {
        this.context = context;
        this.taskList = taskList;
        this.deferredTaskList = deferredTaskList;
        this.dragStartListener = dragStartListener;
        this.totalTimeBurdenListener = totalTimeBurdenListener;
        this.totalTimeBurden = TaskUtils.calculateTotalTimeBurden(taskList);
        this.timeOptions = context.getResources().getStringArray(R.array.time_options);
        setHasStableIds(true);
    }

    public void setRemainingTime(long remainingTime) {
        this.remainingTime = remainingTime;
        totalTimeBurdenListener.onTotalTimeBurdenUpdated(TaskUtils.formatTime(totalTimeBurden), remainingTime);
    }

    @Override
    public long getItemId(int position) {
        Task task = getTaskAtPosition(position);
        if (task != null) {
            return task.getId().hashCode();
        } else if (getItemViewType(position) == VIEW_TYPE_SEPARATOR) {
            return SEPARATOR_ID;
        }
        return RecyclerView.NO_ID;
    }

    private Task getTaskAtPosition(int position) {
        if (position < taskList.size()) {
            return taskList.get(position);
        } else if (position > taskList.size()) { // Position after separator
            return deferredTaskList.get(position - taskList.size() - 1);
        }
        return null; // Separator or invalid position
    }


    @Override
    public int getItemViewType(int position) {
        if (position < taskList.size()) {
            return VIEW_TYPE_TASK;
        } else if (position == taskList.size() && !taskList.isEmpty()) {
            return VIEW_TYPE_SEPARATOR;
        } else {
            return VIEW_TYPE_DEFERRED_TASK;
        }
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_TASK) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.task_item, parent, false);
            TaskViewHolder viewHolder = new TaskViewHolder(view, new CustomTextWatcher(this), this);
            viewHolder.setTimerStateChangeListener(timerStateChangeListener);
            return viewHolder;
        } else if (viewType == VIEW_TYPE_SEPARATOR) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.separator_item, parent, false);
            return new SeparatorViewHolder(view);
        } else {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.task_item, parent, false);
            TaskViewHolder viewHolder = new TaskViewHolder(view, new CustomTextWatcher(this), this);
            viewHolder.setTimerStateChangeListener(timerStateChangeListener);
            return viewHolder;
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof TaskViewHolder) {
            TaskViewHolder taskViewHolder = (TaskViewHolder) holder;
            Task task;
            boolean isDeferred;

            if (position < taskList.size()) {
                task = taskList.get(position);
                isDeferred = false;
            } else if (position == taskList.size() && !taskList.isEmpty()) {
                // Separator; no binding needed or handle separator-specific UI
                return;
            } else {
                if (!taskList.isEmpty()) {
                    task = deferredTaskList.get(position - taskList.size() - 1);
                } else {
                    task = deferredTaskList.get(position);
                }
                isDeferred = true;
            }

            taskViewHolder.setTask(task);
            TaskViewBinder.bindTaskViewHolder(
                    taskViewHolder,
                    task,
                    position,
                    isDeferred,
                    this,
                    timeOptions,
                    currentActiveTask,
                    context,
                    totalTimeBurden,
                    remainingTime,
                    dragStartListener,
                    totalTimeBurdenListener
            );

            // Remove any existing TextWatcher before adding a new one
            if (taskViewHolder.editTextTask.getTag() instanceof TextWatcher) {
                taskViewHolder.editTextTask.removeTextChangedListener((TextWatcher) taskViewHolder.editTextTask.getTag());
            }

            // Attach the new TextWatcher and set it as a tag
            taskViewHolder.editTextTask.addTextChangedListener((TextWatcher) taskViewHolder.customTextWatcher);
            taskViewHolder.editTextTask.setTag(taskViewHolder.customTextWatcher);
        } else if (holder instanceof SeparatorViewHolder) {

        }
    }



    public void deferTask(int position) {
        if (position < taskList.size()) {
            Task task = taskList.remove(position);

            // Cancel the timer if it's running
            if (task.isCountdownActive()) {
                RecyclerView.ViewHolder viewHolder = ((MainActivity) context).findViewByPosition(position);
                if (viewHolder instanceof TaskViewHolder) {
                    ((TaskViewHolder) viewHolder).cancelCountdownTimer();
                }
                task.setCountdownActive(false);
                task.setPaused(false);
                task.setRemainingTime(0);
                clearActiveTask();
            }

            // Set the deferred date to tomorrow
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.DAY_OF_YEAR, 1);
            task.setDeferredDate(calendar.getTime());

            deferredTaskList.add(task);

            notifyItemRemoved(position);

            if (deferredTaskList.size() == 1 && !taskList.isEmpty()) {
                // Insert separator only if there are still active tasks
                notifyItemInserted(taskList.size());
            }

            // Insert the deferred task
            int insertPosition;
            if (!taskList.isEmpty()) {
                insertPosition = taskList.size() + 1 + (deferredTaskList.size() - 1);
            } else {
                insertPosition = deferredTaskList.size() - 1;
            }
            notifyItemInserted(insertPosition);

            if (!task.isCompleted()) {
                totalTimeBurden -= TaskUtils.parseTime(task.getTimeBurden());
                totalTimeBurdenListener.onTotalTimeBurdenUpdated(
                        TaskUtils.formatTime(totalTimeBurden),
                        remainingTime
                );
            }
            saveTasks();
            Toast.makeText(context, "Task deferred to tomorrow", Toast.LENGTH_SHORT).show();
        }
    }




    public int getCurrentTaskCount() {
        return taskList.size();
    }

    private Task getTaskAt(int position) {
        if (position < taskList.size()) {
            return taskList.get(position);
        } else {
            return deferredTaskList.get(position - taskList.size());
        }
    }

    public void clearActiveTask() {
        currentActiveTask = null;
    }

    void undeferAndCompleteTask(int position) {
        // Get the task and remove it from the deferred list
        Task task = deferredTaskList.remove(position - taskList.size() - 1);
        task.setDeferredDate(null);
        task.setCompleted(true);

        // Add the task to the active list
        taskList.add(task);
        notifyItemRemoved(position);
        notifyItemInserted(taskList.size() - 1); // Insert at the end of the current tasks list

        // No need to update the total time burden since the task was deferred and not contributing to it
        totalTimeBurdenListener.onTotalTimeBurdenUpdated(TaskUtils.formatTime(totalTimeBurden), remainingTime);

        saveTasks();
        Toast.makeText(context, "Task moved to today and marked as completed", Toast.LENGTH_SHORT).show();
    }

    @Override
    public int getItemCount() {
        int count = taskList.size() + deferredTaskList.size();
        if (!taskList.isEmpty() && !deferredTaskList.isEmpty()) {
            count += 1; // Add separator only if both lists are not empty
        }
        return count;
    }


    public void addTask(int position, String taskDescription, String timeBurden) {
        if (timeBurden == null || timeBurden.isEmpty()) {
            timeBurden = "5m";
        }
        Task newTask = new Task(UUID.randomUUID().toString(), taskDescription, timeBurden, false, position);

        if (position <= taskList.size()) {
            taskList.add(position, newTask);
            notifyItemInserted(position);
        } else {
            int deferredPosition = position - taskList.size() - 1;
            if (deferredPosition <= deferredTaskList.size()) {
                deferredTaskList.add(deferredPosition, newTask);
                notifyItemInserted(position);
            } else {
                // Handle invalid position gracefully
                taskList.add(newTask);
                notifyItemInserted(taskList.size() - 1);
            }
        }

        if (!timeBurden.equals("0m")) {
            totalTimeBurden += TaskUtils.parseTime(timeBurden);
        }

        totalTimeBurdenListener.onTotalTimeBurdenUpdated(TaskUtils.formatTime(totalTimeBurden), remainingTime);
        saveTasks();

        new Handler().post(() -> {
            RecyclerView.ViewHolder viewHolder = ((MainActivity) context).findViewByPosition(position);
            if (viewHolder instanceof TaskViewHolder) {
                TaskViewHolder taskViewHolder = (TaskViewHolder) viewHolder;
                taskViewHolder.editTextTask.requestFocus();
                InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.showSoftInput(taskViewHolder.editTextTask, InputMethodManager.SHOW_IMPLICIT);
            }
        });
        Intent intent = new Intent("com.markrai.waqt.TASK_DATA_CHANGED");
        context.sendBroadcast(intent);
    }

    public void addDeferredTask(int position, String taskDescription, String timeBurden) {
        if (timeBurden == null || timeBurden.isEmpty()) {
            timeBurden = "5m";
        }
        Task newTask = new Task(UUID.randomUUID().toString(), taskDescription, timeBurden, false, position);
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_YEAR, 1);
        newTask.setDeferredDate(calendar.getTime());

        deferredTaskList.add(newTask);
        notifyItemInserted(taskList.size() + 1 + deferredTaskList.size());
        saveTasks();
    }

    public void undeferTask(int position) {
        if (position >= taskList.size() + 1) {
            int deferredPosition = position - taskList.size() - 1;
            Task task = deferredTaskList.remove(deferredPosition);
            task.setDeferredDate(null);

            // Remove the task from deferred tasks in RecyclerView
            notifyItemRemoved(position);

            if (deferredTaskList.isEmpty()) {
                // Remove the separator
                notifyItemRemoved(taskList.size()); // Separator is at position taskList.size()
            }

            // Add task to taskList
            taskList.add(task);

            // Notify that a new item is inserted at the end of taskList
            notifyItemInserted(taskList.size() - 1);

            if (!task.isCompleted()) {
                totalTimeBurden += TaskUtils.parseTime(task.getTimeBurden());
                totalTimeBurdenListener.onTotalTimeBurdenUpdated(
                        TaskUtils.formatTime(totalTimeBurden),
                        remainingTime
                );
            }
            saveTasks();
            Toast.makeText(context, "Task moved to today", Toast.LENGTH_SHORT).show();
        }
    }


    public void removeTask(int position) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        boolean confirmDelete = prefs.getBoolean("confirm_delete_task", false);

        if (confirmDelete) {
            new AlertDialog.Builder(context, R.style.CustomAlertDialogTheme)
                    .setTitle("Delete Task")
                    .setMessage("Are you sure?")
                    .setPositiveButton(android.R.string.yes, (dialog, which) -> performRemoveTask(position))
                    .setNegativeButton(android.R.string.no, (dialog, which) -> {
                        // Restore the task
                        notifyItemChanged(position);
                    })
                    .setCancelable(false)
                    .show();
        } else {
            performRemoveTask(position);
        }
        Intent intent = new Intent("com.markrai.waqt.TASK_DATA_CHANGED");
        context.sendBroadcast(intent);
    }

    private void performRemoveTask(int position) {
        Task task;
        TaskViewHolder taskViewHolder = (TaskViewHolder) ((MainActivity) context).findViewByPosition(position);

        if (taskViewHolder != null) {
            taskViewHolder.cancelCountdownTimer();
        }

        // If the task being removed is the current active task, clear the active task
        if (currentActiveTask != null && taskList.get(position).equals(currentActiveTask)) {
            clearActiveTask(); // Clear the current active task
        }

        boolean isDeferred = position >= taskList.size() + 1;

        if (isDeferred) {
            int deferredPosition = position - taskList.size() - 1;
            task = deferredTaskList.remove(deferredPosition);
            notifyItemRemoved(position);
            notifyItemRangeChanged(taskList.size() + 1, deferredTaskList.size());
        } else {
            task = taskList.remove(position);
            if (!task.isCompleted()) {
                totalTimeBurden -= TaskUtils.parseTime(task.getTimeBurden());
            }
            notifyItemRemoved(position);
            notifyItemRangeChanged(position, taskList.size());
        }

        totalTimeBurdenListener.onTotalTimeBurdenUpdated(TaskUtils.formatTime(totalTimeBurden), remainingTime);
        saveTasks();
    }

    public void moveItem(int fromPosition, int toPosition) {
        if (fromPosition < taskList.size()) {
            if (toPosition < taskList.size()) {
                Collections.swap(taskList, fromPosition, toPosition);
            } else if (toPosition > taskList.size()) {
                Task task = taskList.remove(fromPosition);
                deferredTaskList.add(toPosition - taskList.size() - 1, task);
            }
        } else {
            if (toPosition > taskList.size()) {
                Collections.swap(deferredTaskList, fromPosition - taskList.size() - 1, toPosition - taskList.size() - 1);
            } else if (toPosition < taskList.size()) {
                Task task = deferredTaskList.remove(fromPosition - taskList.size() - 1);
                taskList.add(toPosition, task);
            }
        }
        notifyItemMoved(fromPosition, toPosition);
        saveTasks();
    }

    void moveItemToBottom(int position) {
        Task task;
        if (position < taskList.size()) {
            task = taskList.remove(position);
            taskList.add(task);
            notifyItemMoved(position, taskList.size() - 1);
        } else {
            task = deferredTaskList.remove(position - taskList.size() - 1);
            deferredTaskList.add(task);
            notifyItemMoved(position, getItemCount() - 1);
        }
        notifyItemRangeChanged(position, getItemCount());
    }

    void moveItemToTop(int position) {
        Task task;
        if (position < taskList.size()) {
            task = taskList.remove(position);
            taskList.add(0, task);
            notifyItemMoved(position, 0);
        } else {
            task = deferredTaskList.remove(position - taskList.size() - 1);
            taskList.add(0, task);
            notifyItemMoved(position, 0);
        }
        notifyItemRangeChanged(0, getItemCount());
    }

    public List<Task> getTaskList() {
        List<Task> combinedTasks = new ArrayList<>(taskList);
        combinedTasks.addAll(deferredTaskList);
        return combinedTasks;
    }

    static class SeparatorViewHolder extends RecyclerView.ViewHolder {
        SeparatorViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }

    class CustomTextWatcher implements TextWatcher {

        private int position;
        private EditText editTextTask;
        private final TaskAdapter adapter;

        public CustomTextWatcher(TaskAdapter adapter) {
            this.adapter = adapter;
        }

        public void setEditTexts(EditText editTextTask) {
            this.editTextTask = editTextTask;
        }

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

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // No action needed before text changed
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if (position >= 0 && position < adapter.getItemCount()) {
                Task task;
                if (position < adapter.taskList.size()) {
                    task = adapter.taskList.get(position);
                } else {
                    task = adapter.deferredTaskList.get(position - adapter.taskList.size() - 1);
                }
                if (task != null) {
                    if (editTextTask.hasFocus()) {
                        task.setTaskDescription(s.toString());
                        adapter.saveTasks();
                    }
                }
            }
        }

        @Override
        public void afterTextChanged(Editable s) {
            // No action needed after text changed
        }
    }

    public interface OnStartDragListener {
        void onStartDrag(RecyclerView.ViewHolder viewHolder);
    }

    public interface TotalTimeBurdenListener {
        void onTotalTimeBurdenUpdated(String formattedTotalTimeBurden, long remainingTime);
    }

    void saveTasks() {
        JsonStorage.saveTasks(context, getTaskList());
    }
}

package com.markrai.waqt.adapter;

import android.media.MediaPlayer;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.markrai.waqt.R;
import com.markrai.waqt.activity.MainActivity;
import com.markrai.waqt.model.Task;
import com.markrai.waqt.util.TaskUtils;


class TaskViewHolder extends RecyclerView.ViewHolder {
    EditText editTextTask;
    Spinner spinnerTimeBurden;
    CheckBox checkBoxTask;
    ImageView dragHandle;
    ImageView imageViewTask;
    ImageView playIcon;
    TaskAdapter.CustomTextWatcher customTextWatcher;
    LinearLayout container;
    private CountDownTimer countDownTimer;
    private long timeRemaining;
    private boolean isPaused = false;
    private Task task;  // Reference to the current task
    TextView textViewTimer;
    private TaskAdapter taskAdapter;  // Reference to the adapter
    private MainActivity.OnTimerStateChangeListener timerStateChangeListener;
    private MediaPlayer mediaPlayer; // MediaPlayer for beep sound


    public TaskViewHolder(@NonNull View itemView, TaskAdapter.CustomTextWatcher customTextWatcher, TaskAdapter taskAdapter) {
        super(itemView);
        editTextTask = itemView.findViewById(R.id.editTextTask);
        spinnerTimeBurden = itemView.findViewById(R.id.spinnerTimeBurden);
        checkBoxTask = itemView.findViewById(R.id.checkBoxTask);
        dragHandle = itemView.findViewById(R.id.dragHandle);
        imageViewTask = itemView.findViewById(R.id.imageViewTask);
        playIcon = itemView.findViewById(R.id.playIcon);
        textViewTimer = itemView.findViewById(R.id.textViewTimer);
        this.customTextWatcher = customTextWatcher;
        this.taskAdapter = taskAdapter;
        this.customTextWatcher.setEditTexts(editTextTask);

        // Add long-press listeners to stop the timer
        View.OnLongClickListener stopTimerListener = v -> {
            stopTimer();
            return true;
        };

        playIcon.setOnLongClickListener(stopTimerListener);
        textViewTimer.setOnLongClickListener(stopTimerListener);
    }

    void cancelCountdownTimer() {
        if (countDownTimer != null) {
            countDownTimer.cancel();
            countDownTimer = null;
        }
    }

    void stopTimer() {
        cancelCountdownTimer();
        // Reset UI elements
        spinnerTimeBurden.setEnabled(true);
        spinnerTimeBurden.setAlpha(1.0f);
        textViewTimer.setVisibility(View.GONE);
        stopBlinking();
        playIcon.setVisibility(View.GONE);
        spinnerTimeBurden.setVisibility(View.VISIBLE);  // Show the spinner again
        task.setCountdownActive(false);
        taskAdapter.clearActiveTask();
        Toast.makeText(taskAdapter.context, "Timer stopped", Toast.LENGTH_SHORT).show();
    }


    void setTask(Task task) {
        this.task = task;
    }

    void startCountdownTimer(long millisInFuture, Runnable onFinish, ArrayAdapter<String> spinnerAdapter) {
        cancelCountdownTimer();

        if (timerStateChangeListener != null) {
            timerStateChangeListener.onTimerStarted();
        }

        // Save the original time burden before changing it
        if (task.getOriginalTimeBurden() == null) {
            task.setOriginalTimeBurden(task.getTimeBurden());
        }

        timeRemaining = millisInFuture;
        task.setRemainingTime(timeRemaining);
        task.setCountdownActive(true);
        isPaused = false;
        task.setPaused(isPaused);
        playIcon.setImageResource(R.drawable.ic_play_arrow_black_24dp);
        playIcon.setVisibility(View.VISIBLE);

        new Handler(Looper.getMainLooper()).post(() -> {
            textViewTimer.setVisibility(View.VISIBLE);  // Ensure the timer TextView is visible
            spinnerTimeBurden.setVisibility(View.INVISIBLE);  // Hide the spinner
        });

        countDownTimer = createCountDownTimer(timeRemaining, onFinish, spinnerAdapter);
        countDownTimer.start();

        playIcon.setOnClickListener(v -> {
            if (isPaused) {
                resumeCountdownTimer(onFinish, spinnerAdapter);
                playIcon.setImageResource(R.drawable.ic_play_arrow_black_24dp);
            } else {
                pauseCountdownTimer();
            }
        });
    }

    private void pauseCountdownTimer() {
        if (countDownTimer != null) {
            countDownTimer.cancel();
        }
        isPaused = true;
        task.setPaused(isPaused);
        playIcon.setImageResource(R.drawable.ic_pause_black_24dp);
        startBlinking();  // Start blinking animation when paused
    }

    private void resumeCountdownTimer(Runnable onFinish, ArrayAdapter<String> spinnerAdapter) {
        stopBlinking();  // Stop blinking animation when resumed
        isPaused = false;
        task.setPaused(isPaused);
        playIcon.setImageResource(R.drawable.ic_pause_black_24dp);

        countDownTimer = createCountDownTimer(timeRemaining, () -> {
            int originalTimeBurden = TaskUtils.parseTime(task.getOriginalTimeBurden());
            taskAdapter.totalTimeBurden -= originalTimeBurden;
            taskAdapter.totalTimeBurdenListener.onTotalTimeBurdenUpdated(TaskUtils.formatTime(taskAdapter.totalTimeBurden), timeRemaining);

            task.setTimeBurden("0m");
            task.setOriginalTimeBurden(null);

            spinnerTimeBurden.setAdapter(spinnerAdapter);
            spinnerTimeBurden.setSelection(spinnerAdapter.getPosition("0m"));
            spinnerTimeBurden.setEnabled(true);

            onFinish.run();
        }, spinnerAdapter);

        countDownTimer.start();
    }

    void startBlinking() {
        // Load the blink animation and start it on the playIcon
        Animation blinkAnimation = AnimationUtils.loadAnimation(itemView.getContext(), R.anim.blink);
        playIcon.startAnimation(blinkAnimation);
    }

    void stopBlinking() {
        // Clear the blinking animation
        playIcon.clearAnimation();
    }

    public void setTimerStateChangeListener(MainActivity.OnTimerStateChangeListener listener) {
        this.timerStateChangeListener = listener;
    }

    private CountDownTimer createCountDownTimer(long millisInFuture, Runnable onFinish, ArrayAdapter<String> spinnerAdapter) {
        return new CountDownTimer(millisInFuture, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                timeRemaining = millisUntilFinished;
                long minutesRemaining = millisUntilFinished / 1000 / 60;
                long secondsRemaining = (millisUntilFinished / 1000) % 60;

                Log.d("TaskAdapter", "Time left: " + minutesRemaining + "m " + secondsRemaining + "s");

                new Handler(Looper.getMainLooper()).post(() -> {
                    textViewTimer.setText(minutesRemaining + "m " + secondsRemaining + "s");
                });
            }

            @Override
            public void onFinish() {
                Log.d("TaskAdapter", "Countdown timer finished for task at position " + getAdapterPosition());

                new Handler(Looper.getMainLooper()).post(() -> {
                    if (task != null) {
                        int originalTimeBurden = TaskUtils.parseTime(task.getOriginalTimeBurden());
                        taskAdapter.totalTimeBurden -= originalTimeBurden;

                        taskAdapter.totalTimeBurdenListener.onTotalTimeBurdenUpdated(
                                TaskUtils.formatTime(taskAdapter.totalTimeBurden),
                                taskAdapter.remainingTime
                        );

                        task.setTimeBurden("0m");
                        task.setOriginalTimeBurden(null);
                        task.setCountdownActive(false);

                        playIcon.setVisibility(View.GONE);
                        stopBlinking();

                        spinnerTimeBurden.setAdapter(spinnerAdapter);
                        spinnerTimeBurden.setSelection(spinnerAdapter.getPosition("0m"));
                        spinnerTimeBurden.setEnabled(true);
                        textViewTimer.setVisibility(View.GONE);
                        spinnerTimeBurden.setVisibility(View.VISIBLE);

                        playDoubleBeep();
                        onFinish.run();
                    }
                });
            }
        };
    }

    private void playDoubleBeep() {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer.create(itemView.getContext(), R.raw.doublebeep);
        }

        if (mediaPlayer != null) {
            mediaPlayer.setOnCompletionListener(mp -> {
                mp.release(); // Release the MediaPlayer resources
                mediaPlayer = null; // Set the mediaPlayer to null to prevent replaying
            });
            mediaPlayer.start(); // Start the beep sound
        }
    }

    @Override
    protected void finalize() throws Throwable {
        if (mediaPlayer != null) {
            mediaPlayer.release();
            mediaPlayer = null;
        }
        super.finalize();
    }
}

package com.markrai.waqt.adapter;

import android.content.Context;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;

import androidx.recyclerview.widget.RecyclerView;

import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;

import androidx.core.widget.CompoundButtonCompat;

import com.markrai.waqt.R;
import com.markrai.waqt.activity.MainActivity;
import com.markrai.waqt.model.Task;
import com.markrai.waqt.storage.JsonStorage;
import com.markrai.waqt.util.TaskUtils;

public class TaskViewBinder {

    static void bindTaskViewHolder(
            TaskViewHolder holder,
            Task task,
            int position,
            boolean isDeferred,
            TaskAdapter adapter,
            String[] timeOptions,
            Task currentActiveTask,
            Context context,
            int totalTimeBurden,
            long remainingTime,
            TaskAdapter.OnStartDragListener dragStartListener,
            TaskAdapter.TotalTimeBurdenListener totalTimeBurdenListener
    ) {

        // Bind task to the holder and set position
        task.setPosition(position);
        holder.setTask(task); // Bind the task to the ViewHolder
        holder.customTextWatcher.updatePosition(holder.getAdapterPosition());
        holder.editTextTask.setText(task.getTaskDescription());
        holder.editTextTask.setSingleLine(true);
        holder.editTextTask.setImeOptions(EditorInfo.IME_ACTION_DONE);
        holder.editTextTask.setInputType(InputType.TYPE_CLASS_TEXT);


        holder.editTextTask.setFilters(new InputFilter[]{new InputFilter() {
            @Override
            public CharSequence filter(CharSequence source, int start, int end,
                                       Spanned dest, int dstart, int dend) {
                for (int i = start; i < end; i++) {
                    if (source.charAt(i) == '\n') {
                        // Prevent manual line breaks (Enter key)
                        return "";
                    }
                }
                return null;
            }
        }});


        // Set up the Spinner
        ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(holder.itemView.getContext(), android.R.layout.simple_spinner_item, timeOptions);
        spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        holder.spinnerTimeBurden.setAdapter(spinnerAdapter);

        // Set the current time burden
        int spinnerPosition = spinnerAdapter.getPosition(task.getTimeBurden());
        if (spinnerPosition == -1) {
            spinnerPosition = 0;
            task.setTimeBurden(timeOptions[spinnerPosition]);
        }
        holder.spinnerTimeBurden.setSelection(spinnerPosition);

        // Restore the countdown timer state
        if (task.isCountdownActive()) {
            holder.playIcon.setVisibility(View.VISIBLE);
            if (task.isPaused()) {
                holder.playIcon.setImageResource(R.drawable.ic_pause_black_24dp);
                holder.startBlinking();
            } else {
                holder.playIcon.setImageResource(R.drawable.ic_play_arrow_black_24dp);
                holder.stopBlinking();
            }

            // Disable spinner and show the countdown time
            holder.spinnerTimeBurden.setEnabled(false);
            holder.spinnerTimeBurden.setAlpha(0.5f);
            holder.spinnerTimeBurden.setVisibility(View.INVISIBLE);
            holder.textViewTimer.setVisibility(View.VISIBLE);
            holder.textViewTimer.setText(TaskUtils.formatCountdownTime(task.getRemainingTime()));

            // Restart the countdown timer, binding it to the task ID
            holder.startCountdownTimer(
                    task.getRemainingTime(),
                    () -> {
                        holder.spinnerTimeBurden.setEnabled(true);
                        holder.spinnerTimeBurden.setAlpha(1.0f);
                        holder.textViewTimer.setVisibility(View.GONE);
                        holder.spinnerTimeBurden.setVisibility(View.VISIBLE);
                        holder.stopBlinking();
                        holder.playIcon.setVisibility(View.GONE);
                        Toast.makeText(context, "Time's up!", Toast.LENGTH_SHORT).show();
                        adapter.clearActiveTask();
                    },
                    spinnerAdapter
            );

        } else {
            // If no countdown is active, reset views to their default state
            holder.playIcon.setVisibility(View.GONE);
            holder.textViewTimer.setVisibility(View.GONE);
            holder.spinnerTimeBurden.setEnabled(true);
            holder.spinnerTimeBurden.setAlpha(1.0f);
            holder.stopBlinking();
            holder.spinnerTimeBurden.setVisibility(View.VISIBLE);

            String timeToDisplay = task.getOriginalTimeBurden() != null ? task.getOriginalTimeBurden() : task.getTimeBurden();
            holder.spinnerTimeBurden.setSelection(spinnerAdapter.getPosition(timeToDisplay));
        }

        // Long press on spinner to start or stop the countdown timer
        holder.spinnerTimeBurden.setOnLongClickListener(v -> {

            if (isDeferred) {
                adapter.undeferTask(position);
                int newPosition = adapter.getTaskList().size() - 1;
                new Handler(Looper.getMainLooper()).post(() -> {
                    RecyclerView.ViewHolder viewHolder = ((MainActivity) context).findViewByPosition(newPosition);
                    if (viewHolder instanceof TaskViewHolder) {
                        TaskViewHolder newHolder = (TaskViewHolder) viewHolder;
                        newHolder.spinnerTimeBurden.performLongClick();
                    }
                });
                return true;
            }

            if (adapter.currentActiveTask != null && !adapter.currentActiveTask.getId().equals(task.getId())) {
                Toast.makeText(context, "Let's focus on one thing at a time 😉", Toast.LENGTH_SHORT).show();
                return true; // Prevent starting a new timer
            }

            if (task.isCountdownActive()) {
                // User wants to stop the timer
                holder.cancelCountdownTimer();
                task.setCountdownActive(false);
                task.setPaused(false);
                task.setRemainingTime(0);
                adapter.saveTasks();

                holder.spinnerTimeBurden.setEnabled(true);
                holder.spinnerTimeBurden.setAlpha(1.0f);
                holder.textViewTimer.setVisibility(View.GONE);
                holder.stopBlinking();
                holder.playIcon.setVisibility(View.GONE);
                holder.spinnerTimeBurden.setVisibility(View.VISIBLE);


                Toast.makeText(context, "Timer stopped.", Toast.LENGTH_SHORT).show();

                adapter.clearActiveTask();
            } else {
                String selectedTime = task.getTimeBurden();
                int timeInMinutes = TaskUtils.parseTime(selectedTime);

                if (timeInMinutes > 0) {
                    long millisInFuture = timeInMinutes * 60 * 1000;

                    Log.d("TaskAdapter", "Starting countdown timer for " + selectedTime + " (" + millisInFuture + "ms) for task with ID " + task.getId());

                    adapter.currentActiveTask = task;

                    holder.spinnerTimeBurden.setEnabled(false);

                    holder.startCountdownTimer(millisInFuture, () -> {
                        holder.spinnerTimeBurden.setEnabled(true);
                        holder.spinnerTimeBurden.setAlpha(1.0f);
                        holder.textViewTimer.setVisibility(View.GONE);
                        holder.stopBlinking();
                        holder.playIcon.setVisibility(View.GONE);
                        Toast.makeText(context, "Time's up!", Toast.LENGTH_SHORT).show();

                        adapter.clearActiveTask();
                    }, spinnerAdapter);

                } else {
                    Toast.makeText(context, "Cannot start timer for 0m", Toast.LENGTH_SHORT).show();
                }
            }
            return true;
        });


        holder.spinnerTimeBurden.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int spinnerPosition, long id) {
                String selectedTime = timeOptions[spinnerPosition];
                int oldTimeBurden = TaskUtils.parseTime(task.getTimeBurden());
                int newTimeBurden = TaskUtils.parseTime(selectedTime);

                if (!task.isCompleted() && !isDeferred) {
                    adapter.totalTimeBurden -= oldTimeBurden;
                    adapter.totalTimeBurden += newTimeBurden;
                }

                task.setTimeBurden(selectedTime);
                if (!isDeferred) {
                    totalTimeBurdenListener.onTotalTimeBurdenUpdated(TaskUtils.formatTime(adapter.totalTimeBurden), remainingTime);
                }
                adapter.saveTasks();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parentView) {
                // No action needed
            }
        });

        holder.checkBoxTask.setChecked(task.isCompleted());
        if (isDeferred) {
            CompoundButtonCompat.setButtonTintList(holder.checkBoxTask, ColorStateList.valueOf(context.getResources().getColor(R.color.white)));
        } else {
            CompoundButtonCompat.setButtonTintList(holder.checkBoxTask, ColorStateList.valueOf(Color.BLACK));
        }

        // Apply gray color to individual items if the task is completed
        if (task.isCompleted()) {
            holder.editTextTask.setTextColor(Color.GRAY);
            holder.editTextTask.setBackgroundTintList(ColorStateList.valueOf(Color.GRAY));
            holder.spinnerTimeBurden.setEnabled(false);
            CompoundButtonCompat.setButtonTintList(holder.checkBoxTask, ColorStateList.valueOf(Color.GRAY));
            holder.dragHandle.setColorFilter(Color.GRAY);
        } else {
            holder.editTextTask.setTextColor(Color.BLACK);
            holder.editTextTask.setBackgroundTintList(ColorStateList.valueOf(Color.BLACK));
            holder.spinnerTimeBurden.setEnabled(true);
            CompoundButtonCompat.setButtonTintList(holder.checkBoxTask, ColorStateList.valueOf(Color.BLACK));
            holder.dragHandle.setColorFilter(Color.BLACK);
        }

        holder.editTextTask.setOnEditorActionListener((v, actionId, event) -> {
            if (actionId == EditorInfo.IME_ACTION_DONE && event == null) {
                String taskDescription = v.getText().toString().trim();
                if (!taskDescription.isEmpty()) {
                    // Ensure time burden is set
                    if (task.getTimeBurden().equals("0m")) {
                        task.setTimeBurden("5m");
                        if (!isDeferred) {
                            adapter.totalTimeBurden += TaskUtils.parseTime("5m");
                            totalTimeBurdenListener.onTotalTimeBurdenUpdated(
                                    TaskUtils.formatTime(adapter.totalTimeBurden),
                                    remainingTime
                            );
                        }
                        adapter.notifyItemChanged(position);
                    }

                    // Find the task's actual index in the taskList or deferredTaskList
                    int taskIndex;
                    if (isDeferred) {
                        taskIndex = adapter.deferredTaskList.indexOf(task);
                        adapter.addDeferredTask(taskIndex + 1, "", "0m");
                    } else {
                        taskIndex = adapter.getTaskList().indexOf(task);
                        adapter.addTask(taskIndex + 1, "", "0m");
                    }

                    // Move focus to the new task's EditText
                    new Handler().post(() -> {
                        RecyclerView.ViewHolder viewHolder = ((MainActivity) context).findViewByPosition(taskIndex + 1);
                        if (viewHolder instanceof TaskViewHolder) {
                            TaskViewHolder taskViewHolder = (TaskViewHolder) viewHolder;
                            taskViewHolder.editTextTask.requestFocus();
                            InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
                            imm.showSoftInput(taskViewHolder.editTextTask, InputMethodManager.SHOW_IMPLICIT);
                        }
                    });
                    return true; // Consume the event
                } else {
                    return true; // Consume the event even if the description is empty
                }
            }
            return false;
        });




        holder.editTextTask.setOnFocusChangeListener((v, hasFocus) -> {
            if (hasFocus) {
                holder.editTextTask.setBackgroundTintList(ColorStateList.valueOf(Color.RED));
            } else {
                holder.editTextTask.setBackgroundTintList(ColorStateList.valueOf(task.isCompleted() ? Color.GRAY : Color.BLACK));
            }
        });


        // Set the touch listener only on the drag handle
        holder.dragHandle.setOnTouchListener((v, event) -> {
            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                dragStartListener.onStartDrag(holder);
                return true;  // Handle the event
            }
            return false;
        });

        // Override performClick or ensure it's called
        holder.dragHandle.setOnClickListener(v -> {
            // Handle click actions
            v.performClick(); // Ensure performClick() is called for accessibility
        });

        holder.checkBoxTask.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (holder.checkBoxTask.isPressed()) {
                if (isDeferred && isChecked) {
                    // Handle the case where a deferred task is marked as completed
                    adapter.undeferAndCompleteTask(position);
                } else {
                    task.setCompleted(isChecked);
                    if (isChecked) {
                        adapter.totalTimeBurden -= TaskUtils.parseTime(task.getTimeBurden());
                        adapter.moveItemToBottom(position);
                    } else {
                        if (!isDeferred) {
                            adapter.totalTimeBurden += TaskUtils.parseTime(task.getTimeBurden());
                        }
                        if (!isDeferred) {
                            adapter.moveItemToTop(position);
                        }
                    }
                    totalTimeBurdenListener.onTotalTimeBurdenUpdated(TaskUtils.formatTime(adapter.totalTimeBurden), remainingTime);
                    adapter.notifyItemChanged(position);
                    adapter.saveTasks();
                }
            }
        });

        holder.editTextTask.addTextChangedListener(holder.customTextWatcher);

    }
}