diff --git a/CHANGELOG.md b/CHANGELOG.md index 590a9345e..4ab25504c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #1238 - Added the ability to edit bookmark folders - #1239 - Added the ability to move bookmark folders - #1068 - Adds the ability to quickly copy the URL by long clicking the URLBar +- #1170: Allow user to add a new site exception to site permissions ### Changed - #1429 - Updated site permissions ui for MVP ### Removed \ No newline at end of file diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index fef728250..937ac60ef 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -25,8 +25,10 @@ import kotlinx.android.synthetic.main.component_search.* import kotlinx.android.synthetic.main.fragment_browser.view.* import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.session.Session @@ -39,12 +41,14 @@ import mozilla.components.feature.session.FullScreenFeature import mozilla.components.feature.session.SessionFeature import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.ThumbnailsFeature +import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissionsFeature import mozilla.components.feature.sitepermissions.SitePermissionsRules import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.view.enterToImmersiveMode import mozilla.components.support.ktx.android.view.exitImmersiveModeIfNeeded +import mozilla.components.support.ktx.kotlin.toUri import org.mozilla.fenix.BrowsingModeManager import org.mozilla.fenix.DefaultThemeManager import org.mozilla.fenix.HomeActivity @@ -73,9 +77,10 @@ import org.mozilla.fenix.quickactionsheet.QuickActionComponent import org.mozilla.fenix.settings.quicksettings.QuickSettingsSheetDialogFragment import org.mozilla.fenix.utils.ItsNotBrokenSnack import org.mozilla.fenix.utils.Settings +import kotlin.coroutines.CoroutineContext @SuppressWarnings("TooManyFunctions", "LargeClass") -class BrowserFragment : Fragment(), BackHandler { +class BrowserFragment : Fragment(), BackHandler, CoroutineScope { private lateinit var toolbarComponent: ToolbarComponent private val sessionFeature = ViewBoundFeatureWrapper() @@ -88,9 +93,17 @@ class BrowserFragment : Fragment(), BackHandler { private val fullScreenFeature = ViewBoundFeatureWrapper() private val thumbnailsFeature = ViewBoundFeatureWrapper() private val customTabsIntegration = ViewBoundFeatureWrapper() + private lateinit var job: Job var sessionId: String? = null + override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + job = Job() + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -263,13 +276,7 @@ class BrowserFragment : Fragment(), BackHandler { ) } toolbarComponent.getView().setOnSiteSecurityClickedListener { - val session = getSessionByIdOrUseSelectedSession() - val quickSettingsSheet = QuickSettingsSheetDialogFragment.newInstance( - url = session.url, - isSecured = session.securityInfo.secure, - isSiteInExceptionList = false - ) - quickSettingsSheet.show(requireFragmentManager(), QuickSettingsSheetDialogFragment.FRAGMENT_TAG) + showQuickSettingsDialog() } } @@ -388,6 +395,11 @@ class BrowserFragment : Fragment(), BackHandler { promptsFeature.withFeature { it.onActivityResult(requestCode, resultCode, data) } } + override fun onDestroy() { + super.onDestroy() + job.cancel() + } + // This method triggers the complexity warning. However it's actually not that hard to understand. @SuppressWarnings("ComplexMethod") private fun trackToolbarItemInteraction(action: SearchAction.ToolbarMenuItemTapped) { @@ -472,6 +484,26 @@ class BrowserFragment : Fragment(), BackHandler { } } + private fun showQuickSettingsDialog() { + val session = getSessionByIdOrUseSelectedSession() + val host = requireNotNull(session.url.toUri().host) + + launch { + val storage = requireContext().components.storage + val sitePermissions: SitePermissions? = storage.findSitePermissionsBy(host) + + launch(Main) { + val quickSettingsSheet = QuickSettingsSheetDialogFragment.newInstance( + url = session.url, + isSecured = session.securityInfo.secure, + sitePermissions = sitePermissions + ) + quickSettingsSheet.sitePermissions = sitePermissions + quickSettingsSheet.show(requireFragmentManager(), QuickSettingsSheetDialogFragment.FRAGMENT_TAG) + } + } + } + private fun getSessionByIdOrUseSelectedSession(): Session { return if (sessionId != null) { requireNotNull(requireContext().components.core.sessionManager.findSessionById(requireNotNull(sessionId))) diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 7eb71a345..c28cadb3f 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -17,4 +17,5 @@ class Components(private val context: Context) { val useCases by lazy { UseCases(context, core.sessionManager, search.searchEngineManager) } val utils by lazy { Utilities(context, core.sessionManager, useCases.sessionUseCases, useCases.searchUseCases) } val analytics by lazy { Analytics(context) } + val storage by lazy { Storage(context) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/Storage.kt b/app/src/main/java/org/mozilla/fenix/components/Storage.kt new file mode 100644 index 000000000..38e17673f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/Storage.kt @@ -0,0 +1,44 @@ +/* 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.components + +import android.content.Context +import mozilla.components.feature.sitepermissions.SitePermissions +import mozilla.components.feature.sitepermissions.SitePermissions.Status +import mozilla.components.feature.sitepermissions.SitePermissionsStorage + +class Storage(private val context: Context) { + + private val permissionsStorage by lazy { + SitePermissionsStorage(context) + } + + fun addSitePermissionException( + origin: String, + location: Status, + notification: Status, + microphone: Status, + camera: Status + ): SitePermissions { + val sitePermissions = SitePermissions( + origin = origin, + location = location, + camera = camera, + microphone = microphone, + notification = notification, + savedAt = System.currentTimeMillis() + ) + permissionsStorage.save(sitePermissions) + return sitePermissions + } + + fun findSitePermissionsBy(origin: String): SitePermissions? { + return permissionsStorage.findSitePermissionsBy(origin) + } + + fun updateSitePermissions(sitePermissions: SitePermissions) { + permissionsStorage.update(sitePermissions) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt b/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt index a3c8c744a..fe7caffc0 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt @@ -15,7 +15,7 @@ internal fun SitePermissionsRules.Action.toString(context: Context): String { context.getString(R.string.preference_option_phone_feature_ask_to_allow) } SitePermissionsRules.Action.BLOCKED -> { - context.getString(R.string.preference_option_phone_feature_block) + context.getString(R.string.preference_option_phone_feature_blocked) } } } @@ -23,13 +23,53 @@ internal fun SitePermissionsRules.Action.toString(context: Context): String { internal fun SitePermissions.Status.toString(context: Context): String { return when (this) { SitePermissions.Status.BLOCKED -> { - context.getString(R.string.preference_option_phone_feature_block) + context.getString(R.string.preference_option_phone_feature_blocked) } SitePermissions.Status.NO_DECISION -> { context.getString(R.string.preference_option_phone_feature_ask_to_allow) } SitePermissions.Status.ALLOWED -> { - context.getString(R.string.phone_feature_no_decision) + context.getString(R.string.preference_option_phone_feature_allowed) + } + } +} + +fun SitePermissionsRules.Action.toStatus(): SitePermissions.Status { + return when (this) { + SitePermissionsRules.Action.BLOCKED -> SitePermissions.Status.BLOCKED + SitePermissionsRules.Action.ASK_TO_ALLOW -> SitePermissions.Status.NO_DECISION + } +} + +fun SitePermissions.Status.toggle(): SitePermissions.Status { + return when (this) { + SitePermissions.Status.BLOCKED -> SitePermissions.Status.ALLOWED + SitePermissions.Status.NO_DECISION -> SitePermissions.Status.ALLOWED + SitePermissions.Status.ALLOWED -> SitePermissions.Status.BLOCKED + } +} + +fun SitePermissions.toggle(featurePhone: PhoneFeature): SitePermissions { + return when (featurePhone) { + PhoneFeature.CAMERA -> { + copy( + camera = camera.toggle() + ) + } + PhoneFeature.LOCATION -> { + copy( + location = location.toggle() + ) + } + PhoneFeature.MICROPHONE -> { + copy( + microphone = microphone.toggle() + ) + } + PhoneFeature.NOTIFICATION -> { + copy( + notification = notification.toggle() + ) } } } 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 44f8efda8..544604b77 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt @@ -58,6 +58,31 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array) } } + fun getStatus(sitePermissions: SitePermissions? = null, settings: Settings): SitePermissions.Status { + return when (this) { + CAMERA -> { + sitePermissions?.camera ?: settings + .getSitePermissionsPhoneFeatureCameraAction() + .toStatus() + } + LOCATION -> { + sitePermissions?.location ?: settings + .getSitePermissionsPhoneFeatureLocation() + .toStatus() + } + MICROPHONE -> { + sitePermissions?.microphone ?: settings + .getSitePermissionsPhoneFeatureMicrophoneAction() + .toStatus() + } + NOTIFICATION -> { + sitePermissions?.notification ?: settings + .getSitePermissionsPhoneFeatureNotificationAction() + .toStatus() + } + } + } + companion object { fun findFeatureBy(permissions: Array): PhoneFeature? { return PhoneFeature.values().find { feature -> 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 index 312b50821..5fa04c30d 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt @@ -4,7 +4,11 @@ package org.mozilla.fenix.settings.quicksettings +import android.content.Context import android.view.ViewGroup +import mozilla.components.feature.sitepermissions.SitePermissions +import mozilla.components.support.ktx.kotlin.toUri +import org.mozilla.fenix.ext.components import org.mozilla.fenix.mvi.Action import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.Change @@ -12,6 +16,9 @@ import org.mozilla.fenix.mvi.UIComponent import org.mozilla.fenix.mvi.UIView import org.mozilla.fenix.mvi.ViewState import org.mozilla.fenix.settings.PhoneFeature +import org.mozilla.fenix.settings.toStatus +import org.mozilla.fenix.settings.toggle +import org.mozilla.fenix.utils.Settings class QuickSettingsComponent( private val container: ViewGroup, @@ -28,18 +35,23 @@ class QuickSettingsComponent( mode = QuickSettingsState.Mode.Normal( change.url, change.isSecured, - change.isSiteInExceptionList + change.sitePermissions ) ) } is QuickSettingsChange.PermissionGranted -> { state.copy( - mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature) + mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature, change.sitePermissions) ) } - QuickSettingsChange.PromptRestarted -> { + is QuickSettingsChange.PromptRestarted -> { state.copy( - mode = QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid + mode = QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid(change.sitePermissions) + ) + } + is QuickSettingsChange.Stored -> { + state.copy( + mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature, change.sitePermissions) ) } } @@ -52,28 +64,62 @@ class QuickSettingsComponent( init { render(reducer) } + + fun toggleSitePermission( + context: Context, + featurePhone: PhoneFeature, + url: String, + sitePermissions: SitePermissions? + ): SitePermissions { + + return if (sitePermissions == null) { + val settings = Settings.getInstance(context) + val origin = requireNotNull(url.toUri().host) + var location = settings.getSitePermissionsPhoneFeatureLocation().toStatus() + var camera = settings.getSitePermissionsPhoneFeatureCameraAction().toStatus() + var microphone = settings.getSitePermissionsPhoneFeatureMicrophoneAction().toStatus() + var notification = settings.getSitePermissionsPhoneFeatureNotificationAction().toStatus() + + when (featurePhone) { + PhoneFeature.CAMERA -> camera = camera.toggle() + PhoneFeature.LOCATION -> location = location.toggle() + PhoneFeature.MICROPHONE -> microphone = microphone.toggle() + PhoneFeature.NOTIFICATION -> notification = notification.toggle() + } + context.components.storage.addSitePermissionException(origin, location, camera, microphone, notification) + } else { + val updatedSitePermissions = sitePermissions.toggle(featurePhone) + context.components.storage.updateSitePermissions(updatedSitePermissions) + updatedSitePermissions + } + } } data class QuickSettingsState(val mode: Mode) : ViewState { sealed class Mode { - data class Normal(val url: String, val isSecured: Boolean, val isSiteInExceptionList: Boolean) : Mode() - data class ActionLabelUpdated(val phoneFeature: PhoneFeature) : Mode() - object CheckPendingFeatureBlockedByAndroid : Mode() + data class Normal(val url: String, val isSecured: 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 SelectBlockedByAndroid(val permissions: Array) : QuickSettingsAction() - object DismissDialog : QuickSettingsAction() + data class TogglePermission(val featurePhone: PhoneFeature) : QuickSettingsAction() } sealed class QuickSettingsChange : Change { data class Change( val url: String, val isSecured: Boolean, - val isSiteInExceptionList: Boolean + val sitePermissions: SitePermissions? ) : QuickSettingsChange() - data class PermissionGranted(val phoneFeature: PhoneFeature) : QuickSettingsChange() - object PromptRestarted : 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() } 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 76e56a834..28a9620d5 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 @@ -11,24 +11,44 @@ import android.view.View import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import mozilla.components.feature.sitepermissions.SitePermissions import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components 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 kotlin.coroutines.CoroutineContext private const val KEY_URL = "KEY_URL" private const val KEY_IS_SECURED = "KEY_IS_SECURED" -private const val KEY_IS_SITE_IN_EXCEPTION_LIST = "KEY_IS_SITE_IN_EXCEPTION_LIST" +private const val KEY_SITE_PERMISSIONS = "KEY_SITE_PERMISSIONS" private const val REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS = 4 @SuppressWarnings("TooManyFunctions") -class QuickSettingsSheetDialogFragment : BottomSheetDialogFragment() { +class QuickSettingsSheetDialogFragment : BottomSheetDialogFragment(), CoroutineScope { private val safeArguments get() = requireNotNull(arguments) private val url: String by lazy { safeArguments.getString(KEY_URL) } private val isSecured: Boolean by lazy { safeArguments.getBoolean(KEY_IS_SECURED) } - private val isSiteInExceptionList: Boolean by lazy { safeArguments.getBoolean(KEY_IS_SITE_IN_EXCEPTION_LIST) } private lateinit var quickSettingsComponent: QuickSettingsComponent + private lateinit var job: Job + + var sitePermissions: SitePermissions? + get() = safeArguments.getParcelable(KEY_SITE_PERMISSIONS) + set(value) { + safeArguments.putParcelable(KEY_SITE_PERMISSIONS, value) + } + + override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + job = Job() + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_quick_settings_dialog_sheet, container, false) @@ -39,7 +59,7 @@ class QuickSettingsSheetDialogFragment : BottomSheetDialogFragment() { quickSettingsComponent = QuickSettingsComponent( rootView as ConstraintLayout, ActionBusFactory.get(this), QuickSettingsState( - QuickSettingsState.Mode.Normal(url, isSecured, isSiteInExceptionList) + QuickSettingsState.Mode.Normal(url, isSecured, sitePermissions) ) ) } @@ -50,7 +70,7 @@ class QuickSettingsSheetDialogFragment : BottomSheetDialogFragment() { fun newInstance( url: String, isSecured: Boolean, - isSiteInExceptionList: Boolean + sitePermissions: SitePermissions? ): QuickSettingsSheetDialogFragment { val fragment = QuickSettingsSheetDialogFragment() @@ -59,7 +79,7 @@ class QuickSettingsSheetDialogFragment : BottomSheetDialogFragment() { with(arguments) { putString(KEY_URL, url) putBoolean(KEY_IS_SECURED, isSecured) - putBoolean(KEY_IS_SITE_IN_EXCEPTION_LIST, isSiteInExceptionList) + putParcelable(KEY_SITE_PERMISSIONS, sitePermissions) } fragment.arguments = arguments return fragment @@ -70,10 +90,15 @@ class QuickSettingsSheetDialogFragment : BottomSheetDialogFragment() { if (arePermissionsGranted(requestCode, grantResults)) { val feature = requireNotNull(PhoneFeature.findFeatureBy(permissions)) getManagedEmitter() - .onNext(QuickSettingsChange.PermissionGranted(feature)) + .onNext(QuickSettingsChange.PermissionGranted(feature, sitePermissions)) } } + override fun onDestroy() { + super.onDestroy() + job.cancel() + } + private fun arePermissionsGranted(requestCode: Int, grantResults: IntArray) = requestCode == REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS && grantResults.all { it == PERMISSION_GRANTED } @@ -85,13 +110,30 @@ class QuickSettingsSheetDialogFragment : BottomSheetDialogFragment() { is QuickSettingsAction.SelectBlockedByAndroid -> { requestPermissions(it.permissions, REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS) } - is QuickSettingsAction.DismissDialog -> dismiss() + is QuickSettingsAction.TogglePermission -> { + + launch { + 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) + .onNext(QuickSettingsChange.PromptRestarted(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 index ecb99e3ee..7ba7a9ab1 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsUIView.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsUIView.kt @@ -4,8 +4,9 @@ package org.mozilla.fenix.settings.quicksettings -import android.util.TypedValue import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.content.res.AppCompatResources @@ -14,9 +15,10 @@ import androidx.core.content.ContextCompat import io.reactivex.Observable import io.reactivex.Observer import io.reactivex.functions.Consumer +import mozilla.components.feature.sitepermissions.SitePermissions +import mozilla.components.feature.sitepermissions.SitePermissions.Status.NO_DECISION import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes import mozilla.components.support.ktx.kotlin.toUri -import org.jetbrains.anko.textColorResource import org.mozilla.fenix.R import org.mozilla.fenix.mvi.UIView import org.mozilla.fenix.settings.PhoneFeature @@ -24,7 +26,6 @@ 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.ItsNotBrokenSnack import org.mozilla.fenix.utils.Settings class QuickSettingsUIView( @@ -38,26 +39,28 @@ class QuickSettingsUIView( private val securityInfoLabel: TextView private val urlLabel: TextView private val cameraActionLabel: TextView + private val cameraLabel: TextView private val microphoneActionLabel: TextView + private val microphoneLabel: TextView private val locationActionLabel: TextView + private val locationLabel: TextView private val notificationActionLabel: TextView + private val notificationLabel: TextView private val blockedByAndroidPhoneFeatures = mutableListOf() private val context get() = view.context private val settings: Settings = Settings.getInstance(context) - private val toolbarTextColorId by lazy { - val typedValue = TypedValue() - context.theme.resolveAttribute(R.attr.toolbarTextColor, typedValue, true) - typedValue.resourceId - } - init { urlLabel = view.findViewById(R.id.url) securityInfoLabel = view.findViewById(R.id.security_info) cameraActionLabel = view.findViewById(R.id.camera_action_label) + cameraLabel = view.findViewById(R.id.camera_icon) microphoneActionLabel = view.findViewById(R.id.microphone_action_label) + microphoneLabel = view.findViewById(R.id.microphone_icon) + locationLabel = view.findViewById(R.id.location_icon) locationActionLabel = view.findViewById(R.id.location_action_label) notificationActionLabel = view.findViewById(R.id.notification_action_label) + notificationLabel = view.findViewById(R.id.notification_icon) } override fun updateView() = Consumer { state -> @@ -65,20 +68,20 @@ class QuickSettingsUIView( is QuickSettingsState.Mode.Normal -> { bindUrl(state.mode.url) bindSecurityInfo(state.mode.isSecured) - bindPhoneFeatureItem(cameraActionLabel, CAMERA) - bindPhoneFeatureItem(microphoneActionLabel, MICROPHONE) - bindPhoneFeatureItem(notificationActionLabel, NOTIFICATION) - bindPhoneFeatureItem(locationActionLabel, LOCATION) - bindManagePermissionsButton() + bindPhoneFeatureItem(cameraActionLabel, CAMERA, state.mode.sitePermissions) + bindPhoneFeatureItem(microphoneActionLabel, MICROPHONE, state.mode.sitePermissions) + bindPhoneFeatureItem(notificationActionLabel, NOTIFICATION, state.mode.sitePermissions) + bindPhoneFeatureItem(locationActionLabel, LOCATION, state.mode.sitePermissions) } is QuickSettingsState.Mode.ActionLabelUpdated -> { bindPhoneFeatureItem( - state.mode.phoneFeature.actionLabel, - state.mode.phoneFeature + state.mode.phoneFeature.labelAndAction.second, + state.mode.phoneFeature, + state.mode.sitePermissions ) } is QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid -> { - checkFeaturesBlockedByAndroid() + checkFeaturesBlockedByAndroid(state.mode.sitePermissions) } } } @@ -108,17 +111,41 @@ class QuickSettingsUIView( securityInfoLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) } - private fun bindPhoneFeatureItem(actionLabel: TextView, phoneFeature: PhoneFeature) { + private fun bindPhoneFeatureItem( + actionLabel: TextView, + phoneFeature: PhoneFeature, + sitePermissions: SitePermissions? = null + ) { + if (phoneFeature.shouldBeHidden(sitePermissions)) { + hide(phoneFeature) + return + } + show(phoneFeature) if (!phoneFeature.isAndroidPermissionGranted(context)) { handleBlockedByAndroidAction(actionLabel, phoneFeature) } else { - bindPhoneAction(actionLabel, phoneFeature) + bindPhoneAction(actionLabel, phoneFeature, sitePermissions) } } + private fun show(phoneFeature: PhoneFeature) { + val (label, action) = phoneFeature.labelAndAction + label.visibility = VISIBLE + action.visibility = VISIBLE + } + + private fun hide(phoneFeature: PhoneFeature) { + val (label, action) = phoneFeature.labelAndAction + label.visibility = GONE + action.visibility = GONE + } + + private fun PhoneFeature.shouldBeHidden(sitePermissions: SitePermissions?): Boolean { + return getStatus(sitePermissions, settings) == NO_DECISION + } + private fun handleBlockedByAndroidAction(actionLabel: TextView, phoneFeature: PhoneFeature) { actionLabel.setText(R.string.phone_feature_blocked_by_android) - actionLabel.setTextColor(ContextCompat.getColor(context, R.color.photonBlue50)) actionLabel.tag = phoneFeature actionLabel.setOnClickListener { val feature = it.tag as PhoneFeature @@ -131,38 +158,44 @@ class QuickSettingsUIView( blockedByAndroidPhoneFeatures.add(phoneFeature) } - private fun bindPhoneAction(actionLabel: TextView, phoneFeature: PhoneFeature) { - actionLabel.text = phoneFeature.getActionLabel(context = context, settings = settings) - actionLabel.textColorResource = toolbarTextColorId - actionLabel.isEnabled = false + private fun bindPhoneAction( + actionLabel: TextView, + phoneFeature: PhoneFeature, + sitePermissions: SitePermissions? = null + ) { + actionLabel.text = phoneFeature.getActionLabel( + context = context, + sitePermissions = sitePermissions, + settings = settings + ) + + actionLabel.tag = phoneFeature + actionLabel.setOnClickListener { + val feature = it.tag as PhoneFeature + actionEmitter.onNext( + QuickSettingsAction.TogglePermission(feature) + ) + } blockedByAndroidPhoneFeatures.remove(phoneFeature) } - private fun bindManagePermissionsButton() { - val urlLabel = view.findViewById(R.id.manage_site_permissions) - urlLabel.setOnClickListener { - actionEmitter.onNext(QuickSettingsAction.DismissDialog) - ItsNotBrokenSnack(context).showSnackbar(issueNumber = "1170") - } - } - - private fun checkFeaturesBlockedByAndroid() { + private fun checkFeaturesBlockedByAndroid(sitePermissions: SitePermissions?) { val clonedList = blockedByAndroidPhoneFeatures.toTypedArray() clonedList.forEach { phoneFeature -> if (phoneFeature.isAndroidPermissionGranted(context)) { - val actionLabel = phoneFeature.actionLabel - bindPhoneAction(actionLabel, phoneFeature) + val actionLabel = phoneFeature.labelAndAction.second + bindPhoneAction(actionLabel, phoneFeature, sitePermissions) } } } - private val PhoneFeature.actionLabel - get(): TextView { + private val PhoneFeature.labelAndAction + get(): Pair { return when (this) { - CAMERA -> cameraActionLabel - LOCATION -> locationActionLabel - MICROPHONE -> microphoneActionLabel - NOTIFICATION -> notificationActionLabel + CAMERA -> cameraLabel to cameraActionLabel + LOCATION -> locationLabel to locationActionLabel + MICROPHONE -> microphoneLabel to microphoneActionLabel + NOTIFICATION -> notificationLabel to notificationActionLabel } } } diff --git a/app/src/main/res/layout/fragment_manage_site_permissions_feature_phone.xml b/app/src/main/res/layout/fragment_manage_site_permissions_feature_phone.xml index fb2aa80ad..a105e8496 100644 --- a/app/src/main/res/layout/fragment_manage_site_permissions_feature_phone.xml +++ b/app/src/main/res/layout/fragment_manage_site_permissions_feature_phone.xml @@ -34,7 +34,7 @@ android:id="@+id/block_radio" android:layout_width="match_parent" android:layout_height="@dimen/radio_button_preference_height" - android:text="@string/preference_option_phone_feature_block" + android:text="@string/preference_option_phone_feature_blocked" android:textAppearance="?android:attr/textAppearanceListItem" android:background="?android:attr/selectableItemBackground" android:button="@null" diff --git a/app/src/main/res/layout/fragment_quick_settings_dialog_sheet.xml b/app/src/main/res/layout/fragment_quick_settings_dialog_sheet.xml index 9ac01da90..752ca7de0 100644 --- a/app/src/main/res/layout/fragment_quick_settings_dialog_sheet.xml +++ b/app/src/main/res/layout/fragment_quick_settings_dialog_sheet.xml @@ -121,16 +121,5 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/notification_action_label"/> - - diff --git a/app/src/main/res/values/permissions_settings_strings.xml b/app/src/main/res/values/permissions_settings_strings.xml index 30e2c74e2..c6e2f33a7 100644 --- a/app/src/main/res/values/permissions_settings_strings.xml +++ b/app/src/main/res/values/permissions_settings_strings.xml @@ -36,7 +36,10 @@ Ask to allow - Block + Blocked + + Allowed + Blocked by Android diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index cc04b00a4..91e13939e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -252,5 +252,6 @@ 24dp end|center_vertical ?android:attr/selectableItemBackground + @color/photonBlue50