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.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog 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.EventAutomationDataChanged
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
@ -17,11 +21,14 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.automation_fragment.* import kotlinx.android.synthetic.main.automation_fragment.*
class AutomationFragment : Fragment() {
class AutomationFragment : Fragment(), OnStartDragListener {
private var disposable: CompositeDisposable = CompositeDisposable() private var disposable: CompositeDisposable = CompositeDisposable()
private var eventListAdapter: EventListAdapter? = null private var eventListAdapter: EventListAdapter? = null
private var itemTouchHelper: ItemTouchHelper? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.automation_fragment, container, false) return inflater.inflate(R.layout.automation_fragment, container, false)
} }
@ -29,7 +36,7 @@ class AutomationFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
eventListAdapter = EventListAdapter(AutomationPlugin.automationEvents, fragmentManager, activity) eventListAdapter = EventListAdapter(AutomationPlugin.automationEvents, fragmentManager, activity, this)
automation_eventListView.layoutManager = LinearLayoutManager(context) automation_eventListView.layoutManager = LinearLayoutManager(context)
automation_eventListView.adapter = eventListAdapter automation_eventListView.adapter = eventListAdapter
@ -42,6 +49,10 @@ class AutomationFragment : Fragment() {
fragmentManager?.let { dialog.show(it, "EditEventDialog") } fragmentManager?.let { dialog.show(it, "EditEventDialog") }
} }
val callback: ItemTouchHelper.Callback = SimpleItemTouchHelperCallback(eventListAdapter!!)
itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper?.attachToRecyclerView(automation_eventListView)
} }
@Synchronized @Synchronized
@ -81,4 +92,8 @@ class AutomationFragment : Fragment() {
automation_logView?.text = sb.toString() 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; package info.nightscout.androidaps.plugins.general.automation;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox; import android.widget.CheckBox;
@ -17,6 +20,7 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; 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.bus.RxBus;
import info.nightscout.androidaps.plugins.general.automation.actions.Action; 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.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.EventAutomationDataChanged;
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector; import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
import info.nightscout.androidaps.utils.OKDialog; 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 List<AutomationEvent> eventList;
private final FragmentManager fragmentManager; private final FragmentManager fragmentManager;
private final Activity activity; 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.eventList = events;
this.fragmentManager = fragmentManager; this.fragmentManager = fragmentManager;
this.activity = activity; this.activity = activity;
mDragStartListener = dragStartListener;
} }
@NonNull @NonNull
@ -54,6 +65,7 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
layout.addView(iv); layout.addView(iv);
} }
@SuppressLint("ClickableViewAccessibility")
@Override @Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) { public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
final AutomationEvent event = eventList.get(position); final AutomationEvent event = eventList.get(position);
@ -91,16 +103,10 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
RxBus.INSTANCE.send(new EventAutomationDataChanged()); 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 // edit event
holder.rootLayout.setOnClickListener(v -> { holder.rootLayout.setOnClickListener(v ->
{
//EditEventDialog dialog = EditEventDialog.Companion.newInstance(event, false); //EditEventDialog dialog = EditEventDialog.Companion.newInstance(event, false);
EditEventDialog dialog = new EditEventDialog(); EditEventDialog dialog = new EditEventDialog();
Bundle args = new Bundle(); Bundle args = new Bundle();
@ -110,6 +116,17 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
if (fragmentManager != null) if (fragmentManager != null)
dialog.show(fragmentManager, "EditEventDialog"); 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 @Override
@ -117,12 +134,33 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
return eventList.size(); 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 RelativeLayout rootLayout;
final LinearLayout iconLayout; final LinearLayout iconLayout;
final TextView eventTitle; final TextView eventTitle;
final Context context; final Context context;
final ImageView iconTrash; final ImageView iconSort;
final CheckBox enabled; final CheckBox enabled;
ViewHolder(View view, Context context) { ViewHolder(View view, Context context) {
@ -131,8 +169,18 @@ class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder>
eventTitle = view.findViewById(R.id.viewEventTitle); eventTitle = view.findViewById(R.id.viewEventTitle);
rootLayout = view.findViewById(R.id.rootLayout); rootLayout = view.findViewById(R.id.rootLayout);
iconLayout = view.findViewById(R.id.iconLayout); 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); 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_marginTop="8dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
android:background="@color/ribbonDefault" android:background="@color/ribbonDefault"
android:padding="8dp"> android:padding="8dp">
@ -26,21 +28,21 @@
android:layout_alignBottom="@+id/automation_enabled" android:layout_alignBottom="@+id/automation_enabled"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:layout_toStartOf="@+id/iconTrash" android:layout_toStartOf="@+id/iconSort"
android:layout_toEndOf="@id/automation_enabled" android:layout_toEndOf="@id/automation_enabled"
android:text="Title" android:text="Title"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textStyle="bold" /> android:textStyle="bold" />
<ImageView <ImageView
android:id="@+id/iconTrash" android:id="@+id/iconSort"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:contentDescription="@string/remove_label" android:contentDescription="@string/reorder_label"
android:orientation="horizontal" android:orientation="horizontal"
android:src="@drawable/ic_trash_outline" /> android:src="@drawable/ic_reorder_gray_24dp" />
<LinearLayout <LinearLayout
android:id="@+id/iconLayout" 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="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="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="high_mark_comment">Higher value of in range area (display only)</string>
<string name="reorder_label">Reorder</string>
</resources> </resources>