11package io.writeopia.ui.drawer.content
22
3+ import androidx.compose.animation.AnimatedVisibility
4+ import androidx.compose.animation.core.Spring
5+ import androidx.compose.animation.core.spring
6+ import androidx.compose.animation.core.tween
7+ import androidx.compose.animation.expandIn
8+ import androidx.compose.animation.fadeIn
9+ import androidx.compose.animation.fadeOut
10+ import androidx.compose.animation.shrinkOut
11+ import androidx.compose.animation.slideIn
12+ import androidx.compose.animation.slideOut
13+ import androidx.compose.foundation.background
314import androidx.compose.foundation.clickable
415import androidx.compose.foundation.interaction.MutableInteractionSource
516import androidx.compose.foundation.layout.Arrangement
17+ import androidx.compose.foundation.layout.Box
618import androidx.compose.foundation.layout.Row
19+ import androidx.compose.foundation.layout.padding
720import androidx.compose.foundation.text.BasicTextField
821import androidx.compose.foundation.text.KeyboardOptions
922import androidx.compose.foundation.text.input.TextFieldState
23+ import androidx.compose.material3.DropdownMenu
1024import androidx.compose.material3.MaterialTheme
1125import androidx.compose.runtime.Composable
1226import androidx.compose.runtime.LaunchedEffect
@@ -17,7 +31,9 @@ import androidx.compose.runtime.mutableStateOf
1731import androidx.compose.runtime.remember
1832import androidx.compose.runtime.rememberCoroutineScope
1933import androidx.compose.runtime.setValue
34+ import androidx.compose.ui.Alignment
2035import androidx.compose.ui.Modifier
36+ import androidx.compose.ui.draw.clip
2137import androidx.compose.ui.focus.FocusRequester
2238import androidx.compose.ui.focus.FocusState
2339import androidx.compose.ui.focus.focusRequester
@@ -32,10 +48,15 @@ import androidx.compose.ui.text.TextStyle
3248import androidx.compose.ui.text.font.FontStyle
3349import androidx.compose.ui.text.input.KeyboardCapitalization
3450import androidx.compose.ui.text.input.TextFieldValue
51+ import androidx.compose.ui.unit.IntOffset
52+ import androidx.compose.ui.unit.IntSize
53+ import androidx.compose.ui.unit.dp
54+ import androidx.compose.ui.window.Popup
3555import io.writeopia.sdk.models.story.StoryStep
3656import io.writeopia.sdk.models.story.StoryTypes
3757import io.writeopia.sdk.models.story.Tag
3858import io.writeopia.sdk.models.story.TagInfo
59+ import io.writeopia.ui.components.EditionScreen
3960import io.writeopia.ui.drawer.SimpleTextDrawer
4061import io.writeopia.ui.drawer.factory.EndOfText
4162import io.writeopia.ui.extensions.toTextRange
@@ -106,6 +127,15 @@ class TextDrawer(
106127 textLayoutResult?.getLineForOffset(inputText.selection.end)
107128 }
108129 }
130+
131+ val selection by remember {
132+ derivedStateOf {
133+ inputText.selection
134+ }
135+ }
136+
137+ val hasSelection = selection.start != selection.end
138+
109139 val realPosition by remember {
110140 derivedStateOf {
111141 val lineStart = textLayoutResult?.multiParagraph?.getLineStart(cursorLine ? : 0 )
@@ -138,95 +168,114 @@ class TextDrawer(
138168
139169 val coroutineScope = rememberCoroutineScope()
140170
141- Row (horizontalArrangement = Arrangement .Center ) {
142- if (isSuggestion) {
171+ Box {
172+ Row (horizontalArrangement = Arrangement .Center ) {
173+ if (isSuggestion) {
174+ BasicTextField (
175+ state = TextFieldState (aiExplanation),
176+ textStyle = textStyle(step).copy(fontStyle = FontStyle .Normal )
177+ )
178+ }
179+
143180 BasicTextField (
144- state = TextFieldState (aiExplanation),
145- textStyle = textStyle(step).copy(fontStyle = FontStyle .Normal )
146- )
147- }
181+ modifier = modifier
182+ .let { modifierLet ->
183+ if (focusRequester != null ) {
184+ modifierLet.focusRequester(focusRequester)
185+ } else {
186+ modifierLet
187+ }
188+ }
189+ .onPreviewKeyEvent { keyEvent ->
190+ onKeyEvent(
191+ keyEvent,
192+ inputText,
193+ step,
194+ drawInfo.position,
195+ emptyErase,
196+ realPosition,
197+ isInLastLine
198+ )
199+ }
200+ .onFocusChanged { focusState ->
201+ onFocusChanged(drawInfo.position, focusState)
202+ }
203+ .testTag(" MessageDrawer_${drawInfo.position} " )
204+ .let { modifierLet ->
205+ if (selectionState) {
206+ modifierLet.clickable { onSelectionLister(drawInfo.position) }
207+ } else {
208+ modifierLet
209+ }
210+ },
211+ value = inputText,
212+ enabled = ! selectionState && ! drawInfo.selectMode && enabled,
213+ onTextLayout = {
214+ textLayoutResult = it
215+ },
216+ onValueChange = { value ->
217+ val start = value.selection.start
218+ val end = value.selection.end
219+ val previousStart = inputText.selection.start
148220
149- BasicTextField (
150- modifier = modifier
151- .let { modifierLet ->
152- if (focusRequester != null ) {
153- modifierLet.focusRequester(focusRequester)
154- } else {
155- modifierLet
221+ val sizeDifference = value.text.length - inputText.text.length
222+
223+ if (abs(sizeDifference) > 0 ) {
224+ spans = Spans .recalculateSpans(spans, previousStart, sizeDifference)
156225 }
157- }
158- .onPreviewKeyEvent { keyEvent ->
159- onKeyEvent(
160- keyEvent,
161- inputText,
162- step,
226+
227+ val edit = {
228+ inputText = value.copy(
229+ Spans .createStringWithSpans(
230+ value.text.replace(" \n " , " " ),
231+ spans,
232+ isDarkTheme
233+ )
234+ )
235+ }
236+
237+ onTextEdit(
238+ TextInput (value.text, start, end, spans),
163239 drawInfo.position,
164- emptyErase,
165- realPosition,
166- isInLastLine
240+ lineBreakByContent,
167241 )
168- }
169- .onFocusChanged { focusState ->
170- onFocusChanged(drawInfo.position, focusState)
171- }
172- .testTag(" MessageDrawer_${drawInfo.position} " )
173- .let { modifierLet ->
174- if (selectionState) {
175- modifierLet.clickable { onSelectionLister(drawInfo.position) }
242+
243+ if (start == 0 || end == 0 ) {
244+ coroutineScope.launch {
245+ // Delay to avoid jumping to previous line too soon when erasing text
246+ delay(70 )
247+ edit()
248+ }
176249 } else {
177- modifierLet
250+ edit()
178251 }
179252 },
180- value = inputText,
181- enabled = ! selectionState && ! drawInfo.selectMode && enabled,
182- onTextLayout = {
183- textLayoutResult = it
184- },
185- onValueChange = { value ->
186- val start = value.selection.start
187- val end = value.selection.end
188- val previousStart = inputText.selection.start
189-
190- val sizeDifference = value.text.length - inputText.text.length
191-
192- if (abs(sizeDifference) > 0 ) {
193- spans = Spans .recalculateSpans(spans, previousStart, sizeDifference)
194- }
195-
196- val edit = {
197- inputText = value.copy(
198- Spans .createStringWithSpans(
199- value.text.replace(" \n " , " " ),
200- spans,
201- isDarkTheme
202- )
203- )
204- }
253+ keyboardOptions = KeyboardOptions (
254+ capitalization = KeyboardCapitalization .Sentences
255+ ),
256+ textStyle = textStyle(step),
257+ cursorBrush = SolidColor (MaterialTheme .colorScheme.primary),
258+ interactionSource = interactionSource,
259+ decorationBox = decorationBox,
260+ )
261+ }
205262
206- onTextEdit(
207- TextInput (value.text, start, end, spans),
208- drawInfo.position,
209- lineBreakByContent,
263+
264+ Popup (offset = IntOffset (0 , - 70 )) {
265+ AnimatedVisibility (
266+ visible = hasSelection,
267+ enter = fadeIn(animationSpec = tween(durationMillis = 150 )),
268+ exit = fadeOut(animationSpec = tween(durationMillis = 150 ))
269+ ) {
270+ EditionScreen (
271+ modifier = Modifier
272+ .padding(bottom = 20 .dp)
273+ .clip(MaterialTheme .shapes.large)
274+ .background(MaterialTheme .colorScheme.primary)
210275 )
276+ }
211277
212- if (start == 0 || end == 0 ) {
213- coroutineScope.launch {
214- // Delay to avoid jumping to previous line too soon when erasing text
215- delay(70 )
216- edit()
217- }
218- } else {
219- edit()
220- }
221- },
222- keyboardOptions = KeyboardOptions (
223- capitalization = KeyboardCapitalization .Sentences
224- ),
225- textStyle = textStyle(step),
226- cursorBrush = SolidColor (MaterialTheme .colorScheme.primary),
227- interactionSource = interactionSource,
228- decorationBox = decorationBox,
229- )
278+ }
230279 }
231280 }
232281}
0 commit comments