mirror of
https://github.com/ev-map/EVMap.git
synced 2026-05-24 06:45:50 -04:00
FilterProfilesFragment: Add rename and delete buttons + undo function
This commit is contained in:
@@ -1,16 +1,23 @@
|
||||
package net.vonforst.evmap.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.MotionEvent
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.databinding.ItemFilterProfileBinding
|
||||
import net.vonforst.evmap.storage.FilterProfile
|
||||
|
||||
class FilterProfilesAdapter(val dragHelper: ItemTouchHelper) : DataBindingAdapter<FilterProfile>() {
|
||||
class FilterProfilesAdapter(
|
||||
val dragHelper: ItemTouchHelper,
|
||||
val onDelete: (FilterProfile) -> Unit,
|
||||
val onRename: (FilterProfile) -> Unit
|
||||
) : DataBindingAdapter<FilterProfile>() {
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun bind(
|
||||
holder: ViewHolder<FilterProfile>,
|
||||
item: FilterProfile
|
||||
@@ -24,6 +31,20 @@ class FilterProfilesAdapter(val dragHelper: ItemTouchHelper) : DataBindingAdapte
|
||||
}
|
||||
false
|
||||
}
|
||||
binding.foreground.translationX = 0f
|
||||
binding.btnDelete.setOnClickListener {
|
||||
binding.foreground.animate()
|
||||
.translationX(binding.foreground.width.toFloat())
|
||||
.setDuration(250)
|
||||
.setInterpolator(AccelerateInterpolator())
|
||||
.withEndAction {
|
||||
onDelete(item)
|
||||
}
|
||||
.start()
|
||||
}
|
||||
binding.btnRename.setOnClickListener {
|
||||
onRename(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.databinding.DataBindingUtil
|
||||
@@ -24,6 +17,7 @@ import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.FiltersAdapter
|
||||
import net.vonforst.evmap.databinding.FragmentFilterBinding
|
||||
import net.vonforst.evmap.ui.showEditTextDialog
|
||||
import net.vonforst.evmap.viewmodel.FilterViewModel
|
||||
import net.vonforst.evmap.viewmodel.viewModelFactory
|
||||
|
||||
@@ -96,54 +90,22 @@ class FilterFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
R.id.menu_save_profile -> {
|
||||
val container = FrameLayout(requireContext())
|
||||
container.setPadding(
|
||||
(16 * resources.displayMetrics.density).toInt(), 0,
|
||||
(16 * resources.displayMetrics.density).toInt(), 0
|
||||
)
|
||||
val input = EditText(requireContext())
|
||||
input.isSingleLine = true
|
||||
vm.filterProfile.value?.let { profile ->
|
||||
input.setText(profile.name)
|
||||
}
|
||||
container.addView(input)
|
||||
|
||||
val dialog = AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.save_as_profile)
|
||||
.setMessage(R.string.save_profile_enter_name)
|
||||
.setView(container)
|
||||
.setPositiveButton(R.string.ok) { di, button ->
|
||||
lifecycleScope.launch {
|
||||
vm.saveAsProfile(input.text.toString())
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
showEditTextDialog(requireContext()) { dialog, input ->
|
||||
vm.filterProfile.value?.let { profile ->
|
||||
input.setText(profile.name)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { di, button ->
|
||||
|
||||
}.show()
|
||||
|
||||
// move dialog to top
|
||||
val attrs = dialog.window?.attributes?.apply {
|
||||
gravity = Gravity.TOP
|
||||
}
|
||||
dialog.window?.attributes = attrs
|
||||
|
||||
// focus and show keyboard
|
||||
input.requestFocus()
|
||||
input.postDelayed({
|
||||
val imm =
|
||||
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT)
|
||||
}, 100)
|
||||
input.setOnEditorActionListener { _, actionId, event ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
val text = input.text
|
||||
if (text != null) {
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
|
||||
return@setOnEditorActionListener true
|
||||
dialog.setTitle(R.string.save_as_profile)
|
||||
.setMessage(R.string.save_profile_enter_name)
|
||||
.setPositiveButton(R.string.ok) { di, button ->
|
||||
lifecycleScope.launch {
|
||||
vm.saveAsProfile(input.text.toString())
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { di, button ->
|
||||
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -11,18 +11,23 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.launch
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.DataBindingAdapter
|
||||
import net.vonforst.evmap.adapter.FilterProfilesAdapter
|
||||
import net.vonforst.evmap.databinding.FragmentFilterProfilesBinding
|
||||
import net.vonforst.evmap.databinding.ItemFilterProfileBinding
|
||||
import net.vonforst.evmap.storage.FilterProfile
|
||||
import net.vonforst.evmap.ui.showEditTextDialog
|
||||
import net.vonforst.evmap.viewmodel.FilterProfilesViewModel
|
||||
import net.vonforst.evmap.viewmodel.viewModelFactory
|
||||
|
||||
@@ -34,6 +39,7 @@ class FilterProfilesFragment : Fragment() {
|
||||
FilterProfilesViewModel(requireActivity().application)
|
||||
}
|
||||
})
|
||||
private var deleteSnackbar: Snackbar? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -86,7 +92,8 @@ class FilterProfilesFragment : Fragment() {
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
vm.delete(viewHolder.itemId)
|
||||
val fp = vm.filterProfiles.value?.find { it.id == viewHolder.itemId }
|
||||
fp?.let { delete(it) }
|
||||
}
|
||||
|
||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
@@ -166,7 +173,24 @@ class FilterProfilesFragment : Fragment() {
|
||||
}
|
||||
})
|
||||
|
||||
val adapter = FilterProfilesAdapter(touchHelper)
|
||||
val adapter = FilterProfilesAdapter(touchHelper, onDelete = { fp ->
|
||||
delete(fp)
|
||||
}, onRename = { fp ->
|
||||
showEditTextDialog(requireContext()) { dialog, input ->
|
||||
input.setText(fp.name)
|
||||
|
||||
dialog.setTitle(R.string.rename)
|
||||
.setMessage(R.string.save_profile_enter_name)
|
||||
.setPositiveButton(R.string.ok) { di, button ->
|
||||
lifecycleScope.launch {
|
||||
vm.insert(fp.copy(name = input.text.toString()))
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { di, button ->
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
binding.filterProfilesList.apply {
|
||||
this.adapter = adapter
|
||||
layoutManager =
|
||||
@@ -184,4 +208,21 @@ class FilterProfilesFragment : Fragment() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(fp: FilterProfile) {
|
||||
vm.delete(fp.id)
|
||||
|
||||
deleteSnackbar?.dismiss()
|
||||
view?.let {
|
||||
val snackbar = Snackbar.make(
|
||||
it,
|
||||
getString(R.string.deleted_filterprofile, fp.name),
|
||||
Snackbar.LENGTH_LONG
|
||||
).setAction(R.string.undo) {
|
||||
vm.insert(fp.copy(id = 0))
|
||||
}
|
||||
deleteSnackbar = snackbar
|
||||
snackbar.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
63
app/src/main/java/net/vonforst/evmap/ui/Dialogs.kt
Normal file
63
app/src/main/java/net/vonforst/evmap/ui/Dialogs.kt
Normal file
@@ -0,0 +1,63 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
||||
private fun dialogEditText(ctx: Context): Pair<View, EditText> {
|
||||
val container = FrameLayout(ctx)
|
||||
container.setPadding(
|
||||
(16 * ctx.resources.displayMetrics.density).toInt(), 0,
|
||||
(16 * ctx.resources.displayMetrics.density).toInt(), 0
|
||||
)
|
||||
val input = EditText(ctx)
|
||||
input.isSingleLine = true
|
||||
container.addView(input)
|
||||
return container to input
|
||||
}
|
||||
|
||||
fun showEditTextDialog(
|
||||
ctx: Context,
|
||||
customize: (AlertDialog.Builder, EditText) -> Unit
|
||||
): AlertDialog {
|
||||
val (container, input) = dialogEditText(ctx)
|
||||
val dialogBuilder = AlertDialog.Builder(ctx)
|
||||
.setView(container)
|
||||
|
||||
customize(dialogBuilder, input)
|
||||
|
||||
val dialog = dialogBuilder.show()
|
||||
|
||||
|
||||
// move dialog to top
|
||||
val attrs = dialog.window?.attributes?.apply {
|
||||
gravity = Gravity.TOP
|
||||
}
|
||||
dialog.window?.attributes = attrs
|
||||
|
||||
// focus and show keyboard
|
||||
input.requestFocus()
|
||||
input.postDelayed({
|
||||
val imm =
|
||||
ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT)
|
||||
}, 100)
|
||||
input.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
val text = input.text
|
||||
val button = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
if (text != null && button != null) {
|
||||
button.performClick()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
@@ -27,6 +27,12 @@ class FilterProfilesViewModel(application: Application) : AndroidViewModel(appli
|
||||
}
|
||||
}
|
||||
|
||||
fun insert(item: FilterProfile) {
|
||||
viewModelScope.launch {
|
||||
db.filterProfileDao().insert(item)
|
||||
}
|
||||
}
|
||||
|
||||
fun reorderProfiles(list: List<FilterProfile>) {
|
||||
viewModelScope.launch {
|
||||
db.filterProfileDao().update(*list.toTypedArray())
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
android:layout_marginRight="16dp"
|
||||
app:tint="@android:color/white"
|
||||
app:srcCompat="@drawable/ic_delete" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -43,17 +44,42 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@{item.name}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/handle"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnRename"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.511"
|
||||
tools:text="Lorem ipsum" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnDelete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:tint="?colorControlNormal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/handle"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_delete"
|
||||
android:contentDescription="@string/delete" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnRename"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:tint="?colorControlNormal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnDelete"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_edit"
|
||||
android:contentDescription="@string/rename" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/handle"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -149,6 +149,9 @@
|
||||
<string name="welcome_to_evmap">Willkommen bei EVMap!</string>
|
||||
<string name="welcome_1">Mit EVMap kannst du Ladestationen für Elektroautos in deiner Nähe finden. EVMap nutzt dafür die Community-gepflegte Datenbank von GoingElectric.de, die sich vor allem auf Europa und den deutschsprachigen Raum konzentriert. Über die Website GoingElectric.de kannst du selbst zum Verzeichnis beitragen.\n\nDie Ladestationen werden auf der Karte mit verschiedenen Farben angezeigt, die die maximale Ladeleistung angeben:</string>
|
||||
<string name="welcome_2">EVMap ist kostenlos und Open Source. Du kannst bei GitHub zur Weiterentwicklung beitragen oder die Entwicklung mit Spenden unterstützen. Die entsprechenden Links findest du unter „Über EVMap” im Menü.</string>
|
||||
<string name="deleted_filterprofile">„%s” gelöscht</string>
|
||||
<string name="undo">Rückgängig</string>
|
||||
<string name="rename">Umbenennen</string>
|
||||
<plurals name="charge_cards_compatible_num">
|
||||
<item quantity="one">%d kompatibler Ladetarif</item>
|
||||
<item quantity="other">%d kompatible Ladetarife</item>
|
||||
|
||||
@@ -148,6 +148,9 @@
|
||||
<string name="welcome_to_evmap">Welcome to EVMap!</string>
|
||||
<string name="welcome_1">Using EVMap, you can find electric vehicle chargers around you. EVMap uses the community-maintained database from GoingElectric.de, which focuses on chargers in Europe and the German-speaking countries. You can contribute to this database on the GoingElectric.de website.\n\nChargers are shown on the map in different colors, which correspond to their maximum charging power:</string>
|
||||
<string name="welcome_2">EVMap is free and Open Source software. You can contribute to the development on GitHub or support me through donations. The corresponding links can be found under “About EVMap” in the menu.</string>
|
||||
<string name="deleted_filterprofile">Deleted “%s”</string>
|
||||
<string name="undo">Undo</string>
|
||||
<string name="rename">Rename</string>
|
||||
<plurals name="charge_cards_compatible_num">
|
||||
<item quantity="one">%d compatible payment method</item>
|
||||
<item quantity="other">%d compatible payment methods</item>
|
||||
|
||||
Reference in New Issue
Block a user