diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt index d285db5f0a..c56b34c032 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt @@ -1,10 +1,10 @@ package info.nightscout.androidaps.plugins.source -import android.graphics.Paint import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.util.SparseArray +import android.view.* +import androidx.appcompat.widget.Toolbar +import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment @@ -54,11 +54,12 @@ class BGSourceFragment : DaggerFragment() { private val disposable = CompositeDisposable() private val millsToThePast = T.hours(36).msecs() + private var selectedItems: SparseArray = SparseArray() + private var toolbar: Toolbar? = null + private var removeActionMode: ActionMode? = null private var _binding: BgsourceFragmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = @@ -66,6 +67,8 @@ class BGSourceFragment : DaggerFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + toolbar = activity?.findViewById(R.id.toolbar) + setHasOptionsMenu(true) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) @@ -94,17 +97,45 @@ class BGSourceFragment : DaggerFragment() { @Synchronized override fun onPause() { + removeActionMode?.finish() disposable.clear() super.onPause() } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_bgsource, menu) + } + + override fun onPrepareOptionsMenu(menu: Menu) { + // Only show when tab bg source is shown + menu.findItem(R.id.nav_remove_items)?.isVisible = isResumed + super.onPrepareOptionsMenu(menu) + } + @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _binding = null } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.nav_remove_items -> { + if (toolbar != null) { + removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) // in overview + } else { + removeActionMode = activity?.startActionMode(RemoveActionModeCallback()) // in Single FragmentActivity + } + true + } + + else -> false + } + } + inner class RecyclerViewAdapter internal constructor(private var glucoseValues: List) : RecyclerView.Adapter() { override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): GlucoseValuesViewHolder { @@ -116,19 +147,43 @@ class BGSourceFragment : DaggerFragment() { val glucoseValue = glucoseValues[position] holder.binding.ns.visibility = (glucoseValue.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.invalid.visibility = (!glucoseValue.isValid).toVisibility() - val sameDayPrevious = position > 0 && dateUtil.isSameDay(glucoseValue.timestamp, glucoseValues[position-1].timestamp) + val sameDayPrevious = position > 0 && dateUtil.isSameDay(glucoseValue.timestamp, glucoseValues[position - 1].timestamp) holder.binding.date.visibility = sameDayPrevious.not().toVisibility() holder.binding.date.text = dateUtil.dateString(glucoseValue.timestamp) holder.binding.time.text = dateUtil.timeString(glucoseValue.timestamp) holder.binding.value.text = glucoseValue.valueToUnitsString(profileFunction.getUnits()) holder.binding.direction.setImageResource(glucoseValue.trendArrow.directionToIcon()) - holder.binding.remove.tag = glucoseValue if (position > 0) { val previous = glucoseValues[position - 1] val diff = previous.timestamp - glucoseValue.timestamp if (diff < T.secs(20).msecs()) holder.binding.root.setBackgroundColor(rh.gc(R.color.errorAlertBackground)) } + fun updateSelection(selected: Boolean) { + if (selected) { + selectedItems.put(position, glucoseValue) + } else { + selectedItems.remove(position) + } + removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + } + holder.binding.root.setOnLongClickListener { + if (removeActionMode == null) { + removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) + } + holder.binding.cbRemove.toggle() + updateSelection(holder.binding.cbRemove.isChecked) + true + } + holder.binding.root.setOnClickListener { + if (removeActionMode != null) { + holder.binding.cbRemove.toggle() + updateSelection(holder.binding.cbRemove.isChecked) + } + } + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) } + holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility() } override fun getItemCount(): Int = glucoseValues.size @@ -136,38 +191,76 @@ class BGSourceFragment : DaggerFragment() { inner class GlucoseValuesViewHolder(view: View) : RecyclerView.ViewHolder(view) { val binding = BgsourceItemBinding.bind(view) - - init { - binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG - binding.remove.setOnClickListener { v: View -> - val glucoseValue = v.tag as GlucoseValue - activity?.let { activity -> - val text = dateUtil.dateAndTimeString(glucoseValue.timestamp) + "\n" + glucoseValue.valueToUnitsString(profileFunction.getUnits()) - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable { - val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) { - R.string.dexcom_app_patched -> Sources.Dexcom - R.string.eversense -> Sources.Eversense - R.string.Glimp -> Sources.Glimp - R.string.MM640g -> Sources.MM640g - R.string.nsclientbg -> Sources.NSClientSource - R.string.poctech -> Sources.PocTech - R.string.tomato -> Sources.Tomato - R.string.glunovo -> Sources.Glunovo - R.string.xdrip -> Sources.Xdrip - else -> Sources.Unknown - } - uel.log( - Action.BG_REMOVED, source, - ValueWithUnit.Timestamp(glucoseValue.timestamp) - ) - repository.runTransactionForResult(InvalidateGlucoseValueTransaction(glucoseValue.id)) - .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating BG value", it) } - .blockingGet() - .also { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bg $it") } } - }) - } - } - } } } + + inner class RemoveActionModeCallback : ActionMode.Callback { + + override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { + mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) + selectedItems.clear() + mode.title = rh.gs(R.string.count_selected, selectedItems.size()) + binding.recyclerview.adapter?.notifyDataSetChanged() + return true + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false + + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + return when (item.itemId) { + R.id.remove_selected -> { + removeSelected() + true + } + + else -> false + } + } + + override fun onDestroyActionMode(mode: ActionMode?) { + removeActionMode = null + binding.recyclerview.adapter?.notifyDataSetChanged() + } + } + + private fun getConfirmationText(): String { + if (selectedItems.size() == 1) { + val glucoseValue = selectedItems.valueAt(0) + return dateUtil.dateAndTimeString(glucoseValue.timestamp) + "\n" + glucoseValue.valueToUnitsString(profileFunction.getUnits()) + } + return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) + } + + private fun removeSelected() { + if (selectedItems.size() > 0) + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { + selectedItems.forEach { _, glucoseValue -> + val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) { + R.string.dexcom_app_patched -> Sources.Dexcom + R.string.eversense -> Sources.Eversense + R.string.Glimp -> Sources.Glimp + R.string.MM640g -> Sources.MM640g + R.string.nsclientbg -> Sources.NSClientSource + R.string.poctech -> Sources.PocTech + R.string.tomato -> Sources.Tomato + R.string.glunovo -> Sources.Glunovo + R.string.xdrip -> Sources.Xdrip + else -> Sources.Unknown + } + uel.log( + Action.BG_REMOVED, source, + ValueWithUnit.Timestamp(glucoseValue.timestamp) + ) + repository.runTransactionForResult(InvalidateGlucoseValueTransaction(glucoseValue.id)) + .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating BG value", it) } + .blockingGet() + .also { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bg $it") } } + } + removeActionMode?.finish() + }) + } + else + removeActionMode?.finish() + } } diff --git a/app/src/main/res/layout/bgsource_item.xml b/app/src/main/res/layout/bgsource_item.xml index 202dff16d3..764a3cdda1 100644 --- a/app/src/main/res/layout/bgsource_item.xml +++ b/app/src/main/res/layout/bgsource_item.xml @@ -68,18 +68,19 @@ android:text="@string/invalid" android:textColor="@android:color/holo_red_light" /> - + + diff --git a/app/src/main/res/menu/menu_bgsource.xml b/app/src/main/res/menu/menu_bgsource.xml new file mode 100644 index 0000000000..87d741a9eb --- /dev/null +++ b/app/src/main/res/menu/menu_bgsource.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index a739f077e2..42e55843f0 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -2,45 +2,57 @@ xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".MainActivity"> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d35da6ce93..5a5f9a8769 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1140,6 +1140,7 @@ Errors Slow down uploads BG data status + Remove BG readings cannula age patch pump age Patch pump