diff --git a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt index 54383b4dc..eb2fd95c5 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt @@ -37,25 +37,29 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array) } } + @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 + } } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt deleted file mode 100644 index b317f4085..000000000 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt +++ /dev/null @@ -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 -) : UIComponent( - bus.getManagedEmitter(QuickSettingsAction::class.java), - bus.getSafeManagedObservable(QuickSettingsChange::class.java), - viewModelProvider -) { - override fun initView(): UIView { - 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) : 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(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 - ) - ) - } - } - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt index fd69fedcf..0ee6595a1 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsSheetDialogFragment.kt @@ -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, - grantResults: IntArray - ) { - if (arePermissionsGranted(requestCode, grantResults)) { - val feature = requireNotNull(PhoneFeature.findFeatureBy(permissions)) - getManagedEmitter() - .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() - .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().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() - .onNext( - QuickSettingsChange.Stored( - it.featurePhone, - sitePermissions - ) - ) - - requireContext().components.useCases.sessionUseCases.reload.invoke() - } - } - } - } - } - - if (isVisible) { - getManagedEmitter() - .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().onNext( - QuickSettingsChange.Change( - url, - session.securityInfo.secure, - session.trackerBlockingEnabled, - sitePermissions - ) - ) - } - } - } - - override fun onTrackerBlockingEnabledChanged(session: Session, blockingEnabled: Boolean) { - getManagedEmitter().onNext( - QuickSettingsChange.Change( - session.url, - session.securityInfo.secure, - blockingEnabled, - sitePermissions - ) - ) - } - - override fun onSecurityChanged(session: Session, securityInfo: Session.SecurityInfo) { - getManagedEmitter().onNext( - QuickSettingsChange.Change( - session.url, - securityInfo.secure, - session.trackerBlockingEnabled, - sitePermissions - ) - ) - } - } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsUIView.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsUIView.kt deleted file mode 100644 index f45a8cc2b..000000000 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsUIView.kt +++ /dev/null @@ -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 - -@Suppress("TooManyFunctions") -class QuickSettingsUIView( - container: ViewGroup, - actionEmitter: Observer, - changesObservable: Observable, - override val view: View -) : UIView( - container, actionEmitter, changesObservable -) { - private val blockedByAndroidPhoneFeatures = mutableListOf() - 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 { 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(labelId) to view.findViewById(actionId) - } -} diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionSettingView.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionSettingView.kt deleted file mode 100644 index 9ec8f9b5a..000000000 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionSettingView.kt +++ /dev/null @@ -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 -) : 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) - ) - } -} diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionStore.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionStore.kt new file mode 100644 index 000000000..6cef214b0 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionStore.kt @@ -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( + 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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionView.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionView.kt new file mode 100644 index 000000000..9d6bf478f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionView.kt @@ -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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsiteInfoStore.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsiteInfoStore.kt new file mode 100644 index 000000000..bc16c84b1 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsiteInfoStore.kt @@ -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( + 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 +) diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsiteInfoView.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsiteInfoView.kt new file mode 100644 index 000000000..d9d7c0bcd --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsiteInfoView.kt @@ -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) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsitePermissionsStore.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsitePermissionsStore.kt new file mode 100644 index 000000000..328d7cd44 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsitePermissionsStore.kt @@ -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( + 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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsitePermissionsView.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsitePermissionsView.kt new file mode 100644 index 000000000..3534e3cfa --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/WebsitePermissionsView.kt @@ -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) { + val (icon, status) = permissionViews + + status.text = permissionState.status + status.isEnabled = permissionState.enabled + icon.isVisible = permissionState.visible + status.isVisible = permissionState.visible + } +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt index 70b3ebaec..44e1cddfa 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt @@ -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) } } diff --git a/app/src/main/res/layout/component_tracking_protection_panel.xml b/app/src/main/res/layout/component_tracking_protection_panel.xml index 7d4f99640..24abffc91 100644 --- a/app/src/main/res/layout/component_tracking_protection_panel.xml +++ b/app/src/main/res/layout/component_tracking_protection_panel.xml @@ -2,7 +2,9 @@ - + app:layout_constraintTop_toBottomOf="@id/trackingProtectionSwitch" /> - @@ -17,174 +17,52 @@ android:background="?foundation" android:contentDescription="@string/quick_settings_sheet"> - + - + + + + app:layout_constraintBottom_toTopOf="@id/websitePermissionsLayout" /> - + + + app:constraint_referenced_ids="trackingProtectionLayout,trackingProtectionDivider" /> - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content" + app:constraint_referenced_ids="websitePermissionsLayout,webSitePermissionsDivider" /> diff --git a/app/src/main/res/layout/quicksettings_permissions.xml b/app/src/main/res/layout/quicksettings_permissions.xml new file mode 100644 index 000000000..d4419a2fb --- /dev/null +++ b/app/src/main/res/layout/quicksettings_permissions.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/quicksettings_tracking_protection.xml b/app/src/main/res/layout/quicksettings_tracking_protection.xml new file mode 100644 index 000000000..7c881ad43 --- /dev/null +++ b/app/src/main/res/layout/quicksettings_tracking_protection.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/quicksettings_website_info.xml b/app/src/main/res/layout/quicksettings_website_info.xml new file mode 100644 index 000000000..8e48fd1bd --- /dev/null +++ b/app/src/main/res/layout/quicksettings_website_info.xml @@ -0,0 +1,36 @@ + + + + + + + + +