پیاده‌سازی کامپوننت‌های سفارشی A2UI

در معماری A2UI، هر سطح توسط یک کاتالوگ کامپوننت هدایت می‌شود. به جای اینکه یک عامل هوش مصنوعی، رابط کاربری اولیه خود را اختراع کند یا کد دلخواه تولید کند، کاتالوگ شما کامپوننت‌ها، طرحواره‌های ویژگی و قابلیت‌های موجود برای عامل را اعلام می‌کند. سپس عامل از این کامپوننت‌ها برای ساخت رابط کاربری استفاده می‌کند.

وقتی یک کاتالوگ سفارشی برای سیستم طراحی برنامه خود می‌سازید، کامپوننت‌هایی را پیاده‌سازی می‌کنید که آن تعاریف کاتالوگ را به عناصر رابط کاربری Jetpack Compose نگاشت می‌کنند. هر کامپوننت A2UI ( A2uiComponent ) قرارداد طرحواره ویژگی خود را تعریف می‌کند، آمادگی را با رسیدن داده‌های پویا ارزیابی می‌کند، ویژگی‌های واکنشی را از مدل داده متصل می‌کند، رابط کاربری Compose را منتشر می‌کند و اقدامات تعامل کاربر را به عامل ارسال می‌کند.

رندرکننده رابط کاربری Compose ( androidx.a2ui.compose:compose-ui ) رابط‌ها و حوزه‌های گیرنده مورد نیاز برای پیاده‌سازی اجزای سفارشی را فراهم می‌کند که از سیستم طراحی برنامه شما پیروی می‌کنند.

اعلان ویژگی‌های کامپوننت با نوع استاتیک

قبل از رندر کردن، ویژگی‌هایی را که یک کامپوننت از عامل انتظار دارد، تعریف کنید. لایه زمان اجرا، APIهای A2uiProperty با نوع استاتیک را ارائه می‌دهد که برای تولید طرحواره JSON و استخراج مقادیر در زمان اجرا استفاده می‌شوند:

// Define static properties, dynamic bindings, and component references
val textProp = A2uiProperty.dynamicString("text", required = true)
val variantProp = A2uiProperty.stringEnum("variant", enumValues = listOf("body", "title"))
val childProp = A2uiProperty.componentId("child", required = true)
val actionProp = A2uiProperty.action("action", required = true)

رابط A2uiComponent را پیاده‌سازی کنید

رابط A2uiComponent را برای تعریف طرحواره یک کامپوننت و نگاشت ویژگی‌های دریافتی از عامل به رابط کاربری Compose پیاده‌سازی کنید:

object CustomTextComponent : A2uiComponent {
    private val textProp = A2uiProperty.dynamicString("text", required = true)
    private val variantProp = A2uiProperty.stringEnum(
        "variant",
        enumValues = listOf("body", "title"),
    )

    override val name = "Text"
    override val description = "Displays dynamic text."
    override val properties = listOf(textProp, variantProp)

    @Composable
    override fun A2uiComponentScope.isReady(properties: A2uiComponentProperties): Boolean {
        // The component does not become ready until dynamic text data arrives
        return properties.bind(textProp) != null
    }

    @Composable
    override fun A2uiComponentScope.Content(
        properties: A2uiComponentProperties,
        modifier: Modifier,
    ) {
        // Reactively resolve dynamic data binding and subscribe to updates
        val text = properties.bind(textProp) ?: ""

        // Read the static configuration property
        val variant = properties[variantProp] ?: "body"
        val textStyle = if (variant == "title") {
            MaterialTheme.typography.titleLarge
        } else {
            MaterialTheme.typography.bodyLarge
        }

        Text(
            text = text,
            style = textStyle,
            modifier = modifier,
        )
    }
}

حل اتصال‌های مدل داده معمولی و دوطرفه

پیاده‌سازی‌های کامپوننت A2uiComponentScope برای حل ویژگی‌های متصل به صورت پویا استفاده می‌کنند. برای ویژگی‌های پویای معمولی، bind مقدار فعلی را برمی‌گرداند و به طور خودکار در به‌روزرسانی‌های مدل داده مشترک می‌شود.

برای کامپوننت‌های ورودی تعاملی، bindUpdater یک لامبدا به‌روزرسانی پایدار برمی‌گرداند. اگر عامل به جای یک مسیر داده قابل نوشتن، یک رشته تحت‌اللفظی ارائه دهد، لامبدا به‌روزرسانی null می‌شود و نشان می‌دهد که فیلد فقط خواندنی است:

val labelProp = A2uiProperty.dynamicString("label", required = true)
val valueProp = A2uiProperty.dynamicBoolean("value")

@Composable
fun A2uiComponentScope.CustomCheckbox(properties: A2uiComponentProperties) {
    // Read a dynamic property from the data model subscribing to updates
    val label = properties.bind(labelProp) ?: ""

    // Bind a property value and its updater to handle two-way data binding
    val checked = properties.bind(valueProp) ?: false
    val onCheckedChange = properties.bindUpdater(valueProp)

    Row(verticalAlignment = Alignment.CenterVertically) {
        Checkbox(
            checked = checked,
            onCheckedChange = onCheckedChange,
            enabled = (onCheckedChange != null), // Read-only if no writable path was bound
        )
        Text(text = label)
    }
}

ارسال اقدامات کاربر به عامل

کامپوننت‌های تعاملی A2uiComponentScope.dispatchAction برای ارسال رویدادهای کاربر به عامل استفاده می‌کنند:

object CustomButtonComponent : A2uiComponent {
    private val childProp = A2uiProperty.componentId("child", required = true)
    private val actionProp = A2uiProperty.action("action", required = true)

    override val name = "Button"
    override val description = "A clickable button."
    override val properties = listOf(childProp, actionProp)

    @Composable
    override fun A2uiComponentScope.Content(
        properties: A2uiComponentProperties,
        modifier: Modifier,
    ) {
        val actionDefinition = properties[actionProp]
        val childId = properties[childProp] ?: return
        val currentAction by rememberUpdatedState(actionDefinition)
        val onClick: () -> Unit = remember {
            { currentAction?.let { dispatchAction(it) } }
        }

        Button(onClick = onClick, modifier = modifier) {
            val childState = observeA2uiComponentState(id = childId)
            when (childState) {
                is A2uiComponentState.Loading -> CircularProgressIndicator()
                is A2uiComponentState.Error -> Text("Error")
                is A2uiComponentState.Success -> A2uiComponent(childState.component)
            }
        }
    }
}

مدیریت کامپوننت‌های فرزند و رندرینگ پیش‌رونده

کامپوننت‌هایی که از کامپوننت‌های فرزند تو در تو پشتیبانی می‌کنند، observeA2uiComponentState(id) برای مشاهده‌ی حالت‌های فرزند استفاده می‌کنند. این امر رندرینگ پیش‌رونده را امکان‌پذیر می‌کند، به این صورت که یک کانتینر والد، پوسته‌ی خود را رندر می‌کند در حالی که کامپوننت‌های فرزند به طور مستقل بارگذاری می‌شوند:

val headerChildProp = A2uiProperty.componentId("headerId", required = true)

@Composable
fun A2uiComponentScope.CustomCompositeContent(
    properties: A2uiComponentProperties,
) {
    val headerId = properties[headerChildProp] ?: return

    val headerState = observeA2uiComponentState(id = headerId)
    when (headerState) {
        is A2uiComponentState.Loading -> {
            // Render a localized loading placeholder
            LinearProgressIndicator()
        }
        is A2uiComponentState.Error -> {
            // Render a localized error fallback
            Text("Failed to load header")
        }
        is A2uiComponentState.Success -> {
            // Forward the resolved child component to the visual UI router
            A2uiComponent(headerState.component)
        }
    }
}

برای مدیریت مجموعه‌ها یا لیست‌های فرزند (مانند موارد موجود در یک ستون، ردیف یا لیست)، یک ویژگی را با استفاده A2uiProperty.childList تعریف کنید و فرزندان را با bindChildReferences شناسایی کنید:

val childrenProp = A2uiProperty.childList("children", required = true)

@Composable
fun A2uiComponentScope.CustomColumn(
    properties: A2uiComponentProperties,
    modifier: Modifier = Modifier,
) {
    // Resolve child references (supports both static ID arrays and dynamic data templates)
    val childReferences = properties.bindChildReferences(childrenProp) ?: return

    Column(modifier = modifier) {
        childReferences.forEach { reference ->
            key(reference.id, reference.baseDataPath) {
                val childState = observeA2uiComponentState(reference)
                when (childState) {
                    is A2uiComponentState.Loading -> CircularProgressIndicator()
                    is A2uiComponentState.Error -> Text("Failed to load child")
                    is A2uiComponentState.Success -> A2uiComponent(childState.component)
                }
            }
        }
    }
}

ادغام رندرینگ رسانه‌های بومی در کاتالوگ پایه

هنگام استفاده از پیاده‌سازی ارائه شده‌ی Basic Catalog ( androidx.compose.material3:material3-a2ui )، می‌توانید کتابخانه‌های رسانه‌ای مورد نظر خود (مانند Coil برای تصاویر یا ExoPlayer برای ویدیو) را به اجزای رسانه‌ای Basic Catalog وصل کنید:

// Configure an Image component for the Basic Catalog using Coil
val coilImage = MaterialA2uiBasicCatalogV1Defaults.image { url, desc, scale, modifier, onError ->
    AsyncImage(
        model = url,
        contentDescription = desc,
        contentScale = scale,
        modifier = modifier,
        onError = { state -> onError(state.result.throwable) },
    )
}

جزئیات پیاده‌سازی

بخش‌های بعدی، انتشار رابط کاربری بازگشتی، ارزیابی پویای ویژگی‌ها و گزارش خطا را توضیح می‌دهند.

مسیرهای کاربری پیاده‌سازی کامپوننت، APIهای کلیدی زیر را معرفی می‌کنند:

  • A2uiComponent : رابطی که فراداده‌های کامپوننت، طرحواره‌های ویژگی، بررسی‌های آمادگی ( isReady ) و انتشار رندر ( Content ) را تعریف می‌کند.
  • A2uiProperty : یک اعلان ویژگی با نوع استاتیک که برای تولید طرحواره JSON و تجزیه و تحلیل مقدار در زمان اجرا استفاده می‌شود.
  • A2uiComponentScope : یک محدوده گیرنده که قابلیت‌های زمینه‌ای (مانند اتصال داده، ارسال اکشن و مشاهده وضعیت فرزند) را برای پیاده‌سازی‌های کامپوننت فراهم می‌کند.
  • A2uiComponentProperties : یک ظرف برای ویژگی‌های کامپوننت دریافتی از عامل که دسترسی به ویژگی‌ها را از نوع ایمن فراهم می‌کند.
  • A2uiComponentState : نشان‌دهنده‌ی وضعیت بارگذاری واکنشی، موفقیت یا رفع خطا در یک کامپوننت است.

انتشار رابط کاربری بازگشتی و مسیریابی پویا

حالت ریشه که توسط فراخوانی‌کننده (یا حالت کامپوننت فرزند که در یک والد حل شده است) بالا برده می‌شود، رندر کامپوننت بازگشتی را از طریق تابع قابل ترکیب A2uiComponent آغاز می‌کند. این تابع به جای اتصال محکم حالت حل شده به یک پیاده‌سازی رابط کاربری خاص، به عنوان یک مسیریاب پویا عمل می‌کند.