From 809ad659b525dd4d04e47cceae011d8e5015707f Mon Sep 17 00:00:00 2001 From: burstein Date: Mon, 6 Apr 2026 18:27:15 +0300 Subject: [PATCH] Supported compose renderer in demo app commit_hash:707bc8cb58bdbe08c98f8376b3aae74b8299cbf4 --- .mapping.json | 2 + .../src/main/assets/application/settings.json | 19 ++ .../divkit/demo/div/DemoRendererFacade.kt | 171 ++++++++++++++++++ .../divkit/demo/div/Div2ScenarioActivity.kt | 86 ++++----- .../divkit/demo/div/editor/DivEditorUi.kt | 110 +++++++---- .../demo/font/ComposeFontFamilyProvider.kt | 25 +++ .../DivComposeScreenshotActivity.kt | 31 +--- .../divkit/demo/settings/Preferences.kt | 2 + .../demo/settings/SettingsActionHandler.kt | 2 + .../divkit/demo/settings/SettingsActivity.kt | 1 + .../src/main/res/menu/div2_scenario_menu.xml | 7 + .../src/main/res/values/strings.xml | 1 + 12 files changed, 349 insertions(+), 108 deletions(-) create mode 100644 client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoRendererFacade.kt create mode 100644 client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/ComposeFontFamilyProvider.kt diff --git a/.mapping.json b/.mapping.json index 66ad929f6..3763b2cf0 100644 --- a/.mapping.json +++ b/.mapping.json @@ -1961,6 +1961,7 @@ "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoDivLottieRawResProvider.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoDivLottieRawResProvider.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoGlobalVariablesController.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoGlobalVariablesController.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoNestedScrollView.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoNestedScrollView.kt", + "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoRendererFacade.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoRendererFacade.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2Activity.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2Activity.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2MetadataBottomSheet.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2MetadataBottomSheet.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2ScenarioActivity.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2ScenarioActivity.kt", @@ -1990,6 +1991,7 @@ "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/editor/list/DivEditorViewHolder.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/editor/list/DivEditorViewHolder.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/histogram/DemoHistogramConfiguration.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/histogram/DemoHistogramConfiguration.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/video/DemoVideoCustomImageCache.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/video/DemoVideoCustomImageCache.kt", + "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/ComposeFontFamilyProvider.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/ComposeFontFamilyProvider.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/RobotoFlexTypefaceProvider.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/RobotoFlexTypefaceProvider.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/YandexSansCondensedTypefaceProvider.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/YandexSansCondensedTypefaceProvider.kt", "client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/regression/RegressionDiv2ViewCreator.kt":"divkit/public/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/regression/RegressionDiv2ViewCreator.kt", diff --git a/client/android/divkit-demo-app/src/main/assets/application/settings.json b/client/android/divkit-demo-app/src/main/assets/application/settings.json index 38e6fa4df..8030bef07 100644 --- a/client/android/divkit-demo-app/src/main/assets/application/settings.json +++ b/client/android/divkit-demo-app/src/main/assets/application/settings.json @@ -726,6 +726,25 @@ } ] }, + { + "type": "settings_item", + "items": [ + { + "type": "settings_item_title", + "text": "Compose Renderer by default" + }, + { + "type": "setting_switch_state", + "id": "compose_renderer", + "active_click_log_id": "set_compose_renderer_disabled", + "active_click_change_state_url": "div-action://set_state?state_id=0/compose_renderer/inactive", + "active_click_change_settings_url": "div-action://set_preferences?name=compose_renderer&value=0", + "inactive_click_log_id": "set_compose_renderer_enabled", + "inactive_click_change_settings_url": "div-action://set_preferences?name=compose_renderer&value=1", + "inactive_click_change_state_url": "div-action://set_state?state_id=0/compose_renderer/active" + } + ] + }, { "type": "text", "text": "@{app_version}", diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoRendererFacade.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoRendererFacade.kt new file mode 100644 index 000000000..2ef7c161b --- /dev/null +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DemoRendererFacade.kt @@ -0,0 +1,171 @@ +package com.yandex.divkit.demo.div + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.ComposeView +import androidx.core.net.toUri +import com.yandex.div.DivDataTag +import com.yandex.div.compose.DivComposeConfiguration +import com.yandex.div.compose.DivView as ComposeDivView +import com.yandex.div.compose.DivContext as ComposeDivContext +import com.yandex.div.compose.createContext +import com.yandex.div.core.Div2Context +import com.yandex.div.core.annotations.InternalApi +import com.yandex.div.core.expression.variables.DivVariableController +import com.yandex.div.core.view2.Div2View +import com.yandex.div.data.DivParsingEnvironment +import com.yandex.div.json.ParsingErrorLogger +import com.yandex.div2.DivAction +import com.yandex.div2.DivData +import com.yandex.div2.DivPatch +import com.yandex.divkit.demo.font.ComposeFontFamilyProvider +import com.yandex.divkit.demo.utils.applyPatchByConfig +import org.json.JSONException +import org.json.JSONObject + +interface DemoRendererFacade { + val view: View + val currentData: DivData? + fun setData(data: DivData, tag: DivDataTag) + fun performAction(action: String) + fun applyPatch(divPatch: DivPatch, errorCallback: () -> Unit): Boolean + fun dismissTooltips() + fun cleanup() + + fun deactivate() { + cleanup() + view.visibility = View.GONE + } + + fun activate(data: DivData? = null, tag: DivDataTag? = null) { + if (data != null && tag != null && currentData == null) { + setData(data, tag) + } + view.visibility = if (currentData != null) View.VISIBLE else View.GONE + } +} + +class Div2RendererFacade( + container: ViewGroup, + private val divContext: Div2Context, + private val actionHandler: DemoDivActionHandler, +) : DemoRendererFacade { + + private val divView = Div2View(divContext).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + } + + init { + container.addView(divView) + } + + override val view: View get() = divView + + override val currentData: DivData? get() = divView.divData + + override fun setData(data: DivData, tag: DivDataTag) { + divView.setData(data, tag) + } + + override fun performAction(action: String) { + try { + val divAction = parseDivAction(action) + actionHandler.handleAction(divAction, divView, divView.expressionResolver) + } catch (_: JSONException) { + actionHandler.handleActionUrl(action.toUri(), divView) + } + } + + override fun applyPatch(divPatch: DivPatch, errorCallback: () -> Unit): Boolean { + divView.applyPatchByConfig(divPatch) { + if (!it) errorCallback() + } + return true + } + + override fun dismissTooltips() { + divContext.cancelTooltips() + } + + override fun cleanup() { + divView.cleanup() + } +} + +@OptIn(InternalApi::class) +class ComposeRendererFacade( + container: ViewGroup, + divVariableController: DivVariableController, + context: Context, +) : DemoRendererFacade { + + private val composeDivContext: ComposeDivContext = DivComposeConfiguration( + fontFamilyProvider = ComposeFontFamilyProvider(context), + variableController = divVariableController, + ).createContext(baseContext = context) + + private val composeView = ComposeView(composeDivContext).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + visibility = View.GONE + } + + private val divData = mutableStateOf(null) + + init { + container.addView(composeView, 0) + composeView.setContent { + divData.value?.let { data -> + ComposeDivView(data = data) + } + } + } + + override val view: View = composeView + + override val currentData: DivData? get() = divData.value + + override fun setData(data: DivData, tag: DivDataTag) { + divData.value = data + } + + override fun performAction(action: String) { + val data = divData.value ?: return + try { + val divAction = parseDivAction(action) + composeDivContext.debugFeatures.performAction(data, divAction) + } catch (_: JSONException) { + val jsonAction = JSONObject().apply { + put("log_id", "menu_action") + put("url", action.toUri().toString()) + } + val divAction = DivAction(DivParsingEnvironment(ParsingErrorLogger.LOG), jsonAction) + composeDivContext.debugFeatures.performAction(data, divAction) + } + } + + override fun applyPatch(divPatch: DivPatch, errorCallback: () -> Unit): Boolean { + // Not supported yet + return false + } + + override fun dismissTooltips() { + // Not supported yet + } + + override fun cleanup() { + divData.value = null + } +} + +private fun parseDivAction(action: String): DivAction { + val env = DivParsingEnvironment(ParsingErrorLogger.LOG) + return DivAction(env, JSONObject(action)) +} diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2ScenarioActivity.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2ScenarioActivity.kt index 348a34995..cc50cf9d5 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2ScenarioActivity.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/Div2ScenarioActivity.kt @@ -7,7 +7,6 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View -import android.view.ViewGroup import android.widget.EditText import androidx.annotation.WorkerThread import androidx.appcompat.app.AppCompatActivity @@ -23,11 +22,10 @@ import com.yandex.div.core.DivErrorsReporter import com.yandex.div.core.DivViewFacade import com.yandex.div.core.experiments.Experiment import com.yandex.div.core.util.SafeAlertDialogBuilder -import com.yandex.div.core.view2.Div2View -import com.yandex.div.data.DivParsingEnvironment import com.yandex.div.font.YandexSansDisplayDivTypefaceProvider import com.yandex.div.font.YandexSansDivTypefaceProvider import com.yandex.div.internal.Assert +import com.yandex.div.internal.KLog import com.yandex.div.json.ParsingErrorLogger import com.yandex.div.json.expressions.ExpressionResolver import com.yandex.div.lottie.DivLottieExtensionHandler @@ -35,7 +33,6 @@ import com.yandex.div.zoom.DivPinchToZoomConfiguration import com.yandex.div.zoom.DivPinchToZoomExtensionHandler import com.yandex.div2.DivAction import com.yandex.div2.DivData -import com.yandex.div2.DivPatch import com.yandex.divkit.demo.Container import com.yandex.divkit.demo.R import com.yandex.divkit.demo.databinding.ActivityDiv2ScenarioBinding @@ -43,7 +40,6 @@ import com.yandex.divkit.demo.div.editor.DivEditorActivityStateKeeper import com.yandex.divkit.demo.div.editor.DivEditorLogger import com.yandex.divkit.demo.div.editor.DivEditorParsingErrorLogger import com.yandex.divkit.demo.div.editor.DivEditorPresenter -import com.yandex.divkit.demo.div.editor.DivEditorState import com.yandex.divkit.demo.div.editor.DivEditorUi import com.yandex.divkit.demo.div.editor.DivEditorWebController import com.yandex.divkit.demo.div.editor.list.DivEditorAdapter @@ -51,7 +47,6 @@ import com.yandex.divkit.demo.div.histogram.LoggingHistogramBridge import com.yandex.divkit.demo.font.RobotoFlexTypefaceProvider import com.yandex.divkit.demo.font.YandexSansCondensedTypefaceProvider import com.yandex.divkit.demo.utils.DivkitDemoUriHandler -import com.yandex.divkit.demo.utils.applyPatchByConfig import com.yandex.divkit.demo.utils.coroutineScope import com.yandex.divkit.demo.utils.lifecycleOwner import com.yandex.divkit.demo.utils.loadText @@ -64,7 +59,6 @@ import kotlinx.coroutines.withContext import org.json.JSONException import org.json.JSONObject import java.util.UUID -import com.yandex.div.internal.KLog class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.MetadataHost { @@ -74,12 +68,12 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada private lateinit var editorStateKeeper: DivEditorActivityStateKeeper private lateinit var div2Adapter: DivEditorAdapter private lateinit var divContext: Div2Context + private lateinit var divEditorUi: DivEditorUi private val actionHandler = DemoDivActionHandler(Container.uriHandler.apply { handlingActivity = this@Div2ScenarioActivity }) private val globalVariableController = DemoGlobalVariablesController() private val preferences by lazy { getSharedPreferences("div2", Context.MODE_PRIVATE) } private var json: String? = null private var url: String? = null - private lateinit var div2View: Div2View override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -150,23 +144,33 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada div2Adapter = DivEditorAdapter(divContext) globalVariableController.bindWith(divContext) - setupDiv2View() + val viewRenderer = Div2RendererFacade(binding.singleContainer, divContext, actionHandler) + val composeRenderer = ComposeRendererFacade( + binding.singleContainer, + divContext.divVariableController, + this + ) with(binding.div2Recycler) { layoutManager = LinearLayoutManager(this@Div2ScenarioActivity, RecyclerView.VERTICAL, false) adapter = div2Adapter } - val divEditorUi = DivEditorUi( + + divEditorUi = DivEditorUi( this, binding.metadataButton, binding.error, binding.singleContainer, - div2View, + divContext, + viewRenderer, + composeRenderer, binding.div2Recycler, div2Adapter, this, Container.flagPreferenceProvider.getExperimentFlag(Experiment.SHOW_RENDERING_TIME) ) + divEditorUi.useComposeRenderer = Container.preferences.useComposeRenderer + updateToolbarTitle() val editorLogger = DivEditorLogger(this::setError) editorStateKeeper = ViewModelProvider(this)[DivEditorActivityStateKeeper::class.java] @@ -195,14 +199,19 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada initDiv() } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.div2_scenario_menu, menu) + menu?.findItem(R.id.div2_compose_renderer)?.isChecked = divEditorUi.useComposeRenderer return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { + R.id.div2_compose_renderer -> { + item.isChecked = !item.isChecked + toggleRenderer() + true + } R.id.div2_perform_action -> { showActionDialog() true @@ -212,7 +221,11 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada true } R.id.div2_download_patch -> { - showDownloadDialog() + if (divEditorUi.useComposeRenderer) { + showToast("Patches are not supported in Compose renderer") + } else { + showDownloadDialog() + } true } R.id.div2_load_div_json -> { @@ -220,7 +233,7 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada true } R.id.div2_close_all_tooltips -> { - divContext.cancelTooltips() + divEditorUi.activeRenderer.dismissTooltips() true } else -> super.onOptionsItemSelected(item) @@ -255,7 +268,7 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada return@launch } - div2View.setData(divData, DivDataTag(UUID.randomUUID().toString())) + divEditorUi.setDivData(divData, DivDataTag(UUID.randomUUID().toString())) showToast("New div json applied!") } } @@ -299,13 +312,7 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada .setPositiveButton("Perform action") { _, _ -> val action = editText.text.toString() preferences.edit().putString(KEY_DIV2_ACTION_URL, action).apply() - try { - val env = DivParsingEnvironment(ParsingErrorLogger.LOG) - val jsonObject = JSONObject(action) - actionHandler.handleAction(DivAction(env, jsonObject), div2View, div2View.expressionResolver) - } catch (e: JSONException) { - actionHandler.handleActionUrl(Uri.parse(action), div2View) - } + divEditorUi.activeRenderer.performAction(action) } adb.create().show() } @@ -360,7 +367,7 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada return@launch } - applyPatch(divPatch) { + divEditorUi.applyPatch(divPatch) { longToast("Error while applying patch!") } } @@ -368,33 +375,20 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada adb.create().show() } - private fun applyPatch(divPatch: DivPatch, errorCallback: () -> Unit) { - if (isSingleCardState()) { - div2View.applyPatchByConfig(divPatch) { - if (!it) errorCallback() - } + private fun toggleRenderer() { + divEditorUi.useComposeRenderer = !divEditorUi.useComposeRenderer + Container.preferences.useComposeRenderer = divEditorUi.useComposeRenderer + updateToolbarTitle() + } + + private fun updateToolbarTitle() { + binding.toolbar.title = if (divEditorUi.useComposeRenderer) { + getString(R.string.demo) + " (Compose)" } else { - div2Adapter.applyPatch(divPatch, errorCallback) + getString(R.string.demo) } } - private fun isSingleCardState(): Boolean { - if (!this::editorStateKeeper.isInitialized) { - return false - } - val state = editorStateKeeper.state - return state is DivEditorState.DivReceivedState && state.isSingleCard - } - - private fun setupDiv2View() { - div2View = Div2View(divContext) - div2View.layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - binding.singleContainer.addView(div2View) - } - @WorkerThread private suspend fun loadJson(uri: Uri): JSONObject { val data = when (uri.scheme) { diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/editor/DivEditorUi.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/editor/DivEditorUi.kt index cdcef85dd..02034f13e 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/editor/DivEditorUi.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/editor/DivEditorUi.kt @@ -12,11 +12,13 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton import com.yandex.div.DivDataTag import com.yandex.div.core.Div2Context import com.yandex.div.core.expression.variables.DivVariablesParser -import com.yandex.div.core.view2.Div2View import com.yandex.div.data.Variable import com.yandex.div.internal.Assert +import com.yandex.div2.DivData +import com.yandex.div2.DivPatch import com.yandex.div2.DivSize import com.yandex.divkit.demo.Container +import com.yandex.divkit.demo.div.DemoRendererFacade import com.yandex.divkit.demo.div.Div2MetadataBottomSheet import com.yandex.divkit.demo.div.editor.DivEditorScreenshot.takeScreenshot import com.yandex.divkit.demo.div.editor.list.DivEditorAdapter @@ -28,7 +30,9 @@ class DivEditorUi( private val metadataButton: FloatingActionButton, private val failedTextMessage: TextView, private val divContainer: ViewGroup, - private val div2View: Div2View, + private val divContext: Div2Context, + private val viewRenderer: DemoRendererFacade, + private val composeRenderer: DemoRendererFacade, private val div2Recycler: RecyclerView, private val div2Adapter: DivEditorAdapter, private val metadataHost: Div2MetadataBottomSheet.MetadataHost, @@ -38,6 +42,22 @@ class DivEditorUi( var onDiv2ViewDrawnListener: ((bitmap: Bitmap) -> Unit)? = null var hasTemplates: Boolean = false + val activeRenderer: DemoRendererFacade + get() = if (useComposeRenderer) composeRenderer else viewRenderer + + private val inactiveRenderer: DemoRendererFacade + get() = if (useComposeRenderer) viewRenderer else composeRenderer + + var useComposeRenderer: Boolean = false + set(value) { + field = value + applyRendererVisibility() + } + + private var isSingleCard: Boolean = false + private var lastDivData: DivData? = null + private var lastDivDataTag: DivDataTag? = null + private val debounceOnViewDrawObserver = DebounceOnViewDrawObserver { updateRenderTime() activity.takeScreenshot { screenshot: Bitmap -> @@ -46,35 +66,57 @@ class DivEditorUi( } init { - div2View.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - div2View.divData?.states?.firstOrNull { it.stateId == div2View.currentStateId }?.let { - val height = when (it.div.value().height) { - is DivSize.MatchParent -> ViewGroup.LayoutParams.MATCH_PARENT - else -> ViewGroup.LayoutParams.WRAP_CONTENT - } - if (divContainer.layoutParams.height == height) return@addOnLayoutChangeListener - divContainer.doOnPreDraw { - divContainer.layoutParams.height = height - divContainer.requestLayout() - } - } + viewRenderer.view.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + val divData = viewRenderer.currentData ?: return@addOnLayoutChangeListener + divContainer.doOnPreDraw { adjustContainerHeight(divData) } } } fun updateState(newState: DivEditorState) { when (newState) { - is DivEditorState.InitialState -> { - showInitialState() - } - is DivEditorState.LoadingState -> { - showLoadingState() - } - is DivEditorState.DivReceivedState -> { - showDivReceivedState(newState) - } - is DivEditorState.FailedState -> { - showFailedState(newState) - } + is DivEditorState.InitialState -> showInitialState() + is DivEditorState.LoadingState -> showLoadingState() + is DivEditorState.DivReceivedState -> showDivReceivedState(newState) + is DivEditorState.FailedState -> showFailedState(newState) + } + } + + fun setDivData(divData: DivData, tag: DivDataTag = DivDataTag(divData.logId)) { + isSingleCard = true + setRendererData(divData, tag) + applyRendererVisibility() + } + + fun applyPatch(divPatch: DivPatch, errorCallback: () -> Unit) { + if (isSingleCard) { + activeRenderer.applyPatch(divPatch, errorCallback) + } else { + div2Adapter.applyPatch(divPatch, errorCallback) + } + } + + private fun setRendererData(data: DivData, tag: DivDataTag) { + lastDivData = data + lastDivDataTag = tag + activeRenderer.setData(data, tag) + } + + private fun applyRendererVisibility() { + if (!isSingleCard) return + inactiveRenderer.deactivate() + activeRenderer.activate(lastDivData, lastDivDataTag) + activeRenderer.currentData?.let { adjustContainerHeight(it) } + } + + private fun adjustContainerHeight(divData: DivData) { + val rootHeight = divData.states.firstOrNull()?.div?.value()?.height + val containerHeight = when (rootHeight) { + is DivSize.MatchParent -> ViewGroup.LayoutParams.MATCH_PARENT + else -> ViewGroup.LayoutParams.WRAP_CONTENT + } + if (divContainer.layoutParams.height != containerHeight) { + divContainer.layoutParams.height = containerHeight + divContainer.requestLayout() } } @@ -125,16 +167,17 @@ class DivEditorUi( hideAll() tryDeclareContextVariables(state.rawDiv) + isSingleCard = state.isSingleCard if (state.isSingleCard) { - div2View.visibility = VISIBLE + val card = state.divDataList.first() + setRendererData(card, DivDataTag(card.logId)) div2Recycler.visibility = GONE div2Adapter.clearList() - val card = state.divDataList.first() - div2View.setData(card, DivDataTag(card.logId)) + applyRendererVisibility() } else { div2Recycler.visibility = VISIBLE - div2View.visibility = GONE - div2View.cleanup() + viewRenderer.cleanup() + composeRenderer.cleanup() div2Adapter.setList(state.divDataList) div2Recycler.viewTreeObserver.addOnDrawListener(debounceOnViewDrawObserver) } @@ -148,13 +191,14 @@ class DivEditorUi( val result: List = DivVariablesParser.parse(variables) { e -> Assert.fail("Error during parsing of variable", e) } - - (div2View.context as Div2Context).divVariableController.declare(*result.toTypedArray()) + divContext.divVariableController.declare(*result.toTypedArray()) } private fun hideAll() { metadataButton.visibility = GONE failedTextMessage.visibility = GONE + viewRenderer.view.visibility = GONE + composeRenderer.view.visibility = GONE divContainer.viewTreeObserver.removeOnDrawListener(debounceOnViewDrawObserver) } } diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/ComposeFontFamilyProvider.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/ComposeFontFamilyProvider.kt new file mode 100644 index 000000000..3f0f741f3 --- /dev/null +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/font/ComposeFontFamilyProvider.kt @@ -0,0 +1,25 @@ +package com.yandex.divkit.demo.font + +import android.content.Context +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import com.yandex.div.compose.DivFontFamilyProvider +import com.yandex.div.core.font.DivTypefaceProvider +import com.yandex.div.font.YandexSansDisplayDivTypefaceProvider +import com.yandex.div.font.YandexSansDivTypefaceProvider + +class ComposeFontFamilyProvider(context: Context) : DivFontFamilyProvider { + + private val defaultProvider = YandexSansDivTypefaceProvider(context) + private val additionalProviders = mapOf( + "display" to YandexSansDisplayDivTypefaceProvider(context) as DivTypefaceProvider, + "condensed" to YandexSansCondensedTypefaceProvider(context) as DivTypefaceProvider, + "roboto_flex" to RobotoFlexTypefaceProvider(context) as DivTypefaceProvider, + ) + + override fun getFontFamily(fontFamilyName: String?, weight: FontWeight): FontFamily { + val provider = fontFamilyName?.let { additionalProviders[it] } ?: defaultProvider + val typeface = provider.getTypefaceFor(weight.weight) ?: return FontFamily.Default + return FontFamily(typeface) + } +} diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt index a368df6d9..b16847a96 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/screenshot/DivComposeScreenshotActivity.kt @@ -4,28 +4,21 @@ import android.os.Bundle import android.widget.LinearLayout import androidx.activity.ComponentActivity import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight import coil3.ImageLoader import coil3.request.allowHardware import com.yandex.div.compose.DivComposeConfiguration import com.yandex.div.compose.DivContext -import com.yandex.div.compose.DivFontFamilyProvider import com.yandex.div.compose.DivView import com.yandex.div.compose.internal.ImageLoaderProvider import com.yandex.div.compose.createContext import com.yandex.div.compose.internal.DivDebugConfiguration import com.yandex.div.core.annotations.InternalApi -import com.yandex.div.core.font.DivTypefaceProvider import com.yandex.div.data.DivParsingEnvironment -import com.yandex.div.font.YandexSansDivTypefaceProvider -import com.yandex.div.font.YandexSansDisplayDivTypefaceProvider import com.yandex.div.json.ParsingErrorLogger import com.yandex.div2.DivAction import com.yandex.div2.DivData import com.yandex.divkit.demo.R -import com.yandex.divkit.demo.font.RobotoFlexTypefaceProvider -import com.yandex.divkit.demo.font.YandexSansCondensedTypefaceProvider +import com.yandex.divkit.demo.font.ComposeFontFamilyProvider import org.json.JSONObject /** @@ -51,19 +44,11 @@ class DivComposeScreenshotActivity : ComponentActivity() { .build() } - val defaultProvider = YandexSansDivTypefaceProvider(this) - val additionalProviders = mapOf( - "display" to YandexSansDisplayDivTypefaceProvider(this) as DivTypefaceProvider, - "condensed" to YandexSansCondensedTypefaceProvider(this) as DivTypefaceProvider, - "roboto_flex" to RobotoFlexTypefaceProvider(this) as DivTypefaceProvider, - ) - val fontFamilyProvider = TypefaceProviderFontFamilyAdapter(defaultProvider, additionalProviders) - divContext = DivComposeConfiguration( debugConfiguration = DivDebugConfiguration( imageLoaderProvider = imageLoaderProvider ), - fontFamilyProvider = fontFamilyProvider, + fontFamilyProvider = ComposeFontFamilyProvider(this), ).createContext(baseContext = this) intent.extras?.getString(EXTRA_DIV_ASSET_NAME)?.let { @@ -102,15 +87,3 @@ class DivComposeScreenshotActivity : ComponentActivity() { const val EXTRA_DIV_ASSET_NAME = "DivComposeScreenshotActivity.EXTRA_DIV_ASSET_NAME" } } - -private class TypefaceProviderFontFamilyAdapter( - private val defaultProvider: DivTypefaceProvider, - private val additionalProviders: Map, -) : DivFontFamilyProvider { - - override fun getFontFamily(fontFamilyName: String?, weight: FontWeight): FontFamily { - val provider = fontFamilyName?.let { additionalProviders[it] } ?: defaultProvider - val typeface = provider.getTypefaceFor(weight.weight) ?: return FontFamily.Default - return FontFamily(typeface) - } -} diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/Preferences.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/Preferences.kt index 2196d4cc2..244ebd602 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/Preferences.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/Preferences.kt @@ -27,4 +27,6 @@ class Preferences(context: Context) : DivkitDemoPreferences(context) { var limitImageBitmapSizeEnabled by BooleanPreference(true) var visualAssertionHandlerEnabled by BooleanPreference(BuildConfig.DEBUG) + + var useComposeRenderer by BooleanPreference(false) } diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/SettingsActionHandler.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/SettingsActionHandler.kt index 68bc6b2b6..2a5f9813f 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/SettingsActionHandler.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/SettingsActionHandler.kt @@ -23,6 +23,7 @@ const val RENDER_EFFECT_ENABLED = "render_effect_enabled" const val BACKGROUND_BINDING = "background_binding" const val LIMIT_IMAGE_BITMAP_SIZE_ENABLED = "limit_image_bitmap_size_enabled" const val VISUAL_ASSERTION_HANDLER = "visual_assertion_handler" +const val COMPOSE_RENDERER = "compose_renderer" const val NIGHT_MODE = "night_mode" const val NIGHT_MODE_NIGHT = "NIGHT" @@ -62,6 +63,7 @@ internal object SettingsActionHandler { BACKGROUND_BINDING -> Container.preferences.useBackgroundBinding = valueInt.toBooleanOrException() LIMIT_IMAGE_BITMAP_SIZE_ENABLED -> Container.preferences.limitImageBitmapSizeEnabled = valueInt.toBooleanOrException() VISUAL_ASSERTION_HANDLER -> Container.preferences.visualAssertionHandlerEnabled = valueInt.toBooleanOrException() + COMPOSE_RENDERER -> Container.preferences.useComposeRenderer = valueInt.toBooleanOrException() else -> return false } diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/SettingsActivity.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/SettingsActivity.kt index 00ebd0831..42a5fc104 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/SettingsActivity.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/settings/SettingsActivity.kt @@ -108,6 +108,7 @@ class SettingsActivity : AppCompatActivity() { val useAsyncBinding = Container.preferences.useBackgroundBinding setPreferenceState(BACKGROUND_BINDING, useAsyncBinding) setPreferenceState(VISUAL_ASSERTION_HANDLER, Container.preferences.visualAssertionHandlerEnabled) + setPreferenceState(COMPOSE_RENDERER, Container.preferences.useComposeRenderer) } private fun setPreferenceState(name: String, flag: Experiment) { diff --git a/client/android/divkit-demo-app/src/main/res/menu/div2_scenario_menu.xml b/client/android/divkit-demo-app/src/main/res/menu/div2_scenario_menu.xml index 0c3783e48..0fd1bfcd5 100644 --- a/client/android/divkit-demo-app/src/main/res/menu/div2_scenario_menu.xml +++ b/client/android/divkit-demo-app/src/main/res/menu/div2_scenario_menu.xml @@ -2,6 +2,13 @@ + + Load patch Load another div json Close all tooltips + Compose Renderer (Demo)