1package com.peachpasswords.ui.vault23import android.view.WindowManager4import androidx.compose.animation.core.animateFloatAsState5import androidx.compose.animation.core.tween6import androidx.compose.foundation.background7import androidx.compose.foundation.clickable8import androidx.compose.foundation.layout.Arrangement9import androidx.compose.foundation.layout.Box10import androidx.compose.foundation.layout.Column11import androidx.compose.foundation.layout.Row12import androidx.compose.foundation.layout.Spacer13import androidx.compose.foundation.layout.fillMaxSize14import androidx.compose.foundation.layout.fillMaxWidth15import androidx.compose.foundation.layout.height16import androidx.compose.foundation.layout.padding17import androidx.compose.foundation.layout.size18import androidx.compose.foundation.layout.statusBarsPadding19import androidx.compose.foundation.layout.width20import androidx.compose.foundation.shape.CircleShape21import androidx.compose.foundation.shape.RoundedCornerShape22import androidx.compose.foundation.text.BasicTextField23import androidx.compose.material.icons.Icons24import androidx.compose.material.icons.automirrored.rounded.ArrowBack25import androidx.compose.material3.Icon26import androidx.compose.material3.Text27import androidx.compose.runtime.Composable28import androidx.compose.runtime.LaunchedEffect29import androidx.compose.runtime.getValue30import androidx.compose.runtime.mutableStateOf31import androidx.compose.runtime.remember32import androidx.compose.runtime.setValue33import androidx.compose.ui.Alignment34import androidx.compose.ui.Modifier35import androidx.compose.ui.draw.alpha36import androidx.compose.ui.draw.clip37import androidx.compose.ui.graphics.SolidColor38import androidx.compose.ui.platform.LocalContext39import androidx.compose.ui.text.AnnotatedString40import androidx.compose.ui.text.SpanStyle41import androidx.compose.ui.text.TextStyle42import androidx.compose.ui.text.buildAnnotatedString43import androidx.compose.ui.text.font.FontStyle44import androidx.compose.ui.text.font.FontWeight45import androidx.compose.ui.text.input.TextFieldValue46import androidx.compose.ui.text.style.TextDecoration47import androidx.compose.ui.text.withStyle48import androidx.compose.ui.unit.dp49import androidx.compose.ui.unit.sp50import androidx.compose.runtime.collectAsState51import androidx.fragment.app.FragmentActivity52import androidx.hilt.navigation.compose.hiltViewModel53import com.peachpasswords.ui.theme.LocalPeachAccent54import com.peachpasswords.ui.theme.peachColors55import com.peachpasswords.ui.theme.PeachType56import com.peachpasswords.ui.theme.ShellBackground575859606162636465666768@Composable69fun NotesModeScreen(70 onBack: () -> Unit,71 vm: NotesModeViewModel = hiltViewModel(),72) {73 val entry by vm.entry.collectAsState()74 val savedPulse by vm.savedPulse.collectAsState()75 val accent = LocalPeachAccent.current76 val context = LocalContext.current7778 79 androidx.compose.runtime.DisposableEffect(Unit) {80 val window = (context as? FragmentActivity)?.window81 window?.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)82 onDispose { window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) }83 }8485 val e = entry8687 88 var showSaved by remember { mutableStateOf(false) }89 LaunchedEffect(savedPulse) {90 if (savedPulse == 0L) return@LaunchedEffect91 showSaved = true92 kotlinx.coroutines.delay(1300)93 showSaved = false94 }95 val savedAlpha by animateFloatAsState(96 targetValue = if (showSaved) 1f else 0f,97 animationSpec = tween(300),98 label = "saved-fade",99 )100101 var title by remember(e?.id) { mutableStateOf(e?.title ?: "") }102 var body by remember(e?.id) { mutableStateOf(TextFieldValue(e?.notes ?: "")) }103104 ShellBackground {105 Column(Modifier.fillMaxSize().statusBarsPadding()) {106 107 Row(108 Modifier.fillMaxWidth().height(56.dp).padding(horizontal = 8.dp),109 verticalAlignment = Alignment.CenterVertically,110 ) {111 Icon(112 Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "back", tint = peachColors.T0,113 modifier = Modifier.size(40.dp).clip(CircleShape)114 .clickable { vm.saveTitleNow(title); onBack() }.padding(8.dp),115 )116 Box(Modifier.weight(1f).padding(horizontal = 4.dp)) {117 if (title.isEmpty()) {118 Text(119 "untitled note",120 color = peachColors.T2,121 style = TextStyle(fontFamily = PeachType.Display, fontStyle = FontStyle.Italic, fontSize = 22.sp),122 )123 }124 BasicTextField(125 value = title,126 onValueChange = { title = it; vm.onTitleChanged(it) },127 singleLine = true,128 textStyle = TextStyle(129 color = peachColors.T0,130 fontFamily = PeachType.Display,131 fontStyle = FontStyle.Italic,132 fontSize = 22.sp,133 ),134 cursorBrush = SolidColor(accent),135 modifier = Modifier.fillMaxWidth(),136 )137 }138 Text(139 "saved.",140 color = peachColors.T2,141 style = TextStyle(fontFamily = PeachType.Mono, fontSize = 9.sp),142 modifier = Modifier.padding(end = 8.dp).alpha(savedAlpha),143 )144 }145146 if (e == null) {147 Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {148 Text("note not found", color = peachColors.T1, style = PeachType.bodyMono)149 }150 return@Column151 }152153 154 Row(155 Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 4.dp),156 horizontalArrangement = Arrangement.spacedBy(6.dp),157 verticalAlignment = Alignment.CenterVertically,158 ) {159 FormatButton("B", FontWeight.Bold) { body = applySpan(body, SpanStyle(fontWeight = FontWeight.Bold)) }160 FormatButton("I", fontStyle = FontStyle.Italic) { body = applySpan(body, SpanStyle(fontStyle = FontStyle.Italic)) }161 FormatButton("U", underline = true) { body = applySpan(body, SpanStyle(textDecoration = TextDecoration.Underline)) }162 }163164 165 Box(Modifier.fillMaxSize().padding(horizontal = 16.dp, vertical = 12.dp)) {166 if (body.text.isEmpty()) {167 Text(168 "Start writing…",169 color = peachColors.T2,170 style = TextStyle(fontFamily = PeachType.Body, fontStyle = FontStyle.Italic, fontSize = 14.sp),171 )172 }173 BasicTextField(174 value = body,175 onValueChange = { body = it; vm.onContentChanged(it.text) },176 textStyle = TextStyle(color = peachColors.T0, fontFamily = PeachType.Body, fontSize = 14.sp, lineHeight = 22.sp),177 cursorBrush = SolidColor(accent),178 modifier = Modifier.fillMaxSize(),179 )180 }181 }182 }183}184185186187188189190private fun applySpan(value: TextFieldValue, style: SpanStyle): TextFieldValue {191 val sel = value.selection192 if (sel.collapsed) return value193 val start = sel.min194 val end = sel.max195 val existing = value.annotatedString196 val merged: AnnotatedString = buildAnnotatedString {197 append(existing)198 addStyle(style, start, end)199 }200 return value.copy(annotatedString = merged)201}202203@Composable204private fun FormatButton(205 label: String,206 fontWeight: FontWeight? = null,207 fontStyle: FontStyle? = null,208 underline: Boolean = false,209 onClick: () -> Unit,210) {211 Box(212 Modifier213 .size(30.dp)214 .clip(RoundedCornerShape(6.dp))215 .background(peachColors.S2)216 .clickable(onClick = onClick),217 contentAlignment = Alignment.Center,218 ) {219 Text(220 label,221 color = peachColors.T0,222 style = TextStyle(223 fontFamily = PeachType.Body,224 fontSize = 14.sp,225 fontWeight = fontWeight,226 fontStyle = fontStyle,227 textDecoration = if (underline) TextDecoration.Underline else null,228 ),229 )230 }231}