allow sorting of automation tasks

This commit is contained in:
Milos Kozak 2019-12-03 21:07:31 +01:00
parent a33b90478b
commit a66b3f7090
9 changed files with 241 additions and 20 deletions

View file

@ -5,10 +5,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.OnStartDragListener
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.SimpleItemTouchHelperCallback
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationDataChanged
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
import info.nightscout.androidaps.utils.FabricPrivacy
@ -17,11 +21,14 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.automation_fragment.*
class AutomationFragment : Fragment() {
class AutomationFragment : Fragment(), OnStartDragListener {
private var disposable: CompositeDisposable = CompositeDisposable()
private var eventListAdapter: EventListAdapter? = null
private var itemTouchHelper: ItemTouchHelper? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.automation_fragment, container, false)
}
@ -29,7 +36,7 @@ class AutomationFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
eventListAdapter = EventListAdapter(AutomationPlugin.automationEvents, fragmentManager, activity)
eventListAdapter = EventListAdapter(AutomationPlugin.automationEvents, fragmentManager, activity, this)
automation_eventListView.layoutManager = LinearLayoutManager(context)
automation_eventListView.adapter = eventListAdapter
@ -42,6 +49,10 @@ class AutomationFragment : Fragment() {
fragmentManager?.let { dialog.show(it, "EditEventDialog") }
}
val callback: ItemTouchHelper.Callback = SimpleItemTouchHelperCallback(eventListAdapter!!)
itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper?.attachToRecyclerView(automation_eventListView)
}
@Synchronized
@ -81,4 +92,8 @@ class AutomationFragment : Fragment() {
automation_logView?.text = sb.toString()
}
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
itemTouchHelper?.startDrag(viewHolder);
}
}

View file

@ -1,9 +1,12 @@
package info.nightscout.androidaps.plugins.general.automation;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
@ -17,6 +20,7 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -25,19 +29,26 @@ import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.general.automation.actions.Action;
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog;
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperAdapter;
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperViewHolder;
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.OnStartDragListener;
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationDataChanged;
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
import info.nightscout.androidaps.utils.OKDialog;
class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder> {
class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder> implements ItemTouchHelperAdapter {
private final List<AutomationEvent> eventList;
private final FragmentManager fragmentManager;
private final Activity activity;
EventListAdapter(List<AutomationEvent> events, FragmentManager fragmentManager, Activity activity) {
private final OnStartDragListener mDragStartListener;
EventListAdapter(List<AutomationEvent> events, FragmentManager fragmentManager, Activity activity, OnStartDragListener dragStartListener) {
this.eventList = events;
this.fragmentManager = fragmentManager;
this.activity = activity;
mDragStartListener = dragStartListener;
}
@NonNull
@ -54,6 +65,7 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
layout.addView(iv);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
final AutomationEvent event = eventList.get(position);
@ -91,16 +103,10 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
RxBus.INSTANCE.send(new EventAutomationDataChanged());
});
// remove event
holder.iconTrash.setOnClickListener(v ->
OKDialog.showConfirmation(activity, MainApp.gs(R.string.removerecord) + " " + event.getTitle(), () -> {
eventList.remove(event);
RxBus.INSTANCE.send(new EventAutomationDataChanged());
})
);
// edit event
holder.rootLayout.setOnClickListener(v -> {
holder.rootLayout.setOnClickListener(v ->
{
//EditEventDialog dialog = EditEventDialog.Companion.newInstance(event, false);
EditEventDialog dialog = new EditEventDialog();
Bundle args = new Bundle();
@ -110,6 +116,17 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
if (fragmentManager != null)
dialog.show(fragmentManager, "EditEventDialog");
});
// Start a drag whenever the handle view it touched
holder.iconSort.setOnTouchListener((v, motionEvent) ->
{
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
mDragStartListener.onStartDrag(holder);
return true;
}
return v.onTouchEvent(motionEvent);
});
}
@Override
@ -117,12 +134,33 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
return eventList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
Collections.swap(eventList, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
RxBus.INSTANCE.send(new EventAutomationDataChanged());
return true;
}
@Override
public void onItemDismiss(int position) {
OKDialog.showConfirmation(activity, MainApp.gs(R.string.removerecord) + " " + eventList.get(position).getTitle(),
() -> {
eventList.remove(position);
notifyItemRemoved(position);
RxBus.INSTANCE.send(new EventAutomationDataChanged());
RxBus.INSTANCE.send(new EventAutomationUpdateGui());
}, () -> {
RxBus.INSTANCE.send(new EventAutomationUpdateGui());
});
}
static class ViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder {
final RelativeLayout rootLayout;
final LinearLayout iconLayout;
final TextView eventTitle;
final Context context;
final ImageView iconTrash;
final ImageView iconSort;
final CheckBox enabled;
ViewHolder(View view, Context context) {
@ -131,8 +169,18 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
eventTitle = view.findViewById(R.id.viewEventTitle);
rootLayout = view.findViewById(R.id.rootLayout);
iconLayout = view.findViewById(R.id.iconLayout);
iconTrash = view.findViewById(R.id.iconTrash);
iconSort = view.findViewById(R.id.iconSort);
enabled = view.findViewById(R.id.automation_enabled);
}
@Override
public void onItemSelected() {
itemView.setBackgroundColor(Color.LTGRAY);
}
@Override
public void onItemClear() {
itemView.setBackgroundColor(MainApp.gc(R.color.ribbonDefault));
}
}
}

View file

@ -0,0 +1,33 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers
interface ItemTouchHelperAdapter {
/**
* Called when an item has been dragged far enough to trigger a move. This is called every time
* an item is shifted, and **not** at the end of a "drop" event.<br></br>
* <br></br>
* Implementations should call [RecyclerView.Adapter.notifyItemMoved] after
* adjusting the underlying data to reflect this move.
*
* @param fromPosition The start position of the moved item.
* @param toPosition Then resolved position of the moved item.
* @return True if the item was moved to the new adapter position.
*
* @see RecyclerView.getAdapterPositionFor
* @see RecyclerView.ViewHolder.getAdapterPosition
*/
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
/**
* Called when an item has been dismissed by a swipe.<br></br>
* <br></br>
* Implementations should call [RecyclerView.Adapter.notifyItemRemoved] after
* adjusting the underlying data to reflect this removal.
*
* @param position The position of the item dismissed.
*
* @see RecyclerView.getAdapterPositionFor
* @see RecyclerView.ViewHolder.getAdapterPosition
*/
fun onItemDismiss(position: Int)
}

View file

@ -0,0 +1,20 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers
/**
* Interface to notify an item ViewHolder of relevant callbacks from [ ].
*
* @author Paul Burke (ipaulpro)
*/
interface ItemTouchHelperViewHolder {
/**
* Called when the [ItemTouchHelper] first registers an item as being moved or swiped.
* Implementations should update the item view to indicate it's active state.
*/
fun onItemSelected()
/**
* Called when the [ItemTouchHelper] has completed the move or swipe, and the active item
* state should be cleared.
*/
fun onItemClear()
}

View file

@ -0,0 +1,12 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers
import androidx.recyclerview.widget.RecyclerView
interface OnStartDragListener {
/**
* Called when a view is requesting a start of a drag.
*
* @param viewHolder The holder of the view to drag.
*/
fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
}

View file

@ -0,0 +1,85 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers
import android.graphics.Canvas
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
/**
* An implementation of [ItemTouchHelper.Callback] that enables basic drag & drop and
* swipe-to-dismiss. Drag events are automatically started by an item long-press.<br></br>
*
* Expects the `RecyclerView.Adapter` to listen for [ ] callbacks and the `RecyclerView.ViewHolder` to implement
* [ItemTouchHelperViewHolder].
*
*/
class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter) : ItemTouchHelper.Callback() {
override fun isLongPressDragEnabled(): Boolean {
return true
}
override fun isItemViewSwipeEnabled(): Boolean {
return true
}
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { // Set movement flags based on the layout manager
return if (recyclerView.layoutManager is GridLayoutManager) {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
val swipeFlags = 0
makeMovementFlags(dragFlags, swipeFlags)
} else {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
makeMovementFlags(dragFlags, swipeFlags)
}
}
override fun onMove(recyclerView: RecyclerView, source: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
if (source.itemViewType != target.itemViewType) {
return false
}
// Notify the adapter of the move
mAdapter.onItemMove(source.adapterPosition, target.adapterPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, i: Int) { // Notify the adapter of the dismissal
mAdapter.onItemDismiss(viewHolder.adapterPosition)
}
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { // Fade out the view as it is swiped out of the parent's bounds
val alpha = ALPHA_FULL - abs(dX) / viewHolder.itemView.width.toFloat()
viewHolder.itemView.alpha = alpha
viewHolder.itemView.translationX = dX
} else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { // We only want the active item to change
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder is ItemTouchHelperViewHolder) { // Let the view holder know that this item is being moved or dragged
val itemViewHolder: ItemTouchHelperViewHolder = viewHolder
itemViewHolder.onItemSelected()
}
}
super.onSelectedChanged(viewHolder, actionState)
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = ALPHA_FULL
if (viewHolder is ItemTouchHelperViewHolder) { // Tell the view holder it's time to restore the idle state
val itemViewHolder: ItemTouchHelperViewHolder = viewHolder
itemViewHolder.onItemClear()
}
}
companion object {
const val ALPHA_FULL = 1.0f
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#C0C0C0"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M3,15h18v-2L3,13v2zM3,19h18v-2L3,17v2zM3,11h18L21,9L3,9v2zM3,5v2h18L21,5L3,5z"/>
</vector>

View file

@ -8,6 +8,8 @@
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
android:background="@color/ribbonDefault"
android:padding="8dp">
@ -26,21 +28,21 @@
android:layout_alignBottom="@+id/automation_enabled"
android:layout_centerVertical="true"
android:layout_marginTop="6dp"
android:layout_toStartOf="@+id/iconTrash"
android:layout_toStartOf="@+id/iconSort"
android:layout_toEndOf="@id/automation_enabled"
android:text="Title"
android:textAlignment="viewStart"
android:textStyle="bold" />
<ImageView
android:id="@+id/iconTrash"
android:id="@+id/iconSort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:contentDescription="@string/remove_label"
android:contentDescription="@string/reorder_label"
android:orientation="horizontal"
android:src="@drawable/ic_trash_outline" />
android:src="@drawable/ic_reorder_gray_24dp" />
<LinearLayout
android:id="@+id/iconLayout"

View file

@ -1660,5 +1660,6 @@
<string name="profilenamecontainsdot">Profile name contains dots.\nThis is not supported by NS.\nProfile is not uploaded to NS.</string>
<string name="low_mark_comment">Lower value of in range area (display only)</string>
<string name="high_mark_comment">Higher value of in range area (display only)</string>
<string name="reorder_label">Reorder</string>
</resources>