1
0
Fork 0

For #4126 - Refactor Views and layouts

Refactored `fragment_quick_settings_dialog_sheet` to now be composed of of
FrameLayouts placeholders in which each independent View will inflate itself.
Refactored the QuickSettingsUIView and Component to 3 standalone Views with
their own lib-state components: Store, State, Actions, Reducer.
master
Mugurell 2019-10-16 20:14:40 +03:00 committed by Emily Kager
parent 5cca5d7a70
commit f1f74bc3d6
17 changed files with 623 additions and 788 deletions

View File

@ -37,25 +37,29 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>)
}
}
@Suppress("ComplexMethod")
fun getActionLabel(
context: Context,
sitePermissions: SitePermissions? = null,
settings: Settings? = null
): String {
@StringRes val stringRes =
when (this) {
AUTOPLAY -> {
when (getStatus(sitePermissions, settings)) {
SitePermissions.Status.BLOCKED -> R.string.preference_option_autoplay_blocked
SitePermissions.Status.ALLOWED -> R.string.preference_option_autoplay_allowed
else -> R.string.preference_option_autoplay_allowed
when (isAndroidPermissionGranted(context)) {
false -> R.string.phone_feature_blocked_by_android
else -> when (this) {
AUTOPLAY -> {
when (getStatus(sitePermissions, settings)) {
SitePermissions.Status.BLOCKED -> R.string.preference_option_autoplay_blocked
SitePermissions.Status.ALLOWED -> R.string.preference_option_autoplay_allowed
else -> R.string.preference_option_autoplay_allowed
}
}
}
else -> {
when (getStatus(sitePermissions, settings)) {
SitePermissions.Status.BLOCKED -> R.string.preference_option_phone_feature_blocked
SitePermissions.Status.NO_DECISION -> R.string.preference_option_phone_feature_ask_to_allow
SitePermissions.Status.ALLOWED -> R.string.preference_option_phone_feature_allowed
else -> {
when (getStatus(sitePermissions, settings)) {
SitePermissions.Status.BLOCKED -> R.string.preference_option_phone_feature_blocked
SitePermissions.Status.NO_DECISION -> R.string.preference_option_phone_feature_ask_to_allow
SitePermissions.Status.ALLOWED -> R.string.preference_option_phone_feature_allowed
}
}
}
}

View File

@ -1,168 +0,0 @@
/* 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.content.Context
import android.view.ViewGroup
import androidx.core.net.toUri
import mozilla.components.feature.sitepermissions.SitePermissions
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.mvi.Action
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.Change
import org.mozilla.fenix.mvi.UIComponent
import org.mozilla.fenix.mvi.UIComponentViewModelBase
import org.mozilla.fenix.mvi.UIComponentViewModelProvider
import org.mozilla.fenix.mvi.UIView
import org.mozilla.fenix.mvi.ViewState
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.toggle
class QuickSettingsComponent(
private val container: ViewGroup,
bus: ActionBusFactory,
viewModelProvider: UIComponentViewModelProvider<QuickSettingsState, QuickSettingsChange>
) : UIComponent<QuickSettingsState, QuickSettingsAction, QuickSettingsChange>(
bus.getManagedEmitter(QuickSettingsAction::class.java),
bus.getSafeManagedObservable(QuickSettingsChange::class.java),
viewModelProvider
) {
override fun initView(): UIView<QuickSettingsState, QuickSettingsAction, QuickSettingsChange> {
return QuickSettingsUIView(container, actionEmitter, changesObservable, container)
}
init {
bind()
}
fun toggleSitePermission(
context: Context,
featurePhone: PhoneFeature,
url: String,
sitePermissions: SitePermissions?
): SitePermissions {
return if (sitePermissions == null) {
val settings = context.settings()
val origin = requireNotNull(url.toUri().host)
var location =
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION).toStatus()
var camera =
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA).toStatus()
var microphone =
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE).toStatus()
var notification =
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION).toStatus()
when (featurePhone) {
PhoneFeature.CAMERA -> camera = camera.toggle()
PhoneFeature.LOCATION -> location = location.toggle()
PhoneFeature.MICROPHONE -> microphone = microphone.toggle()
PhoneFeature.NOTIFICATION -> notification = notification.toggle()
PhoneFeature.AUTOPLAY -> { // not supported by GV or A-C yet
}
}
context.components.core.permissionStorage
.addSitePermissionException(origin, location, notification, microphone, camera)
} else {
val updatedSitePermissions = sitePermissions.toggle(featurePhone)
context.components.core.permissionStorage.updateSitePermissions(updatedSitePermissions)
updatedSitePermissions
}
}
}
data class QuickSettingsState(val mode: Mode) : ViewState {
sealed class Mode {
data class Normal(
val url: String,
val isSecured: Boolean,
val isTrackingProtectionOn: Boolean,
val sitePermissions: SitePermissions?
) : Mode()
data class ActionLabelUpdated(
val phoneFeature: PhoneFeature,
val sitePermissions: SitePermissions?
) :
Mode()
data class CheckPendingFeatureBlockedByAndroid(val sitePermissions: SitePermissions?) :
Mode()
}
}
sealed class QuickSettingsAction : Action {
data class SelectReportProblem(val url: String) : QuickSettingsAction()
object SelectTrackingProtectionSettings : QuickSettingsAction()
data class ToggleTrackingProtection(val trackingProtection: Boolean) : QuickSettingsAction()
data class SelectBlockedByAndroid(val permissions: Array<String>) : QuickSettingsAction()
data class TogglePermission(val featurePhone: PhoneFeature) : QuickSettingsAction()
}
sealed class QuickSettingsChange : Change {
data class Change(
val url: String,
val isSecured: Boolean,
val isTrackingProtectionOn: Boolean,
val sitePermissions: SitePermissions?
) : QuickSettingsChange()
data class PermissionGranted(
val phoneFeature: PhoneFeature,
val sitePermissions: SitePermissions?
) :
QuickSettingsChange()
data class PromptRestarted(val sitePermissions: SitePermissions?) : QuickSettingsChange()
data class Stored(val phoneFeature: PhoneFeature, val sitePermissions: SitePermissions?) :
QuickSettingsChange()
}
class QuickSettingsViewModel(
initialState: QuickSettingsState
) : UIComponentViewModelBase<QuickSettingsState, QuickSettingsChange>(initialState, reducer) {
companion object {
val reducer: (QuickSettingsState, QuickSettingsChange) -> QuickSettingsState =
{ state, change ->
when (change) {
is QuickSettingsChange.Change -> {
state.copy(
mode = QuickSettingsState.Mode.Normal(
change.url,
change.isSecured,
change.isTrackingProtectionOn,
change.sitePermissions
)
)
}
is QuickSettingsChange.PermissionGranted -> {
state.copy(
mode = QuickSettingsState.Mode.ActionLabelUpdated(
change.phoneFeature,
change.sitePermissions
)
)
}
is QuickSettingsChange.PromptRestarted -> {
state.copy(
mode = QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid(
change.sitePermissions
)
)
}
is QuickSettingsChange.Stored -> {
state.copy(
mode = QuickSettingsState.Mode.ActionLabelUpdated(
change.phoneFeature,
change.sitePermissions
)
)
}
}
}
}
}

View File

@ -5,9 +5,6 @@
package org.mozilla.fenix.settings.quicksettings
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
@ -19,75 +16,62 @@ import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.net.toUri
import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import mozilla.components.browser.session.Session
import mozilla.components.feature.sitepermissions.SitePermissions
import org.mozilla.fenix.FenixViewModelProvider
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserFragment
import org.mozilla.fenix.exceptions.ExceptionDomains
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.tryGetHostFromUrl
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
import com.google.android.material.R as MaterialR
private const val REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS = 4
@ObsoleteCoroutinesApi
@SuppressWarnings("TooManyFunctions")
class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
private lateinit var websiteInfoStore: WebsiteInfoStore
private lateinit var websitePermissionsStore: WebsitePermissionsStore
private lateinit var websiteTrackingProtectionStore: TrackingProtectionStore
private lateinit var websiteInfoView: WebsiteInfoView
private lateinit var websitePermissionsView: WebsitePermissionsView
private lateinit var websiteTrackingProtectionView: TrackingProtectionView
private val safeArguments get() = requireNotNull(arguments)
private val sessionId: String by lazy { QuickSettingsSheetDialogFragmentArgs.fromBundle(safeArguments).sessionId }
private val url: String by lazy { QuickSettingsSheetDialogFragmentArgs.fromBundle(safeArguments).url }
private val isSecured: Boolean by lazy { QuickSettingsSheetDialogFragmentArgs.fromBundle(safeArguments).isSecured }
private val isTrackingProtectionOn: Boolean by lazy {
QuickSettingsSheetDialogFragmentArgs.fromBundle(safeArguments).isTrackingProtectionOn
}
private val promptGravity: Int by lazy { QuickSettingsSheetDialogFragmentArgs.fromBundle(safeArguments).gravity }
private lateinit var quickSettingsComponent: QuickSettingsComponent
private var sitePermissions: SitePermissions? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
sitePermissions = QuickSettingsSheetDialogFragmentArgs.fromBundle(safeArguments).sitePermissions
val args = QuickSettingsSheetDialogFragmentArgs.fromBundle(safeArguments)
val rootView = inflateRootView(container)
requireComponents.core.sessionManager.findSessionById(sessionId)?.register(sessionObserver, view = rootView)
quickSettingsComponent = QuickSettingsComponent(
rootView as NestedScrollView,
ActionBusFactory.get(this),
FenixViewModelProvider.create(
this,
QuickSettingsViewModel::class.java
) {
QuickSettingsViewModel(
QuickSettingsState(
QuickSettingsState.Mode.Normal(
url,
isSecured,
isTrackingProtectionOn,
sitePermissions
)
)
)
}
websitePermissionsStore = WebsitePermissionsStore.createStore(
context!!, args.sitePermissions, Settings.getInstance(context!!)
)
websiteInfoStore = WebsiteInfoStore.createStore(args.url, args.isSecured)
if (!FeatureFlags.etpCategories) {
websiteTrackingProtectionStore =
TrackingProtectionStore.createStore(
args.url,
args.isTrackingProtectionOn,
context!!.settings()
)
websiteTrackingProtectionView =
TrackingProtectionView(rootView.trackingProtectionLayout)
} else {
rootView.trackingProtectionGroup.isVisible = false
}
websiteInfoView = WebsiteInfoView(rootView.websiteInfoLayout)
websitePermissionsView = WebsitePermissionsView(rootView.websitePermissionsLayout)
return rootView
}
@ -118,6 +102,17 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
}
}
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
consumeFrom(websiteInfoStore) { websiteInfoView.update(it) }
consumeFrom(websitePermissionsStore) { websitePermissionsView.update(it) }
if (::websiteTrackingProtectionStore.isInitialized) {
consumeFrom(websiteTrackingProtectionStore) { websiteTrackingProtectionView.update(it) }
}
}
private fun Dialog.applyCustomizationsForTopDialog(rootView: View): Dialog {
addContentView(
rootView,
@ -135,146 +130,4 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
}
return this
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (arePermissionsGranted(requestCode, grantResults)) {
val feature = requireNotNull(PhoneFeature.findFeatureBy(permissions))
getManagedEmitter<QuickSettingsChange>()
.onNext(QuickSettingsChange.PermissionGranted(feature, sitePermissions))
}
}
private fun arePermissionsGranted(requestCode: Int, grantResults: IntArray) =
requestCode == REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS && grantResults.all { it == PERMISSION_GRANTED }
private fun toggleTrackingProtection(context: Context, url: String) {
val host = url.tryGetHostFromUrl()
lifecycleScope.launch {
ExceptionDomains(context).toggle(host)
}
}
@ExperimentalCoroutinesApi
override fun onResume() {
super.onResume()
getAutoDisposeObservable<QuickSettingsAction>()
.subscribe {
when (it) {
is QuickSettingsAction.SelectBlockedByAndroid -> {
requestPermissions(it.permissions, REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS)
}
is QuickSettingsAction.SelectTrackingProtectionSettings -> {
val directions =
QuickSettingsSheetDialogFragmentDirections
.actionQuickSettingsSheetDialogFragmentToTrackingProtectionFragment()
findNavController(this@QuickSettingsSheetDialogFragment).navigate(directions)
}
is QuickSettingsAction.SelectReportProblem -> {
lifecycleScope.launch(Dispatchers.Main) {
val reportUrl =
String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, it.url)
requireComponents.useCases.tabsUseCases.addTab.invoke(reportUrl)
val sessionManager = requireComponents.core.sessionManager
if (sessionManager.findSessionById(sessionId)?.isCustomTabSession() == true) {
val intent = Intent(context, IntentReceiverActivity::class.java)
intent.action = Intent.ACTION_VIEW
startActivity(intent)
}
}
dismiss()
}
is QuickSettingsAction.ToggleTrackingProtection -> {
val trackingEnabled = it.trackingProtection
context?.let { context: Context -> toggleTrackingProtection(context, url) }
lifecycleScope.launch(Dispatchers.Main) {
getManagedEmitter<QuickSettingsChange>().onNext(
QuickSettingsChange.Change(
url,
isSecured,
trackingEnabled,
sitePermissions
)
)
requireContext().components.useCases.sessionUseCases.reload.invoke()
}
}
is QuickSettingsAction.TogglePermission -> {
lifecycleScope.launch(Dispatchers.IO) {
sitePermissions = quickSettingsComponent.toggleSitePermission(
context = requireContext(),
featurePhone = it.featurePhone,
url = url,
sitePermissions = sitePermissions
)
launch(Dispatchers.Main) {
getManagedEmitter<QuickSettingsChange>()
.onNext(
QuickSettingsChange.Stored(
it.featurePhone,
sitePermissions
)
)
requireContext().components.useCases.sessionUseCases.reload.invoke()
}
}
}
}
}
if (isVisible) {
getManagedEmitter<QuickSettingsChange>()
.onNext(QuickSettingsChange.PromptRestarted(sitePermissions))
}
}
private val sessionObserver = object : Session.Observer {
override fun onUrlChanged(session: Session, url: String) {
lifecycleScope.launch(Dispatchers.IO) {
val host = session.url.toUri().host
val sitePermissions: SitePermissions? = host?.let {
val storage = requireContext().components.core.permissionStorage
storage.findSitePermissionsBy(it)
}
launch(Dispatchers.Main) {
getManagedEmitter<QuickSettingsChange>().onNext(
QuickSettingsChange.Change(
url,
session.securityInfo.secure,
session.trackerBlockingEnabled,
sitePermissions
)
)
}
}
}
override fun onTrackerBlockingEnabledChanged(session: Session, blockingEnabled: Boolean) {
getManagedEmitter<QuickSettingsChange>().onNext(
QuickSettingsChange.Change(
session.url,
session.securityInfo.secure,
blockingEnabled,
sitePermissions
)
)
}
override fun onSecurityChanged(session: Session, securityInfo: Session.SecurityInfo) {
getManagedEmitter<QuickSettingsChange>().onNext(
QuickSettingsChange.Change(
session.url,
securityInfo.secure,
session.trackerBlockingEnabled,
sitePermissions
)
)
}
}
}

View File

@ -1,185 +0,0 @@
/* 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.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.isVisible
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.functions.Consumer
import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.*
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissions.Status.BLOCKED
import mozilla.components.feature.sitepermissions.SitePermissions.Status.NO_DECISION
import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.mvi.UIView
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.PhoneFeature.CAMERA
import org.mozilla.fenix.settings.PhoneFeature.LOCATION
import org.mozilla.fenix.settings.PhoneFeature.MICROPHONE
import org.mozilla.fenix.settings.PhoneFeature.NOTIFICATION
import org.mozilla.fenix.utils.Settings
typealias LabelActionPair = Pair<TextView, TextView>
@Suppress("TooManyFunctions")
class QuickSettingsUIView(
container: ViewGroup,
actionEmitter: Observer<QuickSettingsAction>,
changesObservable: Observable<QuickSettingsChange>,
override val view: View
) : UIView<QuickSettingsState, QuickSettingsAction, QuickSettingsChange>(
container, actionEmitter, changesObservable
) {
private val blockedByAndroidPhoneFeatures = mutableListOf<PhoneFeature>()
private inline val context get() = view.context
private val settings: Settings = context.settings()
private val trackingProtectionSettingView = TrackingProtectionSettingView(view, actionEmitter)
private val labelAndActions = mapOf(
CAMERA to findLabelActionPair(R.id.camera_icon, R.id.camera_action_label),
LOCATION to findLabelActionPair(R.id.location_icon, R.id.location_action_label),
MICROPHONE to findLabelActionPair(R.id.microphone_icon, R.id.microphone_action_label),
NOTIFICATION to findLabelActionPair(R.id.notification_icon, R.id.notification_action_label)
)
private val blockedByAndroidClickListener = View.OnClickListener {
val feature = it.tag as PhoneFeature
actionEmitter.onNext(
QuickSettingsAction.SelectBlockedByAndroid(feature.androidPermissionsList)
)
}
private val togglePermissionClickListener = View.OnClickListener {
val feature = it.tag as PhoneFeature
actionEmitter.onNext(
QuickSettingsAction.TogglePermission(feature)
)
}
override fun updateView() = Consumer<QuickSettingsState> { state ->
when (state.mode) {
is QuickSettingsState.Mode.Normal -> {
bindUrl(state.mode.url)
bindSecurityInfo(state.mode.isSecured)
bindReportSiteIssueAction(state.mode.url)
trackingProtectionSettingView.bind(state.mode.isTrackingProtectionOn)
bindPhoneFeatureItem(CAMERA, state.mode.sitePermissions)
bindPhoneFeatureItem(MICROPHONE, state.mode.sitePermissions)
bindPhoneFeatureItem(NOTIFICATION, state.mode.sitePermissions)
bindPhoneFeatureItem(LOCATION, state.mode.sitePermissions)
}
is QuickSettingsState.Mode.ActionLabelUpdated -> {
bindPhoneFeatureItem(state.mode.phoneFeature, state.mode.sitePermissions)
}
is QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid -> {
checkFeaturesBlockedByAndroid(state.mode.sitePermissions)
}
}
}
private fun bindUrl(url: String) {
this.url.text = url.toUri().hostWithoutCommonPrefixes
}
private fun bindReportSiteIssueAction(url: String) {
report_site_issue_action.setOnClickListener {
actionEmitter.onNext(
QuickSettingsAction.SelectReportProblem(url)
)
}
}
private fun bindSecurityInfo(isSecured: Boolean) {
@StringRes val stringId: Int
@DrawableRes val drawableId: Int
@ColorRes val drawableTint: Int
if (isSecured) {
stringId = R.string.quick_settings_sheet_secure_connection
drawableId = R.drawable.mozac_ic_lock
drawableTint = R.color.photonGreen50
} else {
stringId = R.string.quick_settings_sheet_insecure_connection
drawableId = R.drawable.mozac_ic_broken_lock
drawableTint = R.color.photonRed50
}
val icon = context.getDrawable(drawableId)
icon?.setTint(ContextCompat.getColor(context, drawableTint))
security_info.setText(stringId)
security_info.putCompoundDrawablesRelativeWithIntrinsicBounds(start = icon)
}
private fun bindPhoneFeatureItem(phoneFeature: PhoneFeature, sitePermissions: SitePermissions? = null) {
val (label, action) = labelAndActions.getValue(phoneFeature)
val shouldBeVisible = phoneFeature.shouldBeVisible(sitePermissions)
label.isVisible = shouldBeVisible
action.isVisible = shouldBeVisible
if (shouldBeVisible) {
if (phoneFeature.isAndroidPermissionGranted(context)) {
bindPhoneAction(phoneFeature, sitePermissions)
} else {
handleBlockedByAndroidAction(phoneFeature)
}
}
}
private fun PhoneFeature.shouldBeVisible(sitePermissions: SitePermissions?): Boolean {
return getStatus(sitePermissions, settings) != NO_DECISION
}
private fun PhoneFeature.isPermissionBlocked(sitePermissions: SitePermissions?): Boolean {
return getStatus(sitePermissions, settings) == BLOCKED
}
private fun handleBlockedByAndroidAction(phoneFeature: PhoneFeature) {
val (label, action) = labelAndActions.getValue(phoneFeature)
action.setText(R.string.phone_feature_blocked_by_android)
action.tag = phoneFeature
action.setOnClickListener(blockedByAndroidClickListener)
label.isEnabled = false
blockedByAndroidPhoneFeatures.add(phoneFeature)
}
private fun bindPhoneAction(phoneFeature: PhoneFeature, sitePermissions: SitePermissions? = null) {
val (label, action) = labelAndActions.getValue(phoneFeature)
action.text = phoneFeature.getActionLabel(
context = context,
sitePermissions = sitePermissions,
settings = settings
)
action.tag = phoneFeature
action.setOnClickListener(togglePermissionClickListener)
label.isEnabled = !phoneFeature.isPermissionBlocked(sitePermissions)
blockedByAndroidPhoneFeatures.remove(phoneFeature)
}
private fun checkFeaturesBlockedByAndroid(sitePermissions: SitePermissions?) {
blockedByAndroidPhoneFeatures.forEach { phoneFeature ->
if (phoneFeature.isAndroidPermissionGranted(context)) {
bindPhoneAction(phoneFeature, sitePermissions)
}
}
}
private fun findLabelActionPair(@IdRes labelId: Int, @IdRes actionId: Int): LabelActionPair {
return view.findViewById<TextView>(labelId) to view.findViewById(actionId)
}
}

View File

@ -1,63 +0,0 @@
/* 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.View
import android.widget.CompoundButton
import android.widget.Switch
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import io.reactivex.Observer
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
class TrackingProtectionSettingView(
container: View,
private val actionEmitter: Observer<QuickSettingsAction>
) : View.OnClickListener, CompoundButton.OnCheckedChangeListener {
private val trackingProtectionSwitch: Switch = container.findViewById(R.id.tracking_protection)
private val trackingProtectionAction: TextView =
container.findViewById(R.id.tracking_protection_action)
private val trackingProtectionSettingView: ConstraintLayout =
container.findViewById(R.id.tracking_protection_view)
init {
trackingProtectionSwitch.putCompoundDrawablesRelativeWithIntrinsicBounds(
start = AppCompatResources.getDrawable(
container.context,
R.drawable.ic_tracking_protection
)
)
}
fun bind(isTrackingProtectionOn: Boolean) {
trackingProtectionSettingView.visibility =
if (FeatureFlags.etpCategories) View.GONE else View.VISIBLE
val globalTPSetting = trackingProtectionSwitch.context.settings().shouldUseTrackingProtection
trackingProtectionAction.isVisible = !globalTPSetting
trackingProtectionAction.setOnClickListener(this)
trackingProtectionSwitch.isChecked = isTrackingProtectionOn
trackingProtectionSwitch.isEnabled = globalTPSetting
trackingProtectionSwitch.setOnCheckedChangeListener(this)
}
override fun onClick(view: View) {
actionEmitter.onNext(
QuickSettingsAction.SelectTrackingProtectionSettings
)
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
actionEmitter.onNext(
QuickSettingsAction.ToggleTrackingProtection(isChecked)
)
}
}

View File

@ -0,0 +1,51 @@
/* 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 mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.utils.Settings
class TrackingProtectionStore(
val initialState: TrackingProtectionState
) : Store<TrackingProtectionState, TrackingProtectionAction>(
initialState, ::trackingProtectionReducer
) {
companion object {
fun createStore(
url: String,
isTrackingProtectionOn: Boolean,
settings: Settings
) = TrackingProtectionStore(
TrackingProtectionState(
websiteUrl = url,
isTrackingProtectionEnabledPerApp = settings.shouldUseTrackingProtection,
isTrackingProtectionEnabledPerWebsite = isTrackingProtectionOn
)
)
}
}
data class TrackingProtectionState(
val websiteUrl: String,
val isTrackingProtectionEnabledPerApp: Boolean,
val isTrackingProtectionEnabledPerWebsite: Boolean
) : State
sealed class TrackingProtectionAction : Action {
object Stub1 : TrackingProtectionAction()
object Stub2 : TrackingProtectionAction()
}
fun trackingProtectionReducer(
state: TrackingProtectionState,
action: TrackingProtectionAction
): TrackingProtectionState {
return when (action) {
TrackingProtectionAction.Stub1 -> state
TrackingProtectionAction.Stub2 -> state
}
}

View File

@ -0,0 +1,39 @@
/* 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 androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.isVisible
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.quicksettings_tracking_protection.*
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
class TrackingProtectionView(
override val containerView: ViewGroup
) : LayoutContainer {
val view: View = LayoutInflater.from(containerView.context)
.inflate(R.layout.quicksettings_tracking_protection, containerView, true)
init {
trackingProtectionSwitch.putCompoundDrawablesRelativeWithIntrinsicBounds(
start = AppCompatResources.getDrawable(
containerView.context,
R.drawable.ic_tracking_protection
)
)
}
fun update(state: TrackingProtectionState) {
trackingProtectionAction.isVisible = !state.isTrackingProtectionEnabledPerApp
trackingProtectionSwitch.isChecked = state.isTrackingProtectionEnabledPerWebsite
trackingProtectionSwitch.isEnabled = state.isTrackingProtectionEnabledPerApp
}
}

View File

@ -0,0 +1,63 @@
/* 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 androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.R
class WebsiteInfoStore(
initialState: WebsiteInfoState
) : Store<WebsiteInfoState, WebsiteInfoAction>(
initialState, ::websiteInfoReducer
) {
companion object {
fun createStore(url: String, isSecured: Boolean): WebsiteInfoStore {
val (stringRes, iconRes, colorRes) = when (isSecured) {
true -> getSecuredWebsiteUiValues()
false -> getInsecureWebsiteUiValues()
}
return WebsiteInfoStore(WebsiteInfoState(url, stringRes, iconRes, colorRes))
}
}
}
data class WebsiteInfoState(
val url: String,
@StringRes val securityInfoRes: Int,
@DrawableRes val iconRes: Int,
@ColorRes val iconTintRes: Int
) : State
sealed class WebsiteInfoAction : Action {
object Stub1 : WebsiteInfoAction()
object Stub2 : WebsiteInfoAction()
}
fun websiteInfoReducer(
state: WebsiteInfoState,
action: WebsiteInfoAction
): WebsiteInfoState {
return when (action) {
WebsiteInfoAction.Stub1 -> state
WebsiteInfoAction.Stub2 -> state
}
}
private fun getSecuredWebsiteUiValues() = Triple(
R.string.quick_settings_sheet_secure_connection,
R.drawable.mozac_ic_lock,
R.color.photonGreen50
)
private fun getInsecureWebsiteUiValues() = Triple(
R.string.quick_settings_sheet_insecure_connection,
R.drawable.mozac_ic_globe,
R.color.photonRed50
)

View File

@ -0,0 +1,46 @@
/* 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 androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.quicksettings_website_info.view.*
import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
class WebsiteInfoView(
override val containerView: ViewGroup
) : LayoutContainer {
val view: View = LayoutInflater.from(containerView.context)
.inflate(R.layout.quicksettings_website_info, containerView, true)
fun update(state: WebsiteInfoState) {
bindUrl(state.url)
bindSecurityInfo(state.securityInfoRes, state.iconRes, state.iconTintRes)
}
private fun bindUrl(url: String) {
view.url.text = url.toUri().hostWithoutCommonPrefixes
}
private fun bindSecurityInfo(
@StringRes securityInfoRes: Int,
@DrawableRes iconRes: Int,
@ColorRes iconTintRes: Int
) {
val icon = view.context.getDrawable(iconRes)
icon?.setTint(ContextCompat.getColor(view.context, iconTintRes))
view.securityInfo.setText(securityInfoRes)
view.securityInfo.putCompoundDrawablesRelativeWithIntrinsicBounds(start = icon)
}
}

View File

@ -0,0 +1,91 @@
/* 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.content.Context
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.utils.Settings
class WebsitePermissionsStore(
initialState: WebsitePermissionsState
) : Store<WebsitePermissionsState, WebsitePermissionAction>(
initialState, ::reducer
) {
companion object {
fun createStore(
context: Context,
permissions: SitePermissions?,
settings: Settings
) = WebsitePermissionsStore(
WebsitePermissionsState(
camera = initWebsitePermission(context, PhoneFeature.CAMERA, permissions, settings),
microphone = initWebsitePermission(context, PhoneFeature.MICROPHONE, permissions, settings),
notification = initWebsitePermission(context, PhoneFeature.NOTIFICATION, permissions, settings),
location = initWebsitePermission(context, PhoneFeature.LOCATION, permissions, settings)
)
)
private fun initWebsitePermission(
context: Context,
phoneFeature: PhoneFeature,
permissions: SitePermissions?,
settings: Settings
): WebsitePermission {
val shouldBeVisible = phoneFeature.shouldBeVisible(permissions, settings)
return WebsitePermission(
name = phoneFeature.name,
status = phoneFeature.getActionLabel(context, permissions, settings),
visible = shouldBeVisible,
enabled = shouldBeVisible &&
phoneFeature.isAndroidPermissionGranted(context) &&
!phoneFeature.isUserPermissionGranted(permissions, settings)
)
}
private fun PhoneFeature.shouldBeVisible(
sitePermissions: SitePermissions?,
settings: Settings
) = getStatus(sitePermissions, settings) != SitePermissions.Status.NO_DECISION
private fun PhoneFeature.isUserPermissionGranted(
sitePermissions: SitePermissions?,
settings: Settings
) = getStatus(sitePermissions, settings) == SitePermissions.Status.BLOCKED
}
}
data class WebsitePermissionsState(
val camera: WebsitePermission,
val microphone: WebsitePermission,
val notification: WebsitePermission,
val location: WebsitePermission
) : State
sealed class WebsitePermissionAction : Action {
object Stub1 : WebsitePermissionAction()
object Stub2 : WebsitePermissionAction()
}
data class WebsitePermission(
val name: String,
val status: String,
val visible: Boolean,
val enabled: Boolean
)
fun reducer(
state: WebsitePermissionsState,
action: WebsitePermissionAction
): WebsitePermissionsState {
return when (action) {
WebsitePermissionAction.Stub1 -> state
WebsitePermissionAction.Stub2 -> state
}
}

View File

@ -0,0 +1,42 @@
/* 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.TextView
import androidx.core.view.isVisible
import kotlinx.android.extensions.LayoutContainer
import org.mozilla.fenix.R
class WebsitePermissionsView(
override val containerView: ViewGroup
) : LayoutContainer {
private val context = containerView.context
val view: View = LayoutInflater.from(context)
.inflate(R.layout.quicksettings_permissions, containerView, true)
fun update(state: WebsitePermissionsState) {
bindPermission(state.camera,
Pair(view.findViewById(R.id.cameraIcon), view.findViewById(R.id.cameraActionLabel)))
bindPermission(state.location,
Pair(view.findViewById(R.id.locationIcon), view.findViewById(R.id.locationActionLabel)))
bindPermission(state.microphone,
Pair(view.findViewById(R.id.microphoneIcon), view.findViewById(R.id.microphoneActionLabel)))
bindPermission(state.notification,
Pair(view.findViewById(R.id.notificationIcon), view.findViewById(R.id.notificationActionLabel)))
}
private fun bindPermission(permissionState: WebsitePermission, permissionViews: Pair<TextView, TextView>) {
val (icon, status) = permissionViews
status.text = permissionState.status
status.isEnabled = permissionState.enabled
icon.isVisible = permissionState.visible
status.isVisible = permissionState.visible
}
}

View File

@ -12,7 +12,6 @@ import androidx.core.net.toUri
import androidx.core.view.isGone
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_tracking_protection_panel.*
import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.url
import kotlinx.android.synthetic.main.switch_with_description.view.*
import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
import org.mozilla.fenix.R
@ -163,11 +162,11 @@ class TrackingProtectionPanelView(
}
private fun bindTrackingProtectionInfo(isTrackingProtectionOn: Boolean) {
tracking_protection.switchItemDescription.text =
trackingProtectionSwitch.switchItemDescription.text =
view.context.getString(if (isTrackingProtectionOn) R.string.etp_panel_on else R.string.etp_panel_off)
tracking_protection.switch_widget.isChecked = isTrackingProtectionOn
trackingProtectionSwitch.switch_widget.isChecked = isTrackingProtectionOn
tracking_protection.switch_widget.setOnCheckedChangeListener { _, isChecked ->
trackingProtectionSwitch.switch_widget.setOnCheckedChangeListener { _, isChecked ->
interactor.trackingProtectionToggled(isChecked)
}
}

View File

@ -2,7 +2,9 @@
<!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/panel_wrapper"
@ -27,7 +29,7 @@
tools:text="https://wikipedia.org" />
<org.mozilla.fenix.trackingprotection.SwitchWithDescription
android:id="@+id/tracking_protection"
android:id="@+id/trackingProtectionSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/tracking_protection_item_height"
@ -48,7 +50,7 @@
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tracking_protection" />
app:layout_constraintTop_toBottomOf="@id/trackingProtectionSwitch" />
<TextView
android:id="@+id/cross_site_tracking"

View File

@ -3,9 +3,9 @@
- 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/. -->
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
@ -17,174 +17,52 @@
android:background="?foundation"
android:contentDescription="@string/quick_settings_sheet">
<TextView
android:id="@+id/url"
style="@style/QuickSettingsText"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="https://wikipedia.org" />
<FrameLayout
android:id="@+id/websiteInfoLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/trackingProtectionDivider" />
<TextView
android:id="@+id/security_info"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/url"
tools:drawableStart="@drawable/mozac_ic_lock"
tools:drawableTint="@color/photonGreen50"
tools:text="Secure connection" />
<FrameLayout
android:id="@+id/trackingProtectionLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/webSitePermissionsDivider" />
<FrameLayout
android:id="@+id/websitePermissionsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
<View
android:id="@+id/line_divider_security"
android:id="@+id/webSitePermissionsDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?neutralFaded"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/security_info" />
app:layout_constraintBottom_toTopOf="@id/websitePermissionsLayout" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/tracking_protection_view"
<View
android:id="@+id/trackingProtectionDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?neutralFaded"
app:layout_constraintBottom_toTopOf="@id/trackingProtectionLayout" />
<androidx.constraintlayout.widget.Group
android:id="@+id/trackingProtectionGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/camera_icon"
app:layout_constraintTop_toBottomOf="@id/line_divider_security">
app:constraint_referenced_ids="trackingProtectionLayout,trackingProtectionDivider" />
<Switch
android:id="@+id/tracking_protection"
style="@style/QuickSettingsText.Icon"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
android:paddingEnd="24dp"
android:text="@string/preferences_tracking_protection"
app:layout_constraintBottom_toTopOf="@id/tracking_protection_action"
app:layout_constraintTop_toTopOf="parent"
tools:drawableStart="@drawable/ic_tracking_protection" />
<TextView
android:id="@+id/tracking_protection_action"
style="@style/QuickSettingsText.Icon"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
android:gravity="top"
android:paddingStart="48dp"
android:paddingEnd="0dp"
android:text="@string/preferences_tracking_protection_turned_off_globally"
android:textColor="?accentBright"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/report_site_issue_action"
app:layout_constraintTop_toBottomOf="@id/tracking_protection" />
<TextView
android:id="@+id/report_site_issue_action"
style="@style/QuickSettingsText.Icon"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
android:gravity="top"
android:paddingStart="48dp"
android:paddingEnd="0dp"
android:text="@string/browser_menu_report_issue"
android:textColor="?accentUsedOnDarkBackground"
app:layout_constraintBottom_toTopOf="@id/line_divider"
app:layout_constraintTop_toBottomOf="@id/tracking_protection_action" />
<View
android:id="@+id/line_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?neutralFaded"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/camera_icon"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
android:drawableStart="@drawable/ic_camera"
android:text="@string/preference_phone_feature_camera"
app:layout_constraintEnd_toStartOf="@+id/camera_action_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tracking_protection_view" />
<TextView
android:id="@+id/camera_action_label"
style="@style/QuickSettingsText.PermissionItemEnd"
<androidx.constraintlayout.widget.Group
android:id="@+id/websitePermissionsGroup"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tracking_protection_view"
tools:text="Allowed" />
<TextView
android:id="@+id/microphone_icon"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
android:drawableStart="@drawable/ic_microphone"
android:text="@string/preference_phone_feature_microphone"
app:layout_constraintEnd_toStartOf="@+id/microphone_action_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/camera_icon" />
<TextView
android:id="@+id/microphone_action_label"
style="@style/QuickSettingsText.PermissionItemEnd"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/camera_action_label"
tools:text="Blocked by Android" />
<TextView
android:id="@+id/notification_icon"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
android:drawableStart="@drawable/ic_notifications"
android:text="@string/preference_phone_feature_notification"
app:layout_constraintEnd_toStartOf="@+id/notification_action_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/microphone_icon" />
<TextView
android:id="@+id/notification_action_label"
style="@style/QuickSettingsText.PermissionItemEnd"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/microphone_action_label"
tools:text="Blocked" />
<TextView
android:id="@+id/location_icon"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
android:drawableStart="@drawable/ic_location"
android:text="@string/preference_phone_feature_location"
app:layout_constraintEnd_toStartOf="@+id/location_action_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/notification_icon" />
<TextView
android:id="@+id/location_action_label"
style="@style/QuickSettingsText.PermissionItemEnd"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/notification_action_label"
tools:text="Blocked" />
android:layout_height="wrap_content"
app:constraint_referenced_ids="websitePermissionsLayout,webSitePermissionsDivider" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/permissions_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/cameraActionLabel"
style="@style/QuickSettingsText.PermissionItemEnd"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintBottom_toTopOf="@id/microphoneActionLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/cameraIcon"
tools:text="Allowed" />
<TextView
android:id="@+id/cameraIcon"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
android:drawableStart="@drawable/ic_camera"
android:text="@string/preference_phone_feature_camera"
app:layout_constraintBottom_toTopOf="@id/microphoneIcon"
app:layout_constraintEnd_toStartOf="@id/cameraActionLabel"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/microphoneActionLabel"
style="@style/QuickSettingsText.PermissionItemEnd"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintBottom_toTopOf="@id/notificationActionLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/microphoneIcon"
tools:text="Blocked by Android" />
<TextView
android:id="@+id/microphoneIcon"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
android:drawableStart="@drawable/ic_microphone"
android:text="@string/preference_phone_feature_microphone"
app:layout_constraintBottom_toTopOf="@id/notificationIcon"
app:layout_constraintEnd_toStartOf="@id/microphoneActionLabel"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/notificationActionLabel"
style="@style/QuickSettingsText.PermissionItemEnd"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintBottom_toTopOf="@id/locationActionLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/notificationIcon"
tools:text="Blocked" />
<TextView
android:id="@+id/notificationIcon"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
android:drawableStart="@drawable/ic_notifications"
android:text="@string/preference_phone_feature_notification"
app:layout_constraintBottom_toTopOf="@id/locationIcon"
app:layout_constraintEnd_toStartOf="@id/notificationActionLabel"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/locationActionLabel"
style="@style/QuickSettingsText.PermissionItemEnd"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/locationIcon"
tools:text="Blocked" />
<TextView
android:id="@+id/locationIcon"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
android:drawableStart="@drawable/ic_location"
android:text="@string/preference_phone_feature_location"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/locationActionLabel"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tracking_protection_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Switch
android:id="@+id/trackingProtectionSwitch"
style="@style/QuickSettingsText.Icon"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
android:paddingEnd="24dp"
android:text="@string/preferences_tracking_protection"
app:layout_constraintBottom_toTopOf="@id/trackingProtectionAction"
app:layout_constraintTop_toTopOf="parent"
tools:drawableStart="@drawable/ic_tracking_protection" />
<TextView
android:id="@+id/trackingProtectionAction"
style="@style/QuickSettingsText.Icon"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
android:gravity="top"
android:paddingStart="48dp"
android:paddingEnd="0dp"
android:text="@string/preferences_tracking_protection_turned_off_globally"
android:textColor="?accentBright"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/reportSiteIssueAction" />
<TextView
android:id="@+id/reportSiteIssueAction"
style="@style/QuickSettingsText.Icon"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
android:gravity="top"
android:paddingStart="48dp"
android:paddingEnd="0dp"
android:text="@string/browser_menu_report_issue"
android:textColor="?accentUsedOnDarkBackground"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/website_info_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/url"
style="@style/QuickSettingsText"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="https://wikipedia.org" />
<TextView
android:id="@+id/securityInfo"
style="@style/QuickSettingsText.Icon"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/url"
tools:drawableStart="@drawable/mozac_ic_lock"
tools:drawableTint="@color/photonGreen50"
tools:text="Secure connection" />
</LinearLayout>