Skip to content

Commit

Permalink
feat(scaffolds): Create Vitamin scaffold component. (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
GerardPaligot committed May 23, 2023
1 parent fd3bdd3 commit b63cf7b
Show file tree
Hide file tree
Showing 25 changed files with 780 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.decathlon.compose.sample.screens.Progress
import com.decathlon.compose.sample.screens.QuantityPicker
import com.decathlon.compose.sample.screens.RadioButtons
import com.decathlon.compose.sample.screens.Ratings
import com.decathlon.compose.sample.screens.Scaffold
import com.decathlon.compose.sample.screens.Skeletons
import com.decathlon.compose.sample.screens.Snackbars
import com.decathlon.compose.sample.screens.Switches
Expand Down Expand Up @@ -77,7 +78,8 @@ class MainActivity : AppCompatActivity() {
Fabs,
Chips,
Icons,
Assets
Assets,
Scaffold
)
}
NavHost(navController = navController, startDestination = "dashboard") {
Expand Down
200 changes: 200 additions & 0 deletions sample/src/main/java/com/decathlon/compose/sample/screens/Scaffold.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package com.decathlon.compose.sample.screens

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.decathlon.vitamin.compose.VitaminIcons
import com.decathlon.vitamin.compose.buttons.VitaminButtons
import com.decathlon.vitamin.compose.scaffolds.BottomActionUi
import com.decathlon.vitamin.compose.scaffolds.BottomActionsUi
import com.decathlon.vitamin.compose.scaffolds.FabActionUi
import com.decathlon.vitamin.compose.scaffolds.TabActionUi
import com.decathlon.vitamin.compose.scaffolds.TabActionsUi
import com.decathlon.vitamin.compose.scaffolds.TopActionUi
import com.decathlon.vitamin.compose.scaffolds.TopActionsUi
import com.decathlon.vitamin.compose.scaffolds.TopBarConfiguration
import com.decathlon.vitamin.compose.scaffolds.VitaminScaffolds
import com.decathlon.vitamin.compose.vitaminicons.Fill
import com.decathlon.vitamin.compose.vitaminicons.Line
import com.decathlon.vitamin.compose.vitaminicons.fill.Calendar
import com.decathlon.vitamin.compose.vitaminicons.line.Add
import com.decathlon.vitamin.compose.vitaminicons.line.Android
import com.decathlon.vitamin.compose.vitaminicons.line.Calendar
import com.decathlon.vitamin.compose.vitaminicons.line.Search

object Scaffold : Screen {
override val name: String
get() = "Scaffold"
override val navigationKey: String
get() = "scaffolds"

@SuppressWarnings("LongMethod")
@Composable
override fun Screen(navController: NavController?) {
val searchValue = remember { mutableStateOf(value = "") }
val topBarConfiguration: MutableState<TopBarConfiguration> =
remember { mutableStateOf(TopBarConfiguration.Primary()) }
val tabActionsUi = remember { mutableStateOf(TabActionsUi(scrollable = true)) }
val tabSelected = remember { mutableStateOf<Int?>(null) }
val topActionsUi = remember {
mutableStateOf(
TopActionsUi(
actions = listOf(
TopActionUi(
id = 0,
icon = VitaminIcons.Line.Search,
contentDescription = "Search"
)
)
)
)
}
val bottomActionUi = remember { mutableStateOf(BottomActionsUi()) }
val bottomSelected = remember { mutableStateOf<String?>(null) }
VitaminScaffolds.Primary(
title = name,
topBarConfiguration = topBarConfiguration.value,
topActionsUi = topActionsUi.value,
onTopActionClicked = { action ->
if (action.id == 0) {
topBarConfiguration.value = TopBarConfiguration.Search(
value = searchValue,
placeholder = "Search",
keyboardActions = KeyboardActions(onDone = {
topBarConfiguration.value = TopBarConfiguration.Primary()
}),
navigationIcon = {
Search(contentDescription = null)
}
)
}
},
tabActionsUi = tabActionsUi.value,
onTabClicked = { action ->
tabSelected.value = tabActionsUi.value.actions
.indexOfFirst { it.route == action.route }
},
tabSelectedIndex = tabSelected.value,
onSearchValueChanged = { newValue ->
searchValue.value = newValue
},
fabAction = FabActionUi(
id = 0,
icon = VitaminIcons.Line.Add,
contentDescription = "Add"
),
bottomActionsUi = bottomActionUi.value,
bottomRouteSelected = bottomSelected.value,
onBottomActionClicked = { action ->
bottomSelected.value = action.route
}
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(it),
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(16.dp)
) {
item {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
VitaminButtons.Primary(text = "Add top action") {
topActionsUi.value = topActionsUi.value.copy(
actions = topActionsUi.value.actions + TopActionUi(
id = topActionsUi.value.actions.size + 1,
icon = VitaminIcons.Line.Android,
contentDescription = "Android ${topActionsUi.value.actions.size + 1}"
)
)
}
VitaminButtons.Primary(
text = "Remove top action",
enabled = topActionsUi.value.actions.size > 1
) {
topActionsUi.value = topActionsUi.value.copy(
actions = topActionsUi.value.actions.dropLast(1)
)
}
}
}
item {
val hasAtLeast2Tabs = tabActionsUi.value.actions.size >= 2
val hasLessThan3Tabs = tabActionsUi.value.actions.size <= 2
val additionText = if (hasAtLeast2Tabs) "Add tab" else "Add 2 tabs"
val removeText = if (hasLessThan3Tabs) "Clear tabs" else "Remove tab"
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
VitaminButtons.Primary(text = additionText) {
if (hasAtLeast2Tabs) {
tabActionsUi.value = tabActionsUi.value.copy(
actions = tabActionsUi.value.actions + TabActionUi(
route = "tab-${tabActionsUi.value.actions.size + 1}",
label = "Tab ${tabActionsUi.value.actions.size + 1}"
)
)
} else {
tabActionsUi.value = tabActionsUi.value.copy(
actions = arrayListOf(
TabActionUi(
route = "tab-1",
label = "Tab 1"
),
TabActionUi(
route = "tab-2",
label = "Tab 2"
)
)
)
}
}
VitaminButtons.Primary(
text = removeText,
enabled = tabActionsUi.value.actions.size > 1
) {
if (hasLessThan3Tabs) {
tabActionsUi.value = tabActionsUi.value.copy(actions = emptyList())
} else {
tabActionsUi.value = tabActionsUi.value.copy(
actions = tabActionsUi.value.actions.dropLast(1)
)
}
}
}
}
item {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
VitaminButtons.Primary(text = "Add bottom action") {
bottomActionUi.value = bottomActionUi.value.copy(
actions = bottomActionUi.value.actions + BottomActionUi(
route = "tab-${bottomActionUi.value.actions.size + 1}",
icon = VitaminIcons.Line.Calendar,
iconSelected = VitaminIcons.Fill.Calendar,
label = "Bottom ${bottomActionUi.value.actions.size + 1}",
contentDescription = null
)
)
}
VitaminButtons.Primary(
text = "Remove bottom action",
enabled = bottomActionUi.value.actions.isNotEmpty()
) {
bottomActionUi.value = bottomActionUi.value.copy(
actions = bottomActionUi.value.actions.dropLast(1)
)
}
}
}
}
}
}
}
1 change: 1 addition & 0 deletions scaffolds/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
71 changes: 71 additions & 0 deletions scaffolds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Scaffold components

## Scaffold design specs

You can find the design specs on [decathlon.design](https://www.decathlon.design/).

## Usage

If you want to use components of this module in your android mobile application, you should
first add the Gradle dependency in your Gradle file:

```kotlin
implementation("com.decathlon.vitamin.compose:scaffolds:<versions>")
```

### Primary

```kotlin
object VitaminScaffolds {
@Composable
fun Primary(
title: String,
modifier: Modifier = Modifier,
topBarConfiguration: TopBarConfiguration = TopBarConfiguration.Primary(),
topActionsUi: TopActionsUi = TopActionsUi(),
tabActionsUi: TabActionsUi = TabActionsUi(),
bottomActionsUi: BottomActionsUi = BottomActionsUi(),
fabAction: FabActionUi? = null,
tabSelectedIndex: Int? = null,
bottomRouteSelected: String? = null,
onTopActionClicked: (TopActionUi) -> Unit = {},
onTabClicked: (TabActionUi) -> Unit = {},
onBottomActionClicked: (BottomActionUi) -> Unit = {},
onFabActionClicked: (FabActionUi) -> Unit = {},
onSearchValueChanged: (String) -> Unit = {},
content: @Composable (PaddingValues) -> Unit
)
}
```

Material design layout with Vitamin components.
Scaffold implements Material design visual layout structure and this component will use Vitamin
TopBar, Tabs, BottomBar and Floating Action Button according to models given in input.

The minimal usage of the component is the title and content but it makes really sense to use this
component when you have TopBar, Tab, BottomBar and/or FAB configuration and more when it should
be dynamic at runtime.

```kotlin
VitaminScaffolds.Primary(title = "My title") { padding ->
// content here
}
```

Parameters | Descriptions
-- | --
`title: String` | Title specified in the Vitamin TopBar.
`modifier: Modifier = Modifier` | The `Modifier` to be applied to the component
`topBarConfiguration: TopBarConfiguration = TopBarConfiguration.Primary()` | Switch dynamically from primary to search configuration.
`topActionsUi: TopActionsUi = TopActionsUi()` | List of the TopBar actions and maximum number of actions with icon you want to show.
`tabActionsUi: TabActionsUi = TabActionsUi()` | List of the Tab actions and whether it should be used in scrollable mode.
`bottomActionsUi: BottomActionsUi = BottomActionsUi()` | List of the BottomBar actions.
`fabAction: FabActionUi? = null` | Display a floating action button at the bottom end of the content.
`tabSelectedIndex: Int? = null` | Indicate which tab is currently selected.
`bottomRouteSelected: String? = null` | Indicate which bottom action is currently selected.
`onTopActionClicked: (TopActionUi) -> Unit = {}` | The callback to be called when the user click on a TopBar action.
`onTabClicked: (TabActionUi) -> Unit = {}` | The callback to be called when the user click on a tab action.
`onBottomActionClicked: (BottomActionUi) -> Unit = {}` | The callback to be called when the user click on a bottom action.
`onFabActionClicked: (FabActionUi) -> Unit = {}` | The callback to be called when the user click on the fab.
`onSearchValueChanged: (String) -> Unit = {}` | The callback to be called when the user type a character in the searchbar.
`content: @Composable (PaddingValues) -> Unit` | The content of your screen. As Material design Scaffold component, the expected [PaddingValues] is passed and should be used by your root component in this api slot.
16 changes: 16 additions & 0 deletions scaffolds/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id("com.android.library")
id("kotlin-android")
id("VitaminComposeLibraryPlugin")
id("com.vanniktech.maven.publish")
id("app.cash.paparazzi")
}

dependencies {
api(project(":foundation:foundation"))
api(project(":appbars"))
api(project(":tabs"))
api(project(":fabs"))
implementation(AndroidX.compose.ui.tooling)
testImplementation("com.google.testparameterinjector:test-parameter-injector:1.8")
}
3 changes: 3 additions & 0 deletions scaffolds/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=scaffolds
POM_NAME=Vitamin Scaffold
POM_DESCRIPTION=Scaffold components which use Vitamin components
21 changes: 21 additions & 0 deletions scaffolds/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
2 changes: 2 additions & 0 deletions scaffolds/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.decathlon.vitamin.compose.scaffolds" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.decathlon.vitamin.compose.scaffolds

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.vector.ImageVector

@Stable
@Immutable
open class BottomActionUi(
val route: String,
val icon: ImageVector,
val iconSelected: ImageVector,
val label: String,
val contentDescription: String?,
val selectedRoutes: List<String> = emptyList()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.decathlon.vitamin.compose.scaffolds

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable

@Stable
@Immutable
data class BottomActionsUi(
val actions: List<BottomActionUi> = emptyList()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.decathlon.vitamin.compose.scaffolds

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.vector.ImageVector

@Stable
@Immutable
open class FabActionUi(
val id: Int,
val icon: ImageVector,
val contentDescription: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.decathlon.vitamin.compose.scaffolds

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.vector.ImageVector

@Stable
@Immutable
data class TabActionUi(
val route: String,
val label: String,
val icon: ImageVector? = null
)
Loading

0 comments on commit b63cf7b

Please sign in to comment.