Closes #1170: Allow user to add a new site exception to site permissions
parent
36e9939d9e
commit
22eba72f8f
|
@ -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
|
|
@ -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<SessionFeature>()
|
||||
|
@ -88,9 +93,17 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
private val fullScreenFeature = ViewBoundFeatureWrapper<FullScreenFeature>()
|
||||
private val thumbnailsFeature = ViewBoundFeatureWrapper<ThumbnailsFeature>()
|
||||
private val customTabsIntegration = ViewBoundFeatureWrapper<CustomTabsIntegration>()
|
||||
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)))
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,31 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>)
|
|||
}
|
||||
}
|
||||
|
||||
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<out String>): PhoneFeature? {
|
||||
return PhoneFeature.values().find { feature ->
|
||||
|
|
|
@ -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<String>) : 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()
|
||||
}
|
||||
|
|
|
@ -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<QuickSettingsChange>()
|
||||
.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<QuickSettingsChange>()
|
||||
.onNext(QuickSettingsChange.Stored(it.featurePhone, sitePermissions))
|
||||
|
||||
requireContext().components.useCases.sessionUseCases.reload.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
getManagedEmitter<QuickSettingsChange>()
|
||||
.onNext(QuickSettingsChange.PromptRestarted)
|
||||
.onNext(QuickSettingsChange.PromptRestarted(sitePermissions))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PhoneFeature>()
|
||||
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<AppCompatTextView>(R.id.url)
|
||||
securityInfoLabel = view.findViewById<AppCompatTextView>(R.id.security_info)
|
||||
cameraActionLabel = view.findViewById<AppCompatTextView>(R.id.camera_action_label)
|
||||
cameraLabel = view.findViewById<AppCompatTextView>(R.id.camera_icon)
|
||||
microphoneActionLabel = view.findViewById<AppCompatTextView>(R.id.microphone_action_label)
|
||||
microphoneLabel = view.findViewById<AppCompatTextView>(R.id.microphone_icon)
|
||||
locationLabel = view.findViewById<AppCompatTextView>(R.id.location_icon)
|
||||
locationActionLabel = view.findViewById<AppCompatTextView>(R.id.location_action_label)
|
||||
notificationActionLabel = view.findViewById<AppCompatTextView>(R.id.notification_action_label)
|
||||
notificationLabel = view.findViewById<AppCompatTextView>(R.id.notification_icon)
|
||||
}
|
||||
|
||||
override fun updateView() = Consumer<QuickSettingsState> { 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<TextView>(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<TextView, TextView> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -121,16 +121,5 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/notification_action_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manage_site_permissions"
|
||||
style="@style/QuickSettingsText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/quicksettings_item_height"
|
||||
android:textColor="@color/photonBlue50"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:text="@string/quick_settings_sheet_manage_site_permissions"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/location_icon"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
@ -36,7 +36,10 @@
|
|||
<!-- Label that indicates that a permission must be asked always -->
|
||||
<string name="preference_option_phone_feature_ask_to_allow">Ask to allow</string>
|
||||
<!-- Label that indicates that a permission must be blocked -->
|
||||
<string name="preference_option_phone_feature_block">Block</string>
|
||||
<string name="preference_option_phone_feature_blocked">Blocked</string>
|
||||
<!-- Label that indicates that a permission must be allowed -->
|
||||
<string name="preference_option_phone_feature_allowed">Allowed</string>
|
||||
|
||||
<!--Label that indicates a permission is by the Android OS-->
|
||||
<string name="phone_feature_blocked_by_android">Blocked by Android</string>
|
||||
<!--Label that indicates that a user hasn't select a value for a site permission-->
|
||||
|
|
|
@ -252,5 +252,6 @@
|
|||
<item name="android:paddingEnd">24dp</item>
|
||||
<item name="android:gravity">end|center_vertical</item>
|
||||
<item name="android:background">?android:attr/selectableItemBackground</item>
|
||||
<item name="android:textColor">@color/photonBlue50</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue