<NME> AbstractCommand.java
<BEF> package cgeo.geocaching.command;

import cgeo.geocaching.utils.AsyncTaskWithProgress;

import android.app.Activity;
import android.os.Parcelable;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;

import com.jensdriller.libs.undobar.UndoBar;
import com.jensdriller.libs.undobar.UndoBar.Listener;
import org.apache.commons.lang3.StringUtils;

/**
 * Default implementation of the command interface with undo support and background processing.
 */
public abstract class AbstractCommand implements Command {

    private static final int UNDO_DURATION_SECONDS = 5;

    @NonNull
    private final Activity context;
    private String progressMessage = null;

    protected AbstractCommand(@NonNull final Activity context, @StringRes final int progressMessageId) {
        this.context = context;
        if (progressMessageId != 0) {
            this.progressMessage = context.getString(progressMessageId);
        }
    }

    protected AbstractCommand(final Activity context) {
        this(context, 0);
    }

    /**
     * checks whether the command can be executed, before running the {@link #execute()} method
     */
    protected boolean canExecute() {
        return true;
    }

    /**
     * Implements the actual work of the command.
     * <p>
     * This runs in a <b>non</b> UI thread.
     * </p>
     */
    protected abstract void doCommand();

    /**
     * Reverse operation to undo the effects of {@link #doCommand()}.
     * <p>
     * This runs in a <b>non</b> UI thread.
     * </p>
     */
    protected abstract void undoCommand();

    /**
     * Called after the execution of {@link #doCommand()} or {@link #undoCommand()} finished.
     * <p>
     * This runs in the UI thread.
     * </p>
     */
    protected abstract void onFinished();

    /**
     * Called after the execution of {@link #undoCommand()} finished. By default, calls {@link #onFinished()}.
     * <p>
     * This runs in the UI thread.
     * </p>
     */
    protected void onFinishedUndo() {
        onFinished();
    }

    /**
     * Message to be shown as toast when the command finished. Can be {@code null}, if the operation does not support
     * undo.
     */
    @Nullable
    protected abstract String getResultMessage();

    @NonNull
    protected Activity getContext() {
        return context;
    }

    @Override
    public void execute() {
        if (!canExecute()) {
            return;
        }
        final AsyncTaskWithProgress<Void, Void> task = new ActionAsyncTask(context, null, progressMessage, true);
        task.execute();
    }

    public final void setProgressMessage(final String progressMessage) {
        this.progressMessage = progressMessage;
    }

    private final class ActionAsyncTask extends AsyncTaskWithProgress<Void, Void> implements Listener {
        private ActionAsyncTask(final Activity activity, final String progressTitle, final String progressMessage, final boolean indeterminate) {
            super(activity, progressTitle, progressMessage, indeterminate);
        }

        @Override
        protected Void doInBackgroundInternal(final Void[] params) {
            doCommand();
            return null;
        }

        @Override
        protected void onPostExecuteInternal(final Void result) {
            onFinished();
            final String resultMessage = getResultMessage();
            showUndoToast(resultMessage);
        }

        private void showUndoToast(final String resultMessage) {
            if (StringUtils.isNotEmpty(resultMessage)) {
                new UndoBar.Builder(context)
                        .setMessage(resultMessage)
                        .setListener(this)
                        .setDuration(UNDO_DURATION_SECONDS * 1000)
                        .show();
            }
        }

        @Override
        public void onHide() {
            // do nothing
        }

        @Override
        public void onUndo(final Parcelable arg0) {
            new UndoTask(context).execute();
        }
    }

    private final class UndoTask extends AsyncTaskWithProgress<Void, Void> {

        UndoTask(final Activity activity) {
        UndoTask(final Activity activity) {
            super(activity, null, null, true);
        }

        @Override
        protected Void doInBackgroundInternal(final Void[] params) {
            undoCommand();
            return null;
        }

        protected void onPostExecuteInternal(final Void result) {
            onFinishedUndo();
        }

    }

}
}

<MSG> replace undobar by snackbar

fixes  #7179

Based on the work from @Bananeweizen saved in https://github.com/UniQP/cgeo/tree/snackbar.
Now it was usable after we are targetting minSdk 17.

<DFF> @@ -1,16 +1,17 @@
 package cgeo.geocaching.command;
 
+import cgeo.geocaching.R;
 import cgeo.geocaching.utils.AsyncTaskWithProgress;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
-import android.os.Parcelable;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
-import com.jensdriller.libs.undobar.UndoBar;
-import com.jensdriller.libs.undobar.UndoBar.Listener;
+import com.google.android.material.snackbar.Snackbar;
 import org.apache.commons.lang3.StringUtils;
 
 /**
@@ -18,7 +19,7 @@ import org.apache.commons.lang3.StringUtils;
  */
 public abstract class AbstractCommand implements Command {
 
-    private static final int UNDO_DURATION_SECONDS = 5;
+    private static final int UNDO_DURATION_MILLISEC = 5_000;
 
     @NonNull
     private final Activity context;
@@ -101,7 +102,8 @@ public abstract class AbstractCommand implements Command {
         this.progressMessage = progressMessage;
     }
 
-    private final class ActionAsyncTask extends AsyncTaskWithProgress<Void, Void> implements Listener {
+    @SuppressLint("StaticFieldLeak")
+    private final class ActionAsyncTask extends AsyncTaskWithProgress<Void, Void> implements View.OnClickListener {
         private ActionAsyncTask(final Activity activity, final String progressTitle, final String progressMessage, final boolean indeterminate) {
             super(activity, progressTitle, progressMessage, indeterminate);
         }
@@ -119,27 +121,22 @@ public abstract class AbstractCommand implements Command {
             showUndoToast(resultMessage);
         }
 
+        @SuppressLint("WrongConstant")
         private void showUndoToast(final String resultMessage) {
             if (StringUtils.isNotEmpty(resultMessage)) {
-                new UndoBar.Builder(context)
-                        .setMessage(resultMessage)
-                        .setListener(this)
-                        .setDuration(UNDO_DURATION_SECONDS * 1000)
+                Snackbar.make(context.findViewById(android.R.id.content), resultMessage, UNDO_DURATION_MILLISEC)
+                        .setAction(context.getString(R.string.undo), this)
                         .show();
             }
         }
 
         @Override
-        public void onHide() {
-            // do nothing
-        }
-
-        @Override
-        public void onUndo(final Parcelable arg0) {
+        public void onClick(final View view) {
             new UndoTask(context).execute();
         }
     }
 
+    @SuppressLint("StaticFieldLeak")
     private final class UndoTask extends AsyncTaskWithProgress<Void, Void> {
 
         UndoTask(final Activity activity) {
@@ -156,7 +153,5 @@ public abstract class AbstractCommand implements Command {
         protected void onPostExecuteInternal(final Void result) {
             onFinishedUndo();
         }
-
     }
-
 }

replace undobar by snackbar