Salt UI Documentation

repository·main·Indexed 19 days ago

https://github.com/moriafly/saltui

A library of UI components built on Compose Multiplatform, used in production applications like Salt Player. It provides a comprehensive set of tools including the SaltTheme, a bidirectional SaltNavigator for browser-style navigation, BottomSheetScaffold for persistent bottom sheets, and a flexible Button system with various appearances and intents. It also includes low-level primitives like BasicButton and lazy layout utilities such as IntervalList and LazyLayoutPrefetchState.

Tokens
9K
Snippets
33
Records
38
Agent score
60%

What's inside Salt UI

  1. Install Salt UI

    main

    Add the Salt UI dependency to your build.gradle file. Replace <TAG> with the latest version available on Maven Central (e.g., 3.0.0-alpha01).

    // Replace <TAG> with the latest version
    // e.g. implementation("io.github.moriafly:salt-ui:3.0.0-alpha01")
    implementation("io.github.moriafly:salt-ui:<TAG>")
  2. Configure Google Play publishing for Salt UI

    main

    Salt UI performs operations related to internal ART APIs. To prevent your app from failing Google Play review due to hidden API usage reporting, you must disable dependencies info reporting in your build.gradle file. Always ensure you are using the latest version of Salt UI to maintain compatibility with new Android versions.

    android {
        dependenciesInfo {
            includeInApk = false
            includeInBundle = false
        }
    }
  3. Configure cell layouts for Staggered Grids using StaggeredGridCells

    main

    The StaggeredGridCells interface is used to define how many columns (in vertical grids) or rows (in horizontal grids) a staggered grid should have and how their sizes are calculated. You can choose between fixed counts, adaptive sizing based on a minimum size, or fixed sizes for each cell.

    There are three primary implementations:

    1. Fixed(count: Int): Defines a grid with a specific, constant number of columns or rows. The available space is divided equally among them.
    2. Adaptive(minSize: Dp): Defines a grid that fits as many columns or rows as possible, provided each one is at least minSize. Any extra space is distributed evenly among the cells.
    3. FixedSize(size: Dp): Defines a grid where every cell is exactly size. Any remaining space in the container is not used by the cells themselves but is instead handled by the grid's Arrangement (e.g., Arrangement.Horizontal or Arrangement.Vertical).
    // Example: Using Fixed for 3 columns
    val cells = StaggeredGridCells.Fixed(3)
    
    // Example: Using Adaptive with a minimum width of 20.dp
    val cells = StaggeredGridCells.Adaptive(20.dp)
    
    // Example: Using FixedSize where each cell is exactly 20.dp
    val cells = StaggeredGridCells.FixedSize(20.dp)
  4. Configure Button appearance and intent

    main

    Buttons in Salt UI are styled using two primary enums:

    ButtonAppearance

    Defines the visual weight of the button:

    • Filled: A high-emphasis, contrasting filled container. Best for the most prominent action on a surface.
    • Subtle: A low-emphasis action using a quiet, neutral container.
    • Plain: A borderless action where the content itself provides the interactive affordance.

    ButtonIntent

    Defines the semantic meaning, which can affect colors (e.g., making a button red for destructive actions):

    • Normal: The standard intent.
    • Destructive: Indicates an action that might cause data loss or irreversible changes.
    // A high-emphasis destructive button
    Button(
        onClick = { /* delete item */ },
        text = "Delete",
        appearance = ButtonAppearance.Filled,
        intent = ButtonIntent.Destructive
    )
  5. Configure page size in Pager using PageSize

    main

    The PageSize interface determines how pages are laid out within a Pager component. By implementing or using predefined PageSize strategies, you can control whether a single page fills the entire viewport or if multiple pages are visible at once.

    There are two primary ways to configure page size:

    1. PageSize.Fill: Each page occupies the entire available space of the Pager. This is the standard behavior for single-page viewports.
    2. PageSize.Fixed(pageSize: Dp): Pages have a specific, fixed width/height in Dp. This allows multiple pages to be visible within the viewport simultaneously.

    Note that calculateMainAxisPageSize is an extension function on Density, so it must be called within a density-aware scope.

    // Example: Using Fixed page size to show multiple pages
    val pageSize = PageSize.Fixed(pageSize = 120.dp)
    
    Pager(
        state = pagerState,
        pageSize = pageSize
    ) { page ->
        // Page content
    }
  6. Manage interval-based lists with IntervalList

    main

    An IntervalList<T> is a read-only collection of intervals, where each interval contains a size and a value of type T. This structure is designed for building custom lazy layouts (similar to LazyColumn), allowing you to group multiple items into single blocks.

    Key properties and methods:

    • size: The total number of items across all intervals (the sum of all Interval.size values).
    • get(index: Int): Returns the Interval<T> that contains the specified item index.
    • forEach(fromIndex: Int, toIndex: Int, block: (Interval<T>) -> Unit): Iterates through the intervals that cover the range from fromIndex to toIndex.

    Note: This is part of the LazyLayout harness and is subject to change.

  7. How SaltNavigator manages bidirectional navigation

    main

    A SaltNavigator is a stateful controller that manages two cooperative stacks to enable browser-style navigation:

    1. Back stack (navBackStack): The primary history of visited routes. The last element in this stack is the currently visible screen.
    2. Forward stack (navForwardStack): A secondary history of routes that were previously popped. This allows users to navigate "forward" to screens they just left.

    Top-Level Routes Behavior

    You can define a set of topLevelRoutes. When a user navigates to one of these routes, the SaltNavigator collapses the back stack to a single element (the root) and replaces it with the new route. This is the standard behavior for bottom-bar or sidebar navigation where selecting a primary tab resets the sub-navigation history.

    • navigate(route): Pushes a new route onto the back stack and clears the forward stack. If the route is a topLevelRoute, the back stack is reset.
    • back(): Pops the current route from the back stack and moves it to the forward stack.
    • forward(): Pops the most recent route from the forward stack and pushes it back onto the back stack.
    val navigator = rememberSaltNavigator(
        configuration = mySavedStateConfiguration,
        initRoute = HomeRoute,
        topLevelRoutes = setOf(HomeRoute, SearchRoute, ProfileRoute)
    )
    
    // Usage
    navigator.navigate(DetailsRoute)
    navigator.back()
    navigator.forward()
  8. Initialize SaltTheme in Compose

    main

    To use Salt UI components, wrap your application content in the SaltTheme composable. You must provide configurations using the saltConfigs() function.

    @Composable
    fun App() {
        SaltTheme(
            configs = saltConfigs()
        ) {
            // ...
        }
    }
  9. Salt UI Compatibility Matrix

    main

    Salt UI version compatibility with Compose Multiplatform and Jetpack Compose. Refer to this table to ensure your project environment matches the library version requirements.

    | Salt UI        | Compose Multiplatform | Jetpack Compose |
    |----------------|-----------------------|-----------------|
    | 3.0.0-alpha01+ | 1.12.0-alpha01        | 1.12.0-alpha02  |
    | 2.9.0-beta02+  | 1.11.0-beta02         | 1.11.0-beta02   |
    | 2.9.0-beta01+  | 1.11.0-beta01         | 1.11.0-beta01   |
    | 2.9.0-alpha08+ | 1.11.0-alpha04        | 1.11.0-alpha06  |
    | 2.9.0-alpha07+ | 1.11.0-alpha03        | 1.11.0-alpha05  |
    | 2.9.0-alpha05+ | 1.11.0-alpha02        | 1.11.0-alpha03  |
    | 2.9.0-alpha01+ | 1.11.0-alpha01        | 1.11.0-alpha01  |
    | 2.8.1+         | 1.10.0-rc02           | 1.10.0          |
    | 2.8.0+         | 1.10.0-rc01           | 1.10.0-rc01     |
    | 2.8.0-rc02+    | 1.10.0-beta02         | 1.10.0-beta02   |
    | 2.8.0-beta01+  | 1.10.0-beta01         | 1.10.0-beta01   |
    | 2.8.0-alpha09+ | 1.10.0-alpha03        | 1.10.0-alpha05  |
    | 2.8.0-alpha01+ | 1.10.0-alpha02        | 1.10.0-alpha04  |
    | 2.7.0-alpha01+ | 1.10.0-alpha01        | 1.10.0-alpha02  |
    | 2.6.0-beta02+  | 1.9.0-rc01            | 1.9.0           |
    | 2.6.0-beta01+  | 1.9.0-beta03          | 1.9.0-rc01      |
    | 2.5.0-alpha05+ | 1.8.2                 | 1.8.2           |
    | 2.4.0+         | 1.8.0                 | 1.8.0           |
    | 2.3.1+         | 1.7.3                 | 1.7.6           |
    | 2.3.0-alpha02+ | 1.7.1                 | 1.7.5           |
    | 2.2.0+         | 1.7.0                 | 1.7.1           |
    | 2.2.0-beta01+  | 1.7.0-rc01            | 1.7.0           |
    | 2.2.0-alpha01+ | 1.7.0-beta02          |                 |
    | 2.0.7+         | 1.7.0-alpha03         |                 |
    | 2.0.4+         | 1.7.0-alpha02          |                 |
    | < 2.0.4        | 1.6.11                |                 |
  10. Use the Button component

    main

    Salt UI provides two main Button overloads for building user interfaces:

    1. Text-based Button: Use this for standard actions with a simple text label and an optional leading icon. It handles text overflow and line limits automatically.
    2. Content-slot Button: Use this when you need a custom layout, such as adding a trailing icon, progress indicators, or rich text formatting within the button.

    Both versions respect the ButtonAppearance (how it looks) and ButtonIntent (the semantic meaning of the action) to determine styling.

    // 1. Simple text button with an icon
    Button(
        onClick = { /* handle click */ },
        text = "Confirm",
        leadingIcon = { Icon(Icons.Default.Check, contentDescription = null) }
    )
    
    // 2. Custom content button
    Button(
        onClick = { /* handle click */ },
        appearance = ButtonAppearance.Subtle
    ) {
        Row {
            Text("Custom")
            Spacer(Modifier.width(8.dp))
            Icon(Icons.Default.ArrowForward, contentDescription = null)
        }
    }
  11. Configure UI dimensions with SaltDimens

    main

    The SaltDimens class allows you to define and manage sizing and spacing constants for the Salt UI. It uses Compose mutableStateOf for its properties, meaning changes to these dimensions can trigger recomposition.

    To create a standard set of dimensions, use the SaltDimens.default() factory method. You can customize specific values like item size, itemIcon size, padding, and subPadding while keeping others at their defaults.

    // Create a custom set of dimensions
    val customDimens = SaltDimens.default(
        item = 60.dp,
        padding = 20.dp,
        subPadding = 10.dp
    )
    
    // Or use the default configuration
    val defaultDimens = SaltDimens.default()
  12. Apply horizontal scrolling with Modifier.horizontalScroll

    main

    Apply horizontal scrolling to a component using Modifier.horizontalScroll. This modifier requires a ScrollState instance.

    Overloads:

    1. Standard: Modifier.horizontalScroll(state, enabled, flingBehavior, reverseScrolling)
      • Uses a local OverscrollEffect factory by default.
    2. Custom Overscroll: Modifier.horizontalScroll(state, overscrollEffect, enabled, flingBehavior, reverseScrolling)
      • Allows providing a custom OverscrollEffect.

    Parameters:

    • state: The ScrollState managing the scroll position.
    • enabled: Whether scrolling is enabled (default true).
    • flingBehavior: The logic for fling gestures (default is ScrollableDefaults.flingBehavior()).
    • reverseScrolling: If true, the scroll direction is reversed (0 pixels means the right side of the content).
    val state = rememberScrollState()
    
    Row(Modifier.horizontalScroll(state = state)) {
        // Scrollable content
    }