diff --git a/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt b/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt index 2a6ae766..ba862841 100644 --- a/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt +++ b/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt @@ -22,7 +22,6 @@ import net.vonforst.evmap.databinding.ItemChargepriceVehicleChipBinding import net.vonforst.evmap.databinding.ItemConnectorButtonBinding import net.vonforst.evmap.model.Chargepoint import net.vonforst.evmap.ui.CheckableConstraintLayout -import net.vonforst.evmap.viewmodel.FavoritesViewModel interface Equatable { override fun equals(other: Any?): Boolean @@ -89,18 +88,6 @@ class ConnectorAdapter : DataBindingAdapter() { - init { - setHasStableIds(true) - } - - override fun getItemViewType(position: Int): Int = R.layout.item_favorite - - override fun getItemId(position: Int): Long = getItem(position).charger.id -} - class ChargepriceAdapter() : DataBindingAdapter() { diff --git a/app/src/main/java/net/vonforst/evmap/adapter/FavoritesAdapter.kt b/app/src/main/java/net/vonforst/evmap/adapter/FavoritesAdapter.kt new file mode 100644 index 00000000..131c7cf6 --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/adapter/FavoritesAdapter.kt @@ -0,0 +1,39 @@ +package net.vonforst.evmap.adapter + +import android.annotation.SuppressLint +import android.view.animation.AccelerateInterpolator +import net.vonforst.evmap.R +import net.vonforst.evmap.databinding.ItemFavoriteBinding +import net.vonforst.evmap.viewmodel.FavoritesViewModel + +class FavoritesAdapter(val onDelete: (FavoritesViewModel.FavoritesListItem) -> Unit) : + DataBindingAdapter() { + init { + setHasStableIds(true) + } + + override fun getItemViewType(position: Int): Int = R.layout.item_favorite + + override fun getItemId(position: Int): Long = getItem(position).charger.id + + @SuppressLint("ClickableViewAccessibility") + override fun bind( + holder: ViewHolder, + item: FavoritesViewModel.FavoritesListItem + ) { + super.bind(holder, item) + + val binding = holder.binding as ItemFavoriteBinding + binding.foreground.translationX = 0f + binding.btnDelete.setOnClickListener { + binding.foreground.animate() + .translationX(binding.foreground.width.toFloat()) + .setDuration(250) + .setInterpolator(AccelerateInterpolator()) + .withEndAction { + onDelete(item) + } + .start() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/fragment/FavoritesFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/FavoritesFragment.kt index 8aeb0da4..23017679 100644 --- a/app/src/main/java/net/vonforst/evmap/fragment/FavoritesFragment.kt +++ b/app/src/main/java/net/vonforst/evmap/fragment/FavoritesFragment.kt @@ -2,10 +2,13 @@ package net.vonforst.evmap.fragment import android.Manifest import android.content.pm.PackageManager +import android.graphics.Canvas import android.os.Bundle +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil @@ -14,20 +17,29 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.ui.setupWithNavController import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.car2go.maps.model.LatLng +import com.google.android.material.snackbar.Snackbar import com.mapzen.android.lost.api.LocationServices import com.mapzen.android.lost.api.LostApiClient import net.vonforst.evmap.MapsActivity import net.vonforst.evmap.R +import net.vonforst.evmap.adapter.DataBindingAdapter import net.vonforst.evmap.adapter.FavoritesAdapter import net.vonforst.evmap.databinding.FragmentFavoritesBinding +import net.vonforst.evmap.databinding.ItemFavoriteBinding +import net.vonforst.evmap.model.ChargeLocation import net.vonforst.evmap.viewmodel.FavoritesViewModel import net.vonforst.evmap.viewmodel.viewModelFactory class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks { private lateinit var binding: FragmentFavoritesBinding private lateinit var locationClient: LostApiClient + private var toDelete: ChargeLocation? = null + private var deleteSnackbar: Snackbar? = null + private lateinit var adapter: FavoritesAdapter private val vm: FavoritesViewModel by viewModels(factoryProducer = { viewModelFactory { @@ -66,13 +78,15 @@ class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks { (requireActivity() as MapsActivity).appBarConfiguration ) - val favAdapter = FavoritesAdapter(vm).apply { + adapter = FavoritesAdapter(onDelete = { + delete(it.charger) + }).apply { onClickListener = { navController.navigate(R.id.action_favs_to_map, MapFragment.showCharger(it.charger)) } } binding.favsList.apply { - adapter = favAdapter + adapter = this@FavoritesFragment.adapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) addItemDecoration( @@ -81,6 +95,7 @@ class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks { ) ) } + createTouchHelper().attachToRecyclerView(binding.favsList) locationClient.connect() } @@ -109,4 +124,136 @@ class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks { locationClient.disconnect() } } + + fun delete(fav: ChargeLocation) { + val position = vm.listData.value?.indexOfFirst { it.charger == fav } ?: return + // if there is already a profile to delete, delete it now + actuallyDelete() + deleteSnackbar?.dismiss() + + toDelete = fav + + view?.let { + val snackbar = Snackbar.make( + it, + getString(R.string.deleted_filterprofile, fav.name), + Snackbar.LENGTH_LONG + ).setAction(R.string.undo) { + toDelete = null + adapter.notifyItemChanged(position) + }.addCallback(object : Snackbar.Callback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + // if undo was not clicked, actually delete + if (event == DISMISS_EVENT_TIMEOUT || event == DISMISS_EVENT_SWIPE) { + actuallyDelete() + } + } + }) + deleteSnackbar = snackbar + snackbar.show() + } ?: run { + actuallyDelete() + } + } + + private fun actuallyDelete() { + toDelete?.let { vm.deleteFavorite(it) } + toDelete = null + } + + private fun createTouchHelper(): ItemTouchHelper { + return ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + 0, + ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return false + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val fav = vm.favorites.value?.find { it.id == viewHolder.itemId } + fav?.let { delete(it) } + } + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + if (viewHolder != null && actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + val binding = + (viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFavoriteBinding + getDefaultUIUtil().onSelected(binding.foreground) + } else { + super.onSelectedChanged(viewHolder, actionState) + } + } + + override fun onChildDrawOver( + c: Canvas, recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, + actionState: Int, isCurrentlyActive: Boolean + ) { + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + val binding = + (viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFavoriteBinding + getDefaultUIUtil().onDrawOver( + c, recyclerView, binding.foreground, dX, dY, + actionState, isCurrentlyActive + ) + val lp = (binding.deleteIcon.layoutParams as FrameLayout.LayoutParams) + lp.gravity = Gravity.CENTER_VERTICAL or if (dX > 0) { + Gravity.START + } else { + Gravity.END + } + binding.deleteIcon.layoutParams = lp + } else { + super.onChildDrawOver( + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive + ) + } + } + + override fun clearView( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ) { + val binding = + (viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFavoriteBinding + getDefaultUIUtil().clearView(binding.foreground) + } + + override fun onChildDraw( + c: Canvas, recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, + actionState: Int, isCurrentlyActive: Boolean + ) { + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + val binding = + (viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFavoriteBinding + getDefaultUIUtil().onDraw( + c, recyclerView, binding.foreground, dX, dY, + actionState, isCurrentlyActive + ) + } else { + super.onChildDraw( + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive + ) + } + } + }) + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/selectable_opaque_background.xml b/app/src/main/res/drawable/selectable_opaque_background.xml new file mode 100644 index 00000000..f18424aa --- /dev/null +++ b/app/src/main/res/drawable/selectable_opaque_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_favorite.xml b/app/src/main/res/layout/item_favorite.xml index 6399980e..d50f5ae6 100644 --- a/app/src/main/res/layout/item_favorite.xml +++ b/app/src/main/res/layout/item_favorite.xml @@ -6,8 +6,11 @@ + + + - + android:layout_height="wrap_content"> - + + + + + + + + android:padding="16dp" + android:background="@drawable/selectable_opaque_background"> + + - + - + - - + + + + + + \ No newline at end of file