This repository has been archived by the owner on Feb 20, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
For #20764 add screen for opting out of experiments
- Loading branch information
Showing
20 changed files
with
956 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
app/src/main/java/org/mozilla/fenix/settings/studies/CustomViewHolder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package org.mozilla.fenix.settings.studies | ||
|
||
import android.view.View | ||
import android.widget.TextView | ||
import androidx.recyclerview.widget.RecyclerView | ||
import com.google.android.material.button.MaterialButton | ||
|
||
/** | ||
* A base view holder for Studies. | ||
*/ | ||
sealed class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) { | ||
/** | ||
* A view holder for displaying section items. | ||
*/ | ||
class SectionViewHolder( | ||
view: View, | ||
val titleView: TextView, | ||
val divider: View | ||
) : CustomViewHolder(view) | ||
|
||
/** | ||
* A view holder for displaying study items. | ||
*/ | ||
class StudyViewHolder( | ||
view: View, | ||
val titleView: TextView, | ||
val summaryView: TextView, | ||
val deleteButton: MaterialButton, | ||
) : CustomViewHolder(view) | ||
} |
164 changes: 164 additions & 0 deletions
164
app/src/main/java/org/mozilla/fenix/settings/studies/StudiesAdapter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package org.mozilla.fenix.settings.studies | ||
|
||
import android.annotation.SuppressLint | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import android.widget.TextView | ||
import androidx.annotation.StringRes | ||
import androidx.annotation.VisibleForTesting | ||
import androidx.core.view.isVisible | ||
import androidx.recyclerview.widget.DiffUtil | ||
import androidx.recyclerview.widget.ListAdapter | ||
import com.google.android.material.button.MaterialButton | ||
import org.mozilla.experiments.nimbus.internal.EnrolledExperiment | ||
import org.mozilla.fenix.R | ||
import org.mozilla.fenix.settings.studies.CustomViewHolder.SectionViewHolder | ||
import org.mozilla.fenix.settings.studies.CustomViewHolder.StudyViewHolder | ||
|
||
private const val VIEW_HOLDER_TYPE_SECTION = 0 | ||
private const val VIEW_HOLDER_TYPE_STUDY = 1 | ||
|
||
/** | ||
* An adapter for displaying studies items. This will display information related to the state of | ||
* a study such as active. In addition, it will perform actions such as removing a study. | ||
* | ||
* @property studiesDelegate Delegate that will provides method for handling | ||
* the studies actions items. | ||
* @param studies The list of studies. | ||
* * @property studiesDelegate Delegate that will provides method for handling | ||
* the studies actions items. | ||
* @param shouldSubmitOnInit The sole purpose of this property is to prevent the submitList function | ||
* to run on init, it should only be used from tests. | ||
*/ | ||
@Suppress("LargeClass") | ||
class StudiesAdapter( | ||
private val studiesDelegate: StudiesAdapterDelegate, | ||
studies: List<EnrolledExperiment>, | ||
@VisibleForTesting | ||
internal val shouldSubmitOnInit: Boolean = true | ||
) : ListAdapter<Any, CustomViewHolder>(DifferCallback) { | ||
/** | ||
* Represents all the studies that will be distributed in multiple headers like | ||
* active, and completed, this helps to have the data source of the items, | ||
* displayed in the UI. | ||
*/ | ||
@VisibleForTesting | ||
internal var studiesMap: MutableMap<String, EnrolledExperiment> = | ||
studies.associateBy({ it.slug }, { it }).toMutableMap() | ||
|
||
init { | ||
if (shouldSubmitOnInit) { | ||
submitList(createListWithSections(studies)) | ||
} | ||
} | ||
|
||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder { | ||
return when (viewType) { | ||
VIEW_HOLDER_TYPE_STUDY -> createStudiesViewHolder(parent) | ||
VIEW_HOLDER_TYPE_SECTION -> createSectionViewHolder(parent) | ||
else -> throw IllegalArgumentException("Unrecognized viewType") | ||
} | ||
} | ||
|
||
private fun createSectionViewHolder(parent: ViewGroup): CustomViewHolder { | ||
val context = parent.context | ||
val inflater = LayoutInflater.from(context) | ||
val view = inflater.inflate(R.layout.studies_section_item, parent, false) | ||
val titleView = view.findViewById<TextView>(R.id.title) | ||
val divider = view.findViewById<View>(R.id.divider) | ||
return SectionViewHolder(view, titleView, divider) | ||
} | ||
|
||
private fun createStudiesViewHolder(parent: ViewGroup): StudyViewHolder { | ||
val context = parent.context | ||
val view = LayoutInflater.from(context).inflate(R.layout.study_item, parent, false) | ||
val titleView = view.findViewById<TextView>(R.id.studyTitle) | ||
val summaryView = view.findViewById<TextView>(R.id.study_description) | ||
val removeButton = view.findViewById<MaterialButton>(R.id.remove_button) | ||
return StudyViewHolder( | ||
view, | ||
titleView, | ||
summaryView, | ||
removeButton | ||
) | ||
} | ||
|
||
override fun getItemViewType(position: Int): Int { | ||
return when (getItem(position)) { | ||
is EnrolledExperiment -> VIEW_HOLDER_TYPE_STUDY | ||
is Section -> VIEW_HOLDER_TYPE_SECTION | ||
else -> throw IllegalArgumentException("items[position] has unrecognized type") | ||
} | ||
} | ||
|
||
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { | ||
val item = getItem(position) | ||
|
||
when (holder) { | ||
is SectionViewHolder -> bindSection(holder, item as Section) | ||
is StudyViewHolder -> bindStudy(holder, item as EnrolledExperiment) | ||
} | ||
} | ||
|
||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) | ||
internal fun bindSection(holder: SectionViewHolder, section: Section) { | ||
holder.titleView.setText(section.title) | ||
holder.divider.isVisible = section.visibleDivider | ||
} | ||
|
||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) | ||
internal fun bindStudy(holder: StudyViewHolder, study: EnrolledExperiment) { | ||
holder.titleView.text = study.userFacingName | ||
holder.summaryView.text = study.userFacingDescription | ||
|
||
holder.deleteButton.setOnClickListener { | ||
studiesDelegate.onRemoveButtonClicked(study) | ||
} | ||
} | ||
|
||
internal fun createListWithSections(studies: List<EnrolledExperiment>): List<Any> { | ||
val itemsWithSections = ArrayList<Any>() | ||
val activeStudies = ArrayList<EnrolledExperiment>() | ||
|
||
activeStudies.addAll(studies) | ||
|
||
if (activeStudies.isNotEmpty()) { | ||
itemsWithSections.add(Section(R.string.studies_active, true)) | ||
itemsWithSections.addAll(activeStudies) | ||
} | ||
|
||
return itemsWithSections | ||
} | ||
|
||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) | ||
internal data class Section(@StringRes val title: Int, val visibleDivider: Boolean = true) | ||
|
||
/** | ||
* Removes the portion of the list that contains the provided [study]. | ||
* @property study The study to be removed. | ||
*/ | ||
fun removeStudy(study: EnrolledExperiment) { | ||
studiesMap.remove(study.slug) | ||
submitList(createListWithSections(studiesMap.values.toList())) | ||
} | ||
|
||
internal object DifferCallback : DiffUtil.ItemCallback<Any>() { | ||
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean { | ||
return when { | ||
oldItem is EnrolledExperiment && newItem is EnrolledExperiment -> oldItem.slug == newItem.slug | ||
oldItem is Section && newItem is Section -> oldItem.title == newItem.title | ||
else -> false | ||
} | ||
} | ||
|
||
@SuppressLint("DiffUtilEquals") | ||
override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean { | ||
return oldItem == newItem | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
app/src/main/java/org/mozilla/fenix/settings/studies/StudiesAdapterDelegate.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package org.mozilla.fenix.settings.studies | ||
|
||
import org.mozilla.experiments.nimbus.internal.EnrolledExperiment | ||
|
||
/** | ||
* Provides methods for handling the studies items. | ||
*/ | ||
interface StudiesAdapterDelegate { | ||
/** | ||
* Handler for when the remove button is clicked. | ||
* | ||
* @param experiment The [EnrolledExperiment] to remove. | ||
*/ | ||
fun onRemoveButtonClicked(experiment: EnrolledExperiment) = Unit | ||
} |
54 changes: 54 additions & 0 deletions
54
app/src/main/java/org/mozilla/fenix/settings/studies/StudiesFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
package org.mozilla.fenix.settings.studies | ||
|
||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import androidx.fragment.app.Fragment | ||
import androidx.lifecycle.lifecycleScope | ||
import org.mozilla.fenix.HomeActivity | ||
import org.mozilla.fenix.databinding.SettingsStudiesBinding | ||
import org.mozilla.fenix.ext.requireComponents | ||
import org.mozilla.fenix.ext.settings | ||
|
||
/** | ||
* Lets the users control studies settings. | ||
*/ | ||
class StudiesFragment : Fragment() { | ||
private var _binding: SettingsStudiesBinding? = null | ||
// This property is only valid between onCreateView and | ||
// onDestroyView. | ||
private val binding get() = _binding!! | ||
|
||
override fun onCreateView( | ||
inflater: LayoutInflater, | ||
container: ViewGroup?, | ||
savedInstanceState: Bundle? | ||
): View { | ||
val experiments = requireComponents.analytics.experiments | ||
_binding = SettingsStudiesBinding.inflate(inflater, container, false) | ||
val interactor = DefaultStudiesInteractor((activity as HomeActivity), experiments) | ||
StudiesView( | ||
lifecycleScope, | ||
requireContext(), | ||
binding, | ||
interactor, | ||
requireContext().settings(), | ||
experiments, | ||
::isAttached | ||
).bind() | ||
|
||
return binding.root | ||
} | ||
|
||
override fun onDestroyView() { | ||
super.onDestroyView() | ||
_binding = null | ||
} | ||
|
||
private fun isAttached(): Boolean = context != null | ||
} |
Oops, something went wrong.