Skip to content

Commit f39bc56

Browse files
leandroBorgesFerreiraLeandro Ferreira
andauthored
Adding text toolbox (#503)
* Adding text toolbox * working textboolbox * adding shadow --------- Co-authored-by: Leandro Ferreira <[email protected]>
1 parent bdcec3b commit f39bc56

File tree

8 files changed

+310
-98
lines changed

8 files changed

+310
-98
lines changed

application/features/editor/src/commonMain/kotlin/io/writeopia/editor/features/editor/viewmodel/NoteEditorKmpViewModel.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,21 @@ class NoteEditorKmpViewModel(
144144
override val showSearchState: StateFlow<Boolean> = _showSearch.asStateFlow()
145145
override val searchText: StateFlow<String> = _searchText.asStateFlow()
146146

147+
private val hasLinesSelection = writeopiaManager.onEditPositions
148+
.map { it.isNotEmpty() }
149+
150+
override val hasSelectedLines: StateFlow<Boolean> =
151+
combine(
152+
hasLinesSelection,
153+
writeopiaManager.textSelectionState
154+
) { hasLines, selection ->
155+
val hasTextSelection = selection.start != selection.end
156+
157+
hasLines || hasTextSelection
158+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
159+
160+
// val selectionOfText = writeopiaManager.
161+
147162
private val findsOfSearch: Flow<Set<Int>> =
148163
combine(writeopiaManager.documentInfo, searchText) { info, query ->
149164
info.id to query
@@ -157,7 +172,7 @@ class NoteEditorKmpViewModel(
157172
override val isEditable: StateFlow<Boolean> = writeopiaManager
158173
.documentInfo
159174
.map { info -> !info.isLocked }
160-
.stateIn(viewModelScope, started = SharingStarted.Lazily, initialValue = false)
175+
.stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(), initialValue = false)
161176

162177
private val _showGlobalMenu = MutableStateFlow(false)
163178
override val showGlobalMenu = _showGlobalMenu.asStateFlow()
@@ -192,7 +207,7 @@ class NoteEditorKmpViewModel(
192207
override val currentTitle by lazy {
193208
writeopiaManager.currentDocument.filterNotNull().map { document ->
194209
document.title
195-
}.stateIn(viewModelScope, started = SharingStarted.Lazily, initialValue = "")
210+
}.stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(), initialValue = "")
196211
}
197212

198213
private val _shouldGoToNextScreen = MutableStateFlow(false)
@@ -207,7 +222,7 @@ class NoteEditorKmpViewModel(
207222

208223
else -> EditState.TEXT
209224
}
210-
}.stateIn(viewModelScope, started = SharingStarted.Lazily, initialValue = EditState.TEXT)
225+
}.stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(), initialValue = EditState.TEXT)
211226
}
212227

213228
private val story: StateFlow<StoryState> = writeopiaManager.currentStory
@@ -229,7 +244,7 @@ class NoteEditorKmpViewModel(
229244
override val notFavorite: StateFlow<Boolean> = writeopiaManager
230245
.documentInfo
231246
.map { info -> !info.isFavorite }
232-
.stateIn(viewModelScope, started = SharingStarted.Lazily, initialValue = false)
247+
.stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(), initialValue = false)
233248

234249
private var aiJob: Job? = null
235250

application/features/editor/src/commonMain/kotlin/io/writeopia/editor/features/editor/viewmodel/NoteEditorViewModel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ interface NoteEditorViewModel : BackstackInform, BackstackHandler {
5050

5151
val searchText: StateFlow<String>
5252

53+
val hasSelectedLines: StateFlow<Boolean>
54+
5355
fun showSearch()
5456

5557
fun hideSearch()

writeopia_ui/src/commonMain/kotlin/io/writeopia/ui/components/EditionScreen.kt

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
2121
import androidx.compose.ui.Alignment
2222
import androidx.compose.ui.Modifier
2323
import androidx.compose.ui.draw.clip
24+
import androidx.compose.ui.unit.Dp
2425
import androidx.compose.ui.unit.dp
2526
import io.writeopia.sdk.models.span.Span
2627
import io.writeopia.ui.icons.WrSdkIcons
@@ -32,6 +33,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
3233
@Composable
3334
fun EditionScreen(
3435
modifier: Modifier = Modifier,
36+
iconSize: Dp = 36.dp,
3537
onSpanClick: (Span) -> Unit = {},
3638
checkboxClick: () -> Unit = {},
3739
listItemClick: () -> Unit = {},
@@ -43,15 +45,13 @@ fun EditionScreen(
4345
) {
4446
val iconPadding = PaddingValues(vertical = 4.dp)
4547
val clipShape = MaterialTheme.shapes.medium
46-
val iconSize = 36.dp
4748
val spaceWidth = 8.dp
4849

49-
Row(modifier = modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
50+
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
5051
val tint = MaterialTheme.colorScheme.onPrimary
5152

5253
Row(
5354
modifier = Modifier
54-
.weight(1f)
5555
.horizontalScroll(rememberScrollState()),
5656
verticalAlignment = Alignment.CenterVertically
5757
) {
@@ -133,7 +133,7 @@ fun EditionScreen(
133133
modifier = Modifier
134134
.clip(clipShape)
135135
.clickable(onClick = onCopy)
136-
.size(32.dp)
136+
.size(iconSize)
137137
.padding(iconPadding),
138138
imageVector = WrSdkIcons.copy,
139139
contentDescription = "Copy",
@@ -146,7 +146,7 @@ fun EditionScreen(
146146
modifier = Modifier
147147
.clip(clipShape)
148148
.clickable(onClick = onCut)
149-
.size(32.dp)
149+
.size(iconSize)
150150
.padding(iconPadding),
151151
imageVector = Icons.Default.ContentCut,
152152
contentDescription = "Cut",
@@ -195,6 +195,110 @@ fun EditionScreen(
195195
}
196196
}
197197

198+
@Composable
199+
fun EditionScreenForText(
200+
modifier: Modifier = Modifier,
201+
iconSize: Dp = 36.dp,
202+
onSpanClick: (Span) -> Unit = {},
203+
) {
204+
val iconPadding = PaddingValues(vertical = 4.dp)
205+
val clipShape = MaterialTheme.shapes.medium
206+
val spaceWidth = 8.dp
207+
208+
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
209+
val tint = MaterialTheme.colorScheme.onPrimary
210+
211+
Row(
212+
modifier = Modifier
213+
.horizontalScroll(rememberScrollState()),
214+
verticalAlignment = Alignment.CenterVertically
215+
) {
216+
Icon(
217+
modifier = Modifier
218+
.clip(clipShape)
219+
.clickable {
220+
onSpanClick(Span.BOLD)
221+
}
222+
.size(iconSize)
223+
.padding(iconPadding),
224+
imageVector = Icons.Outlined.FormatBold,
225+
contentDescription = "BOLD",
226+
// contentDescription = stringResource(R.string.delete),
227+
tint = tint
228+
)
229+
230+
Icon(
231+
modifier = Modifier
232+
.clip(clipShape)
233+
.clickable {
234+
onSpanClick(Span.ITALIC)
235+
}
236+
.size(iconSize)
237+
.padding(iconPadding),
238+
imageVector = Icons.Outlined.FormatItalic,
239+
contentDescription = "ITALIC",
240+
// contentDescription = stringResource(R.string.delete),
241+
tint = tint
242+
)
243+
244+
Spacer(modifier = Modifier.width(spaceWidth))
245+
246+
Icon(
247+
modifier = Modifier
248+
.clip(clipShape)
249+
.clickable {
250+
onSpanClick(Span.UNDERLINE)
251+
}
252+
.size(iconSize)
253+
.padding(iconPadding),
254+
imageVector = Icons.Outlined.FormatUnderlined,
255+
contentDescription = "UNDERLINE",
256+
// contentDescription = stringResource(R.string.delete),
257+
tint = tint
258+
)
259+
260+
// Spacer(modifier = Modifier.width(spaceWidth))
261+
//
262+
// Icon(
263+
// modifier = Modifier
264+
// .clip(clipShape)
265+
// .clickable(onClick = onCopy)
266+
// .size(iconSize)
267+
// .padding(iconPadding),
268+
// imageVector = WrSdkIcons.copy,
269+
// contentDescription = "Copy",
270+
// tint = tint
271+
// )
272+
//
273+
// Spacer(modifier = Modifier.width(spaceWidth))
274+
//
275+
// Icon(
276+
// modifier = Modifier
277+
// .clip(clipShape)
278+
// .clickable(onClick = onCut)
279+
// .size(iconSize)
280+
// .padding(iconPadding),
281+
// imageVector = Icons.Default.ContentCut,
282+
// contentDescription = "Cut",
283+
// tint = tint
284+
// )
285+
286+
// Spacer(modifier = Modifier.width(spaceWidth))
287+
//
288+
// Icon(
289+
// modifier = Modifier
290+
// .clip(clipShape)
291+
// .clickable(onClick = onAddPage)
292+
// .size(iconSize)
293+
// .padding(iconPadding),
294+
// imageVector = WrSdkIcons.linkPage,
295+
// contentDescription = "Link to page",
296+
// tint = tint
297+
// )
298+
}
299+
}
300+
}
301+
198302
@Preview
199303
@Composable
200304
fun EditionScreenPreview() {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.writeopia.ui.drawer
2+
3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.core.tween
5+
import androidx.compose.animation.fadeIn
6+
import androidx.compose.animation.fadeOut
7+
import androidx.compose.foundation.background
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.material3.MaterialTheme
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.draw.clip
13+
import androidx.compose.ui.draw.shadow
14+
import androidx.compose.ui.unit.IntOffset
15+
import androidx.compose.ui.unit.dp
16+
import androidx.compose.ui.window.Popup
17+
import io.writeopia.sdk.models.span.Span
18+
import io.writeopia.ui.components.EditionScreenForText
19+
20+
@Composable
21+
fun TextToolbox(
22+
hasSelection: Boolean,
23+
onSpanClick: (Span) -> Unit = {},
24+
) {
25+
Popup(offset = IntOffset(0, -40)) {
26+
AnimatedVisibility(
27+
modifier = Modifier,
28+
visible = hasSelection,
29+
enter = fadeIn(animationSpec = tween(durationMillis = 150)),
30+
exit = fadeOut(animationSpec = tween(durationMillis = 150))
31+
) {
32+
EditionScreenForText(
33+
modifier = Modifier
34+
.shadow(elevation = 6.dp)
35+
.clip(MaterialTheme.shapes.medium)
36+
.background(MaterialTheme.colorScheme.primary)
37+
.padding(horizontal = 8.dp, vertical = 2.dp),
38+
iconSize = 28.dp,
39+
onSpanClick = onSpanClick,
40+
)
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)