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.
232 lines
9.3 KiB
Kotlin
232 lines
9.3 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.quicksettings
|
|
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import android.widget.AdapterView
|
|
import android.widget.ArrayAdapter
|
|
import android.widget.TextView
|
|
import androidx.annotation.VisibleForTesting
|
|
import androidx.appcompat.widget.AppCompatSpinner
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.core.view.isVisible
|
|
import org.mozilla.fenix.R
|
|
import org.mozilla.fenix.databinding.QuicksettingsPermissionsBinding
|
|
import org.mozilla.fenix.settings.PhoneFeature
|
|
import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY
|
|
import org.mozilla.fenix.settings.PhoneFeature.CAMERA
|
|
import org.mozilla.fenix.settings.PhoneFeature.CROSS_ORIGIN_STORAGE_ACCESS
|
|
import org.mozilla.fenix.settings.PhoneFeature.LOCATION
|
|
import org.mozilla.fenix.settings.PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS
|
|
import org.mozilla.fenix.settings.PhoneFeature.MICROPHONE
|
|
import org.mozilla.fenix.settings.PhoneFeature.NOTIFICATION
|
|
import org.mozilla.fenix.settings.PhoneFeature.PERSISTENT_STORAGE
|
|
import org.mozilla.fenix.settings.quicksettings.WebsitePermissionsView.PermissionViewHolder.SpinnerPermission
|
|
import org.mozilla.fenix.settings.quicksettings.WebsitePermissionsView.PermissionViewHolder.ToggleablePermission
|
|
import java.util.EnumMap
|
|
|
|
/**
|
|
* Contract declaring all possible user interactions with [WebsitePermissionsView]
|
|
*/
|
|
interface WebsitePermissionInteractor {
|
|
/**
|
|
* Indicates there are website permissions allowed / blocked for the current website.
|
|
* which, status which is shown to the user.
|
|
*/
|
|
fun onPermissionsShown()
|
|
|
|
/**
|
|
* Indicates the user changed the status of a certain website permission.
|
|
*
|
|
* @param permissionState current [WebsitePermission] that the user wants toggled.
|
|
*/
|
|
fun onPermissionToggled(permissionState: WebsitePermission)
|
|
|
|
/**
|
|
* Indicates the user changed the status of a an autoplay permission.
|
|
*
|
|
* @param value current [AutoplayValue] that the user wants change.
|
|
*/
|
|
fun onAutoplayChanged(value: AutoplayValue)
|
|
}
|
|
|
|
/**
|
|
* MVI View that knows to display a list of specific website permissions (hardcoded):
|
|
* - location
|
|
* - notification
|
|
* - microphone
|
|
* - camera
|
|
*
|
|
* @param containerView [ViewGroup] in which this View will inflate itself.
|
|
* @param interactor [WebsitePermissionInteractor] which will have delegated to all user interactions.
|
|
*/
|
|
class WebsitePermissionsView(
|
|
containerView: ViewGroup,
|
|
val interactor: WebsitePermissionInteractor,
|
|
) {
|
|
private val context = containerView.context
|
|
|
|
val binding =
|
|
QuicksettingsPermissionsBinding.inflate(LayoutInflater.from(context), containerView, true)
|
|
|
|
@VisibleForTesting
|
|
internal var permissionViews: Map<PhoneFeature, PermissionViewHolder> = EnumMap(
|
|
mapOf(
|
|
CAMERA to ToggleablePermission(binding.cameraLabel, binding.cameraStatus),
|
|
LOCATION to ToggleablePermission(binding.locationLabel, binding.locationStatus),
|
|
MICROPHONE to ToggleablePermission(
|
|
binding.microphoneLabel,
|
|
binding.microphoneStatus,
|
|
),
|
|
NOTIFICATION to ToggleablePermission(
|
|
binding.notificationLabel,
|
|
binding.notificationStatus,
|
|
),
|
|
PERSISTENT_STORAGE to ToggleablePermission(
|
|
binding.persistentStorageLabel,
|
|
binding.persistentStorageStatus,
|
|
),
|
|
CROSS_ORIGIN_STORAGE_ACCESS to ToggleablePermission(
|
|
binding.crossOriginStorageAccessLabel,
|
|
binding.crossOriginStorageAccessStatus,
|
|
),
|
|
MEDIA_KEY_SYSTEM_ACCESS to ToggleablePermission(
|
|
binding.mediaKeySystemAccessLabel,
|
|
binding.mediaKeySystemAccessStatus,
|
|
),
|
|
AUTOPLAY to SpinnerPermission(
|
|
binding.autoplayLabel,
|
|
binding.autoplayStatus,
|
|
),
|
|
),
|
|
)
|
|
|
|
/**
|
|
* Allows changing what this View displays.
|
|
*
|
|
* @param state [WebsitePermissionsState] to be rendered.
|
|
*/
|
|
fun update(state: WebsitePermissionsState) {
|
|
val isVisible = permissionViews.keys
|
|
.map { feature -> state.getValue(feature) }
|
|
.any { it.isVisible }
|
|
if (isVisible) {
|
|
interactor.onPermissionsShown()
|
|
}
|
|
|
|
// If more permissions are added into this View we can display them into a list
|
|
// and also use DiffUtil to only update one item in case of a permission change
|
|
for ((feature, views) in permissionViews) {
|
|
bindPermission(state.getValue(feature), views)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method that can map a specific website permission to a dedicated permission row
|
|
* which will display permission's [icon, label, status] and register user inputs.
|
|
*
|
|
* @param permissionState [WebsitePermission] specific permission that can be shown to the user.
|
|
* @param viewHolder Views that will render [WebsitePermission]'s state.
|
|
*/
|
|
@VisibleForTesting
|
|
internal fun bindPermission(
|
|
permissionState: WebsitePermission,
|
|
viewHolder: PermissionViewHolder,
|
|
) {
|
|
viewHolder.label.isEnabled = permissionState.isEnabled
|
|
viewHolder.label.isVisible = permissionState.isVisible
|
|
viewHolder.status.isVisible = permissionState.isVisible
|
|
|
|
when (viewHolder) {
|
|
is ToggleablePermission -> {
|
|
viewHolder.status.text = permissionState.status
|
|
viewHolder.status.setOnClickListener {
|
|
interactor.onPermissionToggled(
|
|
permissionState,
|
|
)
|
|
}
|
|
}
|
|
is SpinnerPermission -> {
|
|
if (permissionState !is WebsitePermission.Autoplay) {
|
|
throw IllegalArgumentException("${permissionState.phoneFeature} is not supported")
|
|
}
|
|
|
|
val selectedIndex = permissionState.options.indexOf(permissionState.autoplayValue)
|
|
|
|
val adapter = object : ArrayAdapter<AutoplayValue>(
|
|
context,
|
|
R.layout.quicksettings_permission_spinner_item,
|
|
permissionState.options,
|
|
) {
|
|
override fun getDropDownView(
|
|
position: Int,
|
|
convertView: View?,
|
|
parent: ViewGroup,
|
|
): View {
|
|
val view = super.getDropDownView(
|
|
position,
|
|
convertView,
|
|
parent,
|
|
)
|
|
if (position == viewHolder.status.selectedItemPosition) {
|
|
view.setBackgroundColor(
|
|
ContextCompat.getColor(
|
|
context,
|
|
R.color.spinner_selected_item,
|
|
),
|
|
)
|
|
}
|
|
return view
|
|
}
|
|
}
|
|
|
|
adapter.setDropDownViewResource(R.layout.quicksetting_permission_spinner_dropdown)
|
|
viewHolder.status.adapter = adapter
|
|
|
|
viewHolder.status.tag = permissionState.autoplayValue
|
|
viewHolder.status.setSelection(selectedIndex)
|
|
viewHolder.status.onItemSelectedListener =
|
|
object : AdapterView.OnItemSelectedListener {
|
|
override fun onItemSelected(
|
|
parent: AdapterView<*>?,
|
|
view: View?,
|
|
position: Int,
|
|
id: Long,
|
|
) {
|
|
// Unfortunately the spinner component triggers an selection event when initialized,
|
|
// to avoid that, we are using the tag property to store the selected value and
|
|
// be able to differentiate from an initialization event from a normal selection event
|
|
// see https://stackoverflow.com/questions/21747917/undesired-onitemselected-calls/21751327#21751327
|
|
if (viewHolder.status.selectedItem == viewHolder.status.tag) {
|
|
return
|
|
}
|
|
viewHolder.status.tag = viewHolder.status.selectedItem
|
|
val type = viewHolder.status.selectedItem as AutoplayValue
|
|
interactor.onAutoplayChanged(type)
|
|
}
|
|
|
|
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sealed class PermissionViewHolder(open val label: TextView, open val status: View) {
|
|
data class ToggleablePermission(
|
|
override val label: TextView,
|
|
override val status: TextView,
|
|
) :
|
|
PermissionViewHolder(label, status)
|
|
|
|
data class SpinnerPermission(
|
|
override val label: TextView,
|
|
override val status: AppCompatSpinner,
|
|
) :
|
|
PermissionViewHolder(label, status)
|
|
}
|
|
}
|