From 4df910aecfdca6585b8bde833fa13bc1a227753b Mon Sep 17 00:00:00 2001 From: Mykhailo Hudov Date: Wed, 20 Sep 2023 11:43:57 +0100 Subject: [PATCH 1/3] Bottom scroll end spacing was implemented --- app/src/main/res/values/dimens.xml | 2 ++ .../com/cube/styleguide/StyleGuideFragment.kt | 15 ++++++++ .../stylehandlers/ScrollEndHandler.kt | 17 ++++++++++ .../main/res/layout/fragment_style_guide.xml | 16 +++++++++ .../layout/item_hirizontal_spacing_view.xml | 2 +- library/src/main/res/layout/item_radius.xml | 2 +- .../src/main/res/layout/item_scroll_end.xml | 34 +++++++++++++++++++ .../src/main/res/layout/spacing_item_view.xml | 2 +- library/src/main/res/values/strings.xml | 4 ++- 9 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 library/src/main/java/com/cube/styleguide/stylehandlers/ScrollEndHandler.kt create mode 100644 library/src/main/res/layout/item_scroll_end.xml diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index a5e17ab..09790b8 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -29,4 +29,6 @@ 12dp 14dp 30dp + + 40dp \ No newline at end of file diff --git a/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt b/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt index f62de4f..0370c11 100644 --- a/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt +++ b/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt @@ -23,6 +23,7 @@ import com.cube.styleguide.fragments.BottomSheetFragment import com.cube.styleguide.stylehandlers.ColorsHandler import com.cube.styleguide.stylehandlers.HorizontalSpacingHandler import com.cube.styleguide.stylehandlers.RadiusHandler +import com.cube.styleguide.stylehandlers.ScrollEndHandler import com.cube.styleguide.stylehandlers.ShadowsHandler import com.cube.styleguide.stylehandlers.SpacingHandler import com.cube.styleguide.utils.Extensions.getPackageNameFlavorAdapted @@ -58,6 +59,8 @@ open class StyleGuideFragment : BottomSheetFragment(R.layout.fragment_style_guid populateRadius() + populateScrollEnd() + populateStyles(packageName) } } @@ -351,6 +354,18 @@ open class StyleGuideFragment : BottomSheetFragment(R.layout.fragment_style_guid } } + private fun populateScrollEnd() { + binding?.apply { + val scrollendsList = ScrollEndHandler.getScrollEnd(requireContext()) + scrollEndView.isVisible = !scrollendsList.isNullOrEmpty() + scrollendsList ?: return + viewScrollend.headerTitle.text = getString(R.string.guidestyle_padding) + val size = scrollendsList[0].second + viewScrollend.spacingInDp.text = getString(R.string.guidestyle_dp_text, size) + viewScrollend.view.layoutParams.height = size + } + } + private fun populateColorsAndShadow() { binding?.apply { val colorList = ColorsHandler.getColors(requireContext()) diff --git a/library/src/main/java/com/cube/styleguide/stylehandlers/ScrollEndHandler.kt b/library/src/main/java/com/cube/styleguide/stylehandlers/ScrollEndHandler.kt new file mode 100644 index 0000000..fff6f54 --- /dev/null +++ b/library/src/main/java/com/cube/styleguide/stylehandlers/ScrollEndHandler.kt @@ -0,0 +1,17 @@ +package com.cube.styleguide.stylehandlers + +import android.content.Context +import java.lang.reflect.Field + +class ScrollEndHandler { + companion object { + fun getScrollEnd(context: Context) = Class.forName("${context.packageName}.R\$dimen").declaredFields + .filter { it.scrollendSpacingName.startsWith("scrollend", true) } // takes only "scrollend" + .map { Pair(it.scrollendSpacingName, context.resources.getDimensionPixelOffset(it.scrollendSpacingId)) } // creates Pairs with scrollendName and scrollendId + .sortedBy { it.second } // sorts by the float spacing dimension + .ifEmpty { null } + } +} + +private val Field.scrollendSpacingId get() = getInt(null) +private val Field.scrollendSpacingName get() = name \ No newline at end of file diff --git a/library/src/main/res/layout/fragment_style_guide.xml b/library/src/main/res/layout/fragment_style_guide.xml index 0327f6c..bf0130e 100644 --- a/library/src/main/res/layout/fragment_style_guide.xml +++ b/library/src/main/res/layout/fragment_style_guide.xml @@ -160,6 +160,22 @@ app:spanCount="4" /> + + + + + + + tools:text="Content Margin \n10 dp" /> + tools:text="10 dp" /> \ No newline at end of file diff --git a/library/src/main/res/layout/item_scroll_end.xml b/library/src/main/res/layout/item_scroll_end.xml new file mode 100644 index 0000000..489eb72 --- /dev/null +++ b/library/src/main/res/layout/item_scroll_end.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout/spacing_item_view.xml b/library/src/main/res/layout/spacing_item_view.xml index b2487bc..35b9416 100644 --- a/library/src/main/res/layout/spacing_item_view.xml +++ b/library/src/main/res/layout/spacing_item_view.xml @@ -31,5 +31,5 @@ android:layout_height="wrap_content" android:id="@+id/spacing_in_dp" style="@style/GuideStyleBody" - tools:text="10 px" /> + tools:text="10 dp" /> \ No newline at end of file diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml index 6ac4b86..564c760 100644 --- a/library/src/main/res/values/strings.xml +++ b/library/src/main/res/values/strings.xml @@ -1,12 +1,13 @@ Guide Style Library Variant - %d px + %d dp GuideStyle by 3SidedCube Colours & Shadows Text Styles Layout Corner Radius + End of scroll Buttons Enabled Disabled @@ -17,4 +18,5 @@ Custom Views Failed to load the package: %s Switch Buttons + Padding \ No newline at end of file From 417815202e9d2ce8303bc038f5b8bcd7f84e2a8a Mon Sep 17 00:00:00 2001 From: Mykhailo Hudov Date: Wed, 20 Sep 2023 14:16:05 +0100 Subject: [PATCH 2/3] TextStyles feed is ordered now --- app/src/main/res/values/styles.xml | 47 +++++++++++++++++++ .../com/cube/styleguide/StyleGuideFragment.kt | 28 +++-------- .../stylehandlers/TextStylesHandler.kt | 32 +++++++++++++ 3 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 library/src/main/java/com/cube/styleguide/stylehandlers/TextStylesHandler.kt diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4da997c..707cb7a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,43 +1,85 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt b/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt index 0370c11..2b44df2 100644 --- a/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt +++ b/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt @@ -26,6 +26,7 @@ import com.cube.styleguide.stylehandlers.RadiusHandler import com.cube.styleguide.stylehandlers.ScrollEndHandler import com.cube.styleguide.stylehandlers.ShadowsHandler import com.cube.styleguide.stylehandlers.SpacingHandler +import com.cube.styleguide.stylehandlers.TextStylesHandler import com.cube.styleguide.utils.Extensions.getPackageNameFlavorAdapted import com.cube.styleguide.utils.ShakeSensorListener import com.google.android.material.button.MaterialButton @@ -93,19 +94,11 @@ open class StyleGuideFragment : BottomSheetFragment(R.layout.fragment_style_guid * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. */ - fun populateTextStyles( - themes: Array, - prefixesList: List = listOf("Body", "Heading", "Caption", "Subtitle", "Bold", "Regular") - ) { - val textStylesList = mutableListOf>() - - themes.forEach { theme -> - if (prefixesList.any { theme.name.startsWith(it) }) { - textStylesList.add(Pair(theme.name, theme.getInt(null))) - } - } - - addViewsToRelevantSection("Text", textStylesList) + private fun populateTextStyles() { + binding?.apply { + textContainerView.isVisible = true + textRecyclerView.adapter = TextStylesAdapter(TextStylesHandler.getTextStyles(requireContext()) ?: return) + } } /** @@ -216,13 +209,6 @@ open class StyleGuideFragment : BottomSheetFragment(R.layout.fragment_style_guid } } - "Text" -> { - binding?.apply { - textContainerView.isVisible = true - textRecyclerView.adapter = TextStylesAdapter(stylesList) - } - } - "Checkbox" -> { binding?.apply { checkboxContainerView.isVisible = true @@ -324,7 +310,7 @@ open class StyleGuideFragment : BottomSheetFragment(R.layout.fragment_style_guid private fun populateStyles(packageName: String) { val themes: Array = Class.forName("$packageName.R\$style").declaredFields populateButtonStyles(themes) - populateTextStyles(themes) + populateTextStyles() populateCheckboxStyles(themes) populateRadioButtonStyles(themes) populateSwitchStyles(themes) diff --git a/library/src/main/java/com/cube/styleguide/stylehandlers/TextStylesHandler.kt b/library/src/main/java/com/cube/styleguide/stylehandlers/TextStylesHandler.kt new file mode 100644 index 0000000..57975d0 --- /dev/null +++ b/library/src/main/java/com/cube/styleguide/stylehandlers/TextStylesHandler.kt @@ -0,0 +1,32 @@ +package com.cube.styleguide.stylehandlers + +import android.content.Context + +class TextStylesHandler { + companion object { + private const val textStyleAttribute = android.R.attr.textStyle + private const val textSizeAttribute = android.R.attr.textSize + fun getTextStyles( + context: Context, + prefixesList: List = listOf("Body", "Heading", "Caption", "Subtitle", "Bold", "Regular") + ) = Class.forName("${context.packageName}.R\$style").declaredFields.let { themes -> + themes.mapNotNull { theme -> + if (prefixesList.any { theme.name.startsWith(it) }) Pair(theme.name, theme.getInt(null)) else null + + // sorts by the android.R.attr.textStyle attribute, then descending by the android.R.attr.textSize attribute and ascending by style name + }.sortedWith(compareByDescending> { + val themeTypedArray = context.obtainStyledAttributes(it.second, intArrayOf(textStyleAttribute)) + val textStyle = themeTypedArray.getString(0) + themeTypedArray.recycle() + textStyle + }.thenByDescending { + val themeTypedArray = context.obtainStyledAttributes(it.second, intArrayOf(textSizeAttribute)) + val textSize = themeTypedArray.getDimensionPixelSize(0, 0) + themeTypedArray.recycle() + textSize + }.thenBy { + it.first + }).ifEmpty { return@let null } + } + } +} \ No newline at end of file From 8e30b140b120a72d9436a662be4bcf2acc839213 Mon Sep 17 00:00:00 2001 From: Mykhailo Hudov Date: Tue, 26 Sep 2023 16:05:04 +0100 Subject: [PATCH 3/3] ButtonSection logic modified --- .../drawable/background_button_primary.xml | 19 + .../background_button_primary_disabled.xml | 5 + .../background_button_primary_selector.xml | 5 + .../drawable/background_button_secondary.xml | 22 + .../background_button_secondary_disabled.xml | 8 + .../background_button_secondary_selector.xml | 5 + app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/styles.xml | 50 ++ .../com/cube/styleguide/StyleGuideActivity.kt | 18 +- .../com/cube/styleguide/StyleGuideFragment.kt | 595 +++++++++--------- .../stylehandlers/ButtonsHandler.kt | 29 + .../stylehandlers/TextStylesHandler.kt | 2 +- .../com/cube/styleguide/utils/Extensions.kt | 3 + .../main/res/layout/fragment_style_guide.xml | 40 +- .../main/res/layout/item_button_container.xml | 26 + 15 files changed, 495 insertions(+), 334 deletions(-) create mode 100644 app/src/main/res/drawable/background_button_primary.xml create mode 100644 app/src/main/res/drawable/background_button_primary_disabled.xml create mode 100644 app/src/main/res/drawable/background_button_primary_selector.xml create mode 100644 app/src/main/res/drawable/background_button_secondary.xml create mode 100644 app/src/main/res/drawable/background_button_secondary_disabled.xml create mode 100644 app/src/main/res/drawable/background_button_secondary_selector.xml create mode 100644 library/src/main/java/com/cube/styleguide/stylehandlers/ButtonsHandler.kt create mode 100644 library/src/main/res/layout/item_button_container.xml diff --git a/app/src/main/res/drawable/background_button_primary.xml b/app/src/main/res/drawable/background_button_primary.xml new file mode 100644 index 0000000..1deba82 --- /dev/null +++ b/app/src/main/res/drawable/background_button_primary.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/background_button_primary_disabled.xml b/app/src/main/res/drawable/background_button_primary_disabled.xml new file mode 100644 index 0000000..c9a2390 --- /dev/null +++ b/app/src/main/res/drawable/background_button_primary_disabled.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_primary_selector.xml b/app/src/main/res/drawable/background_button_primary_selector.xml new file mode 100644 index 0000000..dcb59ab --- /dev/null +++ b/app/src/main/res/drawable/background_button_primary_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_secondary.xml b/app/src/main/res/drawable/background_button_secondary.xml new file mode 100644 index 0000000..3e9013b --- /dev/null +++ b/app/src/main/res/drawable/background_button_secondary.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_secondary_disabled.xml b/app/src/main/res/drawable/background_button_secondary_disabled.xml new file mode 100644 index 0000000..6e43a72 --- /dev/null +++ b/app/src/main/res/drawable/background_button_secondary_disabled.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_secondary_selector.xml b/app/src/main/res/drawable/background_button_secondary_selector.xml new file mode 100644 index 0000000..af8d522 --- /dev/null +++ b/app/src/main/res/drawable/background_button_secondary_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index c6bfee2..450983a 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -41,4 +41,6 @@ #EF5567 #F1CBCF + + #FFFFFF \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 707cb7a..672a70b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -93,4 +93,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/java/com/cube/styleguide/StyleGuideActivity.kt b/library/src/main/java/com/cube/styleguide/StyleGuideActivity.kt index 0f15554..454173c 100644 --- a/library/src/main/java/com/cube/styleguide/StyleGuideActivity.kt +++ b/library/src/main/java/com/cube/styleguide/StyleGuideActivity.kt @@ -1,11 +1,25 @@ package com.cube.styleguide +import android.content.Context +import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.cube.styleguide.databinding.ActivityGuidestyleBinding class StyleGuideActivity : AppCompatActivity() { + companion object { + const val KEY_TEXT_STYLE_PREFIXES_LIST = "KEY_TEXT_STYLE_PREFIXES_LIST" + + /** + * @param textPrefixesList - it can be list of strings like: "Body", "Heading", "Caption", "Subtitle", "Bold", "Regular" + * the logic will be searching these represented prefixes in the styles.xml and classify them as textStyles + * */ + fun getIntent(context: Context, textPrefixesList: ArrayList?) = Intent(context, StyleGuideActivity::class.java).apply { + this.putStringArrayListExtra(KEY_TEXT_STYLE_PREFIXES_LIST, textPrefixesList) + } + } + private lateinit var binding: ActivityGuidestyleBinding override fun onCreate(savedInstanceState: Bundle?) { @@ -14,6 +28,8 @@ class StyleGuideActivity : AppCompatActivity() { binding = ActivityGuidestyleBinding.inflate(layoutInflater) setContentView(binding.root) - StyleGuideFragment().show(supportFragmentManager, StyleGuideFragment::class.java.name) + StyleGuideFragment.getInstance( + textStylePrefixesList = intent.getStringArrayListExtra(KEY_TEXT_STYLE_PREFIXES_LIST) + ).show(supportFragmentManager, StyleGuideFragment::class.java.name) } } \ No newline at end of file diff --git a/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt b/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt index 2b44df2..2dedbef 100644 --- a/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt +++ b/library/src/main/java/com/cube/styleguide/StyleGuideFragment.kt @@ -11,7 +11,9 @@ import androidx.appcompat.widget.AppCompatButton import androidx.appcompat.widget.AppCompatCheckBox import androidx.appcompat.widget.AppCompatRadioButton import androidx.appcompat.widget.SwitchCompat +import androidx.core.os.bundleOf import androidx.core.view.isVisible +import com.cube.styleguide.StyleGuideActivity.Companion.KEY_TEXT_STYLE_PREFIXES_LIST import com.cube.styleguide.adapter.ColorAdapter import com.cube.styleguide.adapter.HorizontalSpacingAdapter import com.cube.styleguide.adapter.RadiusAdapter @@ -19,7 +21,9 @@ import com.cube.styleguide.adapter.ShadowAdapter import com.cube.styleguide.adapter.SpacingAdapter import com.cube.styleguide.adapter.TextStylesAdapter import com.cube.styleguide.databinding.FragmentStyleGuideBinding +import com.cube.styleguide.databinding.ItemButtonContainerBinding import com.cube.styleguide.fragments.BottomSheetFragment +import com.cube.styleguide.stylehandlers.ButtonsHandler import com.cube.styleguide.stylehandlers.ColorsHandler import com.cube.styleguide.stylehandlers.HorizontalSpacingHandler import com.cube.styleguide.stylehandlers.RadiusHandler @@ -36,287 +40,290 @@ import com.google.android.material.radiobutton.MaterialRadioButton import java.lang.reflect.Field open class StyleGuideFragment : BottomSheetFragment(R.layout.fragment_style_guide) { - private var binding: FragmentStyleGuideBinding? = null - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = FragmentStyleGuideBinding.inflate(inflater, container, false) - binding?.closeButton?.setOnClickListener { dismiss() } - return binding?.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val packageName = requireContext().getPackageNameFlavorAdapted() - if (packageName.isEmpty()) { - binding?.apply { - nestedScrollView.visibility = View.GONE - errorMessage.text = getString(R.string.guidestyle_failed_to_load_the_package, context?.packageName!!) - errorMessage.visibility = View.VISIBLE - } - } else { + private var binding: FragmentStyleGuideBinding? = null + + companion object { + fun getInstance(textStylePrefixesList: List?) = StyleGuideFragment().also { + textStylePrefixesList?.let { prefixesList -> + it.arguments = bundleOf(KEY_TEXT_STYLE_PREFIXES_LIST to prefixesList) + } + } + } + + private val getTextStylePrefixesList get() = arguments?.getStringArrayList(KEY_TEXT_STYLE_PREFIXES_LIST) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FragmentStyleGuideBinding.inflate(inflater, container, false) + binding?.closeButton?.setOnClickListener { dismiss() } + return binding?.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val packageName = requireContext().getPackageNameFlavorAdapted() + if (packageName.isEmpty()) { + binding?.apply { + nestedScrollView.visibility = View.GONE + errorMessage.text = getString(R.string.guidestyle_failed_to_load_the_package, context?.packageName!!) + errorMessage.visibility = View.VISIBLE + } + } else { populateColorsAndShadow() - populateSpacings() + populateSpacings() populateRadius() populateScrollEnd() - populateStyles(packageName) - } - } - - /** - * Call this function to populate the style guide with your button styles, and to choose the component type. - * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. - * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. - * @param usesMaterialComponents Identifies whether styles for this component should be applied to a materialComponent or an appCompatComponent. - */ - fun populateButtonStyles( - themes: Array, - prefixesList: List = listOf("Button", "button"), - usesMaterialComponents: Boolean = false - ) { - - val buttonStylesList = mutableListOf>() - - themes.forEach { theme -> - if (prefixesList.any { theme.name.startsWith(it) }) { - buttonStylesList.add(Pair(theme.name, theme.getInt(null))) - } - } - - addViewsToRelevantSection("Button", buttonStylesList, usesMaterialComponents) - } - - /** - * Call this function to populate the style guide with your text styles. - * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. - * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. - */ - private fun populateTextStyles() { + populateStyles(packageName) + } + } + + /** + * Call this function to populate the style guide with your button styles, and to choose the component type. + * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. + * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. + * @param usesMaterialComponents Identifies whether styles for this component should be applied to a materialComponent or an appCompatComponent. + */ + private fun populateButtonStyles() { + val stylesList = ButtonsHandler.getButtonsStyles(requireContext()) + binding?.buttonContainerView?.isVisible = stylesList != null + stylesList ?: return + + val usesMaterialComponents = false + + val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + val bottom = resources.getDimensionPixelSize(R.dimen.guidestyle_largest_spacing) + val vertical = resources.getDimensionPixelSize(R.dimen.guidestyle_small_spacing) + params.setMargins(vertical, 0, vertical, bottom) + + stylesList.forEach { + val container = ItemButtonContainerBinding.inflate(layoutInflater) + val (button, disabledButton) = if (usesMaterialComponents) Pair(getMaterialButton(it), getMaterialButton(it)) + else Pair(getAppCompatButton(it), getAppCompatButton(it)) + + button.text = "Active" + button.layoutParams = params + button.isClickable = true + + disabledButton.text = "Inactive" + disabledButton.isEnabled = false + disabledButton.layoutParams = params + container.buttonContainer.addView(button) + + container.sectionLabel.text = it.first + container.buttonContainer.addView(disabledButton) + + binding?.buttonsContainer?.addView(container.root) + } + } + + private fun getMaterialButton(style: Pair) = MaterialButton(ContextThemeWrapper(requireContext(), style.second), null, style.second) + private fun getAppCompatButton(style: Pair) = AppCompatButton(ContextThemeWrapper(requireContext(), style.second), null, style.second) + + /** + * Call this function to populate the style guide with your text styles. + * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. + * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. + */ + private fun populateTextStyles() { binding?.apply { textContainerView.isVisible = true - textRecyclerView.adapter = TextStylesAdapter(TextStylesHandler.getTextStyles(requireContext()) ?: return) + val textStyles = getTextStylePrefixesList ?: listOf("Body", "Heading", "Caption", "Subtitle", "Bold", "Regular") + textRecyclerView.adapter = TextStylesAdapter(TextStylesHandler.getTextStyles(requireContext(), textStyles) ?: return) + } + } + + /** + * Call this function to populate the style guide with your checkbox styles, and to choose the component type. + * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. + * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. + * @param usesMaterialComponents Identifies whether styles for this component should be applied to a materialComponent or an appCompatComponent. + */ + fun populateCheckboxStyles( + themes: Array, + prefixesList: List = listOf("Checkbox", "checkbox", "CheckBox"), + usesMaterialComponents: Boolean = false + ) { + val checkboxStylesList = mutableListOf>() + + themes.forEach { theme -> + if (prefixesList.any { theme.name.startsWith(it) }) { + checkboxStylesList.add(Pair(theme.name, theme.getInt(null))) + } + } + + addViewsToRelevantSection("Checkbox", checkboxStylesList, usesMaterialComponents) + } + + /** + * Call this function to populate the style guide with your radio button styles, and to choose the component type. + * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. + * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. + * @param usesMaterialComponents Identifies whether styles for this component should be applied to a materialComponent or an appCompatComponent. + */ + fun populateRadioButtonStyles( + themes: Array, + prefixesList: List = listOf("RadioButton", "Radiobutton", "radiobutton"), + usesMaterialComponents: Boolean = false + ) { + val radiobuttonStyleList = mutableListOf>() + + themes.forEach { theme -> + if (prefixesList.any { theme.name.startsWith(it) }) { + radiobuttonStyleList.add(Pair(theme.name, theme.getInt(null))) + } } - } - - /** - * Call this function to populate the style guide with your checkbox styles, and to choose the component type. - * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. - * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. - * @param usesMaterialComponents Identifies whether styles for this component should be applied to a materialComponent or an appCompatComponent. - */ - fun populateCheckboxStyles( - themes: Array, - prefixesList: List = listOf("Checkbox", "checkbox", "CheckBox"), - usesMaterialComponents: Boolean = false - ) { - val checkboxStylesList = mutableListOf>() - - themes.forEach { theme -> - if (prefixesList.any { theme.name.startsWith(it) }) { - checkboxStylesList.add(Pair(theme.name, theme.getInt(null))) - } - } - - addViewsToRelevantSection("Checkbox", checkboxStylesList, usesMaterialComponents) - } - - /** - * Call this function to populate the style guide with your radio button styles, and to choose the component type. - * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. - * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. - * @param usesMaterialComponents Identifies whether styles for this component should be applied to a materialComponent or an appCompatComponent. - */ - fun populateRadioButtonStyles( - themes: Array, - prefixesList: List = listOf("RadioButton", "Radiobutton", "radiobutton"), - usesMaterialComponents: Boolean = false - ) { - val radiobuttonStyleList = mutableListOf>() - - themes.forEach { theme -> - if (prefixesList.any { theme.name.startsWith(it) }) { - radiobuttonStyleList.add(Pair(theme.name, theme.getInt(null))) - } - } - - addViewsToRelevantSection("RadioButton", radiobuttonStyleList, usesMaterialComponents) - } - - /** - * Call this function to populate the style guide with your switch styles, and to choose the component type. - * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. - * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. - * @param usesMaterialComponents Identifies whether styles for this component should be applied to a materialComponent or an appCompatComponent. - */ - fun populateSwitchStyles( - themes: Array, - prefixesList: List = listOf("Switch", "switch"), - usesMaterialComponents: Boolean = false - ) { - val switchStyleList = mutableListOf>() - - themes.forEach { theme -> - if (prefixesList.any { theme.name.startsWith(it) }) { - switchStyleList.add(Pair(theme.name, theme.getInt(null))) - } - } - - addViewsToRelevantSection("Switch", switchStyleList, usesMaterialComponents) - } - - /** - * @param componentName A string name for the view component. - * @param stylesList A list of styles for the component. - * @param usesMaterialComponents Tells the function whether to create the views using material components or app compat components. - */ - private fun addViewsToRelevantSection(componentName: String, stylesList: List>, usesMaterialComponents: Boolean = false) { - if (stylesList.isEmpty()) return - - val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) - val bottom = resources.getDimensionPixelSize(R.dimen.guidestyle_largest_spacing) - val vertical = resources.getDimensionPixelSize(R.dimen.guidestyle_small_spacing) - params.setMargins(vertical, 0, vertical, bottom) - - - when (componentName) { - "Button" -> { - binding?.apply { - buttonContainerView.isVisible = true - buttonEnabledContainerView.removeAllViews() - buttonDisabledContainerView.removeAllViews() - - stylesList.forEach { - val (button, disabledButton) = if (usesMaterialComponents) { - Pair(MaterialButton(ContextThemeWrapper(requireContext(), it.second)), MaterialButton(ContextThemeWrapper(requireContext(), it.second))) - } else { - Pair(AppCompatButton(ContextThemeWrapper(requireContext(), it.second)), AppCompatButton(ContextThemeWrapper(requireContext(), it.second))) - } - - button.text = it.first - button.layoutParams = params - button.isClickable = true - - disabledButton.text = it.first - disabledButton.isEnabled = false - disabledButton.layoutParams = params - buttonEnabledContainerView.addView(button) - - buttonDisabledContainerView.addView(disabledButton) - } - } - } - - "Checkbox" -> { - binding?.apply { - checkboxContainerView.isVisible = true - checkboxEnabledContainerView.removeAllViews() - checkboxDisabledContainerView.removeAllViews() - - stylesList.forEach { style -> - val (checkboxChecked, checkboxNotChecked, checkboxCheckedDisabled, checkboxNotCheckedDisabled) = List(4) { - if (usesMaterialComponents) { - MaterialCheckBox(ContextThemeWrapper(requireContext(), style.second)) - } else { - AppCompatCheckBox(ContextThemeWrapper(requireContext(), style.second)) - } - }.onEach { - it.text = style.first - it.layoutParams = params - } - - checkboxChecked.isChecked = true - checkboxCheckedDisabled.isChecked = true - checkboxCheckedDisabled.isEnabled = false - checkboxNotCheckedDisabled.isEnabled = false - - checkboxEnabledContainerView.addView(checkboxChecked) - checkboxEnabledContainerView.addView(checkboxNotChecked) - checkboxDisabledContainerView.addView(checkboxCheckedDisabled) - checkboxDisabledContainerView.addView(checkboxNotCheckedDisabled) - } - } - } - - "RadioButton" -> { - binding?.apply { - radiobuttonContainerView.isVisible = true - radiobuttonEnabledContainerView.removeAllViews() - radiobuttonDisabledContainerView.removeAllViews() - - stylesList.forEach { style -> - val (radioButtonChecked, radioButtonNotChecked, radioButtonCheckedDisabled, radioButtonNotCheckedDisabled) = List(4) { - if (usesMaterialComponents) { - MaterialRadioButton(ContextThemeWrapper(requireContext(), style.second)) - } else { - AppCompatRadioButton(ContextThemeWrapper(requireContext(), style.second)) - } - }.onEach { - it.layoutParams = params - it.text = style.first - } - - radioButtonChecked.isChecked = true - radioButtonCheckedDisabled.isChecked = true - radioButtonCheckedDisabled.isEnabled = false - radioButtonNotCheckedDisabled.isEnabled = false - - radiobuttonEnabledContainerView.addView(radioButtonChecked) - radiobuttonEnabledContainerView.addView(radioButtonNotChecked) - radiobuttonDisabledContainerView.addView(radioButtonCheckedDisabled) - radiobuttonDisabledContainerView.addView(radioButtonNotCheckedDisabled) - } - } - } - - "Switch" -> { - binding?.apply { - switchContainerView.visibility = View.VISIBLE - switchEnabledContainerView.removeAllViews() - switchDisabledContainerView.removeAllViews() - - stylesList.forEach { style -> - val (switchChecked, switchNotChecked, switchCheckedDisabled, switchNotCheckedDisabled) = List(4) { - if (usesMaterialComponents) { - MaterialSwitch(ContextThemeWrapper(requireContext(), style.second)) - } else { - SwitchCompat(ContextThemeWrapper(requireContext(), style.second)) - } - }.onEach { - it.layoutParams = params - it.text = style.first - } - - switchChecked.isChecked = true - switchCheckedDisabled.isChecked = true - switchCheckedDisabled.isEnabled = false - switchNotCheckedDisabled.isEnabled = false - - switchEnabledContainerView.addView(switchChecked) - switchEnabledContainerView.addView(switchNotChecked) - switchDisabledContainerView.addView(switchCheckedDisabled) - switchDisabledContainerView.addView(switchNotCheckedDisabled) - } - } - } - } - } - - /** - * Function to be called when the fragment loads. It will call the various populate functions for the style guide. These functions can then be called again if you need to customise the content. - */ - private fun populateStyles(packageName: String) { - val themes: Array = Class.forName("$packageName.R\$style").declaredFields - populateButtonStyles(themes) - populateTextStyles() - populateCheckboxStyles(themes) - populateRadioButtonStyles(themes) - populateSwitchStyles(themes) - } - - private fun populateSpacings() { + + addViewsToRelevantSection("RadioButton", radiobuttonStyleList, usesMaterialComponents) + } + + /** + * Call this function to populate the style guide with your switch styles, and to choose the component type. + * @param themes A list of styles these can be retrieved using Class.forName("$packageName.R\$style").declaredFields. + * @param prefixesList A list of prefixes used in your styles, by default this is a list of commonly used prefixes. + * @param usesMaterialComponents Identifies whether styles for this component should be applied to a materialComponent or an appCompatComponent. + */ + fun populateSwitchStyles( + themes: Array, + prefixesList: List = listOf("Switch", "switch"), + usesMaterialComponents: Boolean = false + ) { + val switchStyleList = mutableListOf>() + + themes.forEach { theme -> + if (prefixesList.any { theme.name.startsWith(it) }) { + switchStyleList.add(Pair(theme.name, theme.getInt(null))) + } + } + + addViewsToRelevantSection("Switch", switchStyleList, usesMaterialComponents) + } + + /** + * @param componentName A string name for the view component. + * @param stylesList A list of styles for the component. + * @param usesMaterialComponents Tells the function whether to create the views using material components or app compat components. + */ + private fun addViewsToRelevantSection(componentName: String, stylesList: List>, usesMaterialComponents: Boolean = false) { + if (stylesList.isEmpty()) return + + val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + val bottom = resources.getDimensionPixelSize(R.dimen.guidestyle_largest_spacing) + val vertical = resources.getDimensionPixelSize(R.dimen.guidestyle_small_spacing) + params.setMargins(vertical, 0, vertical, bottom) + + + when (componentName) { + "Checkbox" -> { + binding?.apply { + checkboxContainerView.isVisible = true + checkboxEnabledContainerView.removeAllViews() + checkboxDisabledContainerView.removeAllViews() + + stylesList.forEach { style -> + val (checkboxChecked, checkboxNotChecked, checkboxCheckedDisabled, checkboxNotCheckedDisabled) = List(4) { + if (usesMaterialComponents) { + MaterialCheckBox(ContextThemeWrapper(requireContext(), style.second)) + } else { + AppCompatCheckBox(ContextThemeWrapper(requireContext(), style.second)) + } + }.onEach { + it.text = style.first + it.layoutParams = params + } + + checkboxChecked.isChecked = true + checkboxCheckedDisabled.isChecked = true + checkboxCheckedDisabled.isEnabled = false + checkboxNotCheckedDisabled.isEnabled = false + + checkboxEnabledContainerView.addView(checkboxChecked) + checkboxEnabledContainerView.addView(checkboxNotChecked) + checkboxDisabledContainerView.addView(checkboxCheckedDisabled) + checkboxDisabledContainerView.addView(checkboxNotCheckedDisabled) + } + } + } + + "RadioButton" -> { + binding?.apply { + radiobuttonContainerView.isVisible = true + radiobuttonEnabledContainerView.removeAllViews() + radiobuttonDisabledContainerView.removeAllViews() + + stylesList.forEach { style -> + val (radioButtonChecked, radioButtonNotChecked, radioButtonCheckedDisabled, radioButtonNotCheckedDisabled) = List(4) { + if (usesMaterialComponents) { + MaterialRadioButton(ContextThemeWrapper(requireContext(), style.second)) + } else { + AppCompatRadioButton(ContextThemeWrapper(requireContext(), style.second)) + } + }.onEach { + it.layoutParams = params + it.text = style.first + } + + radioButtonChecked.isChecked = true + radioButtonCheckedDisabled.isChecked = true + radioButtonCheckedDisabled.isEnabled = false + radioButtonNotCheckedDisabled.isEnabled = false + + radiobuttonEnabledContainerView.addView(radioButtonChecked) + radiobuttonEnabledContainerView.addView(radioButtonNotChecked) + radiobuttonDisabledContainerView.addView(radioButtonCheckedDisabled) + radiobuttonDisabledContainerView.addView(radioButtonNotCheckedDisabled) + } + } + } + + "Switch" -> { + binding?.apply { + switchContainerView.visibility = View.VISIBLE + switchEnabledContainerView.removeAllViews() + switchDisabledContainerView.removeAllViews() + + stylesList.forEach { style -> + val (switchChecked, switchNotChecked, switchCheckedDisabled, switchNotCheckedDisabled) = List(4) { + if (usesMaterialComponents) { + MaterialSwitch(ContextThemeWrapper(requireContext(), style.second)) + } else { + SwitchCompat(ContextThemeWrapper(requireContext(), style.second)) + } + }.onEach { + it.layoutParams = params + it.text = style.first + } + + switchChecked.isChecked = true + switchCheckedDisabled.isChecked = true + switchCheckedDisabled.isEnabled = false + switchNotCheckedDisabled.isEnabled = false + + switchEnabledContainerView.addView(switchChecked) + switchEnabledContainerView.addView(switchNotChecked) + switchDisabledContainerView.addView(switchCheckedDisabled) + switchDisabledContainerView.addView(switchNotCheckedDisabled) + } + } + } + } + } + + /** + * Function to be called when the fragment loads. It will call the various populate functions for the style guide. These functions can then be called again if you need to customise the content. + */ + private fun populateStyles(packageName: String) { + val themes: Array = Class.forName("$packageName.R\$style").declaredFields + populateButtonStyles() + populateTextStyles() + populateCheckboxStyles(themes) + populateRadioButtonStyles(themes) + populateSwitchStyles(themes) + } + + private fun populateSpacings() { binding?.apply { val spacingsList = SpacingHandler.getSpacings(requireContext()) spacingsList?.let { @@ -330,7 +337,7 @@ open class StyleGuideFragment : BottomSheetFragment(R.layout.fragment_style_guid layoutContainerView.isVisible = !spacingsList.isNullOrEmpty() || !horizontalSpacingsList.isNullOrEmpty() } - } + } private fun populateRadius() { binding?.apply { @@ -368,27 +375,27 @@ open class StyleGuideFragment : BottomSheetFragment(R.layout.fragment_style_guid } } - /** - * Override the onViewCreated and call this function in order to add you custom views - */ - fun addCustomView(view: View) { - binding?.customViews?.addView(view) - binding?.customViewContainer?.visibility = View.VISIBLE - } - - override fun onDestroyView() { - super.onDestroyView() - /** - * Move sure to remove the custom views, cos otherwise you need to create new views everytime due to the parent of the view being the previous instance of the Fragment - * This is not an issue for the rest of the views cos they get recreated everytime. While custom views remain in the [GuideStyleManager] - */ - binding?.customViews?.removeAllViews() - binding = null - } - - override fun onDismiss(dialog: DialogInterface) { - super.onDismiss(dialog) - - (activity as? ShakeSensorListener.StyleGuideHandler)?.registerListener() - } + /** + * Override the onViewCreated and call this function in order to add you custom views + */ + fun addCustomView(view: View) { + binding?.customViews?.addView(view) + binding?.customViewContainer?.visibility = View.VISIBLE + } + + override fun onDestroyView() { + super.onDestroyView() + /** + * Move sure to remove the custom views, cos otherwise you need to create new views everytime due to the parent of the view being the previous instance of the Fragment + * This is not an issue for the rest of the views cos they get recreated everytime. While custom views remain in the [GuideStyleManager] + */ + binding?.customViews?.removeAllViews() + binding = null + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + + (activity as? ShakeSensorListener.StyleGuideHandler)?.registerListener() + } } diff --git a/library/src/main/java/com/cube/styleguide/stylehandlers/ButtonsHandler.kt b/library/src/main/java/com/cube/styleguide/stylehandlers/ButtonsHandler.kt new file mode 100644 index 0000000..9f7bae0 --- /dev/null +++ b/library/src/main/java/com/cube/styleguide/stylehandlers/ButtonsHandler.kt @@ -0,0 +1,29 @@ +package com.cube.styleguide.stylehandlers + +import android.content.Context +import com.cube.styleguide.utils.Extensions.lastPart +import java.lang.reflect.Field + +class ButtonsHandler { + companion object { + + private const val UNDEFINED_BTN_POSITION = 999 + fun getButtonsStyles(context: Context) = Class.forName("${context.packageName}.R\$style").declaredFields + .filter { it.buttonName.lowercase().startsWith("button") } + .map { theme -> Pair(theme.name.lastPart("_"), theme.buttonId) } + .sortedBy { button -> + ButtonOrdering.values().toList().firstOrNull { orderItem -> button.first.lowercase().contains(orderItem.name.lowercase()) }?.position ?: UNDEFINED_BTN_POSITION + }.ifEmpty { null } + } +} + +private enum class ButtonOrdering(val position: Int) { + Primary(0), + Secondary(1), + Fab(2), + Tertiary(3), + Sticky(4) +} + +private val Field.buttonId get() = getInt(null) +private val Field.buttonName get() = name \ No newline at end of file diff --git a/library/src/main/java/com/cube/styleguide/stylehandlers/TextStylesHandler.kt b/library/src/main/java/com/cube/styleguide/stylehandlers/TextStylesHandler.kt index 57975d0..53cb54e 100644 --- a/library/src/main/java/com/cube/styleguide/stylehandlers/TextStylesHandler.kt +++ b/library/src/main/java/com/cube/styleguide/stylehandlers/TextStylesHandler.kt @@ -8,7 +8,7 @@ class TextStylesHandler { private const val textSizeAttribute = android.R.attr.textSize fun getTextStyles( context: Context, - prefixesList: List = listOf("Body", "Heading", "Caption", "Subtitle", "Bold", "Regular") + prefixesList: List ) = Class.forName("${context.packageName}.R\$style").declaredFields.let { themes -> themes.mapNotNull { theme -> if (prefixesList.any { theme.name.startsWith(it) }) Pair(theme.name, theme.getInt(null)) else null diff --git a/library/src/main/java/com/cube/styleguide/utils/Extensions.kt b/library/src/main/java/com/cube/styleguide/utils/Extensions.kt index 74d1107..5f5119b 100644 --- a/library/src/main/java/com/cube/styleguide/utils/Extensions.kt +++ b/library/src/main/java/com/cube/styleguide/utils/Extensions.kt @@ -8,6 +8,9 @@ import com.cube.styleguide.R import java.util.Locale object Extensions { + + fun String.lastPart(divider: String) = split(divider).lastOrNull() ?: this + fun String.firstPart(): String { val content = split("_") return if (content.size > 1) { diff --git a/library/src/main/res/layout/fragment_style_guide.xml b/library/src/main/res/layout/fragment_style_guide.xml index bf0130e..da5b664 100644 --- a/library/src/main/res/layout/fragment_style_guide.xml +++ b/library/src/main/res/layout/fragment_style_guide.xml @@ -191,44 +191,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/buttons_container" - android:orientation="vertical"> - - - - - - - - - + android:orientation="vertical" + android:visibility="gone" /> diff --git a/library/src/main/res/layout/item_button_container.xml b/library/src/main/res/layout/item_button_container.xml new file mode 100644 index 0000000..0f31f0d --- /dev/null +++ b/library/src/main/res/layout/item_button_container.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file