Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
41 changes: 41 additions & 0 deletions .github/scripts/check-preview-contract.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Сверяет отпечатки копии Preview Protocol (integration-core/preview-contract) с каноническими из design-system-builder.
#
# Переменные:
# DSB_CONTRACT_REF ветка или тег design-system-builder (по умолчанию dev)
# CONTRACT_URL полный адрес CONTRACT.sha256 (для проверки скрипта)
# MODE warn - расхождение даёт предупреждение GitHub Actions, error - ошибку (по умолчанию error)
set -uo pipefail

ref="${DSB_CONTRACT_REF:-dev}"
mode="${MODE:-error}"
digest="integration-core/preview-contract/src/commonMain/resources/schemas/v1/DIGEST"
url="${CONTRACT_URL:-https://raw.githubusercontent.com/salute-developers/design-system-builder/${ref}/backend-kt/preview/contract/CONTRACT.sha256}"

report() {
if [ "$mode" = "warn" ]; then
echo "::warning title=Preview contract::$1"
exit 0
fi
echo "::error title=Preview contract::$1"
exit 1
}

canonical="$(mktemp)"
if ! curl -fsSL --retry 3 "$url" -o "$canonical"; then
report "Не удалось скачать $url"
fi

# В копии фикстуры лежат в fixtures/v1/shared, в каноническом каталоге - в fixtures/v1.
local_normalized="$(sed -E 's# fixtures/v1/shared/# fixtures/v1/#' "$digest" | LC_ALL=C sort -k2)"
canonical_sorted="$(LC_ALL=C sort -k2 "$canonical")"

if [ "$local_normalized" = "$canonical_sorted" ]; then
echo "Копия Preview Protocol совпадает с design-system-builder@${ref}"
exit 0
fi

changed="$(diff <(echo "$canonical_sorted") <(echo "$local_normalized") | grep '^[<>]' | awk '{print $3}' | sort -u | tr '\n' ' ')"
echo "Расхождение с design-system-builder@${ref}:"
diff <(echo "$canonical_sorted") <(echo "$local_normalized") || true
report "Копия контракта отличается от design-system-builder@${ref}: ${changed}. Обновите: backend-kt/preview/contract/fingerprint.sh --sync-to integration-core/preview-contract/src/commonMain/resources"
40 changes: 40 additions & 0 deletions .github/workflows/check-preview-contract.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: 'Preview contract check'

permissions:
contents: read

# Копия схемы Preview Protocol должна совпадать с каноническим контрактом из design-system-builder.
# В pull request расхождение даёт предупреждение (копия может отставать, пока схема не влита в dev design-system-builder),
# в релизных ветках, main и в ночном запуске - ошибку.
on:
pull_request:
branches:
- main
- develop
- feature/*
- release/*
- hotfix/*
paths:
- 'integration-core/preview-contract/**'
- '.github/scripts/check-preview-contract.sh'
- '.github/workflows/check-preview-contract.yml'
push:
branches:
- main
- release/*
- hotfix/*
schedule:
- cron: '0 3 * * *'
workflow_dispatch:

jobs:
check:
name: Compare with design-system-builder
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Compare fingerprints
env:
DSB_CONTRACT_REF: ${{ vars.DSB_CONTRACT_REF || 'dev' }}
MODE: ${{ github.event_name == 'pull_request' && 'warn' || 'error' }}
run: .github/scripts/check-preview-contract.sh
155 changes: 155 additions & 0 deletions .github/workflows/publish-preview-plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: 'Publish Compose preview plugin'

# Собирает Compose preview plugin и отправляет его на бэкенд design-system-builder командой CLI `dsbuilder`.
# В хранилище пишет бэкенд, а не этот репозиторий: он же импортирует мету из архива и ведёт версии.
#
# push в develop -> dev-бэкенд (включается переменной репозитория DSB_PUBLISH_DEV_ENABLED=true)
# repository_dispatch (publish-release) -> prom-бэкенд, источник - релизный тег (DSB_PUBLISH_PROM_ENABLED=true)
# workflow_dispatch -> выбранное окружение и ref
permissions:
contents: read

on:
push:
branches:
- develop
paths:
- 'integration-core/preview-compose-plugin/**'
- 'integration-core/preview-contract/**'
- 'integration-core/preview-sdk-compose/**'
- 'integration-core/sandbox-core/**'
- 'integration-core/uikit-compose-fixtures/**'
- 'sdds-core/uikit-compose/**'
- 'build-system/**'
- 'gradle/libs.versions.toml'
- '.github/workflows/publish-preview-plugin.yml'
repository_dispatch:
types: [publish-preview-plugin]
workflow_dispatch:
inputs:
target:
description: 'Окружение бэкенда DSB'
type: choice
options:
- dev
- prom
default: dev
required: true
ref:
description: 'Ветка, тэг или коммит (по умолчанию выбранный ref запуска)'
required: false
apply:
description: 'Записать билд (без этого выполняется только пробная отправка)'
type: boolean
default: false

env:
# Версия CLI закрепляется осознанно: релиз `cli-vX.Y.Z` репозитория design-system-builder.
DSB_CLI_VERSION: cli-v0.1.0
DSB_CLI_REPOSITORY: salute-developers/design-system-builder

jobs:
prepare:
name: Определить окружение
runs-on: ubuntu-latest
outputs:
target: ${{ steps.target.outputs.target }}
ref: ${{ steps.target.outputs.ref }}
apply: ${{ steps.target.outputs.apply }}
enabled: ${{ steps.target.outputs.enabled }}
steps:
- name: Выбрать окружение, ref и режим записи
id: target
env:
EVENT: ${{ github.event_name }}
DISPATCH_TAG: ${{ github.event.client_payload.tag }}
INPUT_TARGET: ${{ inputs.target }}
INPUT_REF: ${{ inputs.ref }}
INPUT_APPLY: ${{ inputs.apply }}
PROM_ENABLED: ${{ vars.DSB_PUBLISH_PROM_ENABLED }}
DEV_ENABLED: ${{ vars.DSB_PUBLISH_DEV_ENABLED }}
run: |
case "$EVENT" in
push)
target=dev; ref="$GITHUB_SHA"; apply=true ;;
repository_dispatch)
target=prom; ref="$DISPATCH_TAG"; apply=true ;;
*)
target="$INPUT_TARGET"; ref="${INPUT_REF:-$GITHUB_SHA}"; apply="$INPUT_APPLY" ;;
esac
enabled=true
# Пока бэкенд prom не выкатил публикацию, шаг для prom выключен переменной репозитория.
if [ "$target" = "prom" ] && [ "$PROM_ENABLED" != "true" ]; then enabled=false; fi
# Автоматическая публикация на dev по push в develop выключена, пока не заданы секреты и релиз CLI;
# ручной запуск (workflow_dispatch) этой переменной не ограничен, им можно проверить пробную отправку.
if [ "$EVENT" = "push" ] && [ "$DEV_ENABLED" != "true" ]; then enabled=false; fi
{
echo "target=$target"
echo "ref=$ref"
echo "apply=$apply"
echo "enabled=$enabled"
} >> "$GITHUB_OUTPUT"

publish:
name: Собрать и отправить на ${{ needs.prepare.outputs.target }}
needs: prepare
if: needs.prepare.outputs.enabled == 'true'
runs-on: ubuntu-latest
# Публикации в одно окружение выполняются последовательно и не отменяют друг друга.
concurrency:
group: publish-preview-plugin-${{ needs.prepare.outputs.target }}
cancel-in-progress: false
env:
TARGET: ${{ needs.prepare.outputs.target }}
APPLY: ${{ needs.prepare.outputs.apply }}
DSB_API_URL: ${{ needs.prepare.outputs.target == 'prom' && secrets.DSB_API_URL_PROM || secrets.DSB_API_URL_DEV }}
DSBUILDER_CLIENT_ID: ${{ needs.prepare.outputs.target == 'prom' && secrets.DSB_CLIENT_ID_PROM || secrets.DSB_CLIENT_ID_DEV }}
DSBUILDER_CLIENT_SECRET: ${{ needs.prepare.outputs.target == 'prom' && secrets.DSB_CLIENT_SECRET_PROM || secrets.DSB_CLIENT_SECRET_DEV }}
steps:
- name: Checkout the code
uses: actions/checkout@v4
with:
ref: ${{ needs.prepare.outputs.ref }}
fetch-depth: 0

- name: Prepare environment
uses: ./.github/actions/prepare-android-env

- name: Check the preview contract
# На релизном теге расхождение копии контракта с каноническим останавливает публикацию.
env:
MODE: ${{ needs.prepare.outputs.target == 'prom' && 'error' || 'warn' }}
run: .github/scripts/check-preview-contract.sh

- name: Build and verify the plugin artifact
run: |
./gradlew -p integration-core \
:preview-compose-plugin:jvmTest \
:preview-compose-plugin:previewPluginArtifact \
:preview-compose-plugin:previewPluginBrowserTest \
-PpreviewChromePath="$(command -v google-chrome)"

- name: Install the dsbuilder CLI
env:
# Для закрытого репозитория DSB нужен токен чтения релизов.
GH_TOKEN: ${{ secrets.DSB_CLI_READ_TOKEN || github.token }}
run: |
mkdir -p "$RUNNER_TEMP/dsbuilder"
gh release download "$DSB_CLI_VERSION" --repo "$DSB_CLI_REPOSITORY" \
--pattern dsbuilder-cli-jvm.zip --dir "$RUNNER_TEMP/dsbuilder"
unzip -q "$RUNNER_TEMP/dsbuilder/dsbuilder-cli-jvm.zip" -d "$RUNNER_TEMP/dsbuilder"
echo "$RUNNER_TEMP/dsbuilder/cli-jvm/bin" >> "$GITHUB_PATH"

- name: Dry run
# Пробная отправка проверяет архив, мету и учётные данные до записи.
run: |
dsbuilder preview-plugin publish --platform compose \
--from integration-core/preview-compose-plugin/build/distributions/preview-compose-plugin.zip \
--api-url "$DSB_API_URL"

- name: Publish
if: env.APPLY == 'true'
run: |
dsbuilder preview-plugin publish --platform compose \
--from integration-core/preview-compose-plugin/build/distributions/preview-compose-plugin.zip \
--api-url "$DSB_API_URL" --apply
15 changes: 15 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,18 @@ jobs:
.github/scripts/dispatch-publish.sh "${{ steps.changed.outputs.modules }}" "${{ github.event.inputs.tag_name }}"
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}

- name: Dispatch the Compose preview plugin publication
# Модуль плагина исключён из списка изменённых модулей (.ignore-changed-modules), поэтому публикация
# плагина запускается на каждом релизном теге. Шаг выполняется только когда публикация на prom включена
# переменной репозитория; его сбой не должен ронять выпуск релиза, который к этому моменту уже создан.
if: ${{ vars.DSB_PUBLISH_PROM_ENABLED == 'true' }}
continue-on-error: true
run: |
curl --fail-with-body -sS -X POST "https://api.github.com/repos/${{ github.repository }}/dispatches" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-d "{\"event_type\": \"publish-preview-plugin\", \"client_payload\": {\"tag\": \"${TAG}\"}}"
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
TAG: ${{ github.event.inputs.tag_name }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ google-services.json
# Kotlin/JS local dependency store
tokens/kotlin-js-store/
**/contents.xcworkspacedata

# Артефакты сборки Kotlin/JS (Wasm) в integration-core
integration-core/kotlin-js-store/
3 changes: 3 additions & 0 deletions .ignore-changed-modules
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@
:integration-core:uikit-compose-testcases
:integration-core:uikit-fixtures
:integration-core:uikit-testcases
:integration-core:preview-contract
:integration-core:preview-sdk-compose
:integration-core:preview-compose-plugin
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,27 @@ AsyncImage(
model = "https://cdn.example.com/test.png",
)
```

## Состояния и Motion API

В `Avatar` и `Modifier.avatar(...)` можно передать `motion: Motion<AvatarMotionStyle>`.
`rememberAvatarMotion()` использует `LocalAvatarMotionStyle`, если стиль переходов не указан явно.
`StatefulValue` задаёт значения для состояний, а `AvatarMotionStyle` — переходы фона, текста, иконки действия и фона иконки действия.
По умолчанию все переходы используют `noMotion()`: значения меняются без анимации.

```kotlin
// @sample: com/sdds/compose/uikit/fixtures/samples/avatar/Avatar_Motion.kt
```

Для передачи взаимодействий используйте `motion.context.interactionSource` также в `clickable`.
Статус `AvatarStatus` публикуется в `motion.context.semanticStateSource` автоматически;
индикатор статуса использует общий с аватаром `MotionContext`.
Форма и размеры переключаются по состояниям без интерполяции.
В `AvatarGroup` у каждого аватара собственный контекст: смена статуса одного элемента не анимирует соседей.

### Переход со старых свойств

Существующие вызовы с `interactionSource` сохраняются (перегрузка помечена deprecated) и делегируют motion-реализации.
Для чтения текущих значений используйте stateful-свойства `shapes`, `textStyles`, `colors.textBrushes`,
`colors.backgroundBrushes`, `colors.actionBrushes`, `colors.actionScrimBrushes`
и размерные свойства с суффиксом `Values`, например `dimensionValues.widthValues`.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ title: List

<!-- @screenshot: com.sdds.compose.uikit.fixtures.samples.list.List_Simple -->

## StaticList

`StaticList` — статическая (нескроллируемая) вариация [List](#list) для сгруппированных секций: тот же контент и тот же `ListStyle`, но без собственной прокрутки и с высотой по содержимому.

Используйте `StaticList` внутри прокручиваемых контейнеров (в `item` компонента `List`, в `verticalScroll`-колонке) и на экранах без прокрутки. Для длинных лент используйте `List`.

Почему нельзя вложить `List` в прокручиваемый `List`: `List` — это `LazyColumn`, ему нужно конечное «окно» прокрутки; внутри прокрутки он измеряется с бесконечной высотой (крах измерения)

или требует фиксированной высоты и конфликтует жестами. `StaticList` таких ограничений не имеет — секция с общим фоном, `ListItem` и `Divider` внутри безопасно участвует в одной прокрутке экрана:

```kotlin
// @sample: com/sdds/compose/uikit/fixtures/samples/list/StaticList_Section.kt
```

Отличия от `List`:
- весь контент компонируется сразу (без ленивой раскладки);

Стилизация — такая же, как у `List` (общий `ListStyle`, готовые бренд-стили применимы без изменений).

## Стиль List

В большинстве случаев можно использовать готовые сгенерированные стили, а при необходимости создать собственный стиль через соответствующий builder. Подробнее о том, как работают `Style`, `StyleBuilder` и stateful-параметры стиля, см. в разделе [Стилизация компонентов](../theme/Styles.md).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,26 @@ OverlayHost {
) {...}
}
```

### Учёт системных панелей

По умолчанию `OverlayHost` не учитывает инсеты статус-бара и navigation bar. Для включения автоматического учёта используйте `applySystemBarInsets = true`:

```kotlin
OverlayHost(
applySystemBarInsets = true,
) {
...
}
```

Дополнительный отступ сверх системных инсетов задаётся через `contentPadding`:

```kotlin
OverlayHost(
applySystemBarInsets = true,
contentPadding = PaddingValues(top = 16.dp),
) {
...
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,26 @@ OverlayHost {
) {...}
}
```

### Учёт системных панелей

По умолчанию `OverlayHost` не учитывает инсеты статус-бара и navigation bar. Для включения автоматического учёта используйте `applySystemBarInsets = true`:

```kotlin
OverlayHost(
applySystemBarInsets = true,
) {
...
}
```

Дополнительный отступ сверх системных инсетов задаётся через `contentPadding`:

```kotlin
OverlayHost(
applySystemBarInsets = true,
contentPadding = PaddingValues(top = 16.dp),
) {
...
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ semanticStateSource.setExclusive(

- `getColorAsState`
- `getBrushAsState`
- `getTextStyleAsState`
- `getDpAsState`
- `getFloatAsState`
- `getIntAsState`
Expand Down
Loading
Loading