diff --git a/app/src/main/java/net/vonforst/evmap/adapter/FilterProfilesAdapter.kt b/app/src/main/java/net/vonforst/evmap/adapter/FilterProfilesAdapter.kt index a58d2d29..83db87d4 100644 --- a/app/src/main/java/net/vonforst/evmap/adapter/FilterProfilesAdapter.kt +++ b/app/src/main/java/net/vonforst/evmap/adapter/FilterProfilesAdapter.kt @@ -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() { +class FilterProfilesAdapter( + val dragHelper: ItemTouchHelper, + val onDelete: (FilterProfile) -> Unit, + val onRename: (FilterProfile) -> Unit +) : DataBindingAdapter() { init { setHasStableIds(true) } + @SuppressLint("ClickableViewAccessibility") override fun bind( holder: ViewHolder, 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 { diff --git a/app/src/main/java/net/vonforst/evmap/fragment/FilterFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/FilterFragment.kt index 526423e3..36cf5fdc 100644 --- a/app/src/main/java/net/vonforst/evmap/fragment/FilterFragment.kt +++ b/app/src/main/java/net/vonforst/evmap/fragment/FilterFragment.kt @@ -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 } diff --git a/app/src/main/java/net/vonforst/evmap/fragment/FilterProfilesFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/FilterProfilesFragment.kt index 338277e2..d104cb10 100644 --- a/app/src/main/java/net/vonforst/evmap/fragment/FilterProfilesFragment.kt +++ b/app/src/main/java/net/vonforst/evmap/fragment/FilterProfilesFragment.kt @@ -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() + } + } } \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/ui/Dialogs.kt b/app/src/main/java/net/vonforst/evmap/ui/Dialogs.kt new file mode 100644 index 00000000..1cb4b934 --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/ui/Dialogs.kt @@ -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 { + 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 +} \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/viewmodel/FilterProfilesViewModel.kt b/app/src/main/java/net/vonforst/evmap/viewmodel/FilterProfilesViewModel.kt index 020903b1..3b54d85e 100644 --- a/app/src/main/java/net/vonforst/evmap/viewmodel/FilterProfilesViewModel.kt +++ b/app/src/main/java/net/vonforst/evmap/viewmodel/FilterProfilesViewModel.kt @@ -27,6 +27,12 @@ class FilterProfilesViewModel(application: Application) : AndroidViewModel(appli } } + fun insert(item: FilterProfile) { + viewModelScope.launch { + db.filterProfileDao().insert(item) + } + } + fun reorderProfiles(list: List) { viewModelScope.launch { db.filterProfileDao().update(*list.toTypedArray()) diff --git a/app/src/main/res/layout/item_filter_profile.xml b/app/src/main/res/layout/item_filter_profile.xml index 918fe0cd..f5428ae1 100644 --- a/app/src/main/res/layout/item_filter_profile.xml +++ b/app/src/main/res/layout/item_filter_profile.xml @@ -29,6 +29,7 @@ android:layout_marginRight="16dp" app:tint="@android:color/white" app:srcCompat="@drawable/ic_delete" /> + @@ -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" /> + + + + Willkommen bei EVMap! 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: 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ü. + „%s” gelöscht + Rückgängig + Umbenennen %d kompatibler Ladetarif %d kompatible Ladetarife diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4cee34bb..c82179fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -148,6 +148,9 @@ Welcome to EVMap! 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: 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. + Deleted “%s” + Undo + Rename %d compatible payment method %d compatible payment methods