From 94a37ee0eb22016717e2d2fecacd9475e11a183b Mon Sep 17 00:00:00 2001 From: Alexander Dadukin Date: Sat, 3 Apr 2021 11:54:33 +0100 Subject: [PATCH 1/4] Implemented menu class with support of dynamic reconstruction --- app/build.gradle | 6 +- .../screens/JavaActivity.java | 28 +- .../screens/NotificationBadgeActivity.kt | 6 +- .../ProgrammaticallyCreatedDemoActivity.kt | 84 ++++-- .../screens/XmlDeclaredActivity.kt | 2 +- .../navigation/NavigationComponentActivity.kt | 1 - lib-expandablebottombar/build.gradle | 6 +- .../ExpandableBottomBar.kt | 281 ++++++------------ .../com/st235/lib_expandablebottombar/Menu.kt | 55 ++++ .../st235/lib_expandablebottombar/MenuImpl.kt | 164 ++++++++++ .../st235/lib_expandablebottombar/MenuItem.kt | 5 +- .../MenuItemDescriptor.kt | 93 +++--- .../MenuItemFactory.kt | 70 +++++ .../lib_expandablebottombar/MenuItemImpl.kt | 179 +++-------- .../ExpandableBottomBarNavigationUI.kt | 10 +- .../state/BottomBarSavedState.kt | 2 +- 16 files changed, 554 insertions(+), 438 deletions(-) create mode 100644 lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/Menu.kt create mode 100644 lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuImpl.kt create mode 100644 lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemFactory.kt diff --git a/app/build.gradle b/app/build.gradle index cf417e6..a03a548 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,9 +28,9 @@ dependencies { implementation project(':lib-expandablebottombar') implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.2.1' + implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2' - implementation 'androidx.navigation:navigation-ui-ktx:2.3.2' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.4' } diff --git a/app/src/main/java/github/com/st235/expandablebottombar/screens/JavaActivity.java b/app/src/main/java/github/com/st235/expandablebottombar/screens/JavaActivity.java index e9587a0..5fec78b 100644 --- a/app/src/main/java/github/com/st235/expandablebottombar/screens/JavaActivity.java +++ b/app/src/main/java/github/com/st235/expandablebottombar/screens/JavaActivity.java @@ -8,6 +8,8 @@ import github.com.st235.expandablebottombar.R; import github.com.st235.lib_expandablebottombar.ExpandableBottomBar; +import github.com.st235.lib_expandablebottombar.Menu; +import github.com.st235.lib_expandablebottombar.MenuItem; import github.com.st235.lib_expandablebottombar.MenuItemDescriptor; public class JavaActivity extends AppCompatActivity { @@ -20,22 +22,30 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_java); ExpandableBottomBar bottomBar = findViewById(R.id.expandable_bottom_bar); + Menu menu = bottomBar.getMenu(); - bottomBar.addItems( - new MenuItemDescriptor.Builder(this) - .addItem(R.id.icon_home, R.drawable.ic_home, R.string.text, Color.GRAY) - .addItem(R.id.icon_likes, R.drawable.ic_likes, R.string.text2, 0xffff77a9) - .addItem(R.id.icon_bookmarks, R.drawable.ic_bookmarks, R.string.text3, 0xff58a5f0) - .addItem(R.id.icon_settings, R.drawable.ic_settings, R.string.text4, 0xffbe9c91) - .build() + menu.add( + new MenuItemDescriptor.Builder(this, R.id.icon_home, R.drawable.ic_home, R.string.text, Color.GRAY).build() ); - bottomBar.setOnItemSelectedListener((view, item) -> { + menu.add( + new MenuItemDescriptor.Builder(this, R.id.icon_likes, R.drawable.ic_likes, R.string.text2, 0xffff77a9).build() + ); + + menu.add( + new MenuItemDescriptor.Builder(this, R.id.icon_bookmarks, R.drawable.ic_bookmarks, R.string.text3, 0xff58a5f0).build() + ); + + menu.add( + new MenuItemDescriptor.Builder(this, R.id.icon_settings, R.drawable.ic_settings, R.string.text4, 0xffbe9c91).build() + ); + + bottomBar.setOnItemSelectedListener((view, item, byUser) -> { Log.d(TAG, "selected: " + item.toString()); return null; }); - bottomBar.setOnItemReselectedListener((view, item) -> { + bottomBar.setOnItemReselectedListener((view, item, byUser) -> { Log.d(TAG, "reselected: " + item.toString()); return null; }); diff --git a/app/src/main/java/github/com/st235/expandablebottombar/screens/NotificationBadgeActivity.kt b/app/src/main/java/github/com/st235/expandablebottombar/screens/NotificationBadgeActivity.kt index a33870a..97ad996 100644 --- a/app/src/main/java/github/com/st235/expandablebottombar/screens/NotificationBadgeActivity.kt +++ b/app/src/main/java/github/com/st235/expandablebottombar/screens/NotificationBadgeActivity.kt @@ -27,7 +27,7 @@ class NotificationBadgeActivity : AppCompatActivity() { color.setBackgroundColor(ColorUtils.setAlphaComponent(Color.GRAY, 60)) - bottomBar.onItemSelectedListener = { v, i -> + bottomBar.onItemSelectedListener = { v, i, _ -> val anim = ViewAnimationUtils.createCircularReveal(color, bottomBar.x.toInt() + v.x.toInt() + v.width / 2, bottomBar.y.toInt() + v.y.toInt() + v.height / 2, 0F, @@ -37,7 +37,7 @@ class NotificationBadgeActivity : AppCompatActivity() { anim.start() } - bottomBar.onItemReselectedListener = { v, i -> + bottomBar.onItemReselectedListener = { v, i, _ -> val notification = i.notification() if (v.tag == null) { @@ -59,7 +59,7 @@ class NotificationBadgeActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.clear -> { - for (menuItem in bottomBar.getMenuItems()) { + for (menuItem in bottomBar.menu) { menuItem.notification().clear() } } diff --git a/app/src/main/java/github/com/st235/expandablebottombar/screens/ProgrammaticallyCreatedDemoActivity.kt b/app/src/main/java/github/com/st235/expandablebottombar/screens/ProgrammaticallyCreatedDemoActivity.kt index 191f216..fd34d0d 100644 --- a/app/src/main/java/github/com/st235/expandablebottombar/screens/ProgrammaticallyCreatedDemoActivity.kt +++ b/app/src/main/java/github/com/st235/expandablebottombar/screens/ProgrammaticallyCreatedDemoActivity.kt @@ -2,6 +2,8 @@ package github.com.st235.expandablebottombar.screens import android.graphics.Color import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.util.Log import android.view.View import android.view.ViewAnimationUtils @@ -18,45 +20,69 @@ class ProgrammaticallyCreatedDemoActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_programmatically_declared) - val color: View = findViewById(R.id.color) + val colorView: View = findViewById(R.id.color) val bottomBar: ExpandableBottomBar = findViewById(R.id.expandable_bottom_bar) - color.setBackgroundColor(ColorUtils.setAlphaComponent(Color.GRAY, 60)) - - bottomBar.addItems( - MenuItemDescriptor.Builder(this) - .addItem( - R.id.icon_home, - R.drawable.ic_home, - R.string.text, Color.GRAY) - .addItem( - R.id.icon_likes, - R.drawable.ic_likes - ).textRes(R.string.text2).colorRes( - R.color.colorLike - ).create() - .addItem( - R.id.icon_bookmarks, - R.drawable.ic_bookmarks, - R.string.text3, Color.parseColor("#58a5f0")) - .addItem( - R.id.icon_settings, - R.drawable.ic_settings, - R.string.text4, Color.parseColor("#be9c91")) - .build() + colorView.setBackgroundColor(ColorUtils.setAlphaComponent(Color.GRAY, 60)) + + val menu = bottomBar.menu + + menu.add( + MenuItemDescriptor.Builder( + this, + R.id.icon_home, + R.drawable.ic_home, + R.string.text, Color.GRAY + ) + .build() + ) + + menu.add( + MenuItemDescriptor.Builder( + this, + R.id.icon_likes, + R.drawable.ic_likes + ) + .textRes(R.string.text2) + .colorRes(R.color.colorLike) + .build() + ) + + menu.add( + MenuItemDescriptor.Builder( + this, + R.id.icon_bookmarks, + R.drawable.ic_bookmarks, + R.string.text3, + Color.parseColor("#58a5f0") + ) + .build() + ) + + menu.add( + MenuItemDescriptor.Builder( + this, + R.id.icon_settings, + R.drawable.ic_settings, + R.string.text4, + Color.parseColor("#be9c91") + ) + .build() ) - bottomBar.onItemSelectedListener = { v, i -> - val anim = ViewAnimationUtils.createCircularReveal(color, + bottomBar.onItemSelectedListener = { v, i, _ -> + val anim = ViewAnimationUtils.createCircularReveal( + colorView, bottomBar.x.toInt() + v.x.toInt() + v.width / 2, bottomBar.y.toInt() + v.y.toInt() + v.height / 2, 0F, - findViewById(android.R.id.content).height.toFloat()) - color.setBackgroundColor(ColorUtils.setAlphaComponent(i.activeColor, 60)) + findViewById(android.R.id.content).height.toFloat() + ) + colorView.setBackgroundColor(ColorUtils.setAlphaComponent(i.activeColor, 60)) anim.duration = 420 anim.start() } - bottomBar.onItemReselectedListener = { _, i -> + bottomBar.onItemReselectedListener = { _, i, _ -> Log.d("ExpandableBottomBar", "OnReselected: ${i.id}") } } diff --git a/app/src/main/java/github/com/st235/expandablebottombar/screens/XmlDeclaredActivity.kt b/app/src/main/java/github/com/st235/expandablebottombar/screens/XmlDeclaredActivity.kt index 026f818..ea9f7f8 100644 --- a/app/src/main/java/github/com/st235/expandablebottombar/screens/XmlDeclaredActivity.kt +++ b/app/src/main/java/github/com/st235/expandablebottombar/screens/XmlDeclaredActivity.kt @@ -23,7 +23,7 @@ class XmlDeclaredActivity : AppCompatActivity() { color.setBackgroundColor(ColorUtils.setAlphaComponent(Color.GRAY, 60)) - bottomBar.onItemSelectedListener = { v, i -> + bottomBar.onItemSelectedListener = { v, i, _ -> val anim = ViewAnimationUtils.createCircularReveal(color, bottomBar.x.toInt() + v.x.toInt() + v.width / 2, bottomBar.y.toInt() + v.y.toInt() + v.height / 2, 0F, diff --git a/app/src/main/java/github/com/st235/expandablebottombar/screens/navigation/NavigationComponentActivity.kt b/app/src/main/java/github/com/st235/expandablebottombar/screens/navigation/NavigationComponentActivity.kt index 81656f2..21d49dd 100644 --- a/app/src/main/java/github/com/st235/expandablebottombar/screens/navigation/NavigationComponentActivity.kt +++ b/app/src/main/java/github/com/st235/expandablebottombar/screens/navigation/NavigationComponentActivity.kt @@ -3,7 +3,6 @@ package github.com.st235.expandablebottombar.screens.navigation import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.navigation.Navigation -import androidx.navigation.ui.NavigationUI import github.com.st235.expandablebottombar.R import github.com.st235.lib_expandablebottombar.navigation.ExpandableBottomBarNavigationUI import kotlinx.android.synthetic.main.activiy_navigation.* diff --git a/lib-expandablebottombar/build.gradle b/lib-expandablebottombar/build.gradle index 186f069..4078e1d 100644 --- a/lib-expandablebottombar/build.gradle +++ b/lib-expandablebottombar/build.gradle @@ -37,11 +37,11 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.2.1' + implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4' - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test.ext:junit:1.1.2' diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/ExpandableBottomBar.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/ExpandableBottomBar.kt index 010f72b..bc070fc 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/ExpandableBottomBar.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/ExpandableBottomBar.kt @@ -8,20 +8,13 @@ import android.graphics.Outline import android.graphics.Rect import android.os.Build import android.os.Parcelable -import android.transition.AutoTransition -import android.transition.TransitionManager import android.util.AttributeSet import android.view.Gravity import android.view.View import android.view.ViewOutlineProvider -import androidx.annotation.ColorInt -import androidx.annotation.ColorRes -import androidx.annotation.FloatRange -import androidx.annotation.DimenRes -import androidx.annotation.IdRes +import androidx.annotation.* import androidx.annotation.IntRange import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat @@ -31,17 +24,6 @@ import github.com.st235.lib_expandablebottombar.behavior.ExpandableBottomBarBeha import github.com.st235.lib_expandablebottombar.parsers.ExpandableBottomBarParser import github.com.st235.lib_expandablebottombar.state.BottomBarSavedState import github.com.st235.lib_expandablebottombar.utils.* -import github.com.st235.lib_expandablebottombar.utils.DrawableHelper -import github.com.st235.lib_expandablebottombar.utils.StyleController -import github.com.st235.lib_expandablebottombar.utils.applyForApiLAndHigher -import github.com.st235.lib_expandablebottombar.utils.clamp -import github.com.st235.lib_expandablebottombar.utils.min -import github.com.st235.lib_expandablebottombar.utils.toPx - - -internal const val ITEM_NOT_SELECTED = -1 - -typealias OnItemClickListener = (v: View, menuItem: MenuItem) -> Unit /** * Widget, which implements bottom bar navigation pattern @@ -63,14 +45,21 @@ class ExpandableBottomBar @JvmOverloads constructor( private val bounds = Rect() - @FloatRange(from = 0.0, to = 1.0) private var itemBackgroundOpacity: Float = 0F - @FloatRange(from = 0.0) private var itemBackgroundCornerRadius: Float = 0F - @IntRange(from = 0) private var menuItemHorizontalMargin: Int = 0 - @IntRange(from = 0) private var menuItemVerticalMargin: Int = 0 - @IntRange(from = 0) private var menuHorizontalPadding: Int = 0 - @IntRange(from = 0) private var menuVerticalPadding: Int = 0 + @FloatRange(from = 0.0, to = 1.0) private val itemBackgroundOpacity: Float + @FloatRange(from = 0.0) private val itemBackgroundCornerRadius: Float + @IntRange(from = 0) private val menuItemHorizontalMargin: Int + @IntRange(from = 0) private val menuItemVerticalMargin: Int + @IntRange(from = 0) private val menuHorizontalPadding: Int + @IntRange(from = 0) private val menuVerticalPadding: Int + @ColorInt private val itemInactiveColor: Int + @ColorInt private val globalBadgeColor: Int + @ColorInt private val globalBadgeTextColor: Int + private val transitionDuration: Int + private val menuImpl: MenuImpl + private val styleController: StyleController + private val stateController = ExpandableBottomBarStateController(this) + - @ColorInt private var itemInactiveColor: Int = Color.BLACK @FloatRange(from = 0.0) private var backgroundCornerRadius: Float = 0F set(value) { field = value @@ -79,44 +68,42 @@ class ExpandableBottomBar @JvmOverloads constructor( } } - @ColorInt - private var globalBadgeColor: Int = Color.RED - @ColorInt - private var globalBadgeTextColor: Int = Color.WHITE - - private var transitionDuration: Int = 0 - - @IdRes private var selectedItemId: Int = ITEM_NOT_SELECTED + val menu: Menu + get() { + return menuImpl + } - private val menuItems: MutableMap = mutableMapOf() - private val stateController = ExpandableBottomBarStateController(this) - private lateinit var styleController: StyleController + var onItemSelectedListener: OnItemClickListener? + get() { + return menu.onItemSelectedListener + } + set(value) { + menu.onItemSelectedListener = value + } - var onItemSelectedListener: OnItemClickListener? = null - var onItemReselectedListener: OnItemClickListener? = null + var onItemReselectedListener: OnItemClickListener? + get() { + return menu.onItemReselectedListener + } + set(value) { + menu.onItemReselectedListener = value + } private var animator: Animator? = null init { - initAttrs(context, attrs, defStyleAttr) - } - - override fun getBehavior(): CoordinatorLayout.Behavior<*> = - ExpandableBottomBarBehavior() - - private fun initAttrs(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { - if (attrs == null) { - return - } - if (id == View.NO_ID) { id = View.generateViewId() } contentDescription = resources.getString(R.string.accessibility_description) - val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandableBottomBar, - defStyleAttr, R.style.ExpandableBottomBar) + val typedArray = context.obtainStyledAttributes( + attrs, + R.styleable.ExpandableBottomBar, + defStyleAttr, + R.style.ExpandableBottomBar + ) itemBackgroundOpacity = typedArray.getFloat(R.styleable.ExpandableBottomBar_exb_itemBackgroundOpacity, 0.2F) itemBackgroundCornerRadius = typedArray.getDimension(R.styleable.ExpandableBottomBar_exb_itemBackgroundCornerRadius, 30F.toPx()) @@ -145,16 +132,33 @@ class ExpandableBottomBar @JvmOverloads constructor( clipToOutline = true } + val menuItemFactory = MenuItemFactory( + this, + styleController, + menuVerticalPadding, + menuHorizontalPadding, + itemBackgroundCornerRadius, + itemBackgroundOpacity, + itemInactiveColor, globalBadgeColor, + globalBadgeTextColor + ) + menuImpl = MenuImpl(this, menuItemFactory, menuItemHorizontalMargin, menuItemVerticalMargin, transitionDuration.toLong()) + val menuId = typedArray.getResourceId(R.styleable.ExpandableBottomBar_exb_items, View.NO_ID) if (menuId != View.NO_ID) { val barParser = ExpandableBottomBarParser(context) val items = barParser.inflate(menuId) - addItems(items) + for (item in items) { + menuImpl.add(item) + } } typedArray.recycle() } + override fun getBehavior(): CoordinatorLayout.Behavior<*> = + ExpandableBottomBarBehavior() + override fun setBackgroundColor(@ColorInt color: Int) { setBackgroundColor(color, backgroundCornerRadius) } @@ -173,24 +177,6 @@ class ExpandableBottomBar @JvmOverloads constructor( setBackgroundColor(ContextCompat.getColor(context, colorRes), resources.getDimension(backgroundCornerRadiusRes)) } - fun setNotificationBadgeBackgroundColor(@ColorInt color: Int) { - globalBadgeColor = color - invalidate() - } - - fun setNotificationBadgeBackgroundColorRes(@ColorRes colorRes: Int) { - setNotificationBadgeBackgroundColor(ContextCompat.getColor(context, colorRes)) - } - - fun setNotificationBadgeTextColor(@ColorInt color: Int) { - globalBadgeTextColor = color - invalidate() - } - - fun setNotificationBadgeTextColorRes(@ColorRes colorRes: Int) { - setNotificationBadgeTextColor(ContextCompat.getColor(context, colorRes)) - } - override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -219,78 +205,6 @@ class ExpandableBottomBar @JvmOverloads constructor( bounds.set(0, 0, w, h) } - /** - * Returns menu item for the given id value - * - * @throws NullPointerException when id doesn't exists - */ - fun getMenuItemFor(@IdRes id: Int): MenuItem { - return menuItems.getValue(id) - } - - /** - * Returns notification for passed menu item - * - * @throws NullPointerException when id doesn't exists - */ - @Deprecated( - message = "This method was replaced with MenuItem#notification", - replaceWith = ReplaceWith(expression = "this.getMenuItemFor(id = id).notification()"), - level = DeprecationLevel.ERROR - ) - fun getNotificationFor(@IdRes id: Int): Notification { - return menuItems.getValue(id).notification() - } - - /** - * Adds passed items to widget - * - * @param itemDescriptors - bottom bar menu items - */ - fun addItems(itemDescriptors: List) { - menuItems.clear() - - val firstItemId = itemDescriptors.first().itemId - val lastItemId = itemDescriptors.last().itemId - selectedItemId = firstItemId - - for ((i, item) in itemDescriptors.withIndex()) { - val viewController = createItem(item) - menuItems[item.itemId] = viewController - - val prevIconId = if (i - 1 < 0) firstItemId else itemDescriptors[i - 1].itemId - val nextIconId = if (i + 1 >= itemDescriptors.size) lastItemId else itemDescriptors[i + 1].itemId - - viewController.attachTo( - this, - prevIconId, nextIconId, - menuItemHorizontalMargin, menuItemVerticalMargin - ) - } - - madeMenuItemsAccessible(itemDescriptors) - } - - /** - * Returns all menu items - */ - fun getMenuItems(): List = menuItems.values.toList() - - /** - * Programmatically select item - * - * @param id - identifier of menu item, which should be selected - */ - fun select(@IdRes id: Int) { - val itemToSelect = menuItems.getValue(id) - onItemSelected(itemToSelect) - } - - /** - * Returns currently selected item - */ - fun getSelected(): MenuItem = menuItems.getValue(selectedItemId) - /** * Shows the bottom bar */ @@ -323,68 +237,39 @@ class ExpandableBottomBar @JvmOverloads constructor( } } - private fun madeMenuItemsAccessible(itemDescriptors: List) { - for ((i, item) in itemDescriptors.withIndex()) { - val prev = menuItems[itemDescriptors.getOrNull(i - 1)?.itemId] - val next = menuItems[itemDescriptors.getOrNull(i + 1)?.itemId] - - menuItems[item.itemId]?.setAccessibleWith(prev = prev, next = next) - } - } - - private fun createItem(menuItemDescriptor: MenuItemDescriptor): MenuItemImpl { - val menuItem = - MenuItemImpl.Builder(menuItemDescriptor) - .styleController(styleController) - .itemMargins(menuHorizontalPadding, menuVerticalPadding) - .itemBackground(itemBackgroundCornerRadius, itemBackgroundOpacity) - .itemInactiveColor(itemInactiveColor) - .notificationBadgeColor(globalBadgeColor) - .notificationBadgeTextColor(globalBadgeTextColor) - .onItemClickListener { menuItem: MenuItem, v: View -> - if (!v.isSelected) { - onItemSelected(menuItem) - onItemSelectedListener?.invoke(v, menuItem) - } else { - onItemReselectedListener?.invoke(v, menuItem) - } - } - .build(this) - - if (selectedItemId == menuItemDescriptor.itemId) { - menuItem.select() - } - - return menuItem - } - - private fun onItemSelected(activeMenuItem: MenuItem) { - if (selectedItemId == activeMenuItem.id) { - return - } - - delayTransition(duration = transitionDuration.toLong()) - - val set = ConstraintSet() - set.clone(this) - - menuItems.getValue(activeMenuItem.id).select() - menuItems.getValue(selectedItemId).deselect() - selectedItemId = activeMenuItem.id - - set.applyTo(this) - } - internal class ExpandableBottomBarStateController( private val expandableBottomBar: ExpandableBottomBar ) { - fun store(superState: Parcelable?) = BottomBarSavedState(expandableBottomBar.selectedItemId, superState) + fun store(superState: Parcelable?): Parcelable { + val selectedItem = expandableBottomBar.menu.selectedItem + return BottomBarSavedState(selectedItem?.id, superState) + } fun restore(stateBottomBar: BottomBarSavedState) { val selectedItemId = stateBottomBar.selectedItem - val menuItem = expandableBottomBar.menuItems.getValue(selectedItemId) - expandableBottomBar.onItemSelected(menuItem) + + expandableBottomBar.menu.doSilently { menu -> + if (selectedItemId != null) { + try { + menu.select(selectedItemId) + } catch (e: IllegalArgumentException) { + // catch exception here as it is possible that + // menu item do not exists and should be added later + menu.deselect() + } + } + } + } + + private inline fun Menu.doSilently(scope: (menu: Menu) -> Unit) { + val selectedItemListener = onItemSelectedListener + val reselectedItemListener = onItemReselectedListener + onItemSelectedListener = null + onItemReselectedListener = null + scope(this) + onItemSelectedListener = selectedItemListener + onItemReselectedListener = reselectedItemListener } } diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/Menu.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/Menu.kt new file mode 100644 index 0000000..6b29666 --- /dev/null +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/Menu.kt @@ -0,0 +1,55 @@ +package github.com.st235.lib_expandablebottombar + +import android.view.View +import androidx.annotation.IdRes +import kotlin.jvm.Throws + +typealias OnItemClickListener = (v: View, menuItem: MenuItem, byUser: Boolean) -> Unit + +interface Menu: Iterable { + + var onItemSelectedListener: OnItemClickListener? + + var onItemReselectedListener: OnItemClickListener? + + /** + * Returns currently selected item + */ + val selectedItem: MenuItem? + + /** + * Returns all menu items + */ + val items: List + + /** + * Adds passed item to widget + * + * @param descriptor - bottom bar menu item descriptor + */ + fun add(descriptor: MenuItemDescriptor) + + /** + * Programmatically select item + * + * @param id - identifier of menu item, which should be selected + */ + @Throws(IllegalArgumentException::class) + fun select(@IdRes id: Int) + + fun deselect() + + @Throws(IllegalArgumentException::class) + fun remove(@IdRes id: Int) + + fun removeAll() + + /** + * Returns menu item for the given id value + * + * @throws NullPointerException when id doesn't exists + */ + @Throws(IllegalArgumentException::class) + fun findItemById(@IdRes id: Int): MenuItem + +} \ No newline at end of file diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuImpl.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuImpl.kt new file mode 100644 index 0000000..119ffe8 --- /dev/null +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuImpl.kt @@ -0,0 +1,164 @@ +package github.com.st235.lib_expandablebottombar + +import android.view.View +import androidx.annotation.IdRes +import github.com.st235.lib_expandablebottombar.utils.delayTransition +import kotlin.collections.ArrayList +import kotlin.collections.LinkedHashMap + +internal class MenuImpl( + private val rootView: ExpandableBottomBar, + private val itemFactory: MenuItemFactory, + private val menuItemHorizontalMargin: Int, + private val menuItemVerticalMargin: Int, + private val transitionDuration: Long +): Menu { + + private companion object { + const val ITEM_NOT_SELECTED = -1 + } + + @IdRes + private var selectedItemId: Int = ITEM_NOT_SELECTED + + private val itemsLookup = LinkedHashMap() + + override val selectedItem: MenuItem? + get() { + if (selectedItemId == ITEM_NOT_SELECTED) { + return null + } + + return itemsLookup.getValue(selectedItemId) + } + + override val items: List + get() { + return ArrayList(itemsLookup.values) + } + + override var onItemSelectedListener: OnItemClickListener? = null + + override var onItemReselectedListener: OnItemClickListener? = null + + override fun iterator(): Iterator { + return items.iterator() + } + + override fun add(descriptor: MenuItemDescriptor) { + rootView.delayTransition(duration = transitionDuration) + + if (selectedItemId == ITEM_NOT_SELECTED) { + selectedItemId = descriptor.itemId + } + + val item = createItem(descriptor) + itemsLookup[descriptor.itemId] = item + + item.attachToSuperView(menuItemHorizontalMargin, menuItemVerticalMargin) + reconnectConstraints() + updateAccessibility() + } + + private fun reconnectConstraints() { + val itemsList = items + + for ((index, item) in itemsList.withIndex()) { + val prevIconId = + if (index - 1 < 0) itemsList.first().id else itemsList[index - 1].id + val nextIconId = + if (index + 1 >= itemsList.size) itemsList.last().id else itemsList[index + 1].id + + (item as? MenuItemImpl)?.rebuildSiblingsConnection(prevIconId, nextIconId) + } + } + + override fun select(@IdRes id: Int) { + if (!itemsLookup.containsKey(id)) { + throw IllegalArgumentException("Cannot select item with id $id because it was not found in the menu") + } + + selectItemInternal(itemsLookup.getValue(id)) + val selectedItem = selectedItem!! as MenuItemImpl + onItemSelectedListener?.invoke(selectedItem.getView(), selectedItem, false) + } + + override fun deselect() { + if (itemsLookup.isEmpty()) { + return + } + + val firstMenuItem = items.find { it.isAttached && it.isShown } as MenuItemImpl + selectItemInternal(firstMenuItem) + onItemSelectedListener?.invoke(firstMenuItem.getView(), firstMenuItem, false) + } + + override fun remove(@IdRes id: Int) { + if (!itemsLookup.containsKey(id)) { + throw IllegalArgumentException("Cannot remove item with id $id because it was not found in the menu") + } + + rootView.delayTransition(duration = transitionDuration) + + val menuItemToRemove = itemsLookup.getValue(id) + menuItemToRemove.removeFromSuperView() + itemsLookup.remove(id) + reconnectConstraints() + + if (selectedItemId == id) { + deselect() + } + } + + override fun removeAll() { + itemsLookup.clear() + } + + override fun findItemById(@IdRes id: Int): MenuItem { + if (!itemsLookup.containsKey(id)) { + throw IllegalArgumentException("Cannot find item with id $id") + } + + return itemsLookup.getValue(id) + } + + private fun selectItemInternal(activeMenuItem: MenuItem) { + if (selectedItemId == activeMenuItem.id) { + return + } + + rootView.delayTransition(duration = transitionDuration) + + itemsLookup.getValue(activeMenuItem.id).select() + // can be removed from the menu, that's why can be nullable + itemsLookup[selectedItemId]?.deselect() + selectedItemId = activeMenuItem.id + } + + private fun createItem(menuItemDescriptor: MenuItemDescriptor): MenuItemImpl { + val menuItem = itemFactory.build(menuItemDescriptor) { menuItem: MenuItem, v: View -> + if (!v.isSelected) { + selectItemInternal(menuItem) + onItemSelectedListener?.invoke(v, menuItem, true) + } else { + onItemReselectedListener?.invoke(v, menuItem, true) + } + } + + if (selectedItemId == menuItemDescriptor.itemId) { + menuItem.select() + } + + return menuItem + } + + private fun updateAccessibility() { + val items = items + for ((i, item) in items.withIndex()) { + val prev = itemsLookup[items.getOrNull(i - 1)?.id] + val next = itemsLookup[items.getOrNull(i + 1)?.id] + + itemsLookup[item.id]?.setAccessibleWith(prev = prev, next = next) + } + } +} \ No newline at end of file diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItem.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItem.kt index 4a4c92f..128e06c 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItem.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItem.kt @@ -15,6 +15,8 @@ interface MenuItem { val isShown: Boolean + val isAttached: Boolean + /** * Shows item */ @@ -22,10 +24,7 @@ interface MenuItem { /** * Hides item - * - * @throws IllegalStateException when item is selected */ - @Throws(IllegalStateException::class) fun hide() fun notification(): Notification diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemDescriptor.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemDescriptor.kt index bfd9ce3..eb44780 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemDescriptor.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemDescriptor.kt @@ -8,7 +8,8 @@ import androidx.annotation.DrawableRes import androidx.annotation.IdRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat -import java.lang.IllegalStateException +import kotlin.IllegalStateException +import kotlin.jvm.Throws /** * Menu item for expandable bottom bar @@ -21,10 +22,33 @@ data class MenuItemDescriptor( @ColorInt val badgeBackgroundColor: Int?, @ColorInt val badgeTextColor: Int? ) { - class BuildRequest internal constructor( - private val builder: Builder, + class Builder { + + constructor(context: Context) { + this.context = context + } + + constructor(context: Context, @IdRes itemId: Int, @DrawableRes iconId: Int) { + this.context = context + this.itemId = itemId + this.iconId = iconId + } + + constructor( + context: Context, + @IdRes itemId: Int, + @DrawableRes iconId: Int, + @StringRes textId: Int, + @ColorInt activeColor: Int = Color.BLACK + ) { + this.context = context + this.itemId = itemId + this.iconId = iconId + this.text = context.getText(textId) + this.activeColor = activeColor + } + private val context: Context - ) { @IdRes private var itemId: Int = 0 @@ -40,52 +64,52 @@ data class MenuItemDescriptor( @ColorInt var badgeTextColor: Int? = null - fun id(@IdRes id: Int): BuildRequest { + fun id(@IdRes id: Int): Builder { this.itemId = id return this } - fun icon(@DrawableRes iconId: Int): BuildRequest { + fun icon(@DrawableRes iconId: Int): Builder { this.iconId = iconId return this } - fun text(text: CharSequence): BuildRequest { + fun text(text: CharSequence): Builder { this.text = text return this } - fun textRes(@StringRes textId: Int): BuildRequest { + fun textRes(@StringRes textId: Int): Builder { this.text = context.getText(textId) return this } - fun color(@ColorInt color: Int): BuildRequest { + fun color(@ColorInt color: Int): Builder { this.activeColor = color return this } - fun colorRes(@ColorRes colorId: Int): BuildRequest { + fun colorRes(@ColorRes colorId: Int): Builder { this.activeColor = ContextCompat.getColor(context, colorId) return this } - fun badgeBackgroundColor(@ColorInt badgeBackgroundColor: Int): BuildRequest { + fun badgeBackgroundColor(@ColorInt badgeBackgroundColor: Int): Builder { this.badgeBackgroundColor = badgeBackgroundColor return this } - fun badgeBackgroundColorRes(@ColorRes badgeBackgroundColorRes: Int): BuildRequest { + fun badgeBackgroundColorRes(@ColorRes badgeBackgroundColorRes: Int): Builder { this.badgeBackgroundColor = ContextCompat.getColor(context, badgeBackgroundColorRes) return this } - fun badgeTextColor(@ColorInt badgeTextColor: Int): BuildRequest { + fun badgeTextColor(@ColorInt badgeTextColor: Int): Builder { this.badgeTextColor = badgeTextColor return this } - fun badgeTextColorRes(@ColorRes badgeTextColorRes: Int): BuildRequest { + fun badgeTextColorRes(@ColorRes badgeTextColorRes: Int): Builder { this.badgeTextColor = ContextCompat.getColor(context, badgeTextColorRes) return this } @@ -100,41 +124,18 @@ data class MenuItemDescriptor( } } - fun create(): Builder { + @Throws(IllegalStateException::class) + fun build(): MenuItemDescriptor { assertValidity() - builder.items.add( - MenuItemDescriptor( - itemId, - iconId, - text!!, - activeColor!!, - badgeBackgroundColor, - badgeTextColor - ) + return MenuItemDescriptor( + itemId, + iconId, + text!!, + activeColor!!, + badgeBackgroundColor, + badgeTextColor ) - return builder } } - /** - * Class-helper to create expandable bottom bar menu items - */ - class Builder(private val context: Context) { - internal val items = mutableListOf() - - fun addItem() = BuildRequest(this, context) - - fun addItem(@IdRes itemId: Int, @DrawableRes iconId: Int) = - BuildRequest(this, context).id(itemId).icon(iconId) - - fun addItem( - @IdRes itemId: Int, - @DrawableRes iconId: Int, - @StringRes textId: Int, - @ColorInt activeColor: Int = Color.BLACK - ) = BuildRequest(this, context).id(itemId).icon(iconId).textRes(textId) - .color(activeColor).create() - - fun build(): List = items - } } diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemFactory.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemFactory.kt new file mode 100644 index 0000000..64b92ce --- /dev/null +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemFactory.kt @@ -0,0 +1,70 @@ +package github.com.st235.lib_expandablebottombar + +import android.content.Context +import android.graphics.drawable.Drawable +import android.view.View +import androidx.annotation.ColorInt +import androidx.annotation.FloatRange +import androidx.annotation.Px +import github.com.st235.lib_expandablebottombar.components.MenuItemView +import github.com.st235.lib_expandablebottombar.utils.DrawableHelper +import github.com.st235.lib_expandablebottombar.utils.StyleController + +internal class MenuItemFactory( + private val rootView: ExpandableBottomBar, + private val styleController: StyleController, + @Px private val itemVerticalPadding: Int, + @Px private var itemHorizontalPadding: Int, + @Px private var backgroundCornerRadius: Float, + @FloatRange(from = 0.0, to = 1.0) private var backgroundOpacity: Float, + @ColorInt private var itemInactiveColor: Int, + @ColorInt private var notificationBadgeColor: Int, + @ColorInt private var notificationBadgeTextColor: Int +) { + + fun build( + menuItemDescriptor: MenuItemDescriptor, + onItemClickListener: (MenuItem, View) -> Unit + ): MenuItemImpl { + val context: Context = rootView.context + + val itemView = MenuItemView(context = context) + val menuItem = MenuItemImpl( + menuItemDescriptor, + rootView, + itemView + ) + + val backgroundColorStateList = DrawableHelper.createSelectedUnselectedStateList( + menuItemDescriptor.activeColor, + itemInactiveColor + ) + + with(itemView) { + id = menuItemDescriptor.itemId + contentDescription = context.resources.getString(R.string.accessibility_item_description, menuItemDescriptor.text) + setPadding(itemHorizontalPadding, itemVerticalPadding, itemHorizontalPadding, itemVerticalPadding) + + setIcon(menuItemDescriptor.iconId, backgroundColorStateList) + setText(menuItemDescriptor.text, backgroundColorStateList) + notificationBadgeBackgroundColor = menuItemDescriptor.badgeBackgroundColor ?: notificationBadgeColor + notificationBadgeTextColor = menuItemDescriptor.badgeTextColor ?: notificationBadgeTextColor + + background = createHighlightedMenuShape(menuItemDescriptor) + setOnClickListener { + onItemClickListener(menuItem, it) + } + } + + return menuItem + } + + private fun createHighlightedMenuShape(menuItemDescriptor: MenuItemDescriptor): Drawable { + return styleController.createStateBackground( + menuItemDescriptor.activeColor, + backgroundCornerRadius, + backgroundOpacity + ) + } + +} \ No newline at end of file diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemImpl.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemImpl.kt index 3ebbdf0..07f7877 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemImpl.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemImpl.kt @@ -1,23 +1,14 @@ package github.com.st235.lib_expandablebottombar -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Color -import android.graphics.drawable.Drawable import android.view.View -import androidx.annotation.ColorInt -import androidx.annotation.FloatRange -import androidx.annotation.Px import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.AccessibilityDelegateCompat import androidx.core.view.ViewCompat.setAccessibilityDelegate import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import github.com.st235.lib_expandablebottombar.components.MenuItemView -import github.com.st235.lib_expandablebottombar.utils.* -import github.com.st235.lib_expandablebottombar.utils.DrawableHelper -import github.com.st235.lib_expandablebottombar.utils.StyleController import github.com.st235.lib_expandablebottombar.utils.createChain +import github.com.st235.lib_expandablebottombar.utils.delayTransition import github.com.st235.lib_expandablebottombar.utils.show internal class MenuItemImpl( @@ -50,18 +41,23 @@ internal class MenuItemImpl( return itemView.visibility == View.VISIBLE } + override var isAttached: Boolean = false + private set + override fun show() { rootView.delayTransition() itemView.show() } override fun hide() { - if (rootView.getSelected() == this) { - throw IllegalStateException("Cannot hide active tab") - } + val menu = rootView.menu rootView.delayTransition() itemView.show(isShown = false) + + if (menu.selectedItem == this) { + menu.deselect() + } } override fun notification(): Notification { @@ -76,150 +72,59 @@ internal class MenuItemImpl( itemView.deselect() } - fun attachTo(parent: ConstraintLayout, - previousIconId: Int, - nextIconId: Int, - menuItemHorizontalMargin: Int, - menuItemVerticalMargin: Int) { + fun getView(): View { + return itemView + } + + fun attachToSuperView( + menuItemHorizontalMargin: Int, + menuItemVerticalMargin: Int + ) { + isAttached = true + val lp = ConstraintLayout.LayoutParams( ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT ) - lp.setMargins(menuItemHorizontalMargin, menuItemVerticalMargin, - menuItemHorizontalMargin, menuItemVerticalMargin) + lp.setMargins( + menuItemHorizontalMargin, + menuItemVerticalMargin, + menuItemHorizontalMargin, + menuItemVerticalMargin + ) - parent.addView(itemView, lp) + rootView.addView(itemView, lp) + } + fun removeFromSuperView() { + isAttached = false + rootView.removeView(itemView) + } + + fun rebuildSiblingsConnection( + previousIconId: Int, + nextIconId: Int + ) { val cl = ConstraintSet() - cl.clone(parent) + cl.clone(rootView) - cl.connect(itemView.id, ConstraintSet.TOP, parent.id, ConstraintSet.TOP) - cl.connect(itemView.id, ConstraintSet.BOTTOM, parent.id, ConstraintSet.BOTTOM) + cl.connect(itemView.id, ConstraintSet.TOP, rootView.id, ConstraintSet.TOP) + cl.connect(itemView.id, ConstraintSet.BOTTOM, rootView.id, ConstraintSet.BOTTOM) if (previousIconId == itemView.id) { - cl.connect(itemView.id, ConstraintSet.START, parent.id, ConstraintSet.START) + cl.connect(itemView.id, ConstraintSet.START, rootView.id, ConstraintSet.START) } else { cl.connect(itemView.id, ConstraintSet.START, previousIconId, ConstraintSet.END) cl.createChain(previousIconId, itemView.id, ConstraintSet.CHAIN_PACKED) } if (nextIconId == itemView.id) { - cl.connect(itemView.id, ConstraintSet.END, parent.id, ConstraintSet.END) + cl.connect(itemView.id, ConstraintSet.END, rootView.id, ConstraintSet.END) } else { cl.connect(itemView.id, ConstraintSet.END, nextIconId, ConstraintSet.START) cl.createChain(itemView.id, nextIconId, ConstraintSet.CHAIN_PACKED) } - cl.applyTo(parent) - } - - //TODO(st235): separate this builder to view factory - class Builder(private val menuItemDescriptor: MenuItemDescriptor) { - - @Px - private var itemVerticalPadding: Int = 0 - @Px - private var itemHorizontalPadding: Int = 0 - @Px - @SuppressLint("SupportAnnotationUsage") - private var backgroundCornerRadius: Float = 0.0f - @FloatRange(from = 0.0, to = 1.0) - private var backgroundOpacity: Float = 1.0f - @ColorInt - private var itemInactiveColor: Int = Color.BLACK - @ColorInt - private var notificationBadgeColor: Int = Color.RED - @ColorInt - private var notificationBadgeTextColor: Int = Color.WHITE - - private lateinit var styleController: StyleController - private lateinit var onItemClickListener: (MenuItem, View) -> Unit - - fun itemMargins( - @Px itemHorizontalPadding: Int, - @Px itemVerticalPadding: Int - ): Builder { - this.itemVerticalPadding = itemVerticalPadding - this.itemHorizontalPadding = itemHorizontalPadding - return this - } - - fun itemBackground(backgroundCornerRadius: Float, - @FloatRange(from = 0.0, to = 1.0) backgroundOpacity: Float): Builder { - this.backgroundCornerRadius = backgroundCornerRadius - this.backgroundOpacity = backgroundOpacity - return this - } - - fun itemInactiveColor(@ColorInt itemInactiveColor: Int): Builder { - this.itemInactiveColor = itemInactiveColor - return this - } - - fun onItemClickListener(onItemClickListener: (MenuItem, View) -> Unit): Builder { - this.onItemClickListener = onItemClickListener - return this - } - - fun styleController(styleController: StyleController): Builder { - this.styleController = styleController - return this - } - - fun notificationBadgeColor(@ColorInt notificationBadgeColor: Int): Builder { - this.notificationBadgeColor = notificationBadgeColor - return this - } - - fun notificationBadgeTextColor(@ColorInt notificationBadgeTextColor: Int): Builder { - this.notificationBadgeTextColor = notificationBadgeTextColor - return this - } - - private fun createHighlightedMenuShape(): Drawable { - return styleController.createStateBackground( - menuItemDescriptor.activeColor, - backgroundCornerRadius, - backgroundOpacity - ) - } - - private fun createMenuItemView(context: Context): MenuItemView { - return MenuItemView(context = context) - } - - fun build(rootView: ExpandableBottomBar): MenuItemImpl { - val context: Context = rootView.context - - val itemView = createMenuItemView(context) - val menuItem = MenuItemImpl( - menuItemDescriptor, - rootView, - itemView - ) - - val backgroundColorStateList = DrawableHelper.createSelectedUnselectedStateList( - menuItemDescriptor.activeColor, - itemInactiveColor - ) - - with(itemView) { - id = menuItemDescriptor.itemId - contentDescription = context.resources.getString(R.string.accessibility_item_description, menuItemDescriptor.text) - setPadding(itemHorizontalPadding, itemVerticalPadding, itemHorizontalPadding, itemVerticalPadding) - - setIcon(menuItemDescriptor.iconId, backgroundColorStateList) - setText(menuItemDescriptor.text, backgroundColorStateList) - notificationBadgeBackgroundColor = menuItemDescriptor.badgeBackgroundColor ?: notificationBadgeColor - notificationBadgeTextColor = menuItemDescriptor.badgeTextColor ?: notificationBadgeTextColor - - background = createHighlightedMenuShape() - setOnClickListener { - onItemClickListener(menuItem, it) - } - } - - return menuItem - } + cl.applyTo(rootView) } } diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/navigation/ExpandableBottomBarNavigationUI.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/navigation/ExpandableBottomBarNavigationUI.kt index ca027a8..bd29299 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/navigation/ExpandableBottomBarNavigationUI.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/navigation/ExpandableBottomBarNavigationUI.kt @@ -16,8 +16,10 @@ object ExpandableBottomBarNavigationUI { expandableBottomBar: ExpandableBottomBar, navigationController: NavController ) { - expandableBottomBar.onItemSelectedListener = { _, menuItem -> - onNavDestinationSelected(menuItem, navigationController) + expandableBottomBar.onItemSelectedListener = { _, menuItem, byUser -> + if (byUser) { + onNavDestinationSelected(menuItem, navigationController) + } } val weakReference = WeakReference(expandableBottomBar) @@ -32,9 +34,9 @@ object ExpandableBottomBarNavigationUI { navigationController.removeOnDestinationChangedListener(this) return } - for (menuItem in view.getMenuItems()) { + for (menuItem in view.menu) { if (destination.matchDestination(menuItem.id)) { - expandableBottomBar.select(menuItem.id) + view.menu.select(menuItem.id) } } } diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/state/BottomBarSavedState.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/state/BottomBarSavedState.kt index acdca21..d271c10 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/state/BottomBarSavedState.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/state/BottomBarSavedState.kt @@ -5,6 +5,6 @@ import kotlinx.android.parcel.Parcelize @Parcelize internal data class BottomBarSavedState( - val selectedItem: Int, + val selectedItem: Int?, val superState: Parcelable? ): Parcelable From b2edf3b4d9c7cfbf9bae315d3230b8d408de618e Mon Sep 17 00:00:00 2001 From: Alexander Dadukin Date: Sat, 3 Apr 2021 11:54:47 +0100 Subject: [PATCH 2/4] fixed tests. --- .../MenuItemDescriptorTest.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib-expandablebottombar/src/test/java/github/com/st235/expandablebottombar/MenuItemDescriptorTest.kt b/lib-expandablebottombar/src/test/java/github/com/st235/expandablebottombar/MenuItemDescriptorTest.kt index 7ea7dc3..1205eaf 100644 --- a/lib-expandablebottombar/src/test/java/github/com/st235/expandablebottombar/MenuItemDescriptorTest.kt +++ b/lib-expandablebottombar/src/test/java/github/com/st235/expandablebottombar/MenuItemDescriptorTest.kt @@ -15,46 +15,46 @@ class MenuItemDescriptorTest { @Test(expected = IllegalStateException::class) fun `test that creating empty item will rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).create() + val item = MenuItemDescriptor.Builder(context).build() } @Test(expected = IllegalStateException::class) fun `test that creating item with id only will rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).id(0).create() + val item = MenuItemDescriptor.Builder(context).id(0).build() } @Test(expected = IllegalStateException::class) fun `test that creating item without color and icon will rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).id(0).text("").create() + val item = MenuItemDescriptor.Builder(context).id(0).text("").build() } @Test(expected = IllegalStateException::class) fun `test that creating item without title and icon will rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).id(0).color(0).create() + val item = MenuItemDescriptor.Builder(context).id(0).color(0).build() } @Test(expected = IllegalStateException::class) fun `test that creating item without color and text will rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).id(0).icon(0).create() + val item = MenuItemDescriptor.Builder(context).id(0).icon(0).build() } @Test(expected = IllegalStateException::class) fun `test that creating item without color will rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).id(0).icon(0).text("").create() + val item = MenuItemDescriptor.Builder(context).id(0).icon(0).text("").build() } @Test(expected = IllegalStateException::class) fun `test that creating item without text rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).id(0).color(0).icon(0).create() + val item = MenuItemDescriptor.Builder(context).id(0).color(0).icon(0).build() } @Test(expected = IllegalStateException::class) fun `test that creating item without icon will rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).id(0).text("").color(0).create() + val item = MenuItemDescriptor.Builder(context).id(0).text("").color(0).build() } @Test - fun `test that right created item will not be rise an exception`() { - val item = MenuItemDescriptor.BuildRequest(builder, context).id(1).text("").icon(1).color(1).create() + fun `test that right buildd item will not be rise an exception`() { + val item = MenuItemDescriptor.Builder(context).id(1).text("").icon(1).color(1).build() } } From 10eb471230029cc23d358d0889a340ed0a2474c4 Mon Sep 17 00:00:00 2001 From: Alexander Dadukin Date: Sat, 3 Apr 2021 11:56:26 +0100 Subject: [PATCH 3/4] fixed naming issue. --- .../com/st235/lib_expandablebottombar/MenuItemFactory.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemFactory.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemFactory.kt index 64b92ce..8fd4272 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemFactory.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuItemFactory.kt @@ -18,8 +18,8 @@ internal class MenuItemFactory( @Px private var backgroundCornerRadius: Float, @FloatRange(from = 0.0, to = 1.0) private var backgroundOpacity: Float, @ColorInt private var itemInactiveColor: Int, - @ColorInt private var notificationBadgeColor: Int, - @ColorInt private var notificationBadgeTextColor: Int + @ColorInt private var globalNotificationBadgeColor: Int, + @ColorInt private var globalNotificationBadgeTextColor: Int ) { fun build( @@ -47,8 +47,8 @@ internal class MenuItemFactory( setIcon(menuItemDescriptor.iconId, backgroundColorStateList) setText(menuItemDescriptor.text, backgroundColorStateList) - notificationBadgeBackgroundColor = menuItemDescriptor.badgeBackgroundColor ?: notificationBadgeColor - notificationBadgeTextColor = menuItemDescriptor.badgeTextColor ?: notificationBadgeTextColor + notificationBadgeBackgroundColor = menuItemDescriptor.badgeBackgroundColor ?: globalNotificationBadgeColor + notificationBadgeTextColor = menuItemDescriptor.badgeTextColor ?: globalNotificationBadgeTextColor background = createHighlightedMenuShape(menuItemDescriptor) setOnClickListener { From 565233a029625f286d7e390a3ad557dbb3d575b8 Mon Sep 17 00:00:00 2001 From: Alexander Dadukin Date: Sat, 3 Apr 2021 12:04:26 +0100 Subject: [PATCH 4/4] bumped version up to 1.4.0. implemented menu class. --- README.md | 25 +++++++++++-------- .../screens/NotificationBadgeActivity.kt | 1 - .../ProgrammaticallyCreatedDemoActivity.kt | 2 -- gradle.properties | 4 +-- .../com/st235/lib_expandablebottombar/Menu.kt | 14 +++++++++-- .../st235/lib_expandablebottombar/MenuImpl.kt | 2 +- 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d76d93b..ffe622a 100644 --- a/README.md +++ b/README.md @@ -73,18 +73,22 @@ Firstly, you should declare your view in xml file app:layout_constraintStart_toStartOf="parent" /> ``` -Then you should add menu items to your navigation component +Then you should add menu items to your navigation menu +To access menu call `bottomBar.menu` on your navigation view ```kotlin val bottomBar: ExpandableBottomBar = findViewById(R.id.expandable_bottom_bar) - - bottomBar.addItems( - MenuItemDescriptor.Builder(context = this) - .addItem(R.id.icon_home, R.drawable.ic_bug, R.string.text, Color.GRAY) - .addItem(R.id.icon_go, R.drawable.ic_gift, R.string.text2, 0xFFFF77A9) - .addItem(R.id.icon_left, R.drawable.ic_one, R.string.text3, 0xFF58A5F0) - .addItem(R.id.icon_right, R.drawable.ic_two, R.string.text4, 0xFFBE9C91) - .build() + val menu = bottomBar.menu + + menu.add( + MenuItemDescriptor.Builder( + this, + R.id.icon_home, + R.drawable.ic_home, + R.string.text, + Color.GRAY + ) + .build() ) bottomBar.onItemSelectedListener = { view, menuItem -> @@ -185,7 +189,8 @@ Then you should reference this xml file at the view attributes /** * Returns notification object */ - val notification = bottomBar.getMenuItemFor(i.itemId).notification() // itemId is R.id.action_id + val menu = bottomBar.menu + val notification = menu.findItemById(i.itemId).notification() // itemId is R.id.action_id notification.show() // shows simple dot-notification notification.show("string literal") // shows notification with counter. Counter could not exceed the 4 symbols length diff --git a/app/src/main/java/github/com/st235/expandablebottombar/screens/NotificationBadgeActivity.kt b/app/src/main/java/github/com/st235/expandablebottombar/screens/NotificationBadgeActivity.kt index 97ad996..0a38f80 100644 --- a/app/src/main/java/github/com/st235/expandablebottombar/screens/NotificationBadgeActivity.kt +++ b/app/src/main/java/github/com/st235/expandablebottombar/screens/NotificationBadgeActivity.kt @@ -10,7 +10,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.graphics.ColorUtils import github.com.st235.expandablebottombar.R import github.com.st235.lib_expandablebottombar.ExpandableBottomBar -import github.com.st235.lib_expandablebottombar.Notification import kotlinx.android.synthetic.main.activity_xml_declared.* class NotificationBadgeActivity : AppCompatActivity() { diff --git a/app/src/main/java/github/com/st235/expandablebottombar/screens/ProgrammaticallyCreatedDemoActivity.kt b/app/src/main/java/github/com/st235/expandablebottombar/screens/ProgrammaticallyCreatedDemoActivity.kt index fd34d0d..d496139 100644 --- a/app/src/main/java/github/com/st235/expandablebottombar/screens/ProgrammaticallyCreatedDemoActivity.kt +++ b/app/src/main/java/github/com/st235/expandablebottombar/screens/ProgrammaticallyCreatedDemoActivity.kt @@ -2,8 +2,6 @@ package github.com.st235.expandablebottombar.screens import android.graphics.Color import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.util.Log import android.view.View import android.view.ViewAnimationUtils diff --git a/gradle.properties b/gradle.properties index 9966876..25ea180 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ android.useAndroidX=true GROUP=com.github.st235 -VERSION_CODE=42 -VERSION_NAME=1.3.2 +VERSION_CODE=45 +VERSION_NAME=1.4.0 POM_DESCRIPTION=A new way to improve navigation in your app. POM_URL=https://github.com/st235/ExpandableBottomBar diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/Menu.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/Menu.kt index 6b29666..f390f56 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/Menu.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/Menu.kt @@ -33,23 +33,33 @@ interface Menu: Iterable { * Programmatically select item * * @param id - identifier of menu item, which should be selected + * @throws IllegalArgumentException when menu item cannot be found */ @Throws(IllegalArgumentException::class) fun select(@IdRes id: Int) fun deselect() + /** + * Removes the given menu item + * + * @param id - identifier of menu item, which should be selected + * @throws IllegalArgumentException when menu item cannot be found + */ @Throws(IllegalArgumentException::class) fun remove(@IdRes id: Int) + /** + * Removes all menu items + */ fun removeAll() /** * Returns menu item for the given id value * - * @throws NullPointerException when id doesn't exists + * @throws IllegalArgumentException when menu item cannot be found */ @Throws(IllegalArgumentException::class) fun findItemById(@IdRes id: Int): MenuItem -} \ No newline at end of file +} diff --git a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuImpl.kt b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuImpl.kt index 119ffe8..9a80dc5 100644 --- a/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuImpl.kt +++ b/lib-expandablebottombar/src/main/java/github/com/st235/lib_expandablebottombar/MenuImpl.kt @@ -161,4 +161,4 @@ internal class MenuImpl( itemsLookup[item.id]?.setAccessibleWith(prev = prev, next = next) } } -} \ No newline at end of file +}