From a22b347edc2c0e59bb868a25c399bccac49fc6be Mon Sep 17 00:00:00 2001 From: johan12345 Date: Wed, 17 Aug 2022 17:29:32 +0200 Subject: [PATCH] implement chargeprice feedback dialog #195 --- .../fragment/ChargepriceFeedbackFragment.kt | 74 +++++++ .../evmap/fragment/ChargepriceFragment.kt | 38 ++++ .../net/vonforst/evmap/ui/BindingAdapters.kt | 17 +- .../viewmodel/ChargepriceFeedbackViewModel.kt | 129 +++++++++++ .../main/res/layout/fragment_chargeprice.xml | 22 +- .../layout/fragment_chargeprice_feedback.xml | 209 ++++++++++++++++++ .../main/res/layout/item_simple_multiline.xml | 19 ++ app/src/main/res/menu/chargeprice.xml | 12 +- app/src/main/res/navigation/nav_graph.xml | 32 +++ app/src/main/res/values-de/strings.xml | 9 + app/src/main/res/values/strings.xml | 9 + 11 files changed, 563 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFeedbackFragment.kt create mode 100644 app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceFeedbackViewModel.kt create mode 100644 app/src/main/res/layout/fragment_chargeprice_feedback.xml create mode 100644 app/src/main/res/layout/item_simple_multiline.xml diff --git a/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFeedbackFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFeedbackFragment.kt new file mode 100644 index 000000000..f185093b3 --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFeedbackFragment.kt @@ -0,0 +1,74 @@ +package net.vonforst.evmap.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import androidx.navigation.ui.setupWithNavController +import net.vonforst.evmap.MapsActivity +import net.vonforst.evmap.R +import net.vonforst.evmap.databinding.FragmentChargepriceFeedbackBinding +import net.vonforst.evmap.viewmodel.ChargepriceFeedbackViewModel +import net.vonforst.evmap.viewmodel.viewModelFactory + +class ChargepriceFeedbackFragment : Fragment() { + + private lateinit var binding: FragmentChargepriceFeedbackBinding + private val vm: ChargepriceFeedbackViewModel by viewModels(factoryProducer = { + viewModelFactory { + ChargepriceFeedbackViewModel( + requireActivity().application, + getString(R.string.chargeprice_key), + getString(R.string.chargeprice_api_url) + ) + } + }) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val fragmentArgs: ChargepriceFeedbackFragmentArgs by navArgs() + vm.feedbackType.value = fragmentArgs.feedbackType + vm.charger.value = fragmentArgs.charger + vm.vehicle.value = fragmentArgs.vehicle + vm.chargePrices.value = fragmentArgs.chargePrices?.toList() + vm.batteryRange.value = fragmentArgs.batteryRange?.toList() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = DataBindingUtil.inflate( + inflater, + R.layout.fragment_chargeprice_feedback, container, false + ) + binding.lifecycleOwner = this + binding.vm = vm + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.toolbar.setupWithNavController( + findNavController(), + (requireActivity() as MapsActivity).appBarConfiguration + ) + binding.tariffSpinner.setAdapter( + ArrayAdapter( + requireContext(), + R.layout.item_simple_multiline, + R.id.text, + mutableListOf() + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt index 9f2ae6bed..2e4306bf9 100644 --- a/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt +++ b/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt @@ -30,6 +30,7 @@ import net.vonforst.evmap.api.equivalentPlugTypes import net.vonforst.evmap.databinding.FragmentChargepriceBinding import net.vonforst.evmap.model.Chargepoint import net.vonforst.evmap.storage.PreferenceDataSource +import net.vonforst.evmap.viewmodel.ChargepriceFeedbackType import net.vonforst.evmap.viewmodel.ChargepriceViewModel import net.vonforst.evmap.viewmodel.Status import net.vonforst.evmap.viewmodel.savedStateViewModelFactory @@ -182,6 +183,9 @@ class ChargepriceFragment : Fragment() { binding.btnSettings.setOnClickListener { findNavController().navigate(R.id.action_chargeprice_to_chargepriceSettingsFragment) } + binding.btnFeedbackMissingPrice.setOnClickListener { + feedbackMissingPrice() + } binding.batteryRange.setLabelFormatter { value: Float -> val fmt = NumberFormat.getNumberInstance() @@ -202,6 +206,14 @@ class ChargepriceFragment : Fragment() { (activity as? MapsActivity)?.openUrl(getString(R.string.chargeprice_faq_link)) true } + R.id.menu_feedback_missing_price -> { + feedbackMissingPrice() + true + } + R.id.menu_feedback_wrong_price -> { + feedbackWrongPrice() + true + } else -> false } } @@ -235,4 +247,30 @@ class ChargepriceFragment : Fragment() { view.setBackgroundColor(MaterialColors.getColor(view, android.R.attr.windowBackground)) } + private fun feedbackMissingPrice() { + findNavController().navigate( + R.id.action_chargeprice_to_chargepriceFeedbackFragment, + ChargepriceFeedbackFragmentArgs( + ChargepriceFeedbackType.MISSING_PRICE, + vm.charger.value, + vm.vehicle.value, + vm.chargePricesForChargepoint.value?.data?.toTypedArray(), + vm.batteryRange.value?.toFloatArray() + ).toBundle() + ) + } + + private fun feedbackWrongPrice() { + findNavController().navigate( + R.id.action_chargeprice_to_chargepriceFeedbackFragment, + ChargepriceFeedbackFragmentArgs( + ChargepriceFeedbackType.WRONG_PRICE, + vm.charger.value, + vm.vehicle.value, + vm.chargePricesForChargepoint.value?.data?.toTypedArray(), + vm.batteryRange.value?.toFloatArray() + ).toBundle() + ) + } + } diff --git a/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt b/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt index 301ae87a6..65b966d0b 100644 --- a/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt +++ b/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt @@ -9,6 +9,8 @@ import android.graphics.drawable.LayerDrawable import android.text.SpannableString import android.view.View import android.view.ViewGroup.MarginLayoutParams +import android.widget.ArrayAdapter +import android.widget.AutoCompleteTextView import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt @@ -128,9 +130,18 @@ fun setRecyclerViewData(recyclerView: RecyclerView, items: List?) { } @BindingAdapter("data") -fun setRecyclerViewData(recyclerView: ViewPager2, items: List?) { - if (recyclerView.adapter is ListAdapter<*, *>) { - (recyclerView.adapter as ListAdapter).submitList(items) +fun setViewPager2Data(viewPager: ViewPager2, items: List?) { + if (viewPager.adapter is ListAdapter<*, *>) { + (viewPager.adapter as ListAdapter).submitList(items) + } +} + +@BindingAdapter("data") +fun setAutoCompleteTextViewData(atv: AutoCompleteTextView, items: List?) { + if (atv.adapter is ArrayAdapter<*>) { + val arrayAdapter = atv.adapter as ArrayAdapter + arrayAdapter.clear() + items?.let { arrayAdapter.addAll(it) } } } diff --git a/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceFeedbackViewModel.kt b/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceFeedbackViewModel.kt new file mode 100644 index 000000000..fcc3e37a3 --- /dev/null +++ b/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceFeedbackViewModel.kt @@ -0,0 +1,129 @@ +package net.vonforst.evmap.viewmodel + +import android.app.Application +import androidx.lifecycle.* +import kotlinx.coroutines.launch +import net.vonforst.evmap.R +import net.vonforst.evmap.api.chargeprice.* +import net.vonforst.evmap.model.ChargeLocation +import net.vonforst.evmap.model.Chargepoint +import net.vonforst.evmap.storage.PreferenceDataSource +import net.vonforst.evmap.ui.currency +import java.io.IOException + +enum class ChargepriceFeedbackType { + MISSING_PRICE, WRONG_PRICE, MISSING_VEHICLE +} + +class ChargepriceFeedbackViewModel( + application: Application, + chargepriceApiKey: String, + chargepriceApiUrl: String +) : + AndroidViewModel(application) { + private var api = ChargepriceApi.create(chargepriceApiKey, chargepriceApiUrl) + private var prefs = PreferenceDataSource(application) + + // data supplied through fragment args + val feedbackType = MutableLiveData() + val charger = MutableLiveData() + val chargepoint = MutableLiveData() + val vehicle = MutableLiveData() + val chargePrices = MutableLiveData>() + val batteryRange = MutableLiveData>() + + // data input by user + val tariff = MutableLiveData() + val price = MutableLiveData() + val notes = MutableLiveData() + val email = MutableLiveData() + + val loading = MutableLiveData().apply { value = false } + + val chargePricesStrings = chargePrices.map { + it.map { + val name = if (!it.tariffName.lowercase().startsWith(it.provider.lowercase())) { + "${it.provider} ${it.tariffName}" + } else it.tariffName + val price = application.getString( + R.string.charge_price_format, + it.chargepointPrices[0].price, + currency(it.currency) + ) + "$name: $price" + }.toList() + } + + private val feedback = MediatorLiveData().apply { + listOf( + feedbackType, + charger, + chargepoint, + vehicle, + chargePrices, + tariff, + price, + notes, + email + ).forEach { + addSource(it) { + try { + value = when (feedbackType.value!!) { + ChargepriceFeedbackType.MISSING_PRICE -> { + ChargepriceMissingPriceFeedback( + tariff.value ?: "", + charger.value?.network?.take(200) ?: "", + price.value ?: "", + charger.value?.let { ChargepriceApi.getPoiUrl(it) } ?: "", + notes.value ?: "", + email.value ?: "", + getChargepriceContext(), + ChargepriceApi.getChargepriceLanguage() + ) + } + ChargepriceFeedbackType.WRONG_PRICE -> { + ChargepriceWrongPriceFeedback( + "", // TODO: dropdown value + charger.value?.network?.take(200) ?: "", + "", // TODO: dropdown value + price.value ?: "", + charger.value?.let { ChargepriceApi.getPoiUrl(it) } ?: "", + notes.value ?: "", + email.value ?: "", + getChargepriceContext(), + ChargepriceApi.getChargepriceLanguage() + ) + } + ChargepriceFeedbackType.MISSING_VEHICLE -> { + TODO() + } + } + } catch (e: IllegalArgumentException) { + value = null + } + } + } + } + + val formValid = feedback.map { it != null } + + fun sendFeedback() { + val feedback = feedback.value ?: return + viewModelScope.launch { + loading.value = true + try { + api.userFeedback(feedback) + } catch (e: IOException) { + + } + loading.value = false + } + } + + private fun getChargepriceContext(): String { + val result = StringBuilder() + vehicle.value?.let { result.append("Vehicle: ${it.brand} ${it.name}\n") } + batteryRange.value?.let { result.append("Battery SOC: ${it[0]} to ${it[1]}\n") } + return result.toString() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_chargeprice.xml b/app/src/main/res/layout/fragment_chargeprice.xml index 838f8c99a..2531d53dc 100644 --- a/app/src/main/res/layout/fragment_chargeprice.xml +++ b/app/src/main/res/layout/fragment_chargeprice.xml @@ -168,7 +168,8 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/battery_range" tools:itemCount="3" - tools:listitem="@layout/item_chargeprice" /> + tools:listitem="@layout/item_chargeprice" + tools:visibility="invisible" /> + app:layout_constraintTop_toTopOf="@+id/charge_prices_list" + app:layout_constraintVertical_chainStyle="packed" /> +