This repository has been archived by the owner on Nov 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 473
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This upstreams the CFR composable already used on Fenix allowing it to be reused on other projects also. The setup process requires quite a few parameters because as it is highly customizable supporting different indicator orientations or positionings in relation to the anchor and also supporting RTL.
- Loading branch information
1 parent
45e99f5
commit 96444dd
Showing
19 changed files
with
1,467 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# [Android Components](../../../README.md) > Compose > Tabs tray | ||
|
||
A standard Contextual Feature Recommendation popup using Jetpack Compose. | ||
|
||
## Usage | ||
|
||
```kotlin | ||
CFRPopup( | ||
anchor = <View>, | ||
properties = CFRPopupProperties( | ||
popupWidth = 256.dp, | ||
popupAlignment = INDICATOR_CENTERED_IN_ANCHOR, | ||
popupBodyColors = listOf( | ||
ContextCompat.getColor(context, R.color.color1), | ||
ContextCompat.getColor(context, R.color.color2) | ||
), | ||
dismissButtonColor = ContextCompat.getColor(context, R.color.color3), | ||
), | ||
onDismiss = { <method call> }, | ||
text = { | ||
Text( | ||
text = stringResource(R.string.string1), | ||
style = MaterialTheme.typography.body2, | ||
) | ||
}, | ||
action = { | ||
Button(onClick = { <method call> }) { | ||
Text(text = stringResource(R.string.string2)) | ||
} | ||
}, | ||
).apply { | ||
show() | ||
} | ||
``` | ||
|
||
|
||
### Setting up the dependency | ||
|
||
Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)): | ||
|
||
```Groovy | ||
implementation "org.mozilla.components:compose-cfr:{latest-version}" | ||
``` | ||
|
||
## License | ||
|
||
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* 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/. */ | ||
|
||
apply plugin: 'com.android.library' | ||
apply plugin: 'kotlin-android' | ||
|
||
android { | ||
compileSdkVersion config.compileSdkVersion | ||
|
||
defaultConfig { | ||
minSdkVersion config.minSdkVersion | ||
targetSdkVersion config.targetSdkVersion | ||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||
} | ||
|
||
buildTypes { | ||
release { | ||
minifyEnabled false | ||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
} | ||
} | ||
|
||
buildFeatures { | ||
compose true | ||
} | ||
|
||
composeOptions { | ||
kotlinCompilerExtensionVersion = Versions.compose_compiler | ||
} | ||
|
||
kotlinOptions { | ||
freeCompilerArgs += "-Xjvm-default=all" | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation project(':support-ktx') | ||
implementation project(':ui-icons') | ||
|
||
implementation Dependencies.androidx_compose_ui | ||
implementation Dependencies.androidx_compose_ui_tooling | ||
implementation Dependencies.androidx_compose_foundation | ||
implementation Dependencies.androidx_compose_material | ||
implementation Dependencies.androidx_core | ||
implementation Dependencies.androidx_core_ktx | ||
implementation Dependencies.androidx_lifecycle_runtime | ||
implementation Dependencies.androidx_savedstate | ||
|
||
testImplementation project(':support-test') | ||
testImplementation Dependencies.androidx_test_core | ||
testImplementation Dependencies.androidx_test_junit | ||
testImplementation Dependencies.testing_junit | ||
testImplementation Dependencies.testing_mockito | ||
testImplementation Dependencies.testing_robolectric | ||
} | ||
|
||
apply from: '../../../publish.gradle' | ||
ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Add project specific ProGuard rules here. | ||
# You can control the set of applied configuration files using the | ||
# proguardFiles setting in build.gradle. | ||
# | ||
# For more details, see | ||
# http://developer.android.com/guide/developing/tools/proguard.html | ||
|
||
# If your project uses WebView with JS, uncomment the following | ||
# and specify the fully qualified class name to the JavaScript interface | ||
# class: | ||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
# public *; | ||
#} | ||
|
||
# Uncomment this to preserve the line number information for | ||
# debugging stack traces. | ||
#-keepattributes SourceFile,LineNumberTable | ||
|
||
# If you keep the line number information, uncomment this to | ||
# hide the original source file name. | ||
#-renamesourcefileattribute SourceFile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<!-- 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/. --> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="mozilla.components.compose.cfr" /> |
166 changes: 166 additions & 0 deletions
166
components/compose/cfr/src/main/java/mozilla/components/compose/cfr/CFRPopup.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
/* 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 mozilla.components.compose.cfr | ||
|
||
import android.view.View | ||
import androidx.annotation.VisibleForTesting | ||
import androidx.compose.material.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.toArgb | ||
import androidx.compose.ui.unit.Dp | ||
import androidx.compose.ui.unit.dp | ||
import mozilla.components.compose.cfr.CFRPopup.IndicatorDirection | ||
import mozilla.components.compose.cfr.CFRPopup.PopupAlignment | ||
import java.lang.ref.WeakReference | ||
|
||
/** | ||
* Properties used to customize the behavior of a [CFRPopup]. | ||
* | ||
* @property popupWidth Width of the popup. Defaults to [CFRPopup.DEFAULT_WIDTH]. | ||
* @property popupAlignment Where in relation to it's anchor should the popup be placed. | ||
* @property popupBodyColors One or more colors serving as the popup background. | ||
* If more colors are provided they will be used in a gradient. | ||
* @property popupVerticalOffset Vertical distance between the indicator arrow and the anchor. | ||
* This only applies if [overlapAnchor] is `false`. | ||
* @property dismissButtonColor The tint color that should be applied to the dismiss button. | ||
* @property dismissOnBackPress Whether the popup can be dismissed by pressing the back button. | ||
* If true, pressing the back button will also call onDismiss(). | ||
* @property dismissOnClickOutside Whether the popup can be dismissed by clicking outside the | ||
* popup's bounds. If true, clicking outside the popup will call onDismiss(). | ||
* @property overlapAnchor How the popup's indicator will be shown in relation to the anchor: | ||
* - true - indicator will be shown exactly in the middle horizontally and vertically | ||
* - false - indicator will be shown horizontally in the middle of the anchor but immediately below or above it | ||
* @property indicatorDirection The direction the indicator arrow is pointing. | ||
* @property indicatorArrowStartOffset Maximum distance between the popup start and the indicator arrow. | ||
* If there isn't enough space this could automatically be overridden up to 0 such that | ||
* the indicator arrow will be pointing to the middle of the anchor. | ||
*/ | ||
data class CFRPopupProperties( | ||
val popupWidth: Dp = CFRPopup.DEFAULT_WIDTH.dp, | ||
val popupAlignment: PopupAlignment = PopupAlignment.BODY_TO_ANCHOR_CENTER, | ||
val popupBodyColors: List<Int> = listOf(Color.Blue.toArgb()), | ||
val popupVerticalOffset: Dp = CFRPopup.DEFAULT_VERTICAL_OFFSET.dp, | ||
val dismissButtonColor: Int = Color.Black.toArgb(), | ||
val dismissOnBackPress: Boolean = true, | ||
val dismissOnClickOutside: Boolean = true, | ||
val overlapAnchor: Boolean = false, | ||
val indicatorDirection: IndicatorDirection = IndicatorDirection.UP, | ||
val indicatorArrowStartOffset: Dp = CFRPopup.DEFAULT_INDICATOR_START_OFFSET.dp, | ||
) | ||
|
||
/** | ||
* CFR - Contextual Feature Recommendation popup. | ||
* | ||
* @param anchor [View] that will serve as the anchor of the popup and serve as lifecycle owner | ||
* for this popup also. | ||
* @param properties [CFRPopupProperties] allowing to customize the popup appearance and behavior. | ||
* @param onDismiss Callback for when the popup is dismissed indicating also if the dismissal | ||
* was explicit - by tapping the "X" button or not. | ||
* @param text [Text] already styled and ready to be shown in the popup. | ||
* @param action Optional other composable to show just below the popup text. | ||
*/ | ||
class CFRPopup( | ||
@get:VisibleForTesting internal val anchor: View, | ||
@get:VisibleForTesting internal val properties: CFRPopupProperties, | ||
@get:VisibleForTesting internal val onDismiss: (Boolean) -> Unit = {}, | ||
@get:VisibleForTesting internal val text: @Composable (() -> Unit), | ||
@get:VisibleForTesting internal val action: @Composable (() -> Unit) = {}, | ||
) { | ||
// This is just a facade for the CFRPopupFullScreenLayout composable offering a cleaner API. | ||
|
||
@VisibleForTesting | ||
internal var popup: WeakReference<CFRPopupFullscreenLayout>? = null | ||
|
||
/** | ||
* Construct and display a styled CFR popup shown at the coordinates of [anchor]. | ||
* This popup will be dismissed when the user clicks on the "x" button or based on other user actions | ||
* with such behavior set in [CFRPopupProperties]. | ||
*/ | ||
fun show() { | ||
anchor.post { | ||
CFRPopupFullscreenLayout(anchor, properties, onDismiss, text, action).apply { | ||
this.show() | ||
popup = WeakReference(this) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Immediately dismiss this CFR popup. | ||
* The [onDismiss] callback won't be fired. | ||
*/ | ||
fun dismiss() { | ||
popup?.get()?.dismiss() | ||
} | ||
|
||
/** | ||
* Possible direction for the arrow indicator of a CFR popup. | ||
* The direction is expressed in relation with the popup body containing the text. | ||
*/ | ||
enum class IndicatorDirection { | ||
UP, | ||
DOWN, | ||
} | ||
|
||
/** | ||
* Possible alignments of the popup in relation to it's anchor. | ||
*/ | ||
enum class PopupAlignment { | ||
/** | ||
* The popup body will be centered in the space occupied by the anchor. | ||
* Recommended to be used when the anchor is wider than the popup. | ||
*/ | ||
BODY_TO_ANCHOR_CENTER, | ||
|
||
/** | ||
* The popup body will be shown aligned to exactly the anchor start. | ||
*/ | ||
BODY_TO_ANCHOR_START, | ||
|
||
/** | ||
* The popup will be aligned such that the indicator arrow will point to exactly the middle of the anchor. | ||
* Recommended to be used when there are multiple widgets displayed horizontally so that this will allow | ||
* to indicate exactly which widget the popup refers to. | ||
*/ | ||
INDICATOR_CENTERED_IN_ANCHOR, | ||
} | ||
|
||
companion object { | ||
/** | ||
* Default width for all CFRs. | ||
*/ | ||
internal const val DEFAULT_WIDTH = 335 | ||
|
||
/** | ||
* Fixed horizontal padding. | ||
* Allows the close button to extend with 10dp more to the end and intercept touches to | ||
* a bit outside of the popup to ensure it respects a11y recommendations of 48dp size while | ||
* also offer a bit more space to the text. | ||
*/ | ||
internal const val DEFAULT_EXTRA_HORIZONTAL_PADDING = 10 | ||
|
||
/** | ||
* How tall the indicator arrow should be. | ||
* This will also affect the width of the indicator's base which is double the height value. | ||
*/ | ||
internal const val DEFAULT_INDICATOR_HEIGHT = 7 | ||
|
||
/** | ||
* Maximum distance between the popup start and the indicator. | ||
*/ | ||
internal const val DEFAULT_INDICATOR_START_OFFSET = 30 | ||
|
||
/** | ||
* Corner radius for the popup body. | ||
*/ | ||
internal const val DEFAULT_CORNER_RADIUS = 12 | ||
|
||
/** | ||
* Vertical distance between the indicator arrow and the anchor. | ||
*/ | ||
internal const val DEFAULT_VERTICAL_OFFSET = 9 | ||
} | ||
} |
Oops, something went wrong.