diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /mobile/android/android-components/components/concept/menu | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/android-components/components/concept/menu')
16 files changed, 858 insertions, 0 deletions
diff --git a/mobile/android/android-components/components/concept/menu/build.gradle b/mobile/android/android-components/components/concept/menu/build.gradle new file mode 100644 index 0000000000..3ba2e45a89 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/build.gradle @@ -0,0 +1,38 @@ +/* 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 { + defaultConfig { + minSdkVersion config.minSdkVersion + compileSdk config.compileSdkVersion + targetSdkVersion config.targetSdkVersion + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + namespace 'mozilla.components.concept.menu' +} + +dependencies { + implementation ComponentsDependencies.androidx_annotation + implementation ComponentsDependencies.androidx_appcompat + implementation project(':support-base') + implementation project(':support-ktx') + + testImplementation ComponentsDependencies.androidx_test_junit + testImplementation ComponentsDependencies.testing_robolectric + testImplementation project(':support-test') +} + +apply from: '../../../android-lint.gradle' +apply from: '../../../publish.gradle' +ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) diff --git a/mobile/android/android-components/components/concept/menu/proguard-rules.pro b/mobile/android/android-components/components/concept/menu/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/proguard-rules.pro @@ -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 diff --git a/mobile/android/android-components/components/concept/menu/src/main/AndroidManifest.xml b/mobile/android/android-components/components/concept/menu/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e16cda1d34 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<!-- 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 /> diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuButton.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuButton.kt new file mode 100644 index 0000000000..c59f25cb70 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuButton.kt @@ -0,0 +1,47 @@ +/* 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.concept.menu + +import androidx.annotation.ColorInt +import mozilla.components.concept.menu.candidate.MenuEffect +import mozilla.components.support.base.observer.Observable + +/** + * A `three-dot` button used for expanding menus. + * + * If you are using a browser toolbar, do not use this class directly. + */ +interface MenuButton : Observable<MenuButton.Observer> { + + /** + * Sets a [MenuController] that will be used to create a menu when this button is clicked. + */ + var menuController: MenuController? + + /** + * Show the indicator for a browser menu effect. + */ + fun setEffect(effect: MenuEffect?) + + /** + * Sets the tint of the 3-dot menu icon. + */ + fun setColorFilter(@ColorInt color: Int) + + /** + * Observer for the menu button. + */ + interface Observer { + /** + * Listener called when the menu is shown. + */ + fun onShow() = Unit + + /** + * Listener called when the menu is dismissed. + */ + fun onDismiss() = Unit + } +} diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuController.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuController.kt new file mode 100644 index 0000000000..d3a8e0f7e6 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuController.kt @@ -0,0 +1,53 @@ +/* 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.concept.menu + +import android.view.View +import android.widget.PopupWindow +import mozilla.components.concept.menu.candidate.MenuCandidate +import mozilla.components.support.base.observer.Observable + +/** + * Controls a popup menu composed of MenuCandidate objects. + */ +interface MenuController : Observable<MenuController.Observer> { + + /** + * @param anchor The view on which to pin the popup window. + * @param orientation The preferred orientation to show the popup window. + * @param autoDismiss True if the popup window should be dismissed when the device orientation + * is changed. + */ + fun show( + anchor: View, + orientation: Orientation? = null, + autoDismiss: Boolean = true, + ): PopupWindow + + /** + * Dismiss the menu popup if the menu is visible. + */ + fun dismiss() + + /** + * Changes the contents of the menu. + */ + fun submitList(list: List<MenuCandidate>) + + /** + * Observer for the menu controller. + */ + interface Observer { + /** + * Called when the menu contents have changed. + */ + fun onMenuListSubmit(list: List<MenuCandidate>) = Unit + + /** + * Called when the menu has been dismissed. + */ + fun onDismiss() = Unit + } +} diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuStyle.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuStyle.kt new file mode 100644 index 0000000000..7db0b70b35 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/MenuStyle.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 mozilla.components.concept.menu + +import android.content.res.ColorStateList +import androidx.annotation.ColorInt +import androidx.annotation.Px + +/** + * Declare custom styles for a menu. + * + * @property backgroundColor Custom background color for the menu. + * @property minWidth Custom minimum width for the menu. + * @property maxWidth Custom maximum width for the menu. + * @property horizontalOffset Custom horizontal offset for the menu. + * @property verticalOffset Custom vertical offset for the menu. + * @property completelyOverlap Forces menu to overlap the anchor completely. + */ +data class MenuStyle( + val backgroundColor: ColorStateList? = null, + @Px val minWidth: Int? = null, + @Px val maxWidth: Int? = null, + @Px val horizontalOffset: Int? = null, + @Px val verticalOffset: Int? = null, + val completelyOverlap: Boolean = false, +) { + constructor( + @ColorInt backgroundColor: Int, + @Px minWidth: Int? = null, + @Px maxWidth: Int? = null, + @Px horizontalOffset: Int? = null, + @Px verticalOffset: Int? = null, + completelyOverlap: Boolean = false, + ) : this( + backgroundColor = ColorStateList.valueOf(backgroundColor), + minWidth = minWidth, + maxWidth = maxWidth, + horizontalOffset = horizontalOffset, + verticalOffset = verticalOffset, + completelyOverlap = completelyOverlap, + ) +} diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/Orientation.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/Orientation.kt new file mode 100644 index 0000000000..60d453480c --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/Orientation.kt @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.menu + +import android.view.Gravity + +/** + * Indicates the preferred orientation to show the menu. + */ +enum class Orientation { + /** + * Position the menu above the toolbar. + */ + UP, + + /** + * Position the menu below the toolbar. + */ + DOWN, + + ; + + companion object { + + /** + * Returns an orientation that matches the given [Gravity] value. + * Meant to be used with a CoordinatorLayout's gravity. + */ + fun fromGravity(gravity: Int): Orientation { + return if ((gravity and Gravity.BOTTOM) == Gravity.BOTTOM) { + UP + } else { + DOWN + } + } + } +} diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/Side.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/Side.kt new file mode 100644 index 0000000000..6a956f6e73 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/Side.kt @@ -0,0 +1,20 @@ +/* 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.concept.menu + +/** + * Indicates the starting or ending side of the menu or an option. + */ +enum class Side { + /** + * Starting side (top or left). + */ + START, + + /** + * Ending side (bottom or right). + */ + END, +} diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/ContainerStyle.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/ContainerStyle.kt new file mode 100644 index 0000000000..df6242b0e9 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/ContainerStyle.kt @@ -0,0 +1,16 @@ +/* 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.concept.menu.candidate + +/** + * Describes styling for the menu option container. + * + * @property isVisible When false, the option will not be displayed. + * @property isEnabled When false, the option will be greyed out and disabled. + */ +data class ContainerStyle( + val isVisible: Boolean = true, + val isEnabled: Boolean = true, +) diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuCandidate.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuCandidate.kt new file mode 100644 index 0000000000..48a12909d1 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuCandidate.kt @@ -0,0 +1,123 @@ +/* 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.concept.menu.candidate + +/** + * Menu option data classes to be shown in the browser menu. + */ +sealed class MenuCandidate { + abstract val containerStyle: ContainerStyle +} + +/** + * Interactive menu option that displays some text. + * + * @property text Text to display. + * @property start Icon to display before the text. + * @property end Icon to display after the text. + * @property textStyle Styling to apply to the text. + * @property containerStyle Styling to apply to the container. + * @property effect Effects to apply to the option. + * @property onClick Click listener called when this menu option is clicked. + */ +data class TextMenuCandidate( + val text: String, + val start: MenuIcon? = null, + val end: MenuIcon? = null, + val textStyle: TextStyle = TextStyle(), + override val containerStyle: ContainerStyle = ContainerStyle(), + val effect: MenuCandidateEffect? = null, + val onClick: () -> Unit = {}, +) : MenuCandidate() + +/** + * Menu option that displays static text. + * + * @property text Text to display. + * @property height Custom height for the menu option. + * @property textStyle Styling to apply to the text. + * @property containerStyle Styling to apply to the container. + */ +data class DecorativeTextMenuCandidate( + val text: String, + val height: Int? = null, + val textStyle: TextStyle = TextStyle(), + override val containerStyle: ContainerStyle = ContainerStyle(), +) : MenuCandidate() + +/** + * Menu option that shows a switch or checkbox. + * + * @property text Text to display. + * @property start Icon to display before the text. + * @property end Compound button to display after the text. + * @property textStyle Styling to apply to the text. + * @property containerStyle Styling to apply to the container. + * @property effect Effects to apply to the option. + * @property onCheckedChange Listener called when this menu option is checked or unchecked. + */ +data class CompoundMenuCandidate( + val text: String, + val isChecked: Boolean, + val start: MenuIcon? = null, + val end: ButtonType, + val textStyle: TextStyle = TextStyle(), + override val containerStyle: ContainerStyle = ContainerStyle(), + val effect: MenuCandidateEffect? = null, + val onCheckedChange: (Boolean) -> Unit = {}, +) : MenuCandidate() { + + /** + * Compound button types to display with the compound menu option. + */ + enum class ButtonType { + CHECKBOX, + SWITCH, + } +} + +/** + * Menu option that opens a nested sub menu. + * + * @property id Unique ID for this nested menu. Can be a resource ID. + * @property text Text to display. + * @property start Icon to display before the text. + * @property end Icon to display after the text. + * @property subMenuItems Nested menu items to display. + * If null, this item will instead return to the root menu. + * @property textStyle Styling to apply to the text. + * @property containerStyle Styling to apply to the container. + * @property effect Effects to apply to the option. + */ +data class NestedMenuCandidate( + val id: Int, + val text: String, + val start: MenuIcon? = null, + val end: DrawableMenuIcon? = null, + val subMenuItems: List<MenuCandidate>? = emptyList(), + val textStyle: TextStyle = TextStyle(), + override val containerStyle: ContainerStyle = ContainerStyle(), + val effect: MenuCandidateEffect? = null, +) : MenuCandidate() + +/** + * Displays a row of small menu options. + * + * @property items Small menu options to display. + * @property containerStyle Styling to apply to the container. + */ +data class RowMenuCandidate( + val items: List<SmallMenuCandidate>, + override val containerStyle: ContainerStyle = ContainerStyle(), +) : MenuCandidate() + +/** + * Menu option to display a horizontal divider. + * + * @property containerStyle Styling to apply to the divider. + */ +data class DividerMenuCandidate( + override val containerStyle: ContainerStyle = ContainerStyle(), +) : MenuCandidate() diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuEffect.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuEffect.kt new file mode 100644 index 0000000000..b30104a636 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuEffect.kt @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.menu.candidate + +import androidx.annotation.ColorInt + +/** + * Describes an effect for the menu. + * Effects can also alter the button to open the menu. + */ +sealed class MenuEffect + +/** + * Describes an effect for a menu candidate and its container. + * Effects can also alter the button that opens the menu. + */ +sealed class MenuCandidateEffect : MenuEffect() + +/** + * Describes an effect for a menu icon. + * Effects can also alter the button that opens the menu. + */ +sealed class MenuIconEffect : MenuEffect() + +/** + * Displays a notification dot. + * Used for highlighting new features to the user, such as what's new or a recommended feature. + * + * @property notificationTint Tint for the notification dot displayed on the icon and menu button. + */ +data class LowPriorityHighlightEffect( + @ColorInt val notificationTint: Int, +) : MenuIconEffect() + +/** + * Changes the background of the menu item. + * Used for errors that require user attention, like sync errors. + * + * @property backgroundTint Tint for the menu item background color. + * Also used to highlight the menu button. + */ +data class HighPriorityHighlightEffect( + @ColorInt val backgroundTint: Int, +) : MenuCandidateEffect() diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuIcon.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuIcon.kt new file mode 100644 index 0000000000..84a8135012 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/MenuIcon.kt @@ -0,0 +1,97 @@ +/* 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.concept.menu.candidate + +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.appcompat.content.res.AppCompatResources + +/** + * Menu option data classes to be shown alongside menu options + */ +sealed class MenuIcon + +/** + * Menu icon that displays a drawable. + * + * @property drawable Drawable icon to display. + * @property tint Tint to apply to the drawable. + * @property effect Effects to apply to the icon. + */ +data class DrawableMenuIcon( + override val drawable: Drawable?, + @ColorInt override val tint: Int? = null, + val effect: MenuIconEffect? = null, +) : MenuIcon(), MenuIconWithDrawable { + + constructor( + context: Context, + @DrawableRes resource: Int, + @ColorInt tint: Int? = null, + effect: MenuIconEffect? = null, + ) : this(AppCompatResources.getDrawable(context, resource), tint, effect) +} + +/** + * Menu icon that displays an image button. + * + * @property drawable Drawable icon to display. + * @property tint Tint to apply to the drawable. + * @property onClick Click listener called when this menu option is clicked. + */ +data class DrawableButtonMenuIcon( + override val drawable: Drawable?, + @ColorInt override val tint: Int? = null, + val onClick: () -> Unit = {}, +) : MenuIcon(), MenuIconWithDrawable { + + constructor( + context: Context, + @DrawableRes resource: Int, + @ColorInt tint: Int? = null, + onClick: () -> Unit = {}, + ) : this(AppCompatResources.getDrawable(context, resource), tint, onClick) +} + +/** + * Menu icon that displays a drawable. + * + * @property loadDrawable Function that creates drawable icon to display. + * @property loadingDrawable Drawable that is displayed while loadDrawable is running. + * @property fallbackDrawable Drawable that is displayed if loadDrawable fails. + * @property tint Tint to apply to the drawable. + * @property effect Effects to apply to the icon. + */ +data class AsyncDrawableMenuIcon( + val loadDrawable: suspend (width: Int, height: Int) -> Drawable?, + val loadingDrawable: Drawable? = null, + val fallbackDrawable: Drawable? = null, + @ColorInt val tint: Int? = null, + val effect: MenuIconEffect? = null, +) : MenuIcon() + +/** + * Menu icon to display additional text at the end of a menu option. + * + * @property text Text to display. + * @property backgroundTint Color to show behind text. + * @property textStyle Styling to apply to the text. + */ +data class TextMenuIcon( + val text: String, + @ColorInt val backgroundTint: Int? = null, + val textStyle: TextStyle = TextStyle(), +) : MenuIcon() + +/** + * Interface shared by all [MenuIcon]s with drawables. + */ +interface MenuIconWithDrawable { + val drawable: Drawable? + + @get:ColorInt val tint: Int? +} diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/SmallMenuCandidate.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/SmallMenuCandidate.kt new file mode 100644 index 0000000000..51e6fd68cc --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/SmallMenuCandidate.kt @@ -0,0 +1,22 @@ +/* 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.concept.menu.candidate + +/** + * Small icon button menu option. Can only be used with [RowMenuCandidate]. + * + * @property contentDescription Description of the icon. + * @property icon Icon to display. + * @property containerStyle Styling to apply to the container. + * @property onLongClick Listener called when this menu option is long clicked. + * @property onClick Click listener called when this menu option is clicked. + */ +data class SmallMenuCandidate( + val contentDescription: String, + val icon: DrawableMenuIcon, + val containerStyle: ContainerStyle = ContainerStyle(), + val onLongClick: (() -> Boolean)? = null, + val onClick: () -> Unit = {}, +) diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/TextStyle.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/TextStyle.kt new file mode 100644 index 0000000000..eb85581f53 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/candidate/TextStyle.kt @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.menu.candidate + +import android.graphics.Typeface +import android.view.View +import androidx.annotation.ColorInt +import androidx.annotation.Dimension +import androidx.annotation.IntDef + +/** + * Describes styling for text inside a menu option. + * + * @param size: The size of the text. + * @param color: The color to apply to the text. + */ +data class TextStyle( + @Dimension(unit = Dimension.PX) val size: Float? = null, + @ColorInt val color: Int? = null, + @TypefaceStyle val textStyle: Int = Typeface.NORMAL, + @TextAlignment val textAlignment: Int = View.TEXT_ALIGNMENT_INHERIT, +) + +/** + * Enum for [Typeface] values. + */ +@IntDef(value = [Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC]) +annotation class TypefaceStyle + +/** + * Enum for text alignment values. + */ +@IntDef( + value = [ + View.TEXT_ALIGNMENT_GRAVITY, + View.TEXT_ALIGNMENT_INHERIT, + View.TEXT_ALIGNMENT_CENTER, + View.TEXT_ALIGNMENT_TEXT_START, + View.TEXT_ALIGNMENT_TEXT_END, + View.TEXT_ALIGNMENT_VIEW_START, + View.TEXT_ALIGNMENT_VIEW_END, + ], +) +annotation class TextAlignment diff --git a/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/ext/MenuCandidate.kt b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/ext/MenuCandidate.kt new file mode 100644 index 0000000000..b10ebf4ee1 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/main/java/mozilla/components/concept/menu/ext/MenuCandidate.kt @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.menu.ext + +import mozilla.components.concept.menu.candidate.CompoundMenuCandidate +import mozilla.components.concept.menu.candidate.DecorativeTextMenuCandidate +import mozilla.components.concept.menu.candidate.DividerMenuCandidate +import mozilla.components.concept.menu.candidate.DrawableMenuIcon +import mozilla.components.concept.menu.candidate.HighPriorityHighlightEffect +import mozilla.components.concept.menu.candidate.LowPriorityHighlightEffect +import mozilla.components.concept.menu.candidate.MenuCandidate +import mozilla.components.concept.menu.candidate.MenuEffect +import mozilla.components.concept.menu.candidate.MenuIcon +import mozilla.components.concept.menu.candidate.MenuIconEffect +import mozilla.components.concept.menu.candidate.NestedMenuCandidate +import mozilla.components.concept.menu.candidate.RowMenuCandidate +import mozilla.components.concept.menu.candidate.TextMenuCandidate + +private fun MenuIcon?.effect(): MenuIconEffect? = + if (this is DrawableMenuIcon) effect else null + +/** + * Find the effects used by the menu. + * Disabled and invisible menu items are not included. + */ +fun List<MenuCandidate>.effects(): Sequence<MenuEffect> = this.asSequence() + .filter { option -> option.containerStyle.isVisible && option.containerStyle.isEnabled } + .flatMap { option -> + when (option) { + is TextMenuCandidate -> + sequenceOf(option.effect, option.start.effect(), option.end.effect()).filterNotNull() + is CompoundMenuCandidate -> + sequenceOf(option.effect, option.start.effect()).filterNotNull() + is NestedMenuCandidate -> + sequenceOf(option.effect, option.start.effect(), option.end.effect()).filterNotNull() + + option.subMenuItems?.effects().orEmpty() + is RowMenuCandidate -> + option.items.asSequence() + .filter { it.containerStyle.isVisible && it.containerStyle.isEnabled } + .mapNotNull { it.icon.effect } + is DecorativeTextMenuCandidate, is DividerMenuCandidate -> emptySequence() + } + } + +/** + * Find a [NestedMenuCandidate] in the list with a matching [id]. + */ +fun List<MenuCandidate>.findNestedMenuCandidate(id: Int): NestedMenuCandidate? = this.asSequence() + .mapNotNull { it as? NestedMenuCandidate } + .find { it.id == id } + +/** + * Select the highlight with the highest priority. + */ +fun Sequence<MenuEffect>.max() = maxByOrNull { + // Select the highlight with the highest priority + when (it) { + is HighPriorityHighlightEffect -> 2 + is LowPriorityHighlightEffect -> 1 + } +} diff --git a/mobile/android/android-components/components/concept/menu/src/test/java/mozilla/components/concept/menu/ext/MenuCandidateTest.kt b/mobile/android/android-components/components/concept/menu/src/test/java/mozilla/components/concept/menu/ext/MenuCandidateTest.kt new file mode 100644 index 0000000000..5326143dd4 --- /dev/null +++ b/mobile/android/android-components/components/concept/menu/src/test/java/mozilla/components/concept/menu/ext/MenuCandidateTest.kt @@ -0,0 +1,179 @@ +/* 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.concept.menu.ext + +import android.graphics.Color +import mozilla.components.concept.menu.candidate.CompoundMenuCandidate +import mozilla.components.concept.menu.candidate.ContainerStyle +import mozilla.components.concept.menu.candidate.DecorativeTextMenuCandidate +import mozilla.components.concept.menu.candidate.DividerMenuCandidate +import mozilla.components.concept.menu.candidate.DrawableMenuIcon +import mozilla.components.concept.menu.candidate.HighPriorityHighlightEffect +import mozilla.components.concept.menu.candidate.LowPriorityHighlightEffect +import mozilla.components.concept.menu.candidate.RowMenuCandidate +import mozilla.components.concept.menu.candidate.SmallMenuCandidate +import mozilla.components.concept.menu.candidate.TextMenuCandidate +import org.junit.Assert.assertEquals +import org.junit.Test + +class MenuCandidateTest { + + @Test + fun `higher priority items will be selected by max`() { + assertEquals( + HighPriorityHighlightEffect(Color.BLACK), + sequenceOf( + LowPriorityHighlightEffect(Color.BLUE), + HighPriorityHighlightEffect(Color.BLACK), + ).max(), + ) + } + + @Test + fun `items earlier in sequence will be selected by max`() { + assertEquals( + LowPriorityHighlightEffect(Color.BLUE), + sequenceOf( + LowPriorityHighlightEffect(Color.BLUE), + LowPriorityHighlightEffect(Color.YELLOW), + ).max(), + ) + } + + @Test + fun `effects returns effects from row candidate`() { + assertEquals( + listOf( + LowPriorityHighlightEffect(Color.BLUE), + LowPriorityHighlightEffect(Color.YELLOW), + ), + listOf( + RowMenuCandidate( + listOf( + SmallMenuCandidate( + "", + icon = DrawableMenuIcon( + null, + effect = LowPriorityHighlightEffect(Color.BLUE), + ), + ), + SmallMenuCandidate( + "", + icon = DrawableMenuIcon( + null, + effect = LowPriorityHighlightEffect(Color.RED), + ), + containerStyle = ContainerStyle(isVisible = false), + ), + SmallMenuCandidate( + "", + icon = DrawableMenuIcon( + null, + effect = LowPriorityHighlightEffect(Color.RED), + ), + containerStyle = ContainerStyle(isEnabled = false), + ), + SmallMenuCandidate( + "", + icon = DrawableMenuIcon( + null, + effect = LowPriorityHighlightEffect(Color.YELLOW), + ), + ), + ), + ), + ).effects().toList(), + ) + } + + @Test + fun `effects returns effects from text candidates`() { + assertEquals( + listOf( + HighPriorityHighlightEffect(Color.BLUE), + LowPriorityHighlightEffect(Color.YELLOW), + HighPriorityHighlightEffect(Color.BLACK), + HighPriorityHighlightEffect(Color.BLUE), + LowPriorityHighlightEffect(Color.RED), + ), + listOf( + TextMenuCandidate( + "", + start = DrawableMenuIcon( + null, + effect = LowPriorityHighlightEffect(Color.YELLOW), + ), + effect = HighPriorityHighlightEffect(Color.BLUE), + ), + DecorativeTextMenuCandidate(""), + TextMenuCandidate(""), + DividerMenuCandidate(), + TextMenuCandidate( + "", + effect = HighPriorityHighlightEffect(Color.BLACK), + ), + TextMenuCandidate( + "", + containerStyle = ContainerStyle(isVisible = false), + effect = HighPriorityHighlightEffect(Color.BLACK), + ), + TextMenuCandidate( + "", + end = DrawableMenuIcon( + null, + effect = LowPriorityHighlightEffect(Color.RED), + ), + effect = HighPriorityHighlightEffect(Color.BLUE), + ), + ).effects().toList(), + ) + } + + @Test + fun `effects returns effects from compound candidates`() { + assertEquals( + listOf( + HighPriorityHighlightEffect(Color.BLUE), + LowPriorityHighlightEffect(Color.YELLOW), + HighPriorityHighlightEffect(Color.BLACK), + LowPriorityHighlightEffect(Color.RED), + ), + listOf( + CompoundMenuCandidate( + "", + isChecked = true, + start = DrawableMenuIcon( + null, + effect = LowPriorityHighlightEffect(Color.YELLOW), + ), + end = CompoundMenuCandidate.ButtonType.CHECKBOX, + effect = HighPriorityHighlightEffect(Color.BLUE), + ), + CompoundMenuCandidate( + "", + isChecked = false, + end = CompoundMenuCandidate.ButtonType.SWITCH, + effect = HighPriorityHighlightEffect(Color.BLACK), + ), + CompoundMenuCandidate( + "", + isChecked = false, + end = CompoundMenuCandidate.ButtonType.SWITCH, + containerStyle = ContainerStyle(isEnabled = false), + effect = HighPriorityHighlightEffect(Color.BLACK), + ), + CompoundMenuCandidate( + "", + isChecked = true, + start = DrawableMenuIcon( + null, + effect = LowPriorityHighlightEffect(Color.RED), + ), + end = CompoundMenuCandidate.ButtonType.CHECKBOX, + ), + ).effects().toList(), + ) + } +} |