From c3740dc1026f35c39ebe6ed0b2247abe88f31363 Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Sat, 2 Dec 2023 16:13:12 +0100 Subject: [PATCH] Updated activity to retain state on configuration change --- app/src/main/AndroidManifest.xml | 2 +- .../ooniprobe/activity/OverviewActivity.java | 1 + .../CustomWebsiteActivity.kt | 93 ++++++++++++------- .../customwebsites/CustomWebsiteViewModel.kt | 33 +++++++ .../CustomWebsiteRecyclerViewAdapter.kt | 54 +++++++++++ .../CustomWebsiteRecyclerViewAdapter.kt | 75 --------------- .../ooniprobe/di/ActivityComponent.java | 2 +- .../res/layout/activity_customwebsite.xml | 2 +- 8 files changed, 150 insertions(+), 112 deletions(-) rename app/src/main/java/org/openobservatory/ooniprobe/activity/{ => customwebsites}/CustomWebsiteActivity.kt (56%) create mode 100644 app/src/main/java/org/openobservatory/ooniprobe/activity/customwebsites/CustomWebsiteViewModel.kt create mode 100644 app/src/main/java/org/openobservatory/ooniprobe/activity/customwebsites/adapter/CustomWebsiteRecyclerViewAdapter.kt delete mode 100644 app/src/main/java/org/openobservatory/ooniprobe/adapters/CustomWebsiteRecyclerViewAdapter.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f513b8776..b6dc07694 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -95,7 +95,7 @@ - val items = adapter.getItems() - val urls = ArrayList(items.size) - for (value in items) { - val sanitizedUrl = value.replace("\\r\\n|\\r|\\n".toRegex(), " ") - //https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer - if (Patterns.WEB_URL.matcher(sanitizedUrl).matches() && sanitizedUrl.length < 2084) urls.add( - Url.checkExistingUrl(sanitizedUrl).toString() - ) - } - val suite = WebsitesSuite() - suite.getTestList(preferenceManager)[0].inputs = urls - RunningActivity.runAsForegroundService( - this@CustomWebsiteActivity, suite.asArray(), { finish() }, preferenceManager + adapter = CustomWebsiteRecyclerViewAdapter( + onItemRemovedListener = object : ItemRemovedListener { + override fun onItemRemoved(position: Int) { + binding.bottomBar.title = getString( + R.string.OONIRun_URLs, adapter.itemCount.toString() + ) + viewModel.onItemRemoved(position) + } + }, + viewModel = viewModel + ) + viewModel.urls.observe(this) { urls -> + binding.bottomBar.title = getString( + R.string.OONIRun_URLs, urls.size.toString() ) - true } + + binding.bottomBar.setOnMenuItemClickListener { item: MenuItem? -> runTests() } binding.add.setOnClickListener { add() } binding.urlContainer.adapter = adapter - add() - // TODO(aanorbel): Fix: Configuration change triggers loss of data. + if (viewModel.urls.value == null) { + add() + } + } + + override fun onResume() { + super.onResume() + viewModel.urls.value?.let { urls -> + adapter.items = urls + binding.urlContainer.post { adapter.notifyDataSetChanged() } + } + } + + private fun runTests(): Boolean { + val items = viewModel.urls.value ?: listOf() + val urls = ArrayList(items.size) + for (value in items) { + val sanitizedUrl = value.replace("\\r\\n|\\r|\\n".toRegex(), " ") + //https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer + if (Patterns.WEB_URL.matcher(sanitizedUrl) + .matches() && sanitizedUrl.length < 2084 + ) urls.add( + Url.checkExistingUrl(sanitizedUrl).toString() + ) + } + val suite = WebsitesSuite() + suite.getTestList(preferenceManager)[0].inputs = urls + RunningActivity.runAsForegroundService( + this@CustomWebsiteActivity, suite.asArray(), { finish() }, preferenceManager + ) + return true } override fun onBackPressed() { val base = getString(R.string.http) - val edited = adapter.itemCount > 0 && adapter.getItems()[0] != base + val edited = adapter.itemCount > 0 && adapter.items[0] != base if (edited) { ConfirmDialogFragment( title = "Are you sure?", @@ -103,11 +132,7 @@ class CustomWebsiteActivity : AbstractActivity(), ConfirmDialogFragment.OnClickL } fun add() { - adapter.addAll(listOf(getString(R.string.http))) - binding.bottomBar.title = getString( - R.string.OONIRun_URLs, adapter.itemCount.toString() - ) - adapter.notifyDataSetChanged() + viewModel.addUrl(getString(R.string.http)) scrollToBottom() } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/customwebsites/CustomWebsiteViewModel.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/customwebsites/CustomWebsiteViewModel.kt new file mode 100644 index 000000000..1cdb9dd83 --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/customwebsites/CustomWebsiteViewModel.kt @@ -0,0 +1,33 @@ +package org.openobservatory.ooniprobe.activity.customwebsites + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + + +class CustomWebsiteViewModel : ViewModel() { + + val urls = MutableLiveData>() + + fun addUrl(url: String) { + val currentUrls = urls.value ?: ArrayList() + currentUrls.add(url) + urls.value = currentUrls + } + + fun onItemRemoved(position: Int) { + val currentList = urls.value ?: mutableListOf() + if (position < currentList.size) { + currentList.removeAt(position) + urls.value = currentList + } + } + + fun updateUrlAt(position: Int, newUrl: String) { + val currentList = urls.value ?: mutableListOf() + if (position < currentList.size) { + currentList[position] = newUrl + urls.value = currentList + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/customwebsites/adapter/CustomWebsiteRecyclerViewAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/customwebsites/adapter/CustomWebsiteRecyclerViewAdapter.kt new file mode 100644 index 000000000..443ba8ec9 --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/customwebsites/adapter/CustomWebsiteRecyclerViewAdapter.kt @@ -0,0 +1,54 @@ +package org.openobservatory.ooniprobe.activity.customwebsites.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.widget.addTextChangedListener +import androidx.recyclerview.widget.RecyclerView +import org.openobservatory.ooniprobe.activity.customwebsites.CustomWebsiteViewModel +import org.openobservatory.ooniprobe.databinding.EdittextUrlBinding + + +class CustomWebsiteRecyclerViewAdapter( + private val onItemRemovedListener: ItemRemovedListener, + private val viewModel: CustomWebsiteViewModel, + var items: MutableList = mutableListOf() +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder( + parent: ViewGroup, viewType: Int + ): ViewHolder { + return ViewHolder( + EdittextUrlBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder( + holder: ViewHolder, position: Int + ) { + holder.binding.editText.setText(items[position]) + holder.binding.delete.visibility = if (items.size > 1) { + View.VISIBLE + } else { + View.INVISIBLE + } + holder.binding.delete.setOnClickListener { + onItemRemovedListener.onItemRemoved(holder.adapterPosition) + } + holder.binding.editText.addTextChangedListener { + viewModel.updateUrlAt(position, it.toString()) + } + } + + override fun getItemCount(): Int = items.size + + class ViewHolder(val binding: EdittextUrlBinding) : RecyclerView.ViewHolder(binding.root) +} + +interface ItemRemovedListener { + fun onItemRemoved(position: Int) +} \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/adapters/CustomWebsiteRecyclerViewAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/adapters/CustomWebsiteRecyclerViewAdapter.kt deleted file mode 100644 index 95dfc3280..000000000 --- a/app/src/main/java/org/openobservatory/ooniprobe/adapters/CustomWebsiteRecyclerViewAdapter.kt +++ /dev/null @@ -1,75 +0,0 @@ -package org.openobservatory.ooniprobe.adapters - -import android.text.Editable -import android.text.TextWatcher -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.openobservatory.ooniprobe.databinding.EdittextUrlBinding -import java.lang.Boolean.TRUE - - -class CustomWebsiteRecyclerViewAdapter(private val onItemRemovedListener: ItemRemovedListener) : - RecyclerView.Adapter() { - private val mItems: MutableList - private val mVisibility: MutableList - - /** - * Initialize the dataset of the Adapter. - */ - init { - mItems = ArrayList() - mVisibility = ArrayList() - } - - fun addAll(items: List?) { - mItems.addAll(items ?: listOf()) - mVisibility.addAll(mItems.map { TRUE }) - mVisibility[0] = mItems.size > 1 - } - - override fun onCreateViewHolder( - parent: ViewGroup, viewType: Int - ): ViewHolder { - return ViewHolder( - EdittextUrlBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) - } - - override fun onBindViewHolder( - holder: ViewHolder, position: Int - ) { - holder.binding.editText.setText(mItems[position]) - holder.binding.delete.visibility = - if (mVisibility[position]) View.VISIBLE else View.INVISIBLE - holder.binding.delete.setOnClickListener { - mItems.removeAt(holder.adapterPosition) - mVisibility.removeAt(holder.adapterPosition) - mVisibility[0] = mItems.size > 1 - notifyDataSetChanged() - onItemRemovedListener.onItemRemoved(holder.adapterPosition) - } - holder.binding.editText.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} - override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { - mItems[holder.adapterPosition] = charSequence.toString() - } - - override fun afterTextChanged(editable: Editable) {} - }) - } - - override fun getItemCount(): Int = mItems.size - fun getItems(): List = mItems - - class ViewHolder(val binding: EdittextUrlBinding) : RecyclerView.ViewHolder(binding.root) -} - -interface ItemRemovedListener { - fun onItemRemoved(position: Int) -} \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/di/ActivityComponent.java b/app/src/main/java/org/openobservatory/ooniprobe/di/ActivityComponent.java index f88835ebf..7595e7ba1 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/di/ActivityComponent.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/di/ActivityComponent.java @@ -1,7 +1,7 @@ package org.openobservatory.ooniprobe.di; -import org.openobservatory.ooniprobe.activity.CustomWebsiteActivity; +import org.openobservatory.ooniprobe.activity.customwebsites.CustomWebsiteActivity; import org.openobservatory.ooniprobe.activity.LogActivity; import org.openobservatory.ooniprobe.activity.MainActivity; import org.openobservatory.ooniprobe.activity.MeasurementDetailActivity; diff --git a/app/src/main/res/layout/activity_customwebsite.xml b/app/src/main/res/layout/activity_customwebsite.xml index f80a4475c..e140b50e3 100644 --- a/app/src/main/res/layout/activity_customwebsite.xml +++ b/app/src/main/res/layout/activity_customwebsite.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".activity.CustomWebsiteActivity"> + tools:context=".activity.customwebsites.CustomWebsiteActivity">