Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
Expand Down Expand Up @@ -83,6 +90,7 @@ import coil.compose.AsyncImage
* Interactive Live Markdown Editor with immediate real-time synchronization,
* auto-list continuation, instant in-place checklist item toggling, and rich preview.
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun InteractiveMarkdownEditor(
value: TextFieldValue,
Expand All @@ -93,6 +101,9 @@ fun InteractiveMarkdownEditor(
) {
val context = LocalContext.current
var showInteractiveChecklistOverlay by remember { mutableStateOf(false) }
val bringIntoViewRequester = remember { BringIntoViewRequester() }
val coroutineScope = rememberCoroutineScope()


// Check if the current note has checklist items
val hasChecklistItems = remember(value.text) {
Expand Down Expand Up @@ -315,6 +326,15 @@ fun InteractiveMarkdownEditor(
}
}

var textLayoutResult by remember { mutableStateOf<androidx.compose.ui.text.TextLayoutResult?>(null) }

LaunchedEffect(value.selection) {
val cursorRect = textLayoutResult?.getCursorRect(value.selection.start)
if (cursorRect != null) {
bringIntoViewRequester.bringIntoView(cursorRect)
}
}

// Live Markdown Input Field with Instant Synchronous Save & Auto-Continuations
BasicTextField(
value = value,
Expand All @@ -323,6 +343,9 @@ fun InteractiveMarkdownEditor(
val handled = MarkdownAutoListHelper.handleTextChange(value, incoming)
onValueChange(handled)
},
onTextLayout = { result ->
textLayoutResult = result
},
textStyle = TextStyle(
fontSize = 16.sp,
lineHeight = 25.sp,
Expand Down Expand Up @@ -350,7 +373,9 @@ fun InteractiveMarkdownEditor(
innerTextField()
}
},

modifier = Modifier
.bringIntoViewRequester(bringIntoViewRequester)
.fillMaxWidth()
.testTag("note_content_input")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.InputChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
Expand Down Expand Up @@ -213,6 +218,9 @@ fun NoteEditScreen(
val textColor = remember(backgroundColor) { NoteColorPalette.getNoteTextColor(backgroundColor) }
val secondaryTextColor = remember(backgroundColor) { NoteColorPalette.getNoteSecondaryTextColor(backgroundColor) }

val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()

Scaffold(
modifier = modifier
.fillMaxSize()
Expand Down Expand Up @@ -350,6 +358,38 @@ fun NoteEditScreen(
)
)
},
floatingActionButton = {
if (isRawMode) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(bottom = 60.dp) // padding above the toolbar
) {
SmallFloatingActionButton(
onClick = {
coroutineScope.launch {
scrollState.animateScrollTo(0)
}
},
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
) {
Icon(Icons.Default.KeyboardArrowUp, contentDescription = "Scroll to Top")
}
SmallFloatingActionButton(
onClick = {
coroutineScope.launch {
scrollState.animateScrollTo(scrollState.maxValue)
}
},
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
) {
Icon(Icons.Default.KeyboardArrowDown, contentDescription = "Scroll to Bottom")
}
}
}
},
bottomBar = {
if (isRawMode) {
Box(
Expand Down Expand Up @@ -390,7 +430,7 @@ fun NoteEditScreen(
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
.verticalScroll(scrollState),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
// Note Title Input
Expand Down