diff --git a/designsystem/src/main/kotlin/com/natura/android/appbar/AppBar.kt b/designsystem/src/main/kotlin/com/natura/android/appbar/AppBar.kt index a4db7b9fa..56d08fb38 100644 --- a/designsystem/src/main/kotlin/com/natura/android/appbar/AppBar.kt +++ b/designsystem/src/main/kotlin/com/natura/android/appbar/AppBar.kt @@ -12,7 +12,7 @@ import android.view.WindowManager import android.widget.ImageView import androidx.appcompat.widget.Toolbar import com.natura.android.R -import com.natura.android.ext.setVisibilityFromBoolean +import com.natura.android.extensions.setVisibilityFromBoolean import com.natura.android.badge.BadgeDrawable class AppBar(context: Context, attrs: AttributeSet) : Toolbar(context, attrs) { diff --git a/designsystem/src/main/kotlin/com/natura/android/badge/BadgeDrawable.kt b/designsystem/src/main/kotlin/com/natura/android/badge/BadgeDrawable.kt index 9d15b5b9c..94ade1d5b 100644 --- a/designsystem/src/main/kotlin/com/natura/android/badge/BadgeDrawable.kt +++ b/designsystem/src/main/kotlin/com/natura/android/badge/BadgeDrawable.kt @@ -26,7 +26,7 @@ class BadgeDrawable( private fun setBadge() { val icon = parent as LayerDrawable icon.mutate() - icon.setDrawableByLayerId(R.id.ic_badge_placeholder, this) + icon.setDrawableByLayerId(R.id.badge_placeholder, this) } override fun draw(canvas: Canvas) { @@ -57,9 +57,7 @@ class BadgeDrawable( } } - private fun drawBadgeWithText( - canvas: Canvas - ) { + private fun drawBadgeWithText(canvas: Canvas) { defineTextBounds(count.toString()) definePositionToDrawBadge(canvas) } @@ -68,24 +66,16 @@ class BadgeDrawable( mTextPaint.getTextBounds(text, 0, count.toString().length, mTxtRect) } - private fun definePositionToDrawBadge( - canvas: Canvas - ) { + private fun definePositionToDrawBadge(canvas: Canvas) { val bounds = bounds - - val badgeWith = when { - count > 99 -> getDimenFromTheme(R.attr.sizeSemi) - count > 9 -> getDimenFromTheme(R.attr.sizeStandard) - else -> getDimenFromTheme(R.attr.sizeSmall) - } + val badgeWith = mTxtRect.width() + getDimenFromTheme(R.attr.spacingTiny).toInt() context.resources.getDrawable(R.drawable.badge_rounded_rectangle, context.theme).apply { setBounds( - bounds.right - badgeWith.toInt(), + bounds.right - badgeWith, bounds.top, bounds.right, - getDimenFromTheme(R.attr.sizeSmall).toInt() - ) + mTxtRect.height() + getDimenFromTheme(R.attr.spacingMicro).toInt()) draw(canvas) drawText( diff --git a/designsystem/src/main/kotlin/com/natura/android/extensions/TypedValue.kt b/designsystem/src/main/kotlin/com/natura/android/extensions/TypedValue.kt new file mode 100644 index 000000000..5d0a4afe6 --- /dev/null +++ b/designsystem/src/main/kotlin/com/natura/android/extensions/TypedValue.kt @@ -0,0 +1,15 @@ +package com.natura.android.extensions + +import android.util.TypedValue + +/** + * Get an alpha value converted to base 255. Its in interesting use + * this method when you want to access a opacity token defined at + * theme and use it programmatically to set alpha on a component + * that receives a value between 0 and 255 inclusive, with 0 being + * transparent and 255 being opaque + * @return an int with the corresponding alpha value + */ +fun TypedValue.getAlphaAsBase255(): Int { + return (this.float * 255).toInt() +} diff --git a/designsystem/src/main/kotlin/com/natura/android/ext/View.kt b/designsystem/src/main/kotlin/com/natura/android/extensions/View.kt similarity index 82% rename from designsystem/src/main/kotlin/com/natura/android/ext/View.kt rename to designsystem/src/main/kotlin/com/natura/android/extensions/View.kt index adb761e65..5a9225dc7 100644 --- a/designsystem/src/main/kotlin/com/natura/android/ext/View.kt +++ b/designsystem/src/main/kotlin/com/natura/android/extensions/View.kt @@ -1,4 +1,4 @@ -package com.natura.android.ext +package com.natura.android.extensions import android.view.View diff --git a/designsystem/src/main/kotlin/com/natura/android/iconButton/IconButton.kt b/designsystem/src/main/kotlin/com/natura/android/iconButton/IconButton.kt new file mode 100644 index 000000000..a9abf8963 --- /dev/null +++ b/designsystem/src/main/kotlin/com/natura/android/iconButton/IconButton.kt @@ -0,0 +1,186 @@ +package com.natura.android.iconButton + +import android.content.Context +import android.content.res.TypedArray +import android.util.AttributeSet +import android.view.View +import android.widget.ImageView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.content.res.getIntOrThrow +import androidx.core.content.res.getResourceIdOrThrow +import androidx.core.content.res.getStringOrThrow +import com.natura.android.R +import com.natura.android.badge.BadgeDrawable +import com.natura.android.exceptions.MissingThemeException +import com.natura.android.resources.getColorTokenFromTheme +import com.natura.android.resources.getIconResourceIdFromName + +class IconButton @JvmOverloads constructor( + context: Context, + private val attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private var iconButtonAttributesArray: TypedArray + + private var iconColorResourceAttribute = 0 + private var rippleDrawableResourceAttribute = 0 + + private var iconNameAttribute: String? = null + private var colorAttribute: Int? = null + private var notifyAttribute: Int = 0 + private var enabledAttribute: Boolean = false + + private val iconButton by lazy { findViewById(R.id.iconButtonIcon) } + private val iconButtonContainer by lazy { findViewById(R.id.iconButtonContainer) } + private val badgeContainer by lazy { findViewById(R.id.iconButtonBadgeContainer) } + + init { + try { + View.inflate(context, R.layout.icon_button, this) + } catch (e: Exception) { + throw (MissingThemeException()) + } + + iconButtonAttributesArray = context.obtainStyledAttributes(attrs, R.styleable.IconButton) + + getAttributes() + getAppereanceAttributesFromTheme() + + configureAppearance() + configureNotification() + configureEnabled() + + iconButtonAttributesArray.recycle() + } + + private fun configureEnabled() { + isEnabled = enabledAttribute + } + + private fun configureNotification() { + if (notifyAttribute > 0) { + badgeContainer.visibility = View.VISIBLE + BadgeDrawable(context, notifyAttribute, badgeContainer.drawable) + } + } + + override fun setEnabled(enabled: Boolean) { + iconButton.isEnabled = enabled + if (!enabled) { + setDisabledColor() + } + super.setEnabled(enabled) + } + + private fun setDisabledColor() { + iconButton.setColorFilter(getColorTokenFromTheme(context, R.attr.colorMediumEmphasis), android.graphics.PorterDuff.Mode.SRC_IN) + } + + fun setIcon(icon: String?) { + icon?.apply { + val iconDrawableId = getIconResourceIdFromName(context, icon) + iconButton.setImageResource(iconDrawableId) + } + } + + fun getIcon(): ImageView { + return iconButton + } + + fun getBadge(): ImageView { + return badgeContainer + } + + fun getColor(): Int? { + return colorAttribute + } + + private fun getAttributes() { + getIconName() + getColorAttribute() + getEnabledAttribute() + getNotify() + } + + private fun getNotify() { + notifyAttribute = iconButtonAttributesArray.getInteger(R.styleable.IconButton_notify, 0) + } + + private fun getIconName() { + try { + iconNameAttribute = iconButtonAttributesArray.getStringOrThrow(R.styleable.IconButton_iconName) + } catch (e: Exception) { + throw (IllegalArgumentException("⚠️ ⚠️ Missing iconName required argument. You MUST set the icon name.", e)) + } + } + + private fun getColorAttribute() { + try { + colorAttribute = iconButtonAttributesArray.getIntOrThrow(R.styleable.IconButton_buttonColor) + } catch (e: Exception) { + throw (IllegalArgumentException("⚠️ ⚠️ Missing iconButton required argument. You MUST set the iconButton color.", e)) + } + } + + private fun getEnabledAttribute() { + enabledAttribute = iconButtonAttributesArray.getBoolean(R.styleable.IconButton_android_enabled, true) + } + + private fun getAppereanceAttributesFromTheme() { + try { + when (colorAttribute) { + PRIMARY -> { + setColorAttribute(R.attr.iconButtonPrimary) + setDrawableRippleAttribute(R.attr.iconButtonPrimary) + } + DEFAULT -> { + setColorAttribute(R.attr.iconButtonDefault) + setDrawableRippleAttribute(R.attr.iconButtonDefault) + } + } + } catch (e: Exception) { + throw (MissingThemeException()) + } + } + + private fun setDrawableRippleAttribute(iconButtonStyleFromTheme: Int) { + context + .theme + .obtainStyledAttributes( + attrs, + R.styleable.IconButton, + iconButtonStyleFromTheme, + 0 + ) + .apply { + rippleDrawableResourceAttribute = this.getResourceIdOrThrow(R.styleable.IconButton_rippleDrawable) + } + } + + private fun setColorAttribute(attribute: Int) { + context + .theme + .obtainStyledAttributes( + attrs, + R.styleable.IconButton, + attribute, + 0 + ) + .apply { + iconColorResourceAttribute = this.getResourceIdOrThrow(R.styleable.IconButton_iconColor) + } + } + + private fun configureAppearance() { + setIcon(iconNameAttribute) + iconButton.setColorFilter(ContextCompat.getColor(context, iconColorResourceAttribute), android.graphics.PorterDuff.Mode.SRC_IN) + iconButtonContainer.background = resources.getDrawable(rippleDrawableResourceAttribute, context.theme) + } + + companion object { + const val DEFAULT = 0 + const val PRIMARY = 1 + } +} diff --git a/designsystem/src/main/kotlin/com/natura/android/menu/MenuView.kt b/designsystem/src/main/kotlin/com/natura/android/menu/MenuView.kt index beaa46ea9..f6b8b9597 100644 --- a/designsystem/src/main/kotlin/com/natura/android/menu/MenuView.kt +++ b/designsystem/src/main/kotlin/com/natura/android/menu/MenuView.kt @@ -11,7 +11,7 @@ import androidx.appcompat.widget.AppCompatTextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import com.natura.android.R -import com.natura.android.ext.setVisibilityFromBoolean +import com.natura.android.extensions.setVisibilityFromBoolean import com.natura.android.tag.Tag @SuppressLint("CustomViewStyleable") diff --git a/designsystem/src/main/kotlin/com/natura/android/resources/Resources.kt b/designsystem/src/main/kotlin/com/natura/android/resources/Resources.kt new file mode 100644 index 000000000..3c269b5d9 --- /dev/null +++ b/designsystem/src/main/kotlin/com/natura/android/resources/Resources.kt @@ -0,0 +1,18 @@ +package com.natura.android.resources + +import android.content.Context +import com.natura.android.R +import com.natura.android.resources.ResourcesConstants.DRAWABLE_NOT_FOUND + +fun getIconResourceIdFromName(context: Context, iconName: String): Int { + var drawableId = context.resources.getIdentifier(iconName.replace("-", "_"), "drawable", context.packageName) + + if (drawableId == DRAWABLE_NOT_FOUND) { + drawableId = R.drawable.default_icon_outlined_default_mockup + } + return drawableId +} + +object ResourcesConstants { + const val DRAWABLE_NOT_FOUND = 0 +} diff --git a/designsystem/src/main/kotlin/com/natura/android/resources/Theme.kt b/designsystem/src/main/kotlin/com/natura/android/resources/Theme.kt new file mode 100644 index 000000000..ece42921e --- /dev/null +++ b/designsystem/src/main/kotlin/com/natura/android/resources/Theme.kt @@ -0,0 +1,16 @@ +package com.natura.android.resources + +import android.content.Context +import android.util.TypedValue + +fun getColorTokenFromTheme(context: Context, attrColorId: Int): Int { + val value = TypedValue() + context.theme.resolveAttribute(attrColorId, value, true) + return value.data +} + +fun getDimenFromTheme(context: Context, attributeName: Int): Float { + val typedValue = TypedValue() + context.theme.resolveAttribute(attributeName, typedValue, true) + return typedValue.getDimension(context.resources.displayMetrics) +} diff --git a/designsystem/src/main/kotlin/com/natura/android/shortcut/Shortcut.kt b/designsystem/src/main/kotlin/com/natura/android/shortcut/Shortcut.kt index f6b8836e0..438bb35e6 100644 --- a/designsystem/src/main/kotlin/com/natura/android/shortcut/Shortcut.kt +++ b/designsystem/src/main/kotlin/com/natura/android/shortcut/Shortcut.kt @@ -16,6 +16,7 @@ import androidx.core.content.res.getStringOrThrow import androidx.core.graphics.drawable.DrawableCompat import com.natura.android.R import com.natura.android.exceptions.MissingThemeException +import com.natura.android.resources.getIconResourceIdFromName import com.natura.android.extensions.setAppearance class Shortcut @JvmOverloads constructor( @@ -65,22 +66,13 @@ class Shortcut @JvmOverloads constructor( fun setIcon(icon: String?) { icon?.apply { - val drawableId = context.resources.getIdentifier(icon.replace("-", "_"), "drawable", context.packageName) - - if (drawableId == ICON_NOT_FOUND) { - configDefaultIconIfEmpty() - } else { - iconContainer.setImageResource(drawableId) - } + val drawableId = getIconResourceIdFromName(context, icon) + iconContainer.setImageResource(drawableId) iconContainer.setColorFilter(ContextCompat.getColor(context, iconColorResourceAttribute), android.graphics.PorterDuff.Mode.SRC_IN) } } - private fun configDefaultIconIfEmpty() { - iconContainer.setImageResource(R.drawable.default_icon_outlined_default_mockup) - } - fun getIcon(): ImageView { return iconContainer } @@ -184,7 +176,6 @@ class Shortcut @JvmOverloads constructor( } companion object { - const val ICON_NOT_FOUND = 0 const val OUTLINED = 0 const val CONTAINED = 1 } diff --git a/designsystem/src/main/res/drawable/default_icon_outlined_navigation_arrowleft.xml b/designsystem/src/main/res/drawable/default_icon_outlined_navigation_arrowleft.xml new file mode 100644 index 000000000..5dc476c62 --- /dev/null +++ b/designsystem/src/main/res/drawable/default_icon_outlined_navigation_arrowleft.xml @@ -0,0 +1,9 @@ + + + diff --git a/designsystem/src/main/res/drawable/icon_base_badge.xml b/designsystem/src/main/res/drawable/icon_base_badge.xml index 281783ae0..42352cb97 100644 --- a/designsystem/src/main/res/drawable/icon_base_badge.xml +++ b/designsystem/src/main/res/drawable/icon_base_badge.xml @@ -6,7 +6,6 @@ android:gravity="right" /> - \ No newline at end of file diff --git a/designsystem/src/main/res/drawable/icon_button_base_badge.xml b/designsystem/src/main/res/drawable/icon_button_base_badge.xml new file mode 100644 index 000000000..83aa92de8 --- /dev/null +++ b/designsystem/src/main/res/drawable/icon_button_base_badge.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/designsystem/src/main/res/drawable/iconbutton_ripple_background_default.xml b/designsystem/src/main/res/drawable/iconbutton_ripple_background_default.xml new file mode 100644 index 000000000..ab07823b7 --- /dev/null +++ b/designsystem/src/main/res/drawable/iconbutton_ripple_background_default.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/designsystem/src/main/res/drawable/iconbutton_ripple_background_primary.xml b/designsystem/src/main/res/drawable/iconbutton_ripple_background_primary.xml new file mode 100644 index 000000000..1159aad73 --- /dev/null +++ b/designsystem/src/main/res/drawable/iconbutton_ripple_background_primary.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/designsystem/src/main/res/layout/icon_button.xml b/designsystem/src/main/res/layout/icon_button.xml new file mode 100644 index 000000000..6a26c5807 --- /dev/null +++ b/designsystem/src/main/res/layout/icon_button.xml @@ -0,0 +1,42 @@ + + + + + + + + + \ No newline at end of file diff --git a/designsystem/src/main/res/values/ds_attrs.xml b/designsystem/src/main/res/values/ds_attrs.xml index 7af86eb2b..17039dce3 100644 --- a/designsystem/src/main/res/values/ds_attrs.xml +++ b/designsystem/src/main/res/values/ds_attrs.xml @@ -90,4 +90,16 @@ + + + + + + + + + + + + diff --git a/designsystem/src/main/res/values/ds_base_attrs.xml b/designsystem/src/main/res/values/ds_base_attrs.xml index 4962ba9de..9fcb2af30 100644 --- a/designsystem/src/main/res/values/ds_base_attrs.xml +++ b/designsystem/src/main/res/values/ds_base_attrs.xml @@ -112,6 +112,9 @@ + + + diff --git a/designsystem/src/main/res/values/ds_base_themes.xml b/designsystem/src/main/res/values/ds_base_themes.xml index 863217718..aa4ebe515 100644 --- a/designsystem/src/main/res/values/ds_base_themes.xml +++ b/designsystem/src/main/res/values/ds_base_themes.xml @@ -129,6 +129,9 @@ @style/Widget.DS.ButtonLarge.Outlined.V23 @style/Widget.DS.ButtonLarge.TextButton.V23 + @style/Widget.DS.IconButton.Primary + @style/Widget.DS.IconButton.Default + @style/Widget.DS.AppBarTop @dimen/ds_toolbar_height @style/Theme.DS.Toolbar @@ -270,6 +273,9 @@ @style/Widget.DS.ButtonLarge.Outlined.V23 @style/Widget.DS.ButtonLarge.TextButton.V23 + @style/Widget.DS.IconButton.Primary + @style/Widget.DS.IconButton.Default + @style/Widget.DS.AppBarTop @style/Theme.DS.Toolbar @dimen/ds_toolbar_height diff --git a/designsystem/src/main/res/values/styles.xml b/designsystem/src/main/res/values/styles.xml index a0f5b2987..206cc2c14 100644 --- a/designsystem/src/main/res/values/styles.xml +++ b/designsystem/src/main/res/values/styles.xml @@ -341,6 +341,28 @@ ?sizeMedium + + + + + + + + + + +