From fe53b733d0601362e342aa30721aeb7a3007f9d5 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Sat, 28 Nov 2020 23:42:59 +0100 Subject: [PATCH] BGSourceFragment UI tweaks and recognize history change --- .../androidaps/db/DatabaseHelper.java | 2 + .../plugins/source/BGSourceFragment.kt | 72 ++++++++++++++++--- .../androidaps/utils/ListDiffCallback.kt | 65 +++++++++++++++++ 3 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/info/nightscout/androidaps/utils/ListDiffCallback.kt diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index ef38b9e44e..679e119b21 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -417,6 +417,8 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { try { getDaoBgReadings().update(bgReading); openHumansUploader.enqueueBGReading(bgReading); + aapsLogger.debug(LTag.DATABASE, "BG: Updating record from: "+ bgReading.toString()); + scheduleBgHistoryChange(bgReading.date); // trigger cache invalidation } catch (SQLException e) { aapsLogger.error("Unhandled exception", e); } 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 5727383033..c44a5121b1 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 @@ -7,22 +7,27 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.db.BgReading +import info.nightscout.androidaps.events.EventNewBG import info.nightscout.androidaps.interfaces.DatabaseHelperInterface import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.nsclient.NSUpload -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryBgData import info.nightscout.androidaps.plugins.source.BGSourceFragment.RecyclerViewAdapter.BgReadingsViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.ListDiffCallback +import info.nightscout.androidaps.utils.ListUpdateCallbackHelper import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.alertDialogs.OKDialog +import info.nightscout.androidaps.utils.extensions.toVisibility import info.nightscout.androidaps.utils.resources.ResourceHelper import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable @@ -30,6 +35,7 @@ import kotlinx.android.synthetic.main.bgsource_fragment.* import javax.inject.Inject class BGSourceFragment : DaggerFragment() { + @Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var resourceHelper: ResourceHelper @@ -51,14 +57,24 @@ class BGSourceFragment : DaggerFragment() { bgsource_recyclerview.setHasFixedSize(true) bgsource_recyclerview.layoutManager = LinearLayoutManager(view.context) val now = System.currentTimeMillis() - bgsource_recyclerview.adapter = RecyclerViewAdapter(MainApp.getDbHelper().getAllBgreadingsDataFromTime(now - MILLS_TO_THE_PAST, false)) + bgsource_recyclerview.adapter = RecyclerViewAdapter(getBgData(now)) + } + + override fun onDestroyView() { + super.onDestroyView() + bgsource_recyclerview?.adapter = null // avoid leaks } @Synchronized override fun onResume() { super.onResume() disposable.add(rxBus - .toObservable(EventAutosensCalculationFinished::class.java) + .toObservable(EventNewBG::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }) { fabricPrivacy.logException(it) } + ) + disposable.add(rxBus + .toObservable(EventNewHistoryBgData::class.java) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ updateGUI() }) { fabricPrivacy.logException(it) } ) @@ -73,30 +89,62 @@ class BGSourceFragment : DaggerFragment() { private fun updateGUI() { val now = System.currentTimeMillis() - bgsource_recyclerview?.swapAdapter(RecyclerViewAdapter(MainApp.getDbHelper().getAllBgreadingsDataFromTime(now - MILLS_TO_THE_PAST, false)), true) + (bgsource_recyclerview?.adapter as? RecyclerViewAdapter)?.setData(getBgData(now)) } - inner class RecyclerViewAdapter internal constructor(private var bgReadings: List) : RecyclerView.Adapter() { + private fun getBgData(now: Long) = MainApp.getDbHelper() + .getAllBgreadingsDataFromTime(now - MILLS_TO_THE_PAST, false) + + inner class RecyclerViewAdapter internal constructor(bgReadings: List) : RecyclerView.Adapter() { + + private var callbackHelper = ListUpdateCallbackHelper(this) { bgsource_recyclerview?.smoothScrollToPosition(0) } + + private val currentData: MutableList = mutableListOf().also { it.addAll(bgReadings) } + + fun setData(newList: List) { + val diffResult = DiffUtil.calculateDiff(getListDiffCallback(ArrayList(newList), ArrayList(currentData))) + currentData.clear() + currentData.addAll(newList) + diffResult.dispatchUpdatesTo(callbackHelper) + } + + private fun getListDiffCallback(newItems: List, oldItems: List): ListDiffCallback = + object : ListDiffCallback(newItems, oldItems) { + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val new = newItems[newItemPosition] + val old = oldItems[oldItemPosition] + return new.hasValidNS == old.hasValidNS && + new.isValid == old.isValid && + new.date == old.date && + new.value == old.value + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + newItems[newItemPosition].date == oldItems[oldItemPosition].date + } + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): BgReadingsViewHolder { val v = LayoutInflater.from(viewGroup.context).inflate(R.layout.bgsource_item, viewGroup, false) return BgReadingsViewHolder(v) } override fun onBindViewHolder(holder: BgReadingsViewHolder, position: Int) { - val bgReading = bgReadings[position] - holder.ns.visibility = if (NSUpload.isIdValid(bgReading._id)) View.VISIBLE else View.GONE - holder.invalid.visibility = if (!bgReading.isValid) View.VISIBLE else View.GONE + val bgReading = currentData[position] + holder.ns.visibility = (NSUpload.isIdValid(bgReading._id)).toVisibility() + holder.invalid.visibility = bgReading.isValid.not().toVisibility() holder.date.text = dateUtil.dateAndTimeString(bgReading.date) holder.value.text = bgReading.valueToUnitsToString(profileFunction.getUnits()) holder.direction.setImageResource(bgReading.directionToIcon(databaseHelper)) holder.remove.tag = bgReading + holder.remove.visibility = bgReading.isValid.toVisibility() } override fun getItemCount(): Int { - return bgReadings.size + return currentData.size } inner class BgReadingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + var date: TextView = itemView.findViewById(R.id.bgsource_date) var value: TextView = itemView.findViewById(R.id.bgsource_value) var direction: ImageView = itemView.findViewById(R.id.bgsource_direction) @@ -112,7 +160,6 @@ class BGSourceFragment : DaggerFragment() { OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.removerecord), text, Runnable { bgReading.isValid = false MainApp.getDbHelper().update(bgReading) - updateGUI() }) } } @@ -120,4 +167,7 @@ class BGSourceFragment : DaggerFragment() { } } } -} \ No newline at end of file +} + +val BgReading.hasValidNS + get() = NSUpload.isIdValid(this._id) \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/utils/ListDiffCallback.kt b/core/src/main/java/info/nightscout/androidaps/utils/ListDiffCallback.kt new file mode 100644 index 0000000000..d1e6947e74 --- /dev/null +++ b/core/src/main/java/info/nightscout/androidaps/utils/ListDiffCallback.kt @@ -0,0 +1,65 @@ +package info.nightscout.androidaps.utils + +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListUpdateCallback +import androidx.recyclerview.widget.RecyclerView + +open class ListDiffCallback(private val newItems: List, private val oldItems: List) : DiffUtil.Callback() { + + override fun getOldListSize(): Int = oldItems.size + + override fun getNewListSize(): Int = newItems.size + + /** + * Called by the DiffUtil to decide whether two object represent the same Item. + *

+ * For example, if your items have unique ids, this method should check their id equality. + * + * @param oldItemPosition The position of the item in the old list + * @param newItemPosition The position of the item in the new list + * @return True if the two items represent the same object or false if they are different. + */ + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + newItems[newItemPosition] == oldItems[oldItemPosition] + + /** + * Called by the DiffUtil when it wants to check whether two items have the same data. + * DiffUtil uses this information to detect if the contents of an item has changed. + *

+ * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)} + * so that you can change its behavior depending on your UI. + * For example, if you are using DiffUtil with a + * {@link RecyclerView.Adapter RecyclerView.Adapter}, you should + * return whether the items' visual representations are the same. + *

+ * This method is called only if {@link #areItemsTheSame(int, int)} returns + * {@code true} for these items. + * + * @param oldItemPosition The position of the item in the old list + * @param newItemPosition The position of the item in the new list which replaces the + * oldItem + * @return True if the contents of the items are the same or false if they are different. + */ + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + newItems[newItemPosition] == oldItems[oldItemPosition] +} + +class ListUpdateCallbackHelper(val adapter: RecyclerView.Adapter<*>, val insertCallback: (Int) -> Unit) : ListUpdateCallback { + + override fun onChanged(position: Int, count: Int, payload: Any?) { + adapter.notifyItemRangeChanged(position, count, payload) + } + + override fun onInserted(position: Int, count: Int) { + adapter.notifyItemRangeInserted(position, count) + insertCallback(position) + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + adapter.notifyItemMoved(fromPosition, toPosition) + } + + override fun onRemoved(position: Int, count: Int) { + adapter.notifyItemRangeRemoved(position, count) + } +} \ No newline at end of file