1
0
Fork 0

For #4126 - Document the newly introduced public APIs

Now that the code should be in a ready-to-ship form it would be nice to have
all the major functionalities properly documented.
master
Mugurell 2019-10-16 20:16:21 +03:00 committed by Emily Kager
parent d70afcaa90
commit 9f56624788
8 changed files with 377 additions and 1 deletions

View File

@ -25,16 +25,79 @@ import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.toggle
import org.mozilla.fenix.utils.Settings
/**
* [QuickSettingsSheetDialogFragment] controller.
*
* Delegated by View Interactors, handles container business logic and operates changes on it,
* complex Android interactions or communication with other features.
*/
interface QuickSettingsController {
/**
* Handles turning on/off tracking protection.
*
* @param websiteUrl [String] the website URL for which to toggle tracking protection.
*/
fun handleTrackingProtectionToggled(websiteUrl: String, trackingEnabled: Boolean)
/**
* Handles showing the tracking protection settings.
*/
fun handleTrackingProtectionSettingsSelected()
/**
* Handles reporting a webcompat issue for the indicated website.
*
* @param websiteUrl [String] the URL of the web page for which to report a site issue.
*/
fun handleReportTrackingProblem(websiteUrl: String)
/**
* Handles the case of the [TrackingProtectionView] needed to be displayed to the user.
*/
fun handleTrackingProtectionShown()
/**
* Handles the case of the [WebsitePermissionsView] needed to be displayed to the user.
*/
fun handlePermissionsShown()
/**
* Handles toggling a [WebsitePermission].
*
* @param permission [WebsitePermission] needing to be toggled.
*/
fun handlePermissionToggled(permission: WebsitePermission)
/**
* Handles a certain set of Android permissions being explicitly granted by the user.
*
* feature [PhoneFeature] which the user granted Android permission(s) for.
*/
fun handleAndroidPermissionGranted(feature: PhoneFeature)
}
/**
* Default behavior of [QuickSettingsController]. Other implementations are possible.
*
* @param context [Context] used for various Android interactions.
* @param quickSettingsStore [QuickSettingsFragmentStore] holding the [State] for all Views displayed
* in this Controller's Fragment.
* @param coroutineScope [CoroutineScope] used for structed concurrency.
* @param navController NavController] used for navigation.
* @param session [Session]? current browser state.
* @param sitePermissions [SitePermissions]? list of website permissions and their status.
* @param settings [Settings] application settings.
* @param permissionStorage [PermissionStorage] app state for website permissions exception.
* @param trackingExceptions [ExceptionDomains] allows setting whether to allow trackers or not.
* @param reload [ReloadUrlUseCase] callback allowing for reloading the current web page.
* @param addNewTab [AddNewTabUseCase] callback allowing for loading a URL in a new tab.
* @param requestRuntimePermissions [OnNeedToRequestPermissions] callback allowing for requesting
* specific Android runtime permissions.
* @param reportSiteIssue callback allowing to report an issue with the current web page.
* @param displayTrackingProtection callback for when the [TrackingProtectionView] needs to be displayed.
* @param displayPermissions callback for when [WebsitePermissionsView] needs to be displayed.
* @param dismiss callback allowing to request this entire Fragment to be dismissed.
*/
@Suppress("TooManyFunctions")
class DefaultQuickSettingsController(
private val context: Context,
@ -127,10 +190,23 @@ class DefaultQuickSettingsController(
)
}
/**
* Request a certain set of runtime Android permissions.
*
* User's approval should be received in the [handleAndroidPermissionGranted] method but this is not enforced.
*
* @param requestedPermissions [Array]<[String]> runtime permissions needed to be requested.
*/
private fun handleAndroidPermissionRequest(requestedPermissions: Array<String>) {
requestRuntimePermissions(requestedPermissions)
}
/**
* Updates the list of [SitePermissions] for this current website and reloads it to allow / block
* new functionality in the web page.
*
* @param updatedPermissions [SitePermissions] updated website permissions.
*/
private fun handlePermissionsChange(updatedPermissions: SitePermissions) {
coroutineScope.launch(Dispatchers.IO) {
permissionStorage.updateSitePermissions(updatedPermissions)
@ -138,6 +214,11 @@ class DefaultQuickSettingsController(
}
}
/**
* Each [WebsitePermission] is mapped after a [PhoneFeature].
*
* Get this [WebsitePermission]'s [PhoneFeature].
*/
private fun WebsitePermission.getBackingFeature(): PhoneFeature = when (this) {
is WebsitePermission.Camera -> PhoneFeature.CAMERA
is WebsitePermission.Microphone -> PhoneFeature.MICROPHONE
@ -145,6 +226,12 @@ class DefaultQuickSettingsController(
is WebsitePermission.Location -> PhoneFeature.LOCATION
}
/**
* Get the specific [WebsitePermission] implementation which this [PhoneFeature] is tied to.
*
* **The result only informs about the type of [WebsitePermission].
* The resulting object's properties are just stubs and not dependable.**
*/
private fun PhoneFeature.getCorrespondingPermission(): WebsitePermission {
val defaultStatus = ""
val defaultEnabled = false

View File

@ -10,6 +10,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.Reducer
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.FeatureFlags
@ -19,6 +20,18 @@ import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeVisible
import org.mozilla.fenix.utils.Settings
/**
* [QuickSettingsSheetDialogFragment]'s unique [Store].
* Encompasses it's own:
* - [State] for all Views displayed in this Fragment.
* - [Action]s mapping a user / system interaction to an intention to modify the above State.
* - [Reducer]s for modifying the above State based on the above Actions.
*
* The [createStore] helper method can be used for creating one such [State] based on all current
* conditions of the app and web page visited.
*
* @param initialState [QuickSettingsFragmentState] that will be shown initially to the user.
*/
class QuickSettingsFragmentStore(
initialState: QuickSettingsFragmentState
) : Store<QuickSettingsFragmentState, QuickSettingsFragmentAction>(
@ -26,18 +39,37 @@ class QuickSettingsFragmentStore(
::quickSettingsFragmentReducer
) {
companion object {
/**
* String, Drawable & Drawable Tint color used to display that the current website connection is secured.
*/
private val getSecuredWebsiteUiValues = Triple(
R.string.quick_settings_sheet_secure_connection,
R.drawable.mozac_ic_lock,
R.color.photonGreen50
)
/**
* String, Drawable & Drawable Tint color used to display that the current website connection is
* **not** secured.
*/
private val getInsecureWebsiteUiValues = Triple(
R.string.quick_settings_sheet_insecure_connection,
R.drawable.mozac_ic_globe,
R.color.photonRed50
)
/**
* Construct an initial [QuickSettingsFragmentState] for all Views displayed by the
* [QuickSettingsSheetDialogFragment].
*
* @param context [Context] used for access to various Android resources.
* @param websiteUrl [String] the URL of the current web page.
* @param isSecured [Boolean] whether the connection is secured (TLS) or not.
* @param isTrackingProtectionOn [Boolean] whether the "Standard" (as in not "Strict")
* tracking protection is enabled for the current website or not.
* @param permissions [SitePermissions]? list of website permissions and their status.
* @param settings [Settings] application settings.
*/
@Suppress("LongParameterList")
fun createStore(
context: Context,
@ -54,6 +86,17 @@ class QuickSettingsFragmentStore(
)
)
/**
* Construct an initial [TrackingProtectionState] to be rendered by [TrackingProtectionView]
* based on the current state of the app / website.
*
* Users can modify the returned [TrackingProtectionState] after it is initially displayed.
*
* @param websiteUrl [String] the URL of the current web page.
* @param isTrackingProtectionOn [Boolean] whether the "Standard" (as in not "Strict")
* tracking protection is enabled for the current website or not.
* @param settings [Settings] application settings.
*/
private fun createTrackingProtectionState(
websiteUrl: String,
isTrackingProtectionOn: Boolean,
@ -65,6 +108,15 @@ class QuickSettingsFragmentStore(
isTrackingProtectionEnabledPerWebsite = isTrackingProtectionOn
)
/**
* Construct an initial [WebsiteInfoState] to be rendered by [WebsiteInfoView]
* based on the current website's status and connection.
*
* While being displayed users have no way of modifying it.
*
* @param websiteUrl [String] the URL of the current web page.
* @param isSecured [Boolean] whether the connection is secured (TLS) or not.
*/
private fun createWebsiteInfoState(
websiteUrl: String,
isSecured: Boolean
@ -76,6 +128,16 @@ class QuickSettingsFragmentStore(
return WebsiteInfoState(websiteUrl, stringRes, iconRes, colorRes)
}
/**
* Construct an initial [WebsitePermissionsState] to be rendered by [WebsitePermissionsView]
* containing the permissions requested by the current website.
*
* Users can modify the returned [WebsitePermissionsState] after it is initially displayed.
*
* @param context [Context] used for various Android interactions.
* @param permissions [SitePermissions]? list of website permissions and their status.
* @param settings [Settings] application settings.
*/
private fun createWebsitePermissionState(
context: Context,
permissions: SitePermissions?,
@ -93,6 +155,9 @@ class QuickSettingsFragmentStore(
)
}
/**
* [PhoneFeature] to a [WebsitePermission] mapper.
*/
private fun PhoneFeature.toWebsitePermission(
context: Context,
permissions: SitePermissions?,
@ -117,6 +182,9 @@ class QuickSettingsFragmentStore(
}
}
/**
* Helper method for getting the [WebsitePermission] properties based on a specific [PhoneFeature].
*/
private fun PhoneFeature.getPermissionStatus(
context: Context,
permissions: SitePermissions?,
@ -128,6 +196,9 @@ class QuickSettingsFragmentStore(
isBlockedByAndroid = !isAndroidPermissionGranted(context)
)
/**
* Helper class acting as a temporary container of [WebsitePermission] properties.
*/
private data class PermissionStatus(
val status: String,
val isVisible: Boolean,
@ -141,12 +212,27 @@ class QuickSettingsFragmentStore(
// States
// -------------------------------------------------------------------------------------------------
/**
* [State] containing all data displayed to the user by this Fragment.
*
* Partitioned further to contain mutiple states for each standalone View this Fragment holds.
*/
data class QuickSettingsFragmentState(
val trackingProtectionState: TrackingProtectionState,
val webInfoState: WebsiteInfoState,
val websitePermissionsState: WebsitePermissionsState
) : State
/**
* [State] to be rendered by [TrackingProtectionView] indicating the app is blocking certain tracking
* functionality or not.
*
* @param isVisible [Boolean] whether this contains data that needs to be displayed to the user.
* @param websiteUrl [String] the URL of the current web page.
* @param isTrackingProtectionEnabledPerApp [Boolean] whether tracking protection is on/off globally.
* @param isTrackingProtectionEnabledPerWebsite [Boolean] whether the "Standard" (as in not "Strict")
* tracking protection is enabled for the current website or not.
*/
data class TrackingProtectionState(
val isVisible: Boolean,
val websiteUrl: String,
@ -154,6 +240,14 @@ data class TrackingProtectionState(
val isTrackingProtectionEnabledPerWebsite: Boolean
) : State
/**
* [State] to be rendered by [WebsiteInfoView] indicating whether the connection is secure or not.
*
* @param websiteUrl [String] the URL of the current web page.
* @param securityInfoRes [StringRes] for the connection description.
* @param iconRes [DrawableRes] image indicating the connection status.
* @param iconTintRes [ColorRes] icon color.
*/
data class WebsiteInfoState(
val websiteUrl: String,
@StringRes val securityInfoRes: Int,
@ -161,6 +255,18 @@ data class WebsiteInfoState(
@ColorRes val iconTintRes: Int
) : State
/**
* /**
* [State] to be rendered by [WebsitePermissionsView] displaying all explicitly allowed or blocked
* website permissions.
*
* @param isVisible [Boolean] whether this contains data that needs to be displayed to the user.
* @param camera [WebsitePermission] containing all information about the *camera* permission.
* @param microphone [WebsitePermission] containing all information about the *microphone* permission.
* @param notification [notification] containing all information about the *notification* permission.
* @param location [WebsitePermission] containing all information about the *location* permission.
*/
*/
data class WebsitePermissionsState(
val isVisible: Boolean,
val camera: WebsitePermission,
@ -169,12 +275,37 @@ data class WebsitePermissionsState(
val location: WebsitePermission
) : State
/**
* Wrapper over a website permission encompassing all it's needed state to be rendered on the screen.
*
* Contains a limited number of implementations because there is a known, finite number of permissions
* we need to display to the user.
*/
sealed class WebsitePermission {
/**
* The *allowed* / *blocked* permission status to be shown to the user.
*/
abstract val status: String
/**
* Whether this permission should be shown to the user.
*/
abstract val isVisible: Boolean
/**
* Visual indication about whether this permission is *enabled* / *disabled*
*/
abstract val isEnabled: Boolean
/**
* Whether the corresponding *dangerous* Android permission is granted for the app by the user or not.
*/
abstract val isBlockedByAndroid: Boolean
/**
* Helper method mimicking the default generated *copy()* method for a data class.
* Allows us using a familiar API in the reducer.
*/
abstract fun copy(
status: String = this.status,
isVisible: Boolean = this.isVisible,
@ -182,6 +313,9 @@ sealed class WebsitePermission {
isBlockedByAndroid: Boolean = this.isBlockedByAndroid
): WebsitePermission
/**
* Contains all information about the *camera* permission.
*/
data class Camera(
override val status: String,
override val isVisible: Boolean,
@ -203,6 +337,9 @@ sealed class WebsitePermission {
)
}
/**
* Contains all information about the *microphone* permission.
*/
data class Microphone(
override val status: String,
override val isVisible: Boolean,
@ -224,6 +361,9 @@ sealed class WebsitePermission {
)
}
/**
* Contains all information about the *notification* permission.
*/
data class Notification(
override val status: String,
override val isVisible: Boolean,
@ -245,6 +385,9 @@ sealed class WebsitePermission {
)
}
/**
* Contains all information about the *location* permission.
*/
data class Location(
override val status: String,
override val isVisible: Boolean,
@ -271,15 +414,39 @@ sealed class WebsitePermission {
// Actions
// -------------------------------------------------------------------------------------------------
/**
* Parent [Action] for all the [QuickSettingsFragmentState] changes.
*/
sealed class QuickSettingsFragmentAction : Action
/**
* All possible [TrackingProtectionState] changes as result of user / system interactions.
*/
sealed class TrackingProtectionAction : QuickSettingsFragmentAction() {
/**
* Change resulting from toggling the tracking protection status for the current website.
*/
class TrackingProtectionToggled(val trackingEnabled: Boolean) : TrackingProtectionAction()
}
/**
* All possible [WebsiteInfoState] changes as result of user / system interactions.
*/
sealed class WebsiteInfoAction : QuickSettingsFragmentAction()
/**
* All possible [WebsitePermissionsState] changes as result of user / system interactions.
*/
sealed class WebsitePermissionAction : QuickSettingsFragmentAction() {
/**
* Change resulting from toggling a specific [WebsitePermission] for the current website.
*
* @param updatedFeature [PhoneFeature] backing a certain [WebsitePermission].
* Allows to easily identify which permission changed
* **Must be the name of one of the properties of [WebsitePermissionsState]**.
* @param updatedStatus [String] the new [WebsitePermission#status] which will be shown to the user.
* @param updatedEnabledStatus [Boolean] the new [WebsitePermission#enabled] which will be shown to the user.
*/
class TogglePermission(
val websitePermission: WebsitePermission,
val updatedStatus: String,
@ -291,6 +458,9 @@ sealed class WebsitePermissionAction : QuickSettingsFragmentAction() {
// Reducers
// -------------------------------------------------------------------------------------------------
/**
* Parent [Reducer] for all [QuickSettingsFragmentState]s of all Views shown in this Fragment.
*/
fun quickSettingsFragmentReducer(
state: QuickSettingsFragmentState,
action: QuickSettingsFragmentAction
@ -318,6 +488,9 @@ fun quickSettingsFragmentReducer(
}
object TrackingProtectionStateReducer {
/**
* Handles creating a new [TrackingProtectionState] based on the specific [TrackingProtectionAction]
*/
fun reduce(
state: TrackingProtectionState,
action: TrackingProtectionAction
@ -330,8 +503,11 @@ object TrackingProtectionStateReducer {
}
}
@Suppress("UNUSED_PARAMETER")
@Suppress("UNUSED_PARAMETER") // the action paramater is unused
object WebsiteInfoStateReducer {
/**
* Handles creating a new [WebsiteInfoState] based on the specific [WebsiteInfoAction]
*/
fun reduce(
state: WebsiteInfoState,
action: WebsiteInfoAction
@ -344,6 +520,9 @@ object WebsiteInfoStateReducer {
}
object WebsitePermissionsStateReducer {
/**
* Handles creating a new [WebsitePermissionsState] based on the specific [WebsitePermissionAction]
*/
fun reduce(
state: WebsitePermissionsState,
action: WebsitePermissionAction

View File

@ -4,6 +4,15 @@
package org.mozilla.fenix.settings.quicksettings
/**
* [QuickSettingsSheetDialogFragment] interactor.
*
* Implements callbacks for each of [QuickSettingsSheetDialogFragment]'s Views declared possible user interactions,
* delegates all such user events to the [QuickSettingsController].
*
* @param controller [QuickSettingsController] which will be delegated for all users interactions,
* it expected to contain all business logic for how to act in response.
*/
class QuickSettingsInteractor(
private val controller: QuickSettingsController
) : WebsitePermissionInteractor, TrackingProtectionInteractor {

View File

@ -36,6 +36,12 @@ import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.utils.Settings
import com.google.android.material.R as MaterialR
/**
* Dialog that presents the user with information about
* - the current website and whether the connection is secured or not.
* - website tracking protection.
* - website permission.
*/
class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
private lateinit var quickSettingsStore: QuickSettingsFragmentStore
private lateinit var quickSettingsController: QuickSettingsController

View File

@ -14,13 +14,38 @@ import kotlinx.android.synthetic.main.quicksettings_tracking_protection.*
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
/**
* Contract declaring all possible user interactions with [TrackingProtectionView]
*/
interface TrackingProtectionInteractor {
/**
* Indicates the user wants to report a problem with the current website.
*/
fun onReportProblemSelected(websiteUrl: String)
/**
* Indicates the user want to toggle the tracking protection on / off.
*/
fun onProtectionToggled(websiteUrl: String, trackingEnabled: Boolean)
/**
* Indicates the user want to see all tracking protection settings.
*/
fun onProtectionSettingsSelected()
/**
* Indicates the tracking protection status for the current website is shown to the user.
*/
fun onTrackingProtectionShown()
}
/**
* MVI View that knows to display a "normal" (as in not "enhanced") tracking protection panel
* containing the tracking protection status and also allowing on / off toggling.
*
* @param containerView [ViewGroup] in which this View will inflate itself.
* @param interactor [TrackingProtectionInteractor] which will have delegated to all user interactions.
*/
class TrackingProtectionView(
override val containerView: ViewGroup,
val interactor: TrackingProtectionInteractor
@ -38,6 +63,11 @@ class TrackingProtectionView(
)
}
/**
* Allows changing what this View displays.
*
* @param state [TrackingProtectionState] to be rendered.
*/
fun update(state: TrackingProtectionState) {
if (state.isVisible) {
interactor.onTrackingProtectionShown()

View File

@ -18,12 +18,24 @@ import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
/**
* MVI View that knows to display a whether the current website uses a secure connection or not.
*
* Currently it does not support any user interaction.
*
* @param containerView [ViewGroup] in which this View will inflate itself.
*/
class WebsiteInfoView(
override val containerView: ViewGroup
) : LayoutContainer {
val view: View = LayoutInflater.from(containerView.context)
.inflate(R.layout.quicksettings_website_info, containerView, true)
/**
* Allows changing what this View displays.
*
* @param state [WebsiteInfoState] to be rendered.
*/
fun update(state: WebsiteInfoState) {
bindUrl(state.websiteUrl)
bindSecurityInfo(state.securityInfoRes, state.iconRes, state.iconTintRes)

View File

@ -12,11 +12,34 @@ import androidx.core.view.isVisible
import kotlinx.android.extensions.LayoutContainer
import org.mozilla.fenix.R
/**
* Contract declaring all possible user interactions with [WebsitePermissionsView]
*/
interface WebsitePermissionInteractor {
/**
* Indicates there are website permissions allowed / blocked for the current website.
* which, status which is shown to the user.
*/
fun onPermissionsShown()
/**
* Indicates the user changed the status of a certain website permission.
*
* @param permissionState current [WebsitePermission] that the user wants toggled.
*/
fun onPermissionToggled(permissionState: WebsitePermission)
}
/**
* MVI View that knows to display a list of specific website permissions (hardcoded):
* - location
* - notification
* - microphone
* - camera
*
* @param containerView [ViewGroup] in which this View will inflate itself.
* @param interactor [WebsitePermissionInteractor] which will have delegated to all user interactions.
*/
class WebsitePermissionsView(
override val containerView: ViewGroup,
val interactor: WebsitePermissionInteractor
@ -26,6 +49,11 @@ class WebsitePermissionsView(
val view: View = LayoutInflater.from(context)
.inflate(R.layout.quicksettings_permissions, containerView, true)
/**
* Allows changing what this View displays.
*
* @param state [WebsitePermissionsState] to be rendered.
*/
fun update(state: WebsitePermissionsState) {
if (state.isVisible) {
interactor.onPermissionsShown()
@ -43,6 +71,13 @@ class WebsitePermissionsView(
Pair(view.findViewById(R.id.notificationLabel), view.findViewById(R.id.notificationStatus)))
}
/**
* Helper method that can map a specific website permission to a dedicated permission row
* which will display permission's [icon, label, status] and register user inputs.
*
* @param permissionState [WebsitePermission] specific permission that can be shown to the user.
* @param permissionViews Views that will render [WebsitePermission]'s state.
*/
private fun bindPermission(permissionState: WebsitePermission, permissionViews: Pair<TextView, TextView>) {
val (label, status) = permissionViews

View File

@ -9,17 +9,35 @@ import mozilla.components.feature.sitepermissions.SitePermissions
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.utils.Settings
/**
* Common [PhoneFeature] extensions used for **quicksettings**.
*
* Whether the website permission associated with this [PhoneFeature] should be shown to the user.
*/
fun PhoneFeature.shouldBeVisible(
sitePermissions: SitePermissions?,
settings: Settings
) = getStatus(sitePermissions, settings) != SitePermissions.Status.NO_DECISION
/**
* Common [PhoneFeature] extensions used for **quicksettings**.
*
* Whether the website permission associated with this [PhoneFeature] should allow user interaction.
*/
fun PhoneFeature.shouldBeEnabled(
context: Context,
sitePermissions: SitePermissions?,
settings: Settings
) = isAndroidPermissionGranted(context) && isUserPermissionGranted(sitePermissions, settings)
/**
* Common [PhoneFeature] extensions used for **quicksettings**.
*
* Whether the website permission associated with this [PhoneFeature] was specifically allowed by the user.
*
* To check whether the needed Android permission is also allowed [PhoneFeature#isAndroidPermissionGranted()]
* can be used.
*/
fun PhoneFeature.isUserPermissionGranted(
sitePermissions: SitePermissions?,
settings: Settings