Skip to content

Commit cb2e8af

Browse files
committed
Merge branches 'main' and 'main' of github.com:project-sora/sora-editor-docs
2 parents c96e318 + f811634 commit cb2e8af

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

docs/.vitepress/config/en.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ function guideReference(): DefaultTheme.SidebarItem[] {
9292
text: 'Jetpack Compose',
9393
collapsed: false,
9494
items: [
95-
{ text: 'CodeEditor in Compose', link: 'code-editor-in-compose'}
95+
{ text: 'CodeEditor in Compose', link: 'code-editor-in-compose'},
96+
{ text: 'Using ComposeView in PopupWindow', link: 'using-composeview-in-popupwindow'},
9697
]
9798
},
9899
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# Using ComposeView in PopupWindow
6+
7+
`CodeEditor` supports a number of components namely `EditorAutoCompletion`, `EditorTextActionWindow` etc, when you want to customize the layout of them, you will have two approaches:
8+
9+
1. Using legacy XML to define the layouts
10+
2. Using Compose to define the layout with `ComposeView`
11+
12+
In this documentation, we will dive into the approach of using Compose to define the layout for `EditorTextActionWindow`.
13+
14+
## Challenges of attempting `ComposeView` in `PopupWindow`
15+
16+
As the `EditorTextActionWindow` internally uses `PopupWindow`, and provides
17+
`setContentView()` to apply the view to the component.
18+
19+
::: danger ERROR
20+
However, if you directly put the Compose content in the `PopupWindow`, an error will throw.
21+
```kotlin
22+
java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.PopupWindow$PopupDecorView{9dfea2f V.E...... R.....I. 0,0-0,0}
23+
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:242)
24+
at androidx.compose.ui.platform.WindowRecomposer_androidKt.access$createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:1)
25+
...
26+
```
27+
:::
28+
29+
By default, the `PopupWindow` cannot be worked with Compose. To solve this, we need a `FrameLayout` to be the parent layout of the `PopupWindow`, we then use this `FrameLayout` to contain the Compose content, and apply the `ViewTreeLifecycleOwner` and `ViewTreeSavedStateRegistryOwner` to the `FrameLayout`.
30+
31+
::: tip TIP
32+
We can directly retrieve the `ViewTreeLifecycleOwner` and `ViewTreeSavedStateRegistryOwner` via the `CompositionLocal`.
33+
34+
```kotlin
35+
val viewTreeLifecycleOwner = LocalViewTreeLifecycleOwner.current
36+
val viewTreeSavedStateRegistryOwner = LocalViewTreeSavedStateRegistry.current
37+
```
38+
:::
39+
40+
## Define a `FrameLayout`
41+
42+
We will use `android.R.id.content` for the content child of the `View` as it is neccessary to let Compose find the content child.
43+
44+
```kotlin
45+
val composeView = ComposeView(context).apply {
46+
setContent {
47+
// the Compose content...
48+
}
49+
}
50+
val parentView = FrameLayout(context).apply {
51+
id = android.R.id.content
52+
setViewTreeLifecycleOwner(viewTreeLifecycleOwner)
53+
setViewTreeSavedStateRegistryOwner(viewTreeSavedStateRegistryOwner)
54+
layoutParams = FrameLayout.LayoutParams(
55+
ViewGroup.LayoutParams.WRAP_CONTENT,
56+
ViewGroup.LayoutParams.WRAP_CONTENT
57+
)
58+
addView(composeView)
59+
}
60+
```
61+
62+
## Complete the `EditorTextActionWindow` layout
63+
64+
Here is the example of customizing the layout for `EditorTextActionWindow` in Compose.
65+
66+
```kotlin
67+
data class EditorTextActionItem(
68+
val label: String,
69+
val icon: ImageVector
70+
)
71+
```
72+
73+
```kotlin
74+
val actionItems = listOf(
75+
EditorTextActionItem(
76+
label = "Select all",
77+
icon = /* ... */
78+
),
79+
EditorTextActionItem(
80+
label = "Copy",
81+
icon = /* ... */
82+
)
83+
EditorTextActionItem(
84+
label = "Paste",
85+
icon = /* ... */
86+
)
87+
// ...
88+
)
89+
```
90+
91+
```kotlin
92+
@Composable
93+
fun EditorTextActionWindow(
94+
modifier: Modifier = Modifier,
95+
items: List<EditorTextActionItem>,
96+
onItemClick: (EditorTextActionItem) -> Unit
97+
): FrameLayout {
98+
val context = LocalContext.current
99+
val viewTreeLifecycleOwner = LocalViewTreeLifecycleOwner.current
100+
val viewTreeSavedStateRegistryOwner = LocalViewTreeSavedStateRegistry.current
101+
val composeView = ComposeView(context).apply {
102+
setContent {
103+
EditorTextActionContent(modifier, items, onItemClick)
104+
}
105+
}
106+
val parentView = FrameLayout(context).apply {
107+
id = android.R.id.content
108+
setViewTreeLifecycleOwner(viewTreeLifecycleOwner)
109+
setViewTreeSavedStateRegistryOwner(viewTreeSavedStateRegistryOwner)
110+
layoutParams = FrameLayout.LayoutParams(
111+
ViewGroup.LayoutParams.WRAP_CONTENT,
112+
ViewGroup.LayoutParams.WRAP_CONTENT
113+
)
114+
addView(composeView)
115+
}
116+
return parentView
117+
}
118+
119+
@Composable
120+
private fun EditorTextActionContent(
121+
modifier: Modifier = Modifier,
122+
items: List<EditorTextActionItem>,
123+
onItemClick: (EditorTextActionItem) -> Unit
124+
) {
125+
Row(modifier) {
126+
items.forEach { item ->
127+
IconButton(
128+
onClick = { onItemClick(item) }
129+
) {
130+
Icon(
131+
imageVector = item.icon,
132+
contentDescription = item.label
133+
)
134+
}
135+
}
136+
}
137+
}
138+
```
139+
140+
Finally, apply the layout into `EditorTextActionWindow`.
141+
142+
```kotlin
143+
editor.getComponent<EditorTextActionWindow>().setContentView(parentView)
144+
```

0 commit comments

Comments
 (0)