From 7d41e97a306b3bd390fc9e149ae01b0ec89bc1db Mon Sep 17 00:00:00 2001 From: Artem Umerov Date: Sat, 7 Sep 2024 19:36:15 +0300 Subject: [PATCH] 71 71 --- .../fenrir/util/serializeble/json/Json.kt | 3 + .../serializeble/json/JsonConfiguration.kt | 2 +- .../util/serializeble/json/Json.kt | 3 + .../serializeble/json/JsonConfiguration.kt | 2 +- build.gradle | 30 +- libfenrir/ffmpeg.sh | 2 +- .../main/jni/animation/thorvg/inc/thorvg.h | 3 +- .../animation/thorvg/src/common/tvgMath.cpp | 16 +- .../jni/animation/thorvg/src/common/tvgMath.h | 11 +- .../animation/thorvg/src/common/tvgStr.cpp | 10 - .../jni/animation/thorvg/src/common/tvgStr.h | 1 - .../thorvg/src/loaders/svg/tvgSvgLoader.cpp | 16 +- .../src/renderer/sw_engine/tvgSwCommon.h | 4 +- .../src/renderer/sw_engine/tvgSwRaster.cpp | 39 +- .../src/renderer/sw_engine/tvgSwRenderer.cpp | 3 +- .../src/renderer/sw_engine/tvgSwShape.cpp | 2 +- .../thorvg/src/renderer/tvgPaint.cpp | 2 - .../thorvg/src/renderer/tvgPicture.cpp | 2 + .../animation/thorvg/src/renderer/tvgScene.h | 2 + .../animation/thorvg/src/renderer/tvgShape.h | 3 + .../animation/thorvg/src/renderer/tvgText.h | 2 + .../thorvg/src/renderer/tvgWgCanvas.cpp | 9 +- .../android/material/appbar/AppBarLayout.java | 28 +- .../appbar/CollapsingToolbarLayout.java | 5 +- .../material/appbar/MaterialToolbar.java | 3 - .../material/appbar/ViewUtilsLollipop.java | 3 - .../material/appbar/res/values/tokens.xml | 20 +- .../material/badge/res/values/tokens.xml | 2 +- .../bottomappbar/res/values/tokens.xml | 4 +- .../BottomNavigationMenuView.java | 4 +- .../BottomNavigationView.java | 37 -- .../bottomnavigation/res/values/attrs.xml | 3 +- .../bottomnavigation/res/values/dimens.xml | 2 +- .../bottomnavigation/res/values/styles.xml | 2 +- .../bottomnavigation/res/values/tokens.xml | 12 +- .../bottomsheet/BottomSheetBehavior.java | 4 +- .../bottomsheet/BottomSheetDialog.java | 41 +- .../bottomsheet/res/values/tokens.xml | 2 +- .../material/button/MaterialButton.java | 2 - .../material/button/MaterialButtonHelper.java | 77 +-- .../m3_btn_elevated_btn_state_list_anim.xml | 6 +- .../res/animator/m3_btn_state_list_anim.xml | 6 +- .../m3_text_button_ripple_color_selector.xml | 18 +- .../button/res/values/button_group_tokens.xml | 31 + .../material/button/res/values/dimens.xml | 1 - .../button/res/values/icon_btn_tokens.xml | 34 +- .../material/button/res/values/styles.xml | 85 +-- .../material/button/res/values/tokens.xml | 85 ++- .../xml/m3_button_group_child_size_change.xml | 2 +- .../material/card/MaterialCardView.java | 4 +- .../material/card/MaterialCardViewHelper.java | 47 +- .../material/card/res/values/tokens.xml | 2 +- .../material/checkbox/MaterialCheckBox.java | 45 +- .../material/checkbox/res/values/tokens.xml | 2 +- .../google/android/material/chip/Chip.java | 47 +- .../android/material/chip/ChipDrawable.java | 25 +- .../material/chip/res/values/tokens.xml | 2 +- .../circularreveal/CircularRevealCompat.java | 50 +- .../circularreveal/CircularRevealHelper.java | 17 +- .../m3_ref_palette_dynamic_neutral12.xml | 4 +- .../m3_ref_palette_dynamic_neutral17.xml | 4 +- .../m3_ref_palette_dynamic_neutral22.xml | 4 +- .../m3_ref_palette_dynamic_neutral24.xml | 4 +- .../m3_ref_palette_dynamic_neutral4.xml | 4 +- .../m3_ref_palette_dynamic_neutral6.xml | 4 +- .../m3_ref_palette_dynamic_neutral87.xml | 4 +- .../m3_ref_palette_dynamic_neutral92.xml | 4 +- .../m3_ref_palette_dynamic_neutral94.xml | 4 +- .../m3_ref_palette_dynamic_neutral96.xml | 4 +- .../m3_ref_palette_dynamic_neutral98.xml | 4 +- ..._ref_palette_dynamic_neutral_variant98.xml | 5 +- .../m3_ref_palette_dynamic_primary98.xml | 22 + .../m3_ref_palette_dynamic_secondary98.xml | 22 + .../m3_ref_palette_dynamic_tertiary98.xml | 22 + .../material/color/res/values-v31/tokens.xml | 4 +- .../material/color/res/values-v34/tokens.xml | 2 +- .../material/color/res/values-v35/tokens.xml | 2 +- .../material/color/res/values/tokens.xml | 7 +- .../datepicker/CalendarItemStyle.java | 9 +- .../MaterialStyledDatePickerDialog.java | 10 +- .../material/datepicker/res/values/tokens.xml | 2 +- .../material/dialog/res/values/tokens.xml | 2 +- .../material/divider/res/values/tokens.xml | 2 +- .../material/drawable/DrawableUtils.java | 5 +- .../drawable/ScaledDrawableWrapper.java | 3 - .../material/elevation/res/values/tokens.xml | 2 +- .../floatingactionbutton/BorderDrawable.java | 3 - .../FloatingActionButton.java | 15 +- .../FloatingActionButtonImpl.java | 14 - .../FloatingActionButtonImplLollipop.java | 2 - .../res/values/efab_tokens.xml | 3 +- .../res/values/fab_tokens.xml | 3 +- .../imageview/ShapeableImageView.java | 17 +- .../internal/CollapsingTextHelper.java | 32 +- .../material/internal/EdgeToEdgeUtils.java | 7 - .../android/material/internal/FlowLayout.java | 3 - .../internal/ForegroundLinearLayout.java | 5 - .../loadingindicator/res/values/styles.xml | 10 +- .../loadingindicator/res/values/tokens.xml | 31 + .../materialswitch/res/values/tokens.xml | 2 +- .../material/menu/res/values/tokens.xml | 2 +- .../material/motion/res/values/tokens.xml | 2 +- .../navigation/NavigationBarItemView.java | 157 +++-- .../navigation/NavigationBarMenu.java | 19 +- .../navigation/NavigationBarMenuBuilder.java | 129 +++++ .../navigation/NavigationBarMenuItemView.java | 41 ++ .../navigation/NavigationBarMenuView.java | 538 ++++++++++++------ .../navigation/NavigationBarPresenter.java | 4 +- .../navigation/NavigationBarSubMenu.java | 47 ++ .../NavigationBarSubheaderView.java | 123 ++++ .../navigation/NavigationBarView.java | 85 ++- .../material/navigation/NavigationView.java | 13 +- .../navigation/res-public/values/public.xml | 2 + .../layout/m3_navigation_menu_subheader.xml | 29 + .../material/navigation/res/values/attrs.xml | 13 + .../material/navigation/res/values/dimens.xml | 9 +- .../material/navigation/res/values/tokens.xml | 30 +- .../NavigationRailMenuView.java | 33 +- .../navigationrail/NavigationRailView.java | 7 +- .../navigationrail/res/values/tokens.xml | 16 +- .../progressindicator/res/values/styles.xml | 11 +- .../progressindicator/res/values/tokens.xml | 24 +- .../radiobutton/res/values/tokens.xml | 2 +- .../material/resources/TextAppearance.java | 31 +- .../resources/res/values-v21/tokens.xml | 2 +- .../resources/res/values-v28/tokens.xml | 2 +- .../material/resources/res/values/tokens.xml | 2 +- .../android/material/ripple/RippleUtils.java | 124 +--- .../android/material/search/SearchBar.java | 27 +- .../android/material/search/SearchView.java | 7 +- .../material/search/res/values/tokens.xml | 2 +- .../material/shape/MaterialShapeDrawable.java | 22 +- .../android/material/shape/ShapePath.java | 4 - .../material/shape/res/values/tokens.xml | 2 +- .../material/sidesheet/SheetDialog.java | 20 +- .../material/sidesheet/res/values/tokens.xml | 2 +- .../android/material/slider/BaseSlider.java | 4 +- .../material/slider/res/values/tokens.xml | 2 +- .../snackbar/BaseTransientBottomBar.java | 32 +- .../material/snackbar/res/values/tokens.xml | 2 +- .../android/material/tabs/TabLayout.java | 21 +- .../material/tabs/res/values/tokens.xml | 2 +- .../DropdownMenuEndIconDelegate.java | 31 +- .../material/textfield/IconHelper.java | 3 +- .../MaterialAutoCompleteTextView.java | 8 +- .../material/textfield/TextInputLayout.java | 62 +- .../res/values/tokens_dropdown_menu.xml | 2 +- .../textfield/res/values/tokens_textfield.xml | 2 +- .../timepicker/TimePickerClockPresenter.java | 6 +- .../TimePickerTextInputPresenter.java | 41 -- .../material/timepicker/res/values/tokens.xml | 2 +- .../material/tooltip/res/values/tokens.xml | 2 +- .../FabTransformationBehavior.java | 44 +- .../material/transition/TransitionUtils.java | 14 +- .../transition/platform/TransitionUtils.java | 14 +- .../material/typography/res/values/tokens.xml | 6 +- picasso3/build.gradle | 1 + 157 files changed, 1712 insertions(+), 1411 deletions(-) create mode 100644 material/java/com/google/android/material/button/res/values/button_group_tokens.xml create mode 100644 material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_primary98.xml create mode 100644 material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_secondary98.xml create mode 100644 material/java/com/google/android/material/color/res/color-v31/m3_ref_palette_dynamic_tertiary98.xml create mode 100644 material/java/com/google/android/material/loadingindicator/res/values/tokens.xml create mode 100644 material/java/com/google/android/material/navigation/NavigationBarMenuBuilder.java create mode 100644 material/java/com/google/android/material/navigation/NavigationBarMenuItemView.java create mode 100644 material/java/com/google/android/material/navigation/NavigationBarSubMenu.java create mode 100644 material/java/com/google/android/material/navigation/NavigationBarSubheaderView.java create mode 100644 material/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_subheader.xml diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/json/Json.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/json/Json.kt index 125500a0a..150c36e67 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/json/Json.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/json/Json.kt @@ -454,6 +454,9 @@ class JsonBuilder internal constructor(json: Json) { * Removes JSON specification restriction (RFC-4627) and makes parser * more liberal to the malformed input. In lenient mode, unquoted JSON keys and string values are allowed. * + * Example of invalid JSON that is accepted with this flag set: + * `{key: value}` can be parsed into `@Serializable class Data(val key: String)`. + * * Its relaxations can be expanded in the future, so that lenient parser becomes even more * permissive to invalid values in the input. * diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/json/JsonConfiguration.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/json/JsonConfiguration.kt index 108d63169..2c3727715 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/json/JsonConfiguration.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/util/serializeble/json/JsonConfiguration.kt @@ -50,7 +50,7 @@ class JsonConfiguration @OptIn(ExperimentalSerializationApi::class) internal con * Defines which classes and objects should have their serial name included in the json as so-called class discriminator. * * Class discriminator is a JSON field added by kotlinx.serialization that has [JsonBuilder.classDiscriminator] as a key (`type` by default), - * and class' serial name as a value (fully-qualified name by default, can be changed with SerialName annotation). + * and class' serial name as a value (fully qualified name by default, can be changed with SerialName annotation). * * Class discriminator is important for serializing and deserializing [polymorphic class hierarchies](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#sealed-classes). * Default [ClassDiscriminatorMode.POLYMORPHIC] mode adds discriminator only to polymorphic classes. diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/json/Json.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/json/Json.kt index 781ea1daf..ae0782dfd 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/json/Json.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/json/Json.kt @@ -454,6 +454,9 @@ class JsonBuilder internal constructor(json: Json) { * Removes JSON specification restriction (RFC-4627) and makes parser * more liberal to the malformed input. In lenient mode, unquoted JSON keys and string values are allowed. * + * Example of invalid JSON that is accepted with this flag set: + * `{key: value}` can be parsed into `@Serializable class Data(val key: String)`. + * * Its relaxations can be expanded in the future, so that lenient parser becomes even more * permissive to invalid values in the input. * diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/json/JsonConfiguration.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/json/JsonConfiguration.kt index 5279e0504..04f0d34da 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/json/JsonConfiguration.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/serializeble/json/JsonConfiguration.kt @@ -50,7 +50,7 @@ class JsonConfiguration @OptIn(ExperimentalSerializationApi::class) internal con * Defines which classes and objects should have their serial name included in the json as so-called class discriminator. * * Class discriminator is a JSON field added by kotlinx.serialization that has [JsonBuilder.classDiscriminator] as a key (`type` by default), - * and class' serial name as a value (fully-qualified name by default, can be changed with SerialName annotation). + * and class' serial name as a value (fully qualified name by default, can be changed with SerialName annotation). * * Class discriminator is important for serializing and deserializing [polymorphic class hierarchies](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#sealed-classes). * Default [ClassDiscriminatorMode.POLYMORPHIC] mode adds discriminator only to polymorphic classes. diff --git a/build.gradle b/build.gradle index af28c0dc7..cb50a36dd 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { ext.appCompileSDK = 35 ext.appBuildTools = "35.0.0" - ext.appNdk = "27.0.12077973" + ext.appNdk = "27.1.12297006" ext.appMinSDK = is_developer_build ? 29 : 21 ext.appTargetSDK = 31 ext.appFenrirVersionCode = 999 @@ -13,15 +13,15 @@ buildscript { ext.appFileGalleryVersionName = "1.999" //androidx libraries - ext.activityVersion = "1.9.1" + ext.activityVersion = "1.9.2" ext.annotationVersion = "1.8.2" ext.appcompatVersion = "1.7.0" ext.biometricVersion = "1.4.0-alpha02" ext.browserVersion = "1.8.0" ext.cardviewVersion = "1.0.0" - ext.collectionVersion = "1.4.3" + ext.collectionVersion = "1.5.0-alpha01" ext.concurentVersion = "1.2.0" - ext.constraintlayoutVersion = "2.2.0-alpha14" + ext.constraintlayoutVersion = "2.2.0-beta01" ext.coordinatorlayoutVersion = "1.3.0-alpha02" ext.coreVersion = "1.13.1" ext.customviewVersion = "1.2.0-alpha02" @@ -30,9 +30,9 @@ buildscript { ext.drawerlayoutVersion = "1.2.0" ext.dynamicanimationVersion = "1.1.0-alpha03" ext.exifinterfaceVersion = "1.3.7" - ext.fragmentVersion = "1.8.2" - ext.graphicsVersion = "1.0.0" - ext.lifecycleVersion = "2.8.4" + ext.fragmentVersion = "1.8.3" + ext.graphicsVersion = "1.0.1" + ext.lifecycleVersion = "2.8.5" ext.mediaVersion = "1.7.0" ext.media3Version = "1.4.1" ext.resourceInspectionAnnotation = "1.0.1" @@ -41,8 +41,8 @@ buildscript { ext.tracingVersion = "1.2.0" ext.transitionVersion = "1.5.1" ext.vectordrawableVersion = "1.2.0" - ext.webkitVersion = "1.12.0-beta01" - ext.workVersion = "2.10.0-alpha02" + ext.webkitVersion = "1.12.0-rc01" + ext.workVersion = "2.10.0-alpha03" //firebase libraries ext.firebaseDatatransportVersion = "19.0.0" @@ -51,7 +51,7 @@ buildscript { //firebase common libraries ext.firebaseCommonVersion = "21.0.0" ext.firebaseInstallationsInteropVersion = "17.2.0" - ext.firebaseComponentsVersion = "18.0.0" + ext.firebaseComponentsVersion = "18.0.1" ext.firebaseAnnotationsVersion = "16.2.0" ext.playServicesTasksVersion = "18.2.0" ext.playServicesBase = "18.5.0" @@ -60,15 +60,15 @@ buildscript { //common libraries ext.kotlin_version = "2.0.20" ext.kotlin_coroutines = "1.9.0-RC.2" - ext.kotlin_serializer = "1.7.1" + ext.kotlin_serializer = "1.7.2" ext.okhttpLibraryVersion = "5.0.0-SNAPSHOT" //ext.okhttpLibraryVersion = "5.0.0-alpha.14" ext.okioVersion = "3.9.0" ext.guavaVersion = "33.3.0-android" - ext.errorProneVersion = "2.30.0" + ext.errorProneVersion = "2.31.0" ext.checkerCompatQualVersion = "2.5.6" - ext.checkerQualAndroidVersion = "3.46.0" - ext.desugarLibraryVersion = "2.1.0" + ext.checkerQualAndroidVersion = "3.47.0" + ext.desugarLibraryVersion = "2.1.2" //APP_PROPS ext.targetAbi = is_developer_build ? ["arm64-v8a", "x86_64"] : ["arm64-v8a", "armeabi-v7a", "x86_64"] @@ -93,7 +93,7 @@ buildscript { //maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } } dependencies { - classpath "com.android.tools.build:gradle:8.6.0-rc01" + classpath "com.android.tools.build:gradle:8.6.0" classpath "com.google.gms:google-services:4.4.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" diff --git a/libfenrir/ffmpeg.sh b/libfenrir/ffmpeg.sh index 3e9b8439d..340450ae3 100644 --- a/libfenrir/ffmpeg.sh +++ b/libfenrir/ffmpeg.sh @@ -8,7 +8,7 @@ rm -r -f ".git" ENABLED_DECODERS=(mpeg4 h264 hevc mp3 aac ac3 eac3 flac vorbis alac) HOST_PLATFORM="linux-x86_64" -NDK_PATH="$HOME/Android/Sdk/ndk/27.0.12077973" +NDK_PATH="$HOME/Android/Sdk/ndk/27.1.12297006" echo 'Please input platform version (Example 21 - Android 5.0): ' read ANDROID_PLATFORM diff --git a/libfenrir/src/main/jni/animation/thorvg/inc/thorvg.h b/libfenrir/src/main/jni/animation/thorvg/inc/thorvg.h index c16b75fdb..4ef5bb718 100644 --- a/libfenrir/src/main/jni/animation/thorvg/inc/thorvg.h +++ b/libfenrir/src/main/jni/animation/thorvg/inc/thorvg.h @@ -1745,6 +1745,7 @@ class TVG_API WgCanvas final : public Canvas * @param[in] surface WGPUSurface, handle to a presentable surface. * @param[in] w The width of the surface. * @param[in] h The height of the surface. + * @param[in] device WGPUDevice, a desired handle for the wgpu device. If it is @c nullptr, ThorVG will assign an appropriate device internally. * * @retval Result::InsufficientCondition if the canvas is performing rendering. Please ensure the canvas is synced. * @retval Result::NonSupport In case the wg engine is not supported. @@ -1754,7 +1755,7 @@ class TVG_API WgCanvas final : public Canvas * @see Canvas::viewport() * @see Canvas::sync() */ - Result target(void* instance, void* surface, uint32_t w, uint32_t h) noexcept; + Result target(void* instance, void* surface, uint32_t w, uint32_t h, void* device = nullptr) noexcept; /** * @brief Creates a new WgCanvas object. diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.cpp b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.cpp index 2b2f1b321..cb7f24ff4 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.cpp @@ -120,9 +120,8 @@ bool inverse(const Matrix* m, Matrix* out) m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) + m->e13 * (m->e21 * m->e32 - m->e22 * m->e31); - if (tvg::zero(det)) return false; - - auto invDet = 1 / det; + auto invDet = 1.0f / det; + if (std::isinf(invDet)) return false; out->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet; out->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet; @@ -363,4 +362,13 @@ float Bezier::angle(float t) const return rad2deg(tvg::atan2(pt.y, pt.x)); } -} \ No newline at end of file + +uint8_t lerp(const uint8_t &start, const uint8_t &end, float t) +{ + auto result = static_cast(start + (end - start) * t); + tvg::clamp(result, 0, 255); + return static_cast(result); +} + +} + diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.h b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.h index 71509d575..353cccd0f 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.h @@ -26,7 +26,7 @@ #define _USE_MATH_DEFINES #include -#include +#include #include "tvgCommon.h" namespace tvg @@ -68,6 +68,13 @@ static inline bool equal(float a, float b) } +template +static inline void clamp(T& v, const T& min, const T& max) +{ + if (v < min) v = min; + else if (v > max) v = max; +} + /************************************************************************/ /* Matrix functions */ /************************************************************************/ @@ -286,6 +293,8 @@ static inline T lerp(const T &start, const T &end, float t) return static_cast(start + (end - start) * t); } +uint8_t lerp(const uint8_t &start, const uint8_t &end, float t); + } #endif //_TVG_MATH_H_ diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.cpp b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.cpp index 1336f2447..eb436f5b1 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.cpp @@ -207,16 +207,6 @@ float strToFloat(const char *nPtr, char **endPtr) return 0.0f; } - -int str2int(const char* str, size_t n) -{ - int ret = 0; - for(size_t i = 0; i < n; ++i) { - ret = ret * 10 + (str[i] - '0'); - } - return ret; -} - char* strDuplicate(const char *str, size_t n) { auto len = strlen(str); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.h b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.h index 9e5f9ba9b..272ff4ace 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.h @@ -29,7 +29,6 @@ namespace tvg { float strToFloat(const char *nPtr, char **endPtr); //convert to float -int str2int(const char* str, size_t n); //convert to integer char* strDuplicate(const char *str, size_t n); //copy the string char* strDirname(const char* path); //return the full directory name diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp index e6f8995f1..7caffc01c 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -588,6 +588,11 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re float _red = 0, _green = 0, _blue = 0; uint32_t i = 0; + while (hue < 0) hue += 360.0f; + hue = fmod(hue, 360.0f); + saturation = saturation > 0 ? std::min(saturation, 1.0f) : 0.0f; + brightness = brightness > 0 ? std::min(brightness, 1.0f) : 0.0f; + if (tvg::zero(saturation)) _red = _green = _blue = brightness; else { if (tvg::equal(hue, 360.0)) hue = 0.0f; @@ -718,7 +723,6 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** const char* hue = nullptr; if (_parseNumber(&content, &hue, &th) && hue) { const char* saturation = nullptr; - th = float(uint32_t(th) % 360); hue = _skipSpace(hue, nullptr); hue = (char*)_skipComma(hue); hue = _skipSpace(hue, nullptr); @@ -3288,6 +3292,7 @@ static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content, for (unsigned int i = 0; i < sizeof(graphicsTags) / sizeof(graphicsTags[0]); i++) { if (!strncmp(tagName, graphicsTags[i].tag, sz)) { loader->currentGraphicsNode = nullptr; + if (!strncmp(tagName, "text", 4)) loader->openedTag = OpenedTagType::Other; loader->stack.pop(); break; } @@ -3361,11 +3366,9 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes); if (node && !empty) { if (!strcmp(tagName, "text")) loader->openedTag = OpenedTagType::Text; - else { - auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr); - loader->stack.push(defs); - loader->currentGraphicsNode = node; - } + auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr); + loader->stack.push(defs); + loader->currentGraphicsNode = node; } } else if ((gradientMethod = _findGradientFactory(tagName))) { SvgStyleGradient* gradient; @@ -3403,7 +3406,6 @@ static void _svgLoaderParserText(SvgLoaderData* loader, const char* content, uns auto text = &loader->svgParse->node->node.text; if (text->text) free(text->text); text->text = strDuplicate(content, length); - loader->openedTag = OpenedTagType::Other; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwCommon.h index a6db3ec58..d04f92ac7 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwCommon.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwCommon.h @@ -400,7 +400,6 @@ static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t return JOIN(255, c1, c2, c3); } - static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { // s * d @@ -410,7 +409,6 @@ static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_ return JOIN(255, c1, c2, c3); } - static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { // if (2 * d < da) => 2 * s * d, @@ -563,7 +561,7 @@ bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8 bool rasterImage(SwSurface* surface, SwImage* image, const Matrix& transform, const SwBBox& bbox, uint8_t opacity); bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a); bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity); -bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h); +bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val = 0); void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len); void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len); void rasterUnpremultiply(Surface* surface); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp index fe20282aa..712de6921 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp @@ -423,12 +423,11 @@ static bool _rasterBlendingRect(SwSurface* surface, const SwBBox& region, uint8_ auto h = static_cast(region.max.y - region.min.y); auto color = surface->join(r, g, b, a); auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x; - auto ialpha = 255 - a; for (uint32_t y = 0; y < h; ++y) { auto dst = &buffer[y * surface->stride]; for (uint32_t x = 0; x < w; ++x, ++dst) { - *dst = surface->blender(color, *dst, ialpha); + *dst = surface->blender(color, *dst, 255); } } return true; @@ -595,17 +594,16 @@ static bool _rasterBlendingRle(SwSurface* surface, const SwRle* rle, uint8_t r, auto span = rle->spans; auto color = surface->join(r, g, b, a); - auto ialpha = 255 - a; for (uint32_t i = 0; i < rle->size; ++i, ++span) { auto dst = &surface->buf32[span->y * surface->stride + span->x]; if (span->coverage == 255) { for (uint32_t x = 0; x < span->len; ++x, ++dst) { - *dst = surface->blender(color, *dst, ialpha); + *dst = surface->blender(color, *dst, 255); } } else { for (uint32_t x = 0; x < span->len; ++x, ++dst) { - auto tmp = surface->blender(color, *dst, ialpha); + auto tmp = surface->blender(color, *dst, 255); *dst = INTERPOLATE(tmp, *dst, span->coverage); } } @@ -813,9 +811,8 @@ static bool _rasterScaledBlendingRleImage(SwSurface* surface, const SwImage* ima for (uint32_t x = static_cast(span->x); x < static_cast(span->x) + span->len; ++x, ++dst) { SCALED_IMAGE_RANGE_X auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); - if (opacity < 255) src = ALPHA_BLEND(src, opacity); auto tmp = surface->blender(src, *dst, 255); - *dst = INTERPOLATE(tmp, *dst, MULTIPLY(span->coverage, A(src))); + *dst = INTERPOLATE(tmp, *dst, MULTIPLY(alpha, A(src))); } } } @@ -981,18 +978,12 @@ static bool _rasterDirectBlendingRleImage(SwSurface* surface, const SwImage* ima auto alpha = MULTIPLY(span->coverage, opacity); if (alpha == 255) { for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { - *dst = surface->blender(*img, *dst, IA(*img)); - } - } else if (opacity == 255) { - for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { - auto tmp = surface->blender(*img, *dst, 255); - *dst = INTERPOLATE(tmp, *dst, MULTIPLY(span->coverage, A(*img))); + *dst = surface->blender(*img, *dst, 255); } } else { for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { - auto src = ALPHA_BLEND(*img, opacity); - auto tmp = surface->blender(src, *dst, IA(src)); - *dst = INTERPOLATE(tmp, *dst, MULTIPLY(span->coverage, A(src))); + auto tmp = surface->blender(*img, *dst, 255); + *dst = INTERPOLATE(tmp, *dst, MULTIPLY(alpha, A(*img))); } } } @@ -1164,9 +1155,8 @@ static bool _rasterScaledBlendingImage(SwSurface* surface, const SwImage* image, for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { SCALED_IMAGE_RANGE_X auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); - if (opacity < 255) ALPHA_BLEND(src, opacity); auto tmp = surface->blender(src, *dst, 255); - *dst = INTERPOLATE(tmp, *dst, A(src)); + *dst = INTERPOLATE(tmp, *dst, MULTIPLY(opacity, A(src))); } } return true; @@ -1383,10 +1373,9 @@ static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image, *dst = INTERPOLATE(tmp, *dst, A(*src)); } } else { - for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { - auto tmp = ALPHA_BLEND(*src, opacity); - auto tmp2 = surface->blender(tmp, *dst, 255); - *dst = INTERPOLATE(tmp2, *dst, A(tmp)); + for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { + auto tmp = surface->blender(*src, *dst, 255); + *dst = INTERPOLATE(tmp, *dst, MULTIPLY(opacity, A(*src))); } } dbuffer += surface->stride; @@ -1843,7 +1832,7 @@ bool rasterCompositor(SwSurface* surface) } -bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h) +bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val) { if (!surface || !surface->buf32 || surface->stride == 0 || surface->w == 0 || surface->h == 0) return false; @@ -1851,11 +1840,11 @@ bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_ if (surface->channelSize == sizeof(uint32_t)) { //full clear if (w == surface->stride) { - rasterPixel32(surface->buf32, 0x00000000, surface->stride * y, w * h); + rasterPixel32(surface->buf32, val, surface->stride * y, w * h); //partial clear } else { for (uint32_t i = 0; i < h; i++) { - rasterPixel32(surface->buf32, 0x00000000, (surface->stride * y + x) + (surface->stride * i), w); + rasterPixel32(surface->buf32, val, (surface->stride * y + x) + (surface->stride * i), w); } } //8 bits diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 4e30490be..8c2c9ba8a 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -100,7 +100,6 @@ struct SwShapeTask : SwTask return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12)); } - bool clip(SwRle* target) override { if (shape.fastTrack) rleClipRect(target, &bbox); @@ -602,7 +601,7 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs) cmp->w = cmp->compositor->image.w; cmp->h = cmp->compositor->image.h; - rasterClear(cmp, x, y, w, h); + rasterClear(cmp, x, y, w, h, (surface->blendMethod == BlendMethod::Normal) ? 0x00000000 : 0x00ffffff); //Switch render target surface = cmp; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwShape.cpp index 79054048e..fd0548d6e 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwShape.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwShape.cpp @@ -209,7 +209,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct } _outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform); } - if (dash.curLen < 1 && TO_SWCOORD(len) > 1) { + if (dash.curLen < 0.1f && TO_SWCOORD(len) > 1) { //move to next dash dash.curIdx = (dash.curIdx + 1) % dash.cnt; dash.curLen = dash.pattern[dash.curIdx]; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.cpp index 6d6361cee..5a4d008b1 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.cpp @@ -225,8 +225,6 @@ bool Paint::Impl::render(RenderMethod* renderer) if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity); - renderer->blend(blendMethod); - bool ret; PAINT_METHOD(ret, render(renderer)); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.cpp index 6ed151e57..b064eeca2 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.cpp @@ -73,6 +73,8 @@ bool Picture::Impl::needComposition(uint8_t opacity) bool Picture::Impl::render(RenderMethod* renderer) { bool ret = false; + renderer->blend(picture->blend()); + if (surface) return renderer->renderImage(rd); else if (paint) { Compositor* cmp = nullptr; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.h index c9a6c8e1f..46decfaab 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.h @@ -120,6 +120,8 @@ struct Scene::Impl Compositor* cmp = nullptr; auto ret = true; + renderer->blend(scene->blend()); + if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); renderer->beginComposite(cmp, CompositeMethod::None, opacity); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.h index a1e96a4af..221931dee 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.h @@ -54,10 +54,13 @@ struct Shape::Impl Compositor* cmp = nullptr; bool ret; + renderer->blend(shape->blend()); + if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); renderer->beginComposite(cmp, CompositeMethod::None, opacity); } + ret = renderer->renderShape(rd); if (cmp) renderer->endComposite(cmp); return ret; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.h index 76a1b30ea..d9b045ad8 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.h @@ -89,6 +89,8 @@ struct Text::Impl bool render(RenderMethod* renderer) { + if (!loader) return true; + renderer->blend(paint->blend()); return PP(shape)->render(renderer); } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgWgCanvas.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgWgCanvas.cpp index c094ae4d2..9b3394b0c 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgWgCanvas.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgWgCanvas.cpp @@ -50,11 +50,14 @@ WgCanvas::WgCanvas() : Canvas(nullptr), pImpl(nullptr) WgCanvas::~WgCanvas() { - delete pImpl; +#ifdef THORVG_WG_RASTER_SUPPORT + auto renderer = static_cast(Canvas::pImpl->renderer); + renderer->target(nullptr, 0, 0); +#endif } -Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h) noexcept +Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h, void* device) noexcept { #ifdef THORVG_WG_RASTER_SUPPORT if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) { @@ -67,7 +70,7 @@ Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h) n auto renderer = static_cast(Canvas::pImpl->renderer); if (!renderer) return Result::MemoryCorruption; - if (!renderer->target((WGPUInstance)instance, (WGPUSurface)surface, w, h)) return Result::Unknown; + if (!renderer->target((WGPUInstance)instance, (WGPUSurface)surface, w, h, (WGPUDevice)device)) return Result::Unknown; Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h}; renderer->viewport(Canvas::pImpl->vport); diff --git a/material/java/com/google/android/material/appbar/AppBarLayout.java b/material/java/com/google/android/material/appbar/AppBarLayout.java index 82009f18c..f1d0f1bb0 100644 --- a/material/java/com/google/android/material/appbar/AppBarLayout.java +++ b/material/java/com/google/android/material/appbar/AppBarLayout.java @@ -35,7 +35,6 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -57,7 +56,6 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; import androidx.annotation.VisibleForTesting; import androidx.coordinatorlayout.widget.CoordinatorLayout; @@ -238,18 +236,14 @@ public AppBarLayout(@NonNull Context context, @Nullable AttributeSet attrs, int context = getContext(); setOrientation(VERTICAL); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - // Use the bounds view outline provider so that we cast a shadow, even without a - // background - if (getOutlineProvider() == ViewOutlineProvider.BACKGROUND) { - ViewUtilsLollipop.setBoundsViewOutlineProvider(this); - } - - // If we're running on API 21+, we should reset any state list animator from our - // default style - ViewUtilsLollipop.setStateListAnimatorFromAttrs(this, attrs, defStyleAttr, DEF_STYLE_RES); + // Use the bounds view outline provider so that we cast a shadow, even without a background. + if (getOutlineProvider() == ViewOutlineProvider.BACKGROUND) { + ViewUtilsLollipop.setBoundsViewOutlineProvider(this); } + // Reset any state list animator from our default style. + ViewUtilsLollipop.setStateListAnimatorFromAttrs(this, attrs, defStyleAttr, DEF_STYLE_RES); + final TypedArray a = ThemeEnforcement.obtainStyledAttributes( context, attrs, R.styleable.AppBarLayout, defStyleAttr, DEF_STYLE_RES); @@ -287,8 +281,7 @@ public AppBarLayout(@NonNull Context context, @Nullable AttributeSet attrs, int /* force= */ false); } - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP - && a.hasValue(R.styleable.AppBarLayout_elevation)) { + if (a.hasValue(R.styleable.AppBarLayout_elevation)) { ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator( this, a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0)); } @@ -686,7 +679,6 @@ public MaterialShapeDrawable getMaterialShapeBackground() { return background instanceof MaterialShapeDrawable ? (MaterialShapeDrawable) background : null; } - @RequiresApi(VERSION_CODES.LOLLIPOP) @Override public void setElevation(float elevation) { super.setElevation(elevation); @@ -1156,9 +1148,7 @@ private void clearLiftOnScrollTargetView() { */ @Deprecated public void setTargetElevation(float elevation) { - if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(this, elevation); - } + ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(this, elevation); } /** @@ -2206,7 +2196,7 @@ private void updateAppBarLayoutDrawableState( if (VERSION.SDK_INT >= VERSION_CODES.M && layout.getForeground() != null) { layout.getForeground().jumpToCurrentState(); } - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layout.getStateListAnimator() != null) { + if (layout.getStateListAnimator() != null) { layout.getStateListAnimator().jumpToCurrentState(); } } diff --git a/material/java/com/google/android/material/appbar/CollapsingToolbarLayout.java b/material/java/com/google/android/material/appbar/CollapsingToolbarLayout.java index 85f102024..394b967d5 100644 --- a/material/java/com/google/android/material/appbar/CollapsingToolbarLayout.java +++ b/material/java/com/google/android/material/appbar/CollapsingToolbarLayout.java @@ -561,7 +561,7 @@ private void ensureToolbar() { private static boolean isToolbar(View view) { return view instanceof androidx.appcompat.widget.Toolbar - || (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && view instanceof android.widget.Toolbar); + || view instanceof android.widget.Toolbar; } private boolean isToolbarChild(View child) { @@ -752,8 +752,7 @@ private void updateCollapsedBounds(boolean isRtl) { private static CharSequence getToolbarTitle(View view) { if (view instanceof androidx.appcompat.widget.Toolbar) { return ((androidx.appcompat.widget.Toolbar) view).getTitle(); - } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP - && view instanceof android.widget.Toolbar) { + } else if (view instanceof android.widget.Toolbar) { return ((android.widget.Toolbar) view).getTitle(); } else { return null; diff --git a/material/java/com/google/android/material/appbar/MaterialToolbar.java b/material/java/com/google/android/material/appbar/MaterialToolbar.java index e08097f72..87ccbba90 100644 --- a/material/java/com/google/android/material/appbar/MaterialToolbar.java +++ b/material/java/com/google/android/material/appbar/MaterialToolbar.java @@ -26,7 +26,6 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.Drawable; -import android.os.Build.VERSION_CODES; import androidx.appcompat.view.menu.MenuBuilder; import androidx.appcompat.widget.Toolbar; import android.util.AttributeSet; @@ -38,7 +37,6 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.view.ViewCompat; import com.google.android.material.drawable.DrawableUtils; @@ -273,7 +271,6 @@ protected void onAttachedToWindow() { MaterialShapeUtils.setParentAbsoluteElevation(this); } - @RequiresApi(VERSION_CODES.LOLLIPOP) @Override public void setElevation(float elevation) { super.setElevation(elevation); diff --git a/material/java/com/google/android/material/appbar/ViewUtilsLollipop.java b/material/java/com/google/android/material/appbar/ViewUtilsLollipop.java index 8a0476c7a..8f64a7056 100644 --- a/material/java/com/google/android/material/appbar/ViewUtilsLollipop.java +++ b/material/java/com/google/android/material/appbar/ViewUtilsLollipop.java @@ -23,15 +23,12 @@ import android.animation.StateListAnimator; import android.content.Context; import android.content.res.TypedArray; -import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import com.google.android.material.internal.ThemeEnforcement; -@RequiresApi(VERSION_CODES.LOLLIPOP) class ViewUtilsLollipop { private static final int[] STATE_LIST_ANIM_ATTRS = new int[] {android.R.attr.stateListAnimator}; diff --git a/material/java/com/google/android/material/appbar/res/values/tokens.xml b/material/java/com/google/android/material/appbar/res/values/tokens.xml index 9c971b45c..b136bbc71 100644 --- a/material/java/com/google/android/material/appbar/res/values/tokens.xml +++ b/material/java/com/google/android/material/appbar/res/values/tokens.xml @@ -15,37 +15,37 @@ ~ limitations under the License. --> - + - + ?attr/colorSurface ?attr/colorSurfaceContainer 64dp @dimen/m3_sys_elevation_level0 - + ?attr/colorOnSurface - + ?attr/colorOnSurfaceVariant - + ?attr/textAppearanceTitleLarge ?attr/colorOnSurface - + @dimen/m3_sys_elevation_level2 - + 112dp - + ?attr/textAppearanceHeadlineSmall ?attr/colorOnSurface - + 152dp - + ?attr/textAppearanceHeadlineMedium ?attr/colorOnSurface diff --git a/material/java/com/google/android/material/badge/res/values/tokens.xml b/material/java/com/google/android/material/badge/res/values/tokens.xml index 80b402431..3f05d29ee 100644 --- a/material/java/com/google/android/material/badge/res/values/tokens.xml +++ b/material/java/com/google/android/material/badge/res/values/tokens.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> - + diff --git a/material/java/com/google/android/material/bottomappbar/res/values/tokens.xml b/material/java/com/google/android/material/bottomappbar/res/values/tokens.xml index 995d7410c..976dadfdd 100644 --- a/material/java/com/google/android/material/bottomappbar/res/values/tokens.xml +++ b/material/java/com/google/android/material/bottomappbar/res/values/tokens.xml @@ -15,12 +15,12 @@ ~ limitations under the License. --> - + - + ?attr/colorSurfaceContainer diff --git a/material/java/com/google/android/material/bottomnavigation/res/values/tokens.xml b/material/java/com/google/android/material/bottomnavigation/res/values/tokens.xml index 3737a37dd..eee09e84d 100644 --- a/material/java/com/google/android/material/bottomnavigation/res/values/tokens.xml +++ b/material/java/com/google/android/material/bottomnavigation/res/values/tokens.xml @@ -15,17 +15,17 @@ ~ limitations under the License. --> - + - + ?attr/colorSurfaceContainer @@ -165,7 +166,7 @@ start-gravity icon button. If your icon is end-gravity, mirror the padding such that this adjustment happens on the other side. --> @@ -184,7 +185,7 @@ start-gravity icon button. If your icon is end-gravity, mirror the padding such that this adjustment happens to the other side. --> @@ -198,8 +199,8 @@ @dimen/m3_btn_padding_bottom @dimen/m3_btn_inset @dimen/m3_btn_inset - @macro/m3_comp_text_button_label_text_type - @style/ShapeAppearance.M3.Comp.TextButton.Container.Shape + @macro/m3_comp_button_small_label_text + @style/ShapeAppearance.M3.Comp.Button.Small.Container.Shape.Round @style/ThemeOverlay.Material3.Button.TextButton @color/m3_text_button_foreground_color_selector @color/m3_text_button_foreground_color_selector @@ -244,12 +245,12 @@ @dimen/m3_btn_padding_left @dimen/m3_btn_padding_right @color/m3_button_outline_color_selector - @dimen/m3_comp_outlined_button_outline_width + @dimen/m3_comp_button_small_outlined_outline_width @@ -259,7 +260,7 @@ @animator/m3_btn_elevated_btn_state_list_anim @color/m3_text_button_foreground_color_selector - @dimen/m3_btn_elevated_btn_elevation + @dimen/m3_comp_button_elevated_container_elevation @color/m3_text_button_foreground_color_selector @color/m3_text_button_ripple_color_selector @style/ThemeOverlay.Material3.Button.ElevatedButton @@ -267,7 +268,7 @@ @@ -288,6 +289,7 @@ @dimen/m3_btn_icon_only_default_size @color/m3_icon_button_icon_color_selector @dimen/m3_btn_icon_only_icon_padding + @style/ShapeAppearance.M3.Comp.IconButton.Small.Container.Shape.Round @style/ThemeOverlay.Material3.Button.IconButton @@ -303,7 +305,7 @@ @@ -313,27 +315,27 @@ - @@ -344,29 +346,30 @@ diff --git a/material/java/com/google/android/material/button/res/values/tokens.xml b/material/java/com/google/android/material/button/res/values/tokens.xml index cef459993..4e7f97a6b 100644 --- a/material/java/com/google/android/material/button/res/values/tokens.xml +++ b/material/java/com/google/android/material/button/res/values/tokens.xml @@ -15,62 +15,47 @@ ~ limitations under the License. --> - + - - - diff --git a/material/java/com/google/android/material/loadingindicator/res/values/tokens.xml b/material/java/com/google/android/material/loadingindicator/res/values/tokens.xml new file mode 100644 index 000000000..7851f0b9c --- /dev/null +++ b/material/java/com/google/android/material/loadingindicator/res/values/tokens.xml @@ -0,0 +1,31 @@ + + + + + + + + + + ?attr/colorPrimary + ?attr/colorPrimaryContainer + ?attr/colorOnPrimaryContainer + + 48dp + 48dp + + diff --git a/material/java/com/google/android/material/materialswitch/res/values/tokens.xml b/material/java/com/google/android/material/materialswitch/res/values/tokens.xml index 12e8a4855..96d5a8d2e 100644 --- a/material/java/com/google/android/material/materialswitch/res/values/tokens.xml +++ b/material/java/com/google/android/material/materialswitch/res/values/tokens.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> - + diff --git a/material/java/com/google/android/material/menu/res/values/tokens.xml b/material/java/com/google/android/material/menu/res/values/tokens.xml index c6b785728..00b042e1d 100644 --- a/material/java/com/google/android/material/menu/res/values/tokens.xml +++ b/material/java/com/google/android/material/menu/res/values/tokens.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> - + diff --git a/material/java/com/google/android/material/motion/res/values/tokens.xml b/material/java/com/google/android/material/motion/res/values/tokens.xml index d149345c3..4579fdef2 100644 --- a/material/java/com/google/android/material/motion/res/values/tokens.xml +++ b/material/java/com/google/android/material/motion/res/values/tokens.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> - + diff --git a/material/java/com/google/android/material/navigation/NavigationBarItemView.java b/material/java/com/google/android/material/navigation/NavigationBarItemView.java index 9e855d04e..1876e8e46 100644 --- a/material/java/com/google/android/material/navigation/NavigationBarItemView.java +++ b/material/java/com/google/android/material/navigation/NavigationBarItemView.java @@ -32,12 +32,10 @@ import android.content.res.ColorStateList; import android.graphics.Typeface; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.RippleDrawable; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import androidx.appcompat.view.menu.MenuItemImpl; -import androidx.appcompat.view.menu.MenuView; import androidx.appcompat.widget.TooltipCompat; import android.text.TextUtils; import android.util.Log; @@ -86,8 +84,10 @@ * @hide */ @RestrictTo(LIBRARY_GROUP) -public abstract class NavigationBarItemView extends FrameLayout implements MenuView.ItemView { +public abstract class NavigationBarItemView extends FrameLayout + implements NavigationBarMenuItemView { private static final int INVALID_ITEM_POSITION = -1; + private static final int UNSET_VALUE = -1; private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; private boolean initialized = false; @@ -113,7 +113,14 @@ public abstract class NavigationBarItemView extends FrameLayout implements MenuV private final TextView smallLabel; private final TextView largeLabel; private int itemPosition = INVALID_ITEM_POSITION; - @StyleRes private int activeTextAppearance = 0; + @StyleRes private int textAppearanceActive = 0; + @StyleRes private int textAppearanceInactive = 0; + @StyleRes private int horizontalTextAppearanceActive = 0; + @StyleRes private int horizontalTextAppearanceInactive = 0; + @StyleRes private int currentTextAppearanceActive = UNSET_VALUE; + @StyleRes private int currentTextAppearanceInactive = UNSET_VALUE; + @Nullable private ColorStateList textColor; + private boolean boldText = false; @Nullable private MenuItemImpl itemData; @@ -149,6 +156,8 @@ public abstract class NavigationBarItemView extends FrameLayout implements MenuV @ItemIconGravity private int itemIconGravity; private int badgeFixedEdge = BadgeDrawable.BADGE_FIXED_EDGE_START; @ItemGravity private int itemGravity = NavigationBarView.ITEM_GRAVITY_TOP_CENTER; + private boolean expanded = false; + private boolean onlyShowWhenExpanded = false; public NavigationBarItemView(@NonNull Context context) { super(context); @@ -177,7 +186,7 @@ public NavigationBarItemView(@NonNull Context context) { setFocusable(true); calculateTextScaleFactors(smallLabel.getTextSize(), largeLabel.getTextSize()); activeIndicatorExpandedDesiredHeight = getResources().getDimensionPixelSize( - R.dimen.m3_expressive_item_expanded_active_indicator_height_default); + R.dimen.m3_navigation_item_expanded_active_indicator_height_default); // TODO(b/138148581): Support displaying a badge on label-only bottom navigation views. innerContentContainer.addOnLayoutChangeListener( @@ -248,13 +257,20 @@ public void initialize(@NonNull MenuItemImpl itemData, int menuType) { : itemData.getTitle(); // Avoid calling tooltip for L and M devices because long pressing twice may freeze devices. - if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP || VERSION.SDK_INT > VERSION_CODES.M) { + if (VERSION.SDK_INT > VERSION_CODES.M) { TooltipCompat.setTooltipText(this, tooltipText); } - setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); + updateVisibility(); this.initialized = true; } + private void updateVisibility() { + if (itemData != null) { + setVisibility( + itemData.isVisible() && (expanded || !onlyShowWhenExpanded) ? View.VISIBLE : View.GONE); + } + } + /** * Remove state so this View can be reused. * @@ -303,7 +319,7 @@ private void updateItemIconGravity() { if (itemIconGravity == ITEM_ICON_GRAVITY_START) { sideMargin = getResources() - .getDimensionPixelSize(R.dimen.m3_expressive_navigation_item_leading_trailing_space); + .getDimensionPixelSize(R.dimen.m3_navigation_item_leading_trailing_space); labelGroupTopMargin = 0; labelGroupSideMargin = activeIndicatorLabelPadding; badgeFixedEdge = BadgeDrawable.BADGE_FIXED_EDGE_END; @@ -316,6 +332,8 @@ private void updateItemIconGravity() { innerContentContainer.removeView(labelGroup); contentContainer.addView(labelGroup); } + updateActiveLabelTextAppearance(); + updateInactiveLabelTextAppearance(); FrameLayout.LayoutParams contentContainerLp = (LayoutParams) contentContainer.getLayoutParams(); contentContainerLp.gravity = itemGravity; FrameLayout.LayoutParams innerContentLp = @@ -341,6 +359,28 @@ public void setItemIconGravity(@ItemIconGravity int iconGravity) { } } + @Override + public void setExpanded(boolean expanded) { + this.expanded = expanded; + updateVisibility(); + } + + @Override + public boolean isExpanded() { + return this.expanded; + } + + @Override + public void setOnlyShowWhenExpanded(boolean onlyShowWhenExpanded) { + this.onlyShowWhenExpanded = onlyShowWhenExpanded; + updateVisibility(); + } + + @Override + public boolean isOnlyVisibleWhenExpanded() { + return this.onlyShowWhenExpanded; + } + @Override @Nullable public MenuItemImpl getItemData() { @@ -360,7 +400,7 @@ public void setTitle(@Nullable CharSequence title) { ? title : itemData.getTooltipText(); // Avoid calling tooltip for L and M devices because long pressing twice may freeze devices. - if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP || VERSION.SDK_INT > VERSION_CODES.M) { + if (VERSION.SDK_INT > VERSION_CODES.M) { TooltipCompat.setTooltipText(this, tooltipText); } } @@ -694,26 +734,85 @@ public void setMeasureBottomPaddingFromLabelBaseline(boolean measurePaddingFromB requestLayout(); } - public void setTextAppearanceInactive(@StyleRes int inactiveTextAppearance) { - setTextAppearanceWithoutFontScaling(smallLabel, inactiveTextAppearance); + private boolean usingHorizontalTextAppearance(@StyleRes int horizontalTextAppearance) { + return itemIconGravity == ITEM_ICON_GRAVITY_START && horizontalTextAppearance != 0; + } + + private void updateInactiveLabelTextAppearance() { + final int newInactiveTextAppearance = + usingHorizontalTextAppearance(horizontalTextAppearanceInactive) + ? horizontalTextAppearanceInactive + : textAppearanceInactive; + // If it's the same as the current text appearance, no need to update text appearance. + if (currentTextAppearanceInactive == newInactiveTextAppearance) { + return; + } + setTextAppearanceWithoutFontScaling(smallLabel, newInactiveTextAppearance); calculateTextScaleFactors(smallLabel.getTextSize(), largeLabel.getTextSize()); smallLabel.setMinimumHeight( MaterialResources.getUnscaledLineHeight( - smallLabel.getContext(), inactiveTextAppearance, 0)); + smallLabel.getContext(), newInactiveTextAppearance, 0)); + // Set the text color if the user has set it, since it takes precedence + // over a color set in the text appearance. + if (textColor != null) { + smallLabel.setTextColor(textColor); + } + currentTextAppearanceInactive = newInactiveTextAppearance; } - public void setTextAppearanceActive(@StyleRes int activeTextAppearance) { - this.activeTextAppearance = activeTextAppearance; - setTextAppearanceWithoutFontScaling(largeLabel, activeTextAppearance); + private void updateActiveLabelTextAppearance() { + final int newActiveTextAppearance = + usingHorizontalTextAppearance(horizontalTextAppearanceActive) + ? horizontalTextAppearanceActive + : textAppearanceActive; + // If it's the same as the current text appearance, no need to update text appearance. + if (currentTextAppearanceActive == newActiveTextAppearance) { + return; + } + setTextAppearanceWithoutFontScaling(largeLabel, newActiveTextAppearance); calculateTextScaleFactors(smallLabel.getTextSize(), largeLabel.getTextSize()); largeLabel.setMinimumHeight( - MaterialResources.getUnscaledLineHeight(largeLabel.getContext(), activeTextAppearance, 0)); + MaterialResources.getUnscaledLineHeight( + largeLabel.getContext(), newActiveTextAppearance, 0)); + // Set the text color if the user has set it, since it takes precedence + // over a color set in the text appearance. + if (textColor != null) { + largeLabel.setTextColor(textColor); + } + updateActiveLabelBoldness(); + currentTextAppearanceActive = newActiveTextAppearance; + } + + public void setTextAppearanceInactive(@StyleRes int inactiveTextAppearance) { + this.textAppearanceInactive = inactiveTextAppearance; + updateInactiveLabelTextAppearance(); + } + + public void setTextAppearanceActive(@StyleRes int activeTextAppearance) { + this.textAppearanceActive = activeTextAppearance; + updateActiveLabelTextAppearance(); + } + + public void setHorizontalTextAppearanceInactive(@StyleRes int inactiveTextAppearance) { + horizontalTextAppearanceInactive = inactiveTextAppearance; + updateInactiveLabelTextAppearance(); + } + + public void setHorizontalTextAppearanceActive(@StyleRes int activeTextAppearance) { + horizontalTextAppearanceActive = activeTextAppearance; + updateActiveLabelTextAppearance(); } public void setTextAppearanceActiveBoldEnabled(boolean isBold) { - setTextAppearanceActive(activeTextAppearance); + boldText = isBold; + setTextAppearanceActive(textAppearanceActive); + setHorizontalTextAppearanceActive(horizontalTextAppearanceActive); + updateActiveLabelBoldness(); + } + + private void updateActiveLabelBoldness() { // TODO(b/246765947): Use component tokens to control font weight - largeLabel.setTypeface(largeLabel.getTypeface(), isBold ? Typeface.BOLD : Typeface.NORMAL); + largeLabel.setTypeface(largeLabel.getTypeface(), boldText ? Typeface.BOLD : Typeface.NORMAL); } /** @@ -734,6 +833,7 @@ private static void setTextAppearanceWithoutFontScaling( } public void setTextColor(@Nullable ColorStateList color) { + textColor = color; if (color != null) { smallLabel.setTextColor(color); largeLabel.setTextColor(color); @@ -779,11 +879,7 @@ private void refreshItemBackground() { if (itemRippleColor != null) { Drawable maskDrawable = getActiveIndicatorDrawable(); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP - && activeIndicatorEnabled - && getActiveIndicatorDrawable() != null - && maskDrawable != null) { - + if (activeIndicatorEnabled && getActiveIndicatorDrawable() != null && maskDrawable != null) { // Remove the default focus highlight that highlights the entire view and rely on the // active indicator ripple to communicate state. defaultHighlightEnabled = false; @@ -815,20 +911,7 @@ && getActiveIndicatorDrawable() != null */ private static Drawable createItemBackgroundCompat(@NonNull ColorStateList rippleColor) { ColorStateList rippleDrawableColor = RippleUtils.convertToRippleDrawableColor(rippleColor); - Drawable backgroundDrawable; - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - backgroundDrawable = new RippleDrawable(rippleDrawableColor, null, null); - } else { - GradientDrawable rippleDrawable = new GradientDrawable(); - // TODO: Find a workaround for this. Currently on certain devices/versions, LayerDrawable - // will draw a black background underneath any layer with a non-opaque color, - // (e.g. ripple) unless we set the shape to be something that's not a perfect rectangle. - rippleDrawable.setCornerRadius(0.00001F); - Drawable rippleDrawableCompat = DrawableCompat.wrap(rippleDrawable); - DrawableCompat.setTintList(rippleDrawableCompat, rippleDrawableColor); - backgroundDrawable = rippleDrawableCompat; - } - return backgroundDrawable; + return new RippleDrawable(rippleDrawableColor, null, null); } /** diff --git a/material/java/com/google/android/material/navigation/NavigationBarMenu.java b/material/java/com/google/android/material/navigation/NavigationBarMenu.java index 7f178ca6a..5570e12e3 100644 --- a/material/java/com/google/android/material/navigation/NavigationBarMenu.java +++ b/material/java/com/google/android/material/navigation/NavigationBarMenu.java @@ -20,6 +20,8 @@ import android.content.Context; import androidx.appcompat.view.menu.MenuBuilder; +import androidx.appcompat.view.menu.MenuItemImpl; +import androidx.appcompat.view.menu.SubMenuBuilder; import android.view.MenuItem; import android.view.SubMenu; import androidx.annotation.NonNull; @@ -37,12 +39,17 @@ public final class NavigationBarMenu extends MenuBuilder { @NonNull private final Class viewClass; private final int maxItemCount; + private final boolean subMenuSupported; public NavigationBarMenu( - @NonNull Context context, @NonNull Class viewClass, int maxItemCount) { + @NonNull Context context, + @NonNull Class viewClass, + int maxItemCount, + boolean subMenuSupported) { super(context); this.viewClass = viewClass; this.maxItemCount = maxItemCount; + this.subMenuSupported = subMenuSupported; } /** Returns the maximum number of items that can be shown in NavigationBarMenu. */ @@ -53,8 +60,14 @@ public int getMaxItemCount() { @NonNull @Override public SubMenu addSubMenu(int group, int id, int categoryOrder, @NonNull CharSequence title) { - throw new UnsupportedOperationException( - viewClass.getSimpleName() + " does not support submenus"); + if (!subMenuSupported) { + throw new UnsupportedOperationException( + viewClass.getSimpleName() + " does not support submenus"); + } + final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title); + final SubMenuBuilder subMenu = new NavigationBarSubMenu(getContext(), this, item); + item.setSubMenu(subMenu); + return subMenu; } @Override diff --git a/material/java/com/google/android/material/navigation/NavigationBarMenuBuilder.java b/material/java/com/google/android/material/navigation/NavigationBarMenuBuilder.java new file mode 100644 index 000000000..0d04cd664 --- /dev/null +++ b/material/java/com/google/android/material/navigation/NavigationBarMenuBuilder.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.material.navigation; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +import androidx.appcompat.view.menu.MenuBuilder; +import androidx.appcompat.view.menu.MenuPresenter; +import android.view.MenuItem; +import android.view.SubMenu; +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; +import java.util.ArrayList; +import java.util.List; + +/** + * Wrapper class for {@link MenuBuilder} that adds methods to support submenus as a part of the + * menu. + * + * @hide + */ +@RestrictTo(LIBRARY_GROUP) +public class NavigationBarMenuBuilder { + + private final MenuBuilder menuBuilder; + private final List items; + private int contentItemCount = 0; + private int visibleContentItemCount = 0; + private int visibleMainItemCount = 0; + + NavigationBarMenuBuilder(MenuBuilder menuBuilder) { + this.menuBuilder = menuBuilder; + items = new ArrayList<>(); + refreshItems(); + } + + /** + * Returns total number of items in the menu, including submenus and submenu items. For example, + * a Menu with items {Item, SubMenu, SubMenuItem} would have a size of 3. + */ + public int size() { + return items.size(); + } + + /** + * Returns number of content (non-subheader) items in the menu. + */ + public int getContentItemCount() { + return contentItemCount; + } + + /** + * Returns number of visible content (non-subheader) items in the menu. + */ + public int getVisibleContentItemCount() { + return visibleContentItemCount; + } + + /** + * Returns number of visible main items in the menu, which correspond to any content items that + * are not under a subheader. + */ + public int getVisibleMainContentItemCount() { + return visibleMainItemCount; + } + + /** + * Returns the item at the position i. + */ + @NonNull + public MenuItem getItemAt(int i) { + return items.get(i); + } + + /** + * Calls the underlying {@link MenuBuilder#performItemAction(MenuItem, MenuPresenter, int)} + */ + public boolean performItemAction( + @NonNull MenuItem item, @NonNull MenuPresenter presenter, int flags) { + return menuBuilder.performItemAction(item, presenter, flags); + } + + /** + * Refresh the items to match the current state of the underlying {@link MenuBuilder}. + */ + public void refreshItems() { + items.clear(); + contentItemCount = 0; + visibleContentItemCount = 0; + visibleMainItemCount = 0; + for (int i = 0; i < menuBuilder.size(); i++) { + MenuItem item = menuBuilder.getItem(i); + items.add(item); + if (item.hasSubMenu()) { + SubMenu subMenu = item.getSubMenu(); + for (int j = 0; j < subMenu.size(); j++) { + MenuItem submenuItem = subMenu.getItem(j); + if (!item.isVisible()) { + submenuItem.setVisible(false); + } + items.add(submenuItem); + contentItemCount++; + if (submenuItem.isVisible()) { + visibleContentItemCount++; + } + } + } else { + contentItemCount++; + if (item.isVisible()) { + visibleContentItemCount++; + visibleMainItemCount++; + } + } + } + } +} diff --git a/material/java/com/google/android/material/navigation/NavigationBarMenuItemView.java b/material/java/com/google/android/material/navigation/NavigationBarMenuItemView.java new file mode 100644 index 000000000..889986ab2 --- /dev/null +++ b/material/java/com/google/android/material/navigation/NavigationBarMenuItemView.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.material.navigation; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +import androidx.appcompat.view.menu.MenuView; +import androidx.annotation.RestrictTo; + +/** + * Interface for views that represent the Navigation Bar menu items. + * + * @hide + */ +@RestrictTo(LIBRARY_GROUP) +public interface NavigationBarMenuItemView extends MenuView.ItemView { + /** Update the bar expanded state in item. */ + void setExpanded(boolean expanded); + + /** Whether or not the item's bar expanded state is expanded. */ + boolean isExpanded(); + + /** Set whether or not to only show the item when expanded. */ + void setOnlyShowWhenExpanded(boolean onlyShowWhenExpanded); + + /** Whether or not to only show the item when expanded. */ + boolean isOnlyVisibleWhenExpanded(); +} diff --git a/material/java/com/google/android/material/navigation/NavigationBarMenuView.java b/material/java/com/google/android/material/navigation/NavigationBarMenuView.java index df886123d..d79fa98af 100644 --- a/material/java/com/google/android/material/navigation/NavigationBarMenuView.java +++ b/material/java/com/google/android/material/navigation/NavigationBarMenuView.java @@ -19,6 +19,7 @@ import com.google.android.material.R; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; +import static java.lang.Math.min; import android.annotation.SuppressLint; import android.content.Context; @@ -42,7 +43,6 @@ import androidx.annotation.RestrictTo; import androidx.annotation.StyleRes; import androidx.core.util.Pools; -import androidx.core.util.Pools.SynchronizedPool; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat; import androidx.transition.AutoTransition; @@ -80,9 +80,11 @@ public abstract class NavigationBarMenuView extends ViewGroup implements MenuVie @ItemIconGravity private int itemIconGravity; - @Nullable private NavigationBarItemView[] buttons; - private int selectedItemId = 0; - private int selectedItemPosition = 0; + @Nullable private NavigationBarMenuItemView[] buttons; + + private static final int NO_SELECTED_ITEM = -1; + private int selectedItemId = NO_SELECTED_ITEM; + private int selectedItemPosition = NO_SELECTED_ITEM; @Nullable private ColorStateList itemIconTint; @Dimension private int itemIconSize; @@ -90,6 +92,8 @@ public abstract class NavigationBarMenuView extends ViewGroup implements MenuVie @Nullable private final ColorStateList itemTextColorDefault; @StyleRes private int itemTextAppearanceInactive; @StyleRes private int itemTextAppearanceActive; + @StyleRes private int horizontalItemTextAppearanceInactive; + @StyleRes private int horizontalItemTextAppearanceActive; private boolean itemTextAppearanceActiveBoldEnabled; private Drawable itemBackground; @Nullable private ColorStateList itemRippleColor; @@ -114,12 +118,16 @@ public abstract class NavigationBarMenuView extends ViewGroup implements MenuVie private ColorStateList itemActiveIndicatorColor; private NavigationBarPresenter presenter; - private MenuBuilder menu; + private NavigationBarMenuBuilder menu; private boolean measurePaddingFromLabelBaseline; private int itemPoolSize = 0; + private boolean expanded; private MenuItem checkedItem = null; + private static final int DEFAULT_COLLAPSED_MAX_COUNT = 7; + private int collapsedMaxItemCount = DEFAULT_COLLAPSED_MAX_COUNT; + public NavigationBarMenuView(@NonNull Context context) { super(context); @@ -178,9 +186,24 @@ public void setCheckedItem(@NonNull MenuItem checkedItem) { this.checkedItem = checkedItem; } + /** Set the current expanded state. */ + public void setExpanded(boolean expanded) { + this.expanded = expanded; + if (buttons != null) { + for (NavigationBarMenuItemView item : buttons) { + item.setExpanded(expanded); + } + } + } + + /** Returns the current expanded state. */ + public boolean isExpanded() { + return expanded; + } + @Override public void initialize(@NonNull MenuBuilder menu) { - this.menu = menu; + this.menu = new NavigationBarMenuBuilder(menu); } @Override @@ -195,9 +218,9 @@ public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo inf infoCompat.setCollectionInfo( CollectionInfoCompat.obtain( /* rowCount= */ 1, - /* columnCount= */ menu.getVisibleItems().size(), + /* columnCount= */ getCurrentVisibleContentItemCount(), /* hierarchical= */ false, - /* selectionMode = */ CollectionInfoCompat.SELECTION_MODE_SINGLE)); + /* selectionMode= */ CollectionInfoCompat.SELECTION_MODE_SINGLE)); } /** @@ -208,8 +231,10 @@ public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo inf public void setIconTintList(@Nullable ColorStateList tint) { itemIconTint = tint; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setIconTintList(tint); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setIconTintList(tint); + } } } } @@ -234,8 +259,10 @@ public ColorStateList getIconTintList() { public void setItemIconSize(@Dimension int iconSize) { this.itemIconSize = iconSize; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setIconSize(iconSize); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setIconSize(iconSize); + } } } } @@ -254,8 +281,10 @@ public int getItemIconSize() { public void setItemTextColor(@Nullable ColorStateList color) { itemTextColorFromUser = color; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setTextColor(color); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setTextColor(color); + } } } } @@ -278,12 +307,9 @@ public ColorStateList getItemTextColor() { public void setItemTextAppearanceInactive(@StyleRes int textAppearanceRes) { this.itemTextAppearanceInactive = textAppearanceRes; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setTextAppearanceInactive(textAppearanceRes); - // Set the text color if the user has set it, since itemTextColorFromUser takes precedence - // over a color set in the text appearance. - if (itemTextColorFromUser != null) { - item.setTextColor(itemTextColorFromUser); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setTextAppearanceInactive(textAppearanceRes); } } } @@ -307,12 +333,9 @@ public int getItemTextAppearanceInactive() { public void setItemTextAppearanceActive(@StyleRes int textAppearanceRes) { this.itemTextAppearanceActive = textAppearanceRes; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setTextAppearanceActive(textAppearanceRes); - // Set the text color if the user has set it, since itemTextColorFromUser takes precedence - // over a color set in the text appearance. - if (itemTextColorFromUser != null) { - item.setTextColor(itemTextColorFromUser); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setTextAppearanceActive(textAppearanceRes); } } } @@ -326,8 +349,10 @@ public void setItemTextAppearanceActive(@StyleRes int textAppearanceRes) { public void setItemTextAppearanceActiveBoldEnabled(boolean isBold) { this.itemTextAppearanceActiveBoldEnabled = isBold; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setTextAppearanceActiveBoldEnabled(isBold); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setTextAppearanceActiveBoldEnabled(isBold); + } } } } @@ -342,6 +367,64 @@ public int getItemTextAppearanceActive() { return itemTextAppearanceActive; } + /** + * Sets the text appearance to be used for inactive menu item labels when they are in the + * horizontal item layout (when the start icon value is {@link + * ItemIconGravity#ITEM_ICON_GRAVITY_START}). + * + * @param textAppearanceRes the text appearance ID used for inactive menu item labels + */ + public void setHorizontalItemTextAppearanceInactive(@StyleRes int textAppearanceRes) { + this.horizontalItemTextAppearanceInactive = textAppearanceRes; + if (buttons != null) { + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setHorizontalTextAppearanceInactive(textAppearanceRes); + } + } + } + } + + /** + * Returns the text appearance used for inactive menu item labels when they are in the horizontal + * item layout (when the start icon value is {@link ItemIconGravity#ITEM_ICON_GRAVITY_START}). + * + * @return the text appearance ID used for inactive menu item labels + */ + @StyleRes + public int getHorizontalItemTextAppearanceInactive() { + return horizontalItemTextAppearanceInactive; + } + + /** + * Sets the text appearance to be used for the active menu item label when they are in the + * horizontal item layout (when the start icon value is {@link + * ItemIconGravity#ITEM_ICON_GRAVITY_START}). + * + * @param textAppearanceRes the text appearance ID used for the active menu item label + */ + public void setHorizontalItemTextAppearanceActive(@StyleRes int textAppearanceRes) { + this.horizontalItemTextAppearanceActive = textAppearanceRes; + if (buttons != null) { + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setHorizontalTextAppearanceActive(textAppearanceRes); + } + } + } + } + + /** + * Returns the text appearance used for the active menu item label when they are in the horizontal + * item layout (when the start icon value is {@link ItemIconGravity#ITEM_ICON_GRAVITY_START}). + * + * @return the text appearance ID used for the active menu item label + */ + @StyleRes + public int getHorizontalItemTextAppearanceActive() { + return horizontalItemTextAppearanceActive; + } + /** * Sets the resource ID to be used for item backgrounds. * @@ -350,8 +433,10 @@ public int getItemTextAppearanceActive() { public void setItemBackgroundRes(int background) { itemBackgroundRes = background; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setItemBackground(background); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setItemBackground(background); + } } } } @@ -372,8 +457,10 @@ public int getItemPaddingTop() { public void setItemPaddingTop(@Px int paddingTop) { itemPaddingTop = paddingTop; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setItemPaddingTop(paddingTop); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setItemPaddingTop(paddingTop); + } } } } @@ -392,8 +479,10 @@ public int getItemPaddingBottom() { public void setItemPaddingBottom(@Px int paddingBottom) { itemPaddingBottom = paddingBottom; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setItemPaddingBottom(paddingBottom); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setItemPaddingBottom(itemPaddingBottom); + } } } } @@ -401,8 +490,11 @@ public void setItemPaddingBottom(@Px int paddingBottom) { public void setMeasurePaddingFromLabelBaseline(boolean measurePaddingFromLabelBaseline) { this.measurePaddingFromLabelBaseline = measurePaddingFromLabelBaseline; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setMeasureBottomPaddingFromLabelBaseline(measurePaddingFromLabelBaseline); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item) + .setMeasureBottomPaddingFromLabelBaseline(measurePaddingFromLabelBaseline); + } } } } @@ -421,8 +513,11 @@ public int getActiveIndicatorLabelPadding() { public void setActiveIndicatorLabelPadding(@Px int activeIndicatorLabelPadding) { itemActiveIndicatorLabelPadding = activeIndicatorLabelPadding; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorLabelPadding(activeIndicatorLabelPadding); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item) + .setActiveIndicatorLabelPadding(activeIndicatorLabelPadding); + } } } } @@ -444,8 +539,10 @@ public boolean getItemActiveIndicatorEnabled() { public void setItemActiveIndicatorEnabled(boolean enabled) { this.itemActiveIndicatorEnabled = enabled; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorEnabled(enabled); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setActiveIndicatorEnabled(enabled); + } } } } @@ -468,8 +565,10 @@ public int getItemActiveIndicatorWidth() { public void setItemActiveIndicatorWidth(@Px int width) { this.itemActiveIndicatorWidth = width; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorWidth(width); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setActiveIndicatorWidth(width); + } } } } @@ -492,8 +591,10 @@ public int getItemActiveIndicatorHeight() { public void setItemActiveIndicatorHeight(@Px int height) { this.itemActiveIndicatorHeight = height; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorHeight(height); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setActiveIndicatorHeight(height); + } } } } @@ -507,8 +608,10 @@ public void setItemActiveIndicatorHeight(@Px int height) { public void setItemGravity(int itemGravity) { this.itemGravity = itemGravity; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setItemGravity(itemGravity); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setItemGravity(itemGravity); + } } } } @@ -543,8 +646,10 @@ public int getItemActiveIndicatorExpandedWidth() { public void setItemActiveIndicatorExpandedWidth(@Px int width) { this.itemActiveIndicatorExpandedWidth = width; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorExpandedWidth(width); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setActiveIndicatorExpandedWidth(width); + } } } } @@ -568,8 +673,10 @@ public int getItemActiveIndicatorExpandedHeight() { public void setItemActiveIndicatorExpandedHeight(@Px int height) { this.itemActiveIndicatorExpandedHeight = height; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorExpandedHeight(height); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setActiveIndicatorExpandedHeight(height); + } } } } @@ -594,8 +701,10 @@ public int getItemActiveIndicatorMarginHorizontal() { public void setItemActiveIndicatorMarginHorizontal(@Px int marginHorizontal) { itemActiveIndicatorMarginHorizontal = marginHorizontal; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorMarginHorizontal(marginHorizontal); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setActiveIndicatorMarginHorizontal(marginHorizontal); + } } } } @@ -621,8 +730,11 @@ public int getItemActiveIndicatorExpandedMarginHorizontal() { public void setItemActiveIndicatorExpandedMarginHorizontal(@Px int marginHorizontal) { itemActiveIndicatorExpandedMarginHorizontal = marginHorizontal; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorExpandedMarginHorizontal(marginHorizontal); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item) + .setActiveIndicatorExpandedMarginHorizontal(marginHorizontal); + } } } } @@ -646,8 +758,11 @@ public void setItemActiveIndicatorShapeAppearance( @Nullable ShapeAppearanceModel shapeAppearance) { this.itemActiveIndicatorShapeAppearance = shapeAppearance; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorDrawable(createItemActiveIndicatorDrawable()); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item) + .setActiveIndicatorDrawable(createItemActiveIndicatorDrawable()); + } } } } @@ -666,8 +781,10 @@ protected boolean isItemActiveIndicatorResizeable() { protected void setItemActiveIndicatorResizeable(boolean resizeable) { this.itemActiveIndicatorResizeable = resizeable; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorResizeable(resizeable); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setActiveIndicatorResizeable(resizeable); + } } } } @@ -690,8 +807,11 @@ public ColorStateList getItemActiveIndicatorColor() { public void setItemActiveIndicatorColor(@Nullable ColorStateList csl) { this.itemActiveIndicatorColor = csl; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setActiveIndicatorDrawable(createItemActiveIndicatorDrawable()); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item) + .setActiveIndicatorDrawable(createItemActiveIndicatorDrawable()); + } } } } @@ -736,8 +856,10 @@ public int getItemBackgroundRes() { public void setItemBackground(@Nullable Drawable background) { itemBackground = background; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setItemBackground(background); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setItemBackground(background); + } } } } @@ -752,8 +874,10 @@ public void setItemBackground(@Nullable Drawable background) { public void setItemRippleColor(@Nullable ColorStateList itemRippleColor) { this.itemRippleColor = itemRippleColor; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setItemRippleColor(itemRippleColor); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setItemRippleColor(itemRippleColor); + } } } } @@ -776,12 +900,16 @@ public ColorStateList getItemRippleColor() { @Nullable public Drawable getItemBackground() { if (buttons != null && buttons.length > 0) { - // Return button background instead of itemBackground if possible, so that the correct - // drawable is returned if the background is set via #setItemBackgroundRes. - return buttons[0].getBackground(); - } else { - return itemBackground; + // Find the first instance of NavigationBarItemView + for (NavigationBarMenuItemView button : buttons) { + if (button instanceof NavigationBarItemView) { + // Return button background instead of itemBackground if possible, so that the correct + // drawable is returned if the background is set via #setItemBackgroundRes. + return ((NavigationBarItemView) button).getBackground(); + } + } } + return itemBackground; } /** @@ -821,8 +949,10 @@ public int getLabelVisibilityMode() { public void setItemIconGravity(@ItemIconGravity int itemIconGravity) { this.itemIconGravity = itemIconGravity; if (buttons != null) { - for (NavigationBarItemView item : buttons) { - item.setItemIconGravity(itemIconGravity); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item).setItemIconGravity(itemIconGravity); + } } } } @@ -849,9 +979,11 @@ public void setItemOnTouchListener(int menuItemId, @Nullable OnTouchListener onT onTouchListeners.put(menuItemId, onTouchListener); } if (buttons != null) { - for (NavigationBarItemView item : buttons) { - if (item.getItemData().getItemId() == menuItemId) { - item.setOnTouchListener(onTouchListener); + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView + && item.getItemData() != null + && item.getItemData().getItemId() == menuItemId) { + ((NavigationBarItemView) item).setOnTouchListener(onTouchListener); } } } @@ -882,111 +1014,174 @@ public void setPresenter(@NonNull NavigationBarPresenter presenter) { this.presenter = presenter; } - @SuppressLint("ClickableViewAccessibility") - public void buildMenuView() { - removeAllViews(); - if (buttons != null) { - for (NavigationBarItemView item : buttons) { - if (item != null) { - if (itemPool != null) { - itemPool.release(item); - } - item.clear(); + private void releaseItemPool() { + if (buttons != null && itemPool != null) { + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + itemPool.release((NavigationBarItemView) item); + ((NavigationBarItemView) item).clear(); } } } + } + + private NavigationBarItemView createMenuItem( + int index, MenuItemImpl item, boolean shifting, boolean hideWhenCollapsed) { + presenter.setUpdateSuspended(true); + item.setCheckable(true); + presenter.setUpdateSuspended(false); + NavigationBarItemView child = getNewItem(); + child.setShifting(shifting); + child.setIconTintList(itemIconTint); + child.setIconSize(itemIconSize); + // Set the text color the default, then look for another text color in order of precedence. + child.setTextColor(itemTextColorDefault); + child.setTextAppearanceInactive(itemTextAppearanceInactive); + child.setTextAppearanceActive(itemTextAppearanceActive); + child.setHorizontalTextAppearanceInactive(horizontalItemTextAppearanceInactive); + child.setHorizontalTextAppearanceActive(horizontalItemTextAppearanceActive); + child.setTextAppearanceActiveBoldEnabled(itemTextAppearanceActiveBoldEnabled); + child.setTextColor(itemTextColorFromUser); + if (itemPaddingTop != NO_PADDING) { + child.setItemPaddingTop(itemPaddingTop); + } + if (itemPaddingBottom != NO_PADDING) { + child.setItemPaddingBottom(itemPaddingBottom); + } + child.setMeasureBottomPaddingFromLabelBaseline(measurePaddingFromLabelBaseline); + if (itemActiveIndicatorLabelPadding != NO_PADDING) { + child.setActiveIndicatorLabelPadding(itemActiveIndicatorLabelPadding); + } + child.setActiveIndicatorWidth(itemActiveIndicatorWidth); + child.setActiveIndicatorHeight(itemActiveIndicatorHeight); + child.setActiveIndicatorExpandedWidth(itemActiveIndicatorExpandedWidth); + child.setActiveIndicatorExpandedHeight(itemActiveIndicatorExpandedHeight); + child.setActiveIndicatorMarginHorizontal(itemActiveIndicatorMarginHorizontal); + child.setItemGravity(itemGravity); + child.setActiveIndicatorExpandedMarginHorizontal(itemActiveIndicatorExpandedMarginHorizontal); + child.setActiveIndicatorDrawable(createItemActiveIndicatorDrawable()); + child.setActiveIndicatorResizeable(itemActiveIndicatorResizeable); + child.setActiveIndicatorEnabled(itemActiveIndicatorEnabled); + if (itemBackground != null) { + child.setItemBackground(itemBackground); + } else { + child.setItemBackground(itemBackgroundRes); + } + child.setItemRippleColor(itemRippleColor); + child.setLabelVisibilityMode(labelVisibilityMode); + child.setItemIconGravity(itemIconGravity); + child.setOnlyShowWhenExpanded(hideWhenCollapsed); + child.setExpanded(expanded); + child.initialize(item, 0); + child.setItemPosition(index); + int itemId = item.getItemId(); + child.setOnTouchListener(onTouchListeners.get(itemId)); + child.setOnClickListener(onClickListener); + if (selectedItemId != Menu.NONE && itemId == selectedItemId) { + selectedItemPosition = index; + } + setBadgeIfNeeded(child); + return child; + } + + @SuppressLint("ClickableViewAccessibility") + public void buildMenuView() { + removeAllViews(); + releaseItemPool(); + + presenter.setUpdateSuspended(true); + menu.refreshItems(); + presenter.setUpdateSuspended(false); - if (menu.size() == 0) { + int contentItemCount = menu.getContentItemCount(); + if (contentItemCount == 0) { selectedItemId = 0; selectedItemPosition = 0; buttons = null; itemPool = null; return; } - if (itemPool == null || itemPoolSize != menu.size()) { - itemPool = new SynchronizedPool<>(menu.size()); - itemPoolSize = menu.size(); + + if (itemPool == null || itemPoolSize != contentItemCount) { + itemPoolSize = contentItemCount; + itemPool = new Pools.SynchronizedPool<>(contentItemCount); } removeUnusedBadges(); int menuSize = menu.size(); - buttons = new NavigationBarItemView[menuSize]; - boolean shifting = isShifting(labelVisibilityMode, menu.getVisibleItems().size()); + buttons = new NavigationBarMenuItemView[menuSize]; + int collapsedItemsSoFar = 0; + int nextSubheaderItemCount = 0; + boolean shifting = + isShifting(labelVisibilityMode, getCurrentVisibleContentItemCount()); for (int i = 0; i < menuSize; i++) { - presenter.setUpdateSuspended(true); - menu.getItem(i).setCheckable(true); - presenter.setUpdateSuspended(false); - NavigationBarItemView child = getNewItem(); - buttons[i] = child; - child.setIconTintList(itemIconTint); - child.setIconSize(itemIconSize); - // Set the text color the default, then look for another text color in order of precedence. - child.setTextColor(itemTextColorDefault); - child.setTextAppearanceInactive(itemTextAppearanceInactive); - child.setTextAppearanceActive(itemTextAppearanceActive); - child.setTextAppearanceActiveBoldEnabled(itemTextAppearanceActiveBoldEnabled); - child.setTextColor(itemTextColorFromUser); - if (itemPaddingTop != NO_PADDING) { - child.setItemPaddingTop(itemPaddingTop); - } - if (itemPaddingBottom != NO_PADDING) { - child.setItemPaddingBottom(itemPaddingBottom); - } - child.setMeasureBottomPaddingFromLabelBaseline(measurePaddingFromLabelBaseline); - if (itemActiveIndicatorLabelPadding != NO_PADDING) { - child.setActiveIndicatorLabelPadding(itemActiveIndicatorLabelPadding); - } - child.setActiveIndicatorWidth(itemActiveIndicatorWidth); - child.setActiveIndicatorHeight(itemActiveIndicatorHeight); - child.setActiveIndicatorExpandedWidth(itemActiveIndicatorExpandedWidth); - child.setActiveIndicatorExpandedHeight(itemActiveIndicatorExpandedHeight); - child.setActiveIndicatorMarginHorizontal(itemActiveIndicatorMarginHorizontal); - child.setItemGravity(itemGravity); - child.setActiveIndicatorExpandedMarginHorizontal(itemActiveIndicatorExpandedMarginHorizontal); - child.setActiveIndicatorDrawable(createItemActiveIndicatorDrawable()); - child.setActiveIndicatorResizeable(itemActiveIndicatorResizeable); - child.setActiveIndicatorEnabled(itemActiveIndicatorEnabled); - if (itemBackground != null) { - child.setItemBackground(itemBackground); + MenuItem menuItem = menu.getItemAt(i); + NavigationBarMenuItemView child; + if (menuItem.hasSubMenu()) { + if (nextSubheaderItemCount > 0) { + // We do not support submenus inside submenus. If there is still subheader items to be + // instantiated, we should not have another submenu. + throw new IllegalArgumentException( + "Only one layer of submenu is supported; a submenu " + + "inside a submenu is not supported by the Navigation Bar."); + } + // Add subheader item + child = new NavigationBarSubheaderView(getContext()); + child.setOnlyShowWhenExpanded(true); + child.initialize((MenuItemImpl) menuItem, 0); + nextSubheaderItemCount = menuItem.getSubMenu().size(); + } else if (nextSubheaderItemCount > 0) { // Add submenu items + child = + createMenuItem(i, (MenuItemImpl) menuItem, shifting, /* hideWhenCollapsed= */ true); + nextSubheaderItemCount--; } else { - child.setItemBackground(itemBackgroundRes); + child = + createMenuItem( + i, (MenuItemImpl) menuItem, shifting, collapsedItemsSoFar >= collapsedMaxItemCount); + collapsedItemsSoFar++; } - child.setItemRippleColor(itemRippleColor); - child.setShifting(shifting); - child.setLabelVisibilityMode(labelVisibilityMode); - child.setItemIconGravity(itemIconGravity); - MenuItemImpl item = (MenuItemImpl) menu.getItem(i); - child.initialize(item, 0); - child.setItemPosition(i); - int itemId = item.getItemId(); - child.setOnTouchListener(onTouchListeners.get(itemId)); - child.setOnClickListener(onClickListener); - if (selectedItemId != Menu.NONE && itemId == selectedItemId) { + if (menuItem.isCheckable() && selectedItemPosition == NO_SELECTED_ITEM) { selectedItemPosition = i; } - setBadgeIfNeeded(child); - addView(child); + buttons[i] = child; + addView((View) child); + } + selectedItemPosition = min(menuSize - 1, selectedItemPosition); + setCheckedItem(buttons[selectedItemPosition].getItemData()); + } + + private boolean isMenuStructureSame() { + if (buttons == null || menu == null || menu.size() != buttons.length) { + return false; + } + for (int i = 0; i < buttons.length; i++) { + if (menu.getItemAt(i).hasSubMenu() + ? buttons[i] instanceof NavigationBarItemView + : buttons[i] instanceof NavigationBarSubheaderView) { + return false; + } } - selectedItemPosition = Math.min(menu.size() - 1, selectedItemPosition); - setCheckedItem(menu.getItem(selectedItemPosition)); + return true; } public void updateMenuView() { if (menu == null || buttons == null) { return; } + presenter.setUpdateSuspended(true); + menu.refreshItems(); + presenter.setUpdateSuspended(false); - final int menuSize = menu.size(); - if (menuSize != buttons.length) { - // The size has changed. Rebuild menu view from scratch. + if (!isMenuStructureSame()) { buildMenuView(); return; } int previousSelectedId = selectedItemId; + int menuSize = menu.size(); for (int i = 0; i < menuSize; i++) { - MenuItem item = menu.getItem(i); + MenuItem item = menu.getItemAt(i); if (item.isChecked()) { setCheckedItem(item); selectedItemId = item.getItemId(); @@ -998,14 +1193,19 @@ public void updateMenuView() { TransitionManager.beginDelayedTransition(this, set); } - boolean shifting = isShifting(labelVisibilityMode, menu.getVisibleItems().size()); + boolean shifting = + isShifting(labelVisibilityMode, getCurrentVisibleContentItemCount()); for (int i = 0; i < menuSize; i++) { presenter.setUpdateSuspended(true); - buttons[i].setLabelVisibilityMode(labelVisibilityMode); - buttons[i].setItemIconGravity(itemIconGravity); - buttons[i].setItemGravity(itemGravity); - buttons[i].setShifting(shifting); - buttons[i].initialize((MenuItemImpl) menu.getItem(i), 0); + buttons[i].setExpanded(expanded); + if (buttons[i] instanceof NavigationBarItemView) { + NavigationBarItemView itemView = (NavigationBarItemView) buttons[i]; + itemView.setLabelVisibilityMode(labelVisibilityMode); + itemView.setItemIconGravity(itemIconGravity); + itemView.setItemGravity(itemGravity); + itemView.setShifting(shifting); + } + buttons[i].initialize((MenuItemImpl) menu.getItemAt(i), 0); presenter.setUpdateSuspended(false); } } @@ -1018,6 +1218,18 @@ private NavigationBarItemView getNewItem() { return item; } + public void setCollapsedMaxItemCount(int collapsedMaxCount) { + this.collapsedMaxItemCount = collapsedMaxCount; + } + + private int getCollapsedVisibleItemCount() { + return min(collapsedMaxItemCount, menu.getVisibleMainContentItemCount()); + } + + public int getCurrentVisibleContentItemCount() { + return expanded ? menu.getVisibleContentItemCount() : getCollapsedVisibleItemCount(); + } + public int getSelectedItemId() { return selectedItemId; } @@ -1032,7 +1244,7 @@ protected boolean isShifting( void tryRestoreSelectedItemId(int itemId) { final int size = menu.size(); for (int i = 0; i < size; i++) { - MenuItem item = menu.getItem(i); + MenuItem item = menu.getItemAt(i); if (itemId == item.getItemId()) { selectedItemId = itemId; selectedItemPosition = i; @@ -1055,10 +1267,12 @@ void restoreBadgeDrawables(SparseArray badgeDrawables) { } } if (buttons != null) { - for (NavigationBarItemView itemView : buttons) { - BadgeDrawable badge = this.badgeDrawables.get(itemView.getId()); - if (badge != null) { - itemView.setBadge(badge); + for (NavigationBarMenuItemView itemView : buttons) { + if (itemView instanceof NavigationBarItemView) { + BadgeDrawable badge = this.badgeDrawables.get(((NavigationBarItemView) itemView).getId()); + if (badge != null) { + ((NavigationBarItemView) itemView).setBadge(badge); + } } } } @@ -1117,7 +1331,7 @@ private void removeUnusedBadges() { HashSet activeKeys = new HashSet<>(); // Remove keys from badgeDrawables that don't have a corresponding value in the menu. for (int i = 0; i < menu.size(); i++) { - activeKeys.add(menu.getItem(i).getItemId()); + activeKeys.add(menu.getItemAt(i).getItemId()); } for (int i = 0; i < badgeDrawables.size(); i++) { @@ -1132,9 +1346,11 @@ private void removeUnusedBadges() { public NavigationBarItemView findItemView(int menuItemId) { validateMenuItemId(menuItemId); if (buttons != null) { - for (NavigationBarItemView itemView : buttons) { - if (itemView.getId() == menuItemId) { - return itemView; + for (NavigationBarMenuItemView itemView : buttons) { + if (itemView instanceof NavigationBarItemView) { + if (((NavigationBarItemView) itemView).getId() == menuItemId) { + return (NavigationBarItemView) itemView; + } } } } @@ -1150,7 +1366,7 @@ protected int getSelectedItemPosition() { } @Nullable - protected MenuBuilder getMenu() { + protected NavigationBarMenuBuilder getMenu() { return menu; } diff --git a/material/java/com/google/android/material/navigation/NavigationBarPresenter.java b/material/java/com/google/android/material/navigation/NavigationBarPresenter.java index 4def96dc3..e113fa087 100644 --- a/material/java/com/google/android/material/navigation/NavigationBarPresenter.java +++ b/material/java/com/google/android/material/navigation/NavigationBarPresenter.java @@ -42,7 +42,6 @@ */ @RestrictTo(LIBRARY_GROUP) public class NavigationBarPresenter implements MenuPresenter { - private MenuBuilder menu; private NavigationBarMenuView menuView; private boolean updateSuspended = false; private int id; @@ -53,8 +52,7 @@ public void setMenuView(@NonNull NavigationBarMenuView menuView) { @Override public void initForMenu(@NonNull Context context, @NonNull MenuBuilder menu) { - this.menu = menu; - menuView.initialize(this.menu); + menuView.initialize(menu); } @Override diff --git a/material/java/com/google/android/material/navigation/NavigationBarSubMenu.java b/material/java/com/google/android/material/navigation/NavigationBarSubMenu.java new file mode 100644 index 000000000..7c75838e2 --- /dev/null +++ b/material/java/com/google/android/material/navigation/NavigationBarSubMenu.java @@ -0,0 +1,47 @@ +package com.google.android.material.navigation; + +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +import android.content.Context; +import androidx.appcompat.view.menu.MenuBuilder; +import androidx.appcompat.view.menu.MenuItemImpl; +import androidx.appcompat.view.menu.SubMenuBuilder; +import androidx.annotation.NonNull; +import androidx.annotation.RestrictTo; + +/** + * This is a {@link SubMenuBuilder} that it notifies the parent {@link NavigationBarMenu} of its + * menu updates. + * + * @hide + */ +@RestrictTo(LIBRARY_GROUP) +public class NavigationBarSubMenu extends SubMenuBuilder { + + public NavigationBarSubMenu( + @NonNull Context context, @NonNull NavigationBarMenu menu, @NonNull MenuItemImpl item) { + super(context, menu, item); + } + + @Override + public void onItemsChanged(boolean structureChanged) { + super.onItemsChanged(structureChanged); + ((MenuBuilder) getParentMenu()).onItemsChanged(structureChanged); + } +} diff --git a/material/java/com/google/android/material/navigation/NavigationBarSubheaderView.java b/material/java/com/google/android/material/navigation/NavigationBarSubheaderView.java new file mode 100644 index 000000000..702244ff5 --- /dev/null +++ b/material/java/com/google/android/material/navigation/NavigationBarSubheaderView.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.material.navigation; + +import com.google.android.material.R; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import androidx.appcompat.view.menu.MenuItemImpl; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; + +/** + * Provides a view that will be used to render subheader items inside a {@link + * NavigationBarMenuView}. + * + * @hide + */ +@RestrictTo(LIBRARY_GROUP) +public class NavigationBarSubheaderView extends FrameLayout + implements NavigationBarMenuItemView { + private final TextView subheaderLabel; + private boolean expanded; + boolean onlyShowWhenExpanded; + @Nullable + private MenuItemImpl itemData; + + NavigationBarSubheaderView(@NonNull Context context) { + super(context); + LayoutInflater.from(context) + .inflate(R.layout.m3_navigation_menu_subheader, this, true); + subheaderLabel = findViewById(R.id.navigation_menu_subheader_label); + } + + @Override + public void initialize(@NonNull MenuItemImpl menuItem, int i) { + this.itemData = menuItem; + menuItem.setCheckable(false); + subheaderLabel.setText(menuItem.getTitle()); + updateVisibility(); + } + + @Override + @Nullable + public MenuItemImpl getItemData() { + return itemData; + } + + @Override + public void setTitle(@Nullable CharSequence charSequence) {} + + @Override + public void setEnabled(boolean enabled) {} + + @Override + public void setCheckable(boolean checkable) {} + + @Override + public void setChecked(boolean checked) {} + + @Override + public void setShortcut(boolean showShortcut, char shortcutKey) {} + + @Override + public void setIcon(@Nullable Drawable drawable) {} + + @Override + public boolean prefersCondensedTitle() { + return false; + } + + @Override + public boolean showsIcon() { + return false; + } + + @Override + public void setExpanded(boolean expanded) { + this.expanded = expanded; + updateVisibility(); + } + + @Override + public boolean isExpanded() { + return this.expanded; + } + + @Override + public void setOnlyShowWhenExpanded(boolean onlyShowWhenExpanded) { + this.onlyShowWhenExpanded = onlyShowWhenExpanded; + updateVisibility(); + } + + @Override + public boolean isOnlyVisibleWhenExpanded() { + return this.onlyShowWhenExpanded; + } + + private void updateVisibility() { + if (itemData != null) { + setVisibility(itemData.isVisible() && (expanded || !onlyShowWhenExpanded) ? VISIBLE : GONE); + } + } +} diff --git a/material/java/com/google/android/material/navigation/NavigationBarView.java b/material/java/com/google/android/material/navigation/NavigationBarView.java index 97a29a8e5..df292f7fa 100644 --- a/material/java/com/google/android/material/navigation/NavigationBarView.java +++ b/material/java/com/google/android/material/navigation/NavigationBarView.java @@ -26,8 +26,6 @@ import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -203,11 +201,13 @@ public NavigationBarView( R.styleable.NavigationBarView_itemTextAppearanceActive); // Create the menu. - this.menu = new NavigationBarMenu(context, this.getClass(), getMaxItemCount()); + this.menu = + new NavigationBarMenu(context, this.getClass(), getMaxItemCount(), isSubMenuSupported()); // Create the menu view. menuView = createNavigationBarMenuView(context); menuView.setMinimumHeight(getSuggestedMinimumHeight()); + menuView.setCollapsedMaxItemCount(getMaxItemCount()); presenter.setMenuView(menuView); presenter.setId(MENU_PRESENTER_ID); @@ -239,6 +239,16 @@ public NavigationBarView( attributes.getResourceId(R.styleable.NavigationBarView_itemTextAppearanceActive, 0)); } + if (attributes.hasValue(R.styleable.NavigationBarView_horizontalItemTextAppearanceInactive)) { + setHorizontalItemTextAppearanceInactive( + attributes.getResourceId(R.styleable.NavigationBarView_horizontalItemTextAppearanceInactive, 0)); + } + + if (attributes.hasValue(R.styleable.NavigationBarView_horizontalItemTextAppearanceActive)) { + setHorizontalItemTextAppearanceActive( + attributes.getResourceId(R.styleable.NavigationBarView_horizontalItemTextAppearanceActive, 0)); + } + boolean isBold = attributes.getBoolean(R.styleable.NavigationBarView_itemTextAppearanceActiveBoldEnabled, true); setItemTextAppearanceActiveBoldEnabled(isBold); @@ -422,9 +432,7 @@ protected void onAttachedToWindow() { */ @Override public void setElevation(float elevation) { - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - super.setElevation(elevation); - } + super.setElevation(elevation); MaterialShapeUtils.setElevation(this, elevation); } @@ -1005,22 +1013,67 @@ public void setItemTextAppearanceActive(@StyleRes int textAppearanceRes) { } /** - * Sets whether the active menu item labels are bold. + * Returns the text appearance used for the active menu item label. * - * @param isBold whether the active menu item labels are bold + * @return the text appearance ID used for the active menu item label */ - public void setItemTextAppearanceActiveBoldEnabled(boolean isBold) { - menuView.setItemTextAppearanceActiveBoldEnabled(isBold); + @StyleRes + public int getItemTextAppearanceActive() { + return menuView.getItemTextAppearanceActive(); } /** - * Returns the text appearance used for the active menu item label. + * Sets the text appearance to be used for inactive menu item labels when they are in the + * horizontal item layout (when the start icon value is {@link + * ItemIconGravity#ITEM_ICON_GRAVITY_START}). + * + * @param textAppearanceRes the text appearance ID used for inactive menu item labels + */ + public void setHorizontalItemTextAppearanceInactive(@StyleRes int textAppearanceRes) { + menuView.setHorizontalItemTextAppearanceInactive(textAppearanceRes); + } + + /** + * Returns the text appearance used for inactive menu item labels when they are in the + * horizontal item layout (when the start icon value is {@link + * ItemIconGravity#ITEM_ICON_GRAVITY_START}). + * + * @return the text appearance ID used for inactive menu item labels + */ + @StyleRes + public int getHorizontalItemTextAppearanceInactive() { + return menuView.getHorizontalItemTextAppearanceInactive(); + } + + /** + * Sets the text appearance to be used for the menu item labels when they are in the horizontal + * item layout (when the start icon value is {@link ItemIconGravity#ITEM_ICON_GRAVITY_START}). + * + * @param textAppearanceRes the text appearance ID used for menu item labels + */ + public void setHorizontalItemTextAppearanceActive(@StyleRes int textAppearanceRes) { + menuView.setHorizontalItemTextAppearanceActive(textAppearanceRes); + } + + /** + * Returns the text appearance used for the active menu item label when they are in the + * horizontal item layout (when the start icon value is {@link + * ItemIconGravity#ITEM_ICON_GRAVITY_START}). * * @return the text appearance ID used for the active menu item label */ @StyleRes - public int getItemTextAppearanceActive() { - return menuView.getItemTextAppearanceActive(); + public int getHorizontalItemTextAppearanceActive() { + return menuView.getHorizontalItemTextAppearanceActive(); + } + + /** + * Sets whether the active menu item labels are bold. + * + * @param isBold whether the active menu item labels are bold + */ + public void setItemTextAppearanceActiveBoldEnabled(boolean isBold) { + menuView.setItemTextAppearanceActiveBoldEnabled(isBold); } /** @@ -1096,6 +1149,12 @@ public interface OnItemReselectedListener { /** Returns the maximum number of items that can be shown in NavigationBarView. */ public abstract int getMaxItemCount(); + /** Returns whether or not submenus are supported. */ + protected boolean isSubMenuSupported() { + // TODO: b/352634230 - NavigationRail should support submenus once ready + return false; + } + /** * Returns reference to a newly created {@link NavigationBarMenuView} * diff --git a/material/java/com/google/android/material/navigation/NavigationView.java b/material/java/com/google/android/material/navigation/NavigationView.java index 4014d7f10..89a83328a 100644 --- a/material/java/com/google/android/material/navigation/NavigationView.java +++ b/material/java/com/google/android/material/navigation/NavigationView.java @@ -285,10 +285,9 @@ public NavigationView(@NonNull Context context, @Nullable AttributeSet attrs, in MaterialResources.getColorStateList( context, a, R.styleable.NavigationView_itemRippleColor); - // Use a ripple matching the item's shape as the foreground for api level 21+ and if a ripple - // color is set. Otherwise the selectableItemBackground foreground from the item layout will - // be used - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && itemRippleColor != null) { + // Use a ripple matching the item's shape as the foreground if a ripple color is set. + // Otherwise the selectableItemBackground foreground from the item layout will be used. + if (itemRippleColor != null) { Drawable itemRippleMask = createDefaultItemDrawable(a, null); RippleDrawable ripple = new RippleDrawable( @@ -495,9 +494,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { @Override public void setElevation(float elevation) { - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - super.setElevation(elevation); - } + super.setElevation(elevation); MaterialShapeUtils.setElevation(this, elevation); } @@ -1130,7 +1127,7 @@ public void onGlobalLayout() { isOnLeftSide && (isRtl ? isEndInsetScrimEnabled() : isStartInsetScrimEnabled())); Activity activity = ContextUtils.getActivity(getContext()); - if (activity != null && VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (activity != null) { Rect displayBounds = WindowUtils.getCurrentWindowBounds(activity); boolean isBehindSystemNav = displayBounds.height() - getHeight() == tmpLocation[1]; diff --git a/material/java/com/google/android/material/navigation/res-public/values/public.xml b/material/java/com/google/android/material/navigation/res-public/values/public.xml index 87f745c60..b6510ff54 100644 --- a/material/java/com/google/android/material/navigation/res-public/values/public.xml +++ b/material/java/com/google/android/material/navigation/res-public/values/public.xml @@ -35,6 +35,8 @@ + + diff --git a/material/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_subheader.xml b/material/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_subheader.xml new file mode 100644 index 000000000..3204e2679 --- /dev/null +++ b/material/java/com/google/android/material/navigation/res/layout/m3_navigation_menu_subheader.xml @@ -0,0 +1,29 @@ + + + diff --git a/material/java/com/google/android/material/navigation/res/values/attrs.xml b/material/java/com/google/android/material/navigation/res/values/attrs.xml index 6aa006fd9..17d535f51 100644 --- a/material/java/com/google/android/material/navigation/res/values/attrs.xml +++ b/material/java/com/google/android/material/navigation/res/values/attrs.xml @@ -59,6 +59,19 @@ android:textColor in itemTextAppearanceActive. Instead, set itemTextColor to a ColorStateList to make the text color stateful. --> + + + + diff --git a/material/java/com/google/android/material/navigation/res/values/dimens.xml b/material/java/com/google/android/material/navigation/res/values/dimens.xml index d4b3a5533..3cf963769 100644 --- a/material/java/com/google/android/material/navigation/res/values/dimens.xml +++ b/material/java/com/google/android/material/navigation/res/values/dimens.xml @@ -44,7 +44,12 @@ 28dp 16dp 4dp - 16dp - 40dp + 16dp + 40dp + + 16dp + 8dp + 12dp + 20dp diff --git a/material/java/com/google/android/material/navigation/res/values/tokens.xml b/material/java/com/google/android/material/navigation/res/values/tokens.xml index 8fa76c5b7..1ef3b089b 100644 --- a/material/java/com/google/android/material/navigation/res/values/tokens.xml +++ b/material/java/com/google/android/material/navigation/res/values/tokens.xml @@ -15,58 +15,58 @@ ~ limitations under the License. --> - + - + ?attr/colorSurfaceContainerLow @dimen/m3_sys_elevation_level1 @dimen/m3_sys_elevation_level0 360dp - + ?attr/colorOnSecondaryContainer ?attr/colorOnSurfaceVariant ?attr/textAppearanceLabelLarge - + ?attr/colorOnSecondaryContainer ?attr/colorOnSurfaceVariant 24dp - + ?attr/colorOnSurfaceVariant ?attr/textAppearanceTitleSmall - + ?attr/colorSecondaryContainer - + sans-serif diff --git a/picasso3/build.gradle b/picasso3/build.gradle index 62190d757..131d27358 100644 --- a/picasso3/build.gradle +++ b/picasso3/build.gradle @@ -35,6 +35,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines") compileOnly("org.jetbrains.kotlin:kotlin-annotations-jvm:$kotlin_version") implementation("androidx.annotation:annotation:$annotationVersion") + implementation("androidx.collection:collection-ktx:$collectionVersion") implementation("androidx.exifinterface:exifinterface:$exifinterfaceVersion") implementation("androidx.lifecycle:lifecycle-common:$lifecycleVersion") implementation("androidx.core:core-ktx:$coreVersion")