You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/main/java/org/mozilla/fenix/settings/studies/StudiesView.kt

179 lines
6.7 KiB
Kotlin

/* 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.content.Context
import android.text.SpannableStringBuilder
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.URLSpan
import android.view.View
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SwitchCompat
import androidx.core.text.HtmlCompat
import androidx.core.text.getSpans
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.service.nimbus.NimbusApi
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.experiments.nimbus.internal.EnrolledExperiment
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.SettingsStudiesBinding
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.SupportUtils.SumoTopic.OPT_OUT_STUDIES
import org.mozilla.fenix.utils.Settings
import kotlin.system.exitProcess
@Suppress("LongParameterList")
class StudiesView(
private val scope: CoroutineScope,
private val context: Context,
private val binding: SettingsStudiesBinding,
private val interactor: StudiesInteractor,
private val settings: Settings,
private val experiments: NimbusApi,
private val isAttached: () -> Boolean
) : StudiesAdapterDelegate {
private val logger = Logger("StudiesView")
@VisibleForTesting
internal lateinit var adapter: StudiesAdapter
@Suppress("TooGenericExceptionCaught", "ApplySharedPref")
fun bind() {
provideStudiesTitle().text = getSwitchTitle()
provideStudiesSwitch().isChecked = settings.isExperimentationEnabled
provideStudiesSwitch().setOnClickListener {
val isChecked = provideStudiesSwitch().isChecked
provideStudiesTitle().text = getSwitchCheckedTitle()
val builder = AlertDialog.Builder(context)
.setPositiveButton(
R.string.studies_restart_dialog_ok
) { dialog, _ ->
settings.isExperimentationEnabled = isChecked
val experimentsKey = context.getPreferenceKey(R.string.pref_key_experimentation)
// In this case, we are using commit() on purpose as we want to warranty
// that we are changing the setting before quitting the app.
context.settings().preferences.edit().putBoolean(experimentsKey, isChecked)
.commit()
experiments.globalUserParticipation = isChecked
dialog.dismiss()
quitTheApp()
}
.setNegativeButton(
R.string.studies_restart_dialog_cancel
) { dialog, _ ->
provideStudiesSwitch().isChecked = !isChecked
provideStudiesTitle().text = getSwitchTitle()
dialog.dismiss()
}
.setTitle(R.string.preference_experiments_2)
.setMessage(R.string.studies_restart_app)
.setCancelable(false)
val alertDialog: AlertDialog = builder.create()
alertDialog.show()
}
bindDescription()
scope.launch(Dispatchers.IO) {
try {
val experiments = experiments.getActiveExperiments()
scope.launch(Dispatchers.Main) {
if (isAttached()) {
adapter = StudiesAdapter(
this@StudiesView,
experiments
)
provideStudiesList().adapter = adapter
}
}
} catch (e: Throwable) {
logger.error("Failed to getActiveExperiments()", e)
}
}
}
override fun onRemoveButtonClicked(experiment: EnrolledExperiment) {
interactor.removeStudy(experiment)
adapter.removeStudy(experiment)
}
@VisibleForTesting
internal fun bindDescription() {
val sumoUrl = SupportUtils.getSumoURLForTopic(context, OPT_OUT_STUDIES)
val description = context.getString(R.string.studies_description)
val learnMore = context.getString(R.string.studies_learn_more)
val rawText = "$description <a href=\"$sumoUrl\">$learnMore</a>"
val text = HtmlCompat.fromHtml(rawText, HtmlCompat.FROM_HTML_MODE_COMPACT)
val spannableStringBuilder = SpannableStringBuilder(text)
val links = spannableStringBuilder.getSpans<URLSpan>()
for (link in links) {
addActionToLinks(spannableStringBuilder, link)
}
binding.studiesDescription.text = spannableStringBuilder
binding.studiesDescription.movementMethod = LinkMovementMethod.getInstance()
}
private fun addActionToLinks(
spannableStringBuilder: SpannableStringBuilder,
link: URLSpan
) {
val start = spannableStringBuilder.getSpanStart(link)
val end = spannableStringBuilder.getSpanEnd(link)
val flags = spannableStringBuilder.getSpanFlags(link)
val clickable: ClickableSpan = object : ClickableSpan() {
override fun onClick(view: View) {
view.setOnClickListener {
interactor.openWebsite(link.url)
}
}
}
spannableStringBuilder.setSpan(clickable, start, end, flags)
spannableStringBuilder.removeSpan(link)
}
@VisibleForTesting
internal fun getSwitchTitle(): String {
val stringId = if (settings.isExperimentationEnabled) {
R.string.studies_on
} else {
R.string.studies_off
}
return context.getString(stringId)
}
@VisibleForTesting
internal fun getSwitchCheckedTitle(): String {
val stringId = if (provideStudiesSwitch().isChecked) {
R.string.studies_on
} else {
R.string.studies_off
}
return context.getString(stringId)
}
@VisibleForTesting
internal fun provideStudiesTitle(): TextView = binding.studiesTitle
@VisibleForTesting
internal fun provideStudiesSwitch(): SwitchCompat = binding.studiesSwitch
@VisibleForTesting
internal fun provideStudiesList(): RecyclerView = binding.studiesList
@VisibleForTesting
internal fun quitTheApp() {
exitProcess(0)
}
}