📄 src/Views/SwipeToDeleteCallback.cs
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using AndroidX.Core.Content;
using AndroidX.RecyclerView.Widget;

namespace NotesApp.Views;

// Renders the red delete background + trash icon behind a row as it's swiped. OnSwiped only
// reports the position - the caller decides whether to actually delete (after confirmation)
// or snap the row back via NotifyItemChanged.
public class SwipeToDeleteCallback(Action<int> onSwiped, Context context)
    : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.Left | ItemTouchHelper.Right)
{
    private const int IconMarginDp = 16;

    private readonly ColorDrawable _background = new(Color.ParseColor("#D32F2F"));
    private readonly Drawable? _icon = ContextCompat.GetDrawable(
        context,
        Resource.Drawable.ic_delete_24
    );
    private readonly float _density = context.Resources!.DisplayMetrics!.Density;

    public override bool OnMove(
        RecyclerView recyclerView,
        RecyclerView.ViewHolder viewHolder,
        RecyclerView.ViewHolder target
    ) => false;

    public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction) =>
        onSwiped(viewHolder.BindingAdapterPosition);

    public override void OnChildDraw(
        Canvas c,
        RecyclerView recyclerView,
        RecyclerView.ViewHolder viewHolder,
        float dX,
        float dY,
        int actionState,
        bool isCurrentlyActive
    )
    {
        var itemView = viewHolder.ItemView;
        var iconMargin = (int)(IconMarginDp * _density);
        var iconWidth = _icon?.IntrinsicWidth ?? 0;
        var iconHeight = _icon?.IntrinsicHeight ?? 0;
        var iconTop = itemView.Top + (itemView.Height - iconHeight) / 2;
        var iconBottom = iconTop + iconHeight;

        if (dX > 0)
        {
            _background.SetBounds(
                itemView.Left,
                itemView.Top,
                itemView.Left + (int)dX,
                itemView.Bottom
            );
            _icon?.SetBounds(
                itemView.Left + iconMargin,
                iconTop,
                itemView.Left + iconMargin + iconWidth,
                iconBottom
            );
        }
        else if (dX < 0)
        {
            _background.SetBounds(
                itemView.Right + (int)dX,
                itemView.Top,
                itemView.Right,
                itemView.Bottom
            );
            _icon?.SetBounds(
                itemView.Right - iconMargin - iconWidth,
                iconTop,
                itemView.Right - iconMargin,
                iconBottom
            );
        }
        else
        {
            _background.SetBounds(0, 0, 0, 0);
        }

        _background.Draw(c);
        if (dX != 0)
            _icon?.Draw(c);

        base.OnChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }
}