[fenix] For https://github.com/mozilla-mobile/fenix/issues/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.pull/600/head
parent
b3d6bc6f7b
commit
8f07c6fbf0
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
@ -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>
|
Loading…
Reference in New Issue