package com.vgmlr.wedge
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CalendarMonth
import androidx.compose.material.icons.filled.Percent
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.*
import androidx.compose.ui.text.input.PlatformImeOptions
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import java.util.TimeZone
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun NoteEditor(
vm: MainViewModel,
prefs: PreferenceManager,
onSettings: () -> Unit,
onShowData: () -> Unit
) {
val context = LocalContext.current
val density = LocalDensity.current
val focusManager = LocalFocusManager.current
val lifecycleOwner = LocalLifecycleOwner.current
val ime = WindowInsets.ime
val keyboardController = LocalSoftwareKeyboardController.current
val nextTimeBgColor by prefs.nextTimeBg.collectAsState("#486860")
val bgColorHex by prefs.bgColor.collectAsState(initial = "#171717")
val incognitoActive by prefs.incognitoMode.collectAsState(initial = false)
val editorStyle by vm.editorStyle.collectAsState()
val scope = rememberCoroutineScope()
var textState by remember { mutableStateOf(TextFieldValue("")) }
var lastSavedText by remember { mutableStateOf("") }
var isLoaded by remember { mutableStateOf(false) }
var lastDel by remember { mutableLongStateOf(0L) }
val scrollState = rememberScrollState()
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
var containerHeight by remember { mutableIntStateOf(0) }
var menuExpanded by remember { mutableStateOf(false) }
var showVersionDialog by remember { mutableStateOf(false) }
var showCopyDot by remember { mutableStateOf(false) }
var showPasteDot by remember { mutableStateOf(false) }
var undoHistory by remember { mutableStateOf(emptyList<TextFieldValue>()) }
var undoLineIndex by remember { mutableIntStateOf(-1) }
var showCalc by remember { mutableStateOf(false) }
var calcValue by remember { mutableStateOf("") }
val calcFocusRequester = remember { FocusRequester() }
val nextTimeOffset = remember(textState.text) { WedgeEditorEngine.findNextTimeHighlight(textState.text) }
var showDatePicker by remember { mutableStateOf(false) }
var showCalendarOverlay by remember { mutableStateOf(false) }
var selectedDate by remember { mutableLongStateOf(0L) }
var calendarOtcDayStr by remember { mutableStateOf("") }
var calendarTimeStr by remember { mutableStateOf("00:00") }
var calendarTextStr by remember { mutableStateOf("Event") }
val calendarFocusRequester = remember { FocusRequester() }
val boldColorHex by prefs.boldColor.collectAsState(initial = WedgeConfig.BOLD_COLOR_DEFAULT)
val focusColor = parseColor(boldColorHex)
LaunchedEffect(showCalc) {
if (showCalc) {
delay(100)
calcFocusRequester.requestFocus()
keyboardController?.show()
}
}
LaunchedEffect(showCalendarOverlay) {
if (showCalendarOverlay) {
delay(100)
calendarFocusRequester.requestFocus()
keyboardController?.show()
}
}
var showEncrypt by remember { mutableStateOf(false) }
var passPhrase by remember { mutableStateOf("") }
val encryptFocusRequester = remember { FocusRequester() }
var isDecryptMode by remember { mutableStateOf(false) }
val isEncrypted = WedgeSecurity.isEncrypted(textState.text)
LaunchedEffect(showEncrypt) {
if (showEncrypt) {
delay(100)
encryptFocusRequester.requestFocus()
keyboardController?.show()
}
}
val uriHandler = LocalUriHandler.current
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val exportLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("text/plain")
) { uri ->
uri?.let {
context.contentResolver.openOutputStream(it)?.use { stream ->
stream.write(textState.text.toByteArray())
}
}
}
fun refreshAlarms(content: String): String {
val now = java.util.Calendar.getInstance()
val currentMinutes = now.get(java.util.Calendar.HOUR_OF_DAY) * 60 + now.get(java.util.Calendar.MINUTE)
val todayOtcDay = WedgeDate.getOtcParts(now.timeInMillis).first
val lines = content.split("\n")
var currentHeaderOtcDay: Long? = null
val generalHeaderRegex = Regex("""^(\d+)\s+([a-zA-Z]{3})\s+(\d{1,2})\s+([a-zA-Z]{3})""", RegexOption.IGNORE_CASE)
val updatedLines = lines.map { line ->
val headerMatch = generalHeaderRegex.find(line.trim())
if (headerMatch != null) {
currentHeaderOtcDay = headerMatch.groupValues[1].toLongOrNull()
line
} else {
val match = Regex("""^(\d{2}):(\d{2})\s*(.*)(?<!\s)\((-\d+(\.\d+)?|0)?\)$""").find(line.trim())
if (match != null) {
val h = match.groupValues[1].toInt()
val m = match.groupValues[2].toInt()
val desc = match.groupValues[3]
val alarmMinutes = h * 60 + m
val headerDay = currentHeaderOtcDay ?: todayOtcDay
val valueInside = if (headerDay < todayOtcDay) {
"0"
} else if (headerDay == todayOtcDay) {
val diffMinutes = alarmMinutes - currentMinutes
if (diffMinutes <= 0) {
"0"
} else {
val hours = (diffMinutes / 60.0).coerceAtLeast(0.1)
"-${String.format(java.util.Locale.US, "%.1f", hours)}"
}
} else {
val diffDays = headerDay - todayOtcDay
val diffMinutes = (diffDays * 24 * 60) + alarmMinutes - currentMinutes
val hours = (diffMinutes / 60.0).coerceAtLeast(0.1)
"-${String.format(java.util.Locale.US, "%.1f", hours)}"
}
val leadingSpaces = line.takeWhile { it.isWhitespace() }
"${leadingSpaces}${match.groupValues[1]}:${match.groupValues[2]} ${desc.trim()}($valueInside)"
} else {
line
}
}
}
return updatedLines.joinToString("\n")
}
fun refreshDateMath(content: String): String {
val dateMathRefreshed = WedgeRegex.DATE_MATH_REFRESH.replace(content) { match ->
val d = match.groupValues[1].toInt()
val m = match.groupValues[2]
WedgeDate.getDateMath(d, m)?.let { "$d$m$it" } ?: match.value
}
return refreshAlarms(dateMathRefreshed)
}
suspend fun saveNote(content: String, updateWidget: Boolean = false) {
withContext(Dispatchers.IO) {
val isDifferent = content != lastSavedText
if (!isDifferent && !updateWidget) return@withContext
if (isDifferent) {
vm.dao.save(NoteEntity(id = 1, content = content))
withContext(Dispatchers.Main) { lastSavedText = content }
}
if (updateWidget) {
NoteWidgetProvider.triggerUpdate(context)
}
}
}
LaunchedEffect(Unit) {
val initialNote = vm.dao.getNoteSync()
if (initialNote != null) {
val refreshed = refreshDateMath(initialNote.content)
textState = TextFieldValue(refreshed)
if (refreshed != initialNote.content) {
saveNote(refreshed, updateWidget = true)
} else {
lastSavedText = refreshed
}
}
isLoaded = true
}
LaunchedEffect(Unit) {
snapshotFlow { textState.text }.collectLatest { text ->
if (!isLoaded || text == lastSavedText) return@collectLatest
delay(500)
saveNote(text, updateWidget = true)
}
}
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_PAUSE) {
focusManager.clearFocus()
val currentText = textState.text
scope.launch {
withContext(NonCancellable) {
if (isLoaded) saveNote(currentText, updateWidget = true)
}
}
} else if (event == Lifecycle.Event.ON_RESUME) {
if (isLoaded) {
val refreshed = refreshDateMath(textState.text)
if (refreshed != textState.text) {
textState = textState.copy(text = refreshed)
scope.launch {
saveNote(refreshed, updateWidget = true)
}
}
}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
BackHandler { (context as? ComponentActivity)?.finish() }
LaunchedEffect(Unit) { focusManager.clearFocus() }
var lastLen by remember { mutableIntStateOf(0) }
var lastKHeight by remember { mutableIntStateOf(0) }
LaunchedEffect(density) {
snapshotFlow {
val keyboardHeight = ime.getBottom(density)
Triple(textState.selection, keyboardHeight, textState.text.length)
}.collectLatest { (selection, kHeight, currentLen) ->
val delta = currentLen - lastLen
val keyboardOpening = kHeight > lastKHeight
lastLen = currentLen
lastKHeight = kHeight
if (kHeight > 0 && (delta >= 0 || keyboardOpening)) {
delay(40)
textLayoutResult?.let { layout ->
try {
val offset = selection.start
if (offset >= 0) {
val cursorRect = layout.getCursorRect(offset)
val cursorBottom = cursorRect.bottom + with(density) { 16.dp.toPx() }
val buffer = with(density) { 45.dp.toPx() }
if (containerHeight > 0) {
val visibleHeight = containerHeight - kHeight
val viewportBottom = scrollState.value + visibleHeight
if (cursorBottom + buffer > viewportBottom) {
val targetScroll = (cursorBottom + buffer - visibleHeight).toInt()
scrollState.animateScrollTo(
value = targetScroll.coerceAtLeast(0),
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMediumLow
)
)
}
}
}
} catch (_: Exception) {}
}
}
}
}
val isKeyboardOpen = WindowInsets.ime.getBottom(density) > 0
LaunchedEffect(isKeyboardOpen) {
if (!isKeyboardOpen) {
focusManager.clearFocus()
textState = textState.copy(selection = TextRange.Zero)
}
}
Scaffold(
topBar = {
EditorTopBar(
menuExpanded = menuExpanded,
onMenuExpandedChange = { menuExpanded = it },
showCopyDot = showCopyDot,
showPasteDot = showPasteDot,
undoEnabled = undoHistory.isNotEmpty(),
onCopy = {
scope.launch {
saveNote(textState.text)
val sel = textState.selection
val start = sel.min.coerceIn(0, textState.text.length)
val end = sel.max.coerceIn(0, textState.text.length)
if (start != end) {
val selectedText = textState.text.substring(start, end)
clipboardManager.setPrimaryClip(ClipData.newPlainText("wedge_note", selectedText))
showCopyDot = true
delay(1000)
showCopyDot = false
}
}
},
onPaste = {
val clipboardText = clipboardManager.primaryClip?.getItemAt(0)?.text ?: ""
if (clipboardText.isNotEmpty()) {
val sel = textState.selection
val start = sel.min.coerceIn(0, textState.text.length)
val end = sel.max.coerceIn(0, textState.text.length)
val newText = textState.text.replaceRange(start, end, clipboardText)
val newCursorPos = start + clipboardText.length
textState = textState.copy(text = newText, selection = TextRange(newCursorPos))
scope.launch {
showPasteDot = true
delay(1000)
showPasteDot = false
}
}
},
onUndo = {
if (undoHistory.isNotEmpty()) {
textState = undoHistory.first()
undoHistory = undoHistory.drop(1)
}
},
onSelectAll = {
menuExpanded = false
textState = textState.copy(
selection = TextRange(0, textState.text.length)
)
},
onSort = {
menuExpanded = false
val sel = textState.selection
if (!sel.collapsed) {
val txt = textState.text
val start = txt.lastIndexOf('\n', sel.min - 1).let { if (it == -1) 0 else it + 1 }
val end = txt.indexOf('\n', sel.max).let { if (it == -1) txt.length else it }
val block = txt.substring(start, end)
val lines = block.split('\n')
val baseIndent = lines.firstOrNull { it.isNotBlank() }?.takeWhile { it.isWhitespace() }?.length ?: 0
val groups = mutableListOf<MutableList<String>>()
for (line in lines) {
if (groups.isEmpty() || (line.isNotBlank() && line.takeWhile { it.isWhitespace() }.length <= baseIndent)) {
groups.add(mutableListOf(line))
} else {
groups.last().add(line)
}
}
val sorted = groups.sortedWith { g1, g2 -> String.CASE_INSENSITIVE_ORDER.compare(g1[0], g2[0]) }.flatten().joinToString("\n")
textState = textState.copy(
text = txt.replaceRange(start, end, sorted),
selection = TextRange(start, start + sorted.length)
)
}
},
onToggleCalc = {
menuExpanded = false
showCalc = !showCalc
},
showCalc = showCalc,
onShowData = {
menuExpanded = false
onShowData()
},
onExport = {
menuExpanded = false
val otcTimestamp = WedgeDate.getOtcDate()
exportLauncher.launch("wedge_$otcTimestamp.txt")
},
onSettings = {
menuExpanded = false
focusManager.clearFocus()
onSettings()
},
onShowVersion = {
menuExpanded = false
showVersionDialog = true
},
isEncrypted = isEncrypted,
onSecurityAction = {
menuExpanded = false
showCalc = false
isDecryptMode = isEncrypted
showEncrypt = true
}
//onSync = {
// vm.runAutomatedSyncSequence()
//}
)
},
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { p ->
Box(modifier = Modifier
.fillMaxSize()) {
Column(
modifier = Modifier
.padding(p)
.fillMaxSize()
.background(parseColor(bgColorHex))
) {
if (showCalc) {
CalculatorOverlay(
calcValue = calcValue,
onCalcValueChange = { calcValue = it },
onDismiss = { showCalc = false },
focusRequester = calcFocusRequester
)
}
if (showEncrypt) {
EncryptionOverlay(
passPhrase = passPhrase,
onPassPhraseChange = { passPhrase = it },
onDismiss = { showEncrypt = false; passPhrase = "" },
focusRequester = encryptFocusRequester,
isDecrypt = isDecryptMode,
onAction = {
if (passPhrase.isNotEmpty()) {
if (isEncrypted) {
WedgeSecurity.decrypt(textState.text, passPhrase)?.let {
textState = textState.copy(
text = it,
selection = TextRange(0, it.length)
)
showEncrypt = false
passPhrase = ""
} ?: run { passPhrase = "" }
} else {
val encrypted =
WedgeSecurity.encrypt(textState.text, passPhrase)
textState = textState.copy(
text = encrypted,
selection = TextRange(0, encrypted.length)
)
showEncrypt = false
passPhrase = ""
}
}
}
)
}
if (showDatePicker) {
val datePickerState = rememberDatePickerState()
LaunchedEffect(datePickerState.selectedDateMillis) {
datePickerState.selectedDateMillis?.let { selectedUtcMillis ->
val tzOffset = TimeZone.getDefault().getOffset(selectedUtcMillis)
selectedDate = selectedUtcMillis - tzOffset
calendarOtcDayStr = WedgeDate.getOtcParts(selectedDate).first.toString()
calendarTimeStr = ""
calendarTextStr = ""
showCalendarOverlay = true
showDatePicker = false
}
}
Dialog(
properties = DialogProperties(usePlatformDefaultWidth = false),
onDismissRequest = { showDatePicker = false }
) {
Surface(
shape = RoundedCornerShape(8.dp),
modifier = Modifier.padding(horizontal = 20.dp).fillMaxWidth(),
color = colorResource(id = R.color.dialog_bg_color)
) {
Box(
modifier = Modifier.padding(horizontal = 5.dp, vertical = 20.dp)
) {
DatePicker(
state = datePickerState,
title = null,
headline = null,
showModeToggle = false,
colors = DatePickerDefaults.colors(
containerColor = colorResource(id = R.color.dialog_bg_color),
dayContentColor = colorResource(id = R.color.dialog_color),
weekdayContentColor = colorResource(id = R.color.dialog_color),
yearContentColor = colorResource(id = R.color.dialog_color),
navigationContentColor = colorResource(id = R.color.dialog_color),
subheadContentColor = colorResource(id = R.color.dialog_color),
todayDateBorderColor = colorResource(id = R.color.dialog_color),
todayContentColor = colorResource(id = R.color.dialog_color)
)
)
}
}
}
}
if (showCalendarOverlay) {
CalendarOverlay(
daySelected = calendarOtcDayStr,
onDaySelectedChange = {},
timeValue = calendarTimeStr,
onTimeValueChange = { calendarTimeStr = it },
textValue = calendarTextStr,
onTextValueChange = { calendarTextStr = it },
onDismiss = { showCalendarOverlay = false },
onEnterPressed = {
val updatedText = WedgeCalendar.insertCalendarEntry(
currentText = textState.text,
timestamp = selectedDate,
timeStr = calendarTimeStr.ifEmpty { "00:00" },
entryText = calendarTextStr.ifEmpty { "Event" }
)
val rawTime = calendarTimeStr.ifEmpty { "00:00" }
val finalTime = if (!rawTime.contains(":") && rawTime.length == 4) {
"${rawTime.substring(0, 2)}:${rawTime.substring(2)}"
} else if (!rawTime.contains(":") && rawTime.length == 3) {
"0${rawTime.substring(0, 1)}:${rawTime.substring(1)}"
} else { rawTime }
val finalTxt = calendarTextStr.ifEmpty { "Event" }
val standardPattern = "$finalTime $finalTxt"
val omittedPattern = " $finalTxt"
val standardIndex = updatedText.indexOf(standardPattern)
val newEntryIndex = if (standardIndex != -1) standardIndex else updatedText.indexOf(omittedPattern)
val newSelection = if (newEntryIndex != -1) {
val matchLength = if (standardIndex != -1) standardPattern.length else omittedPattern.length
TextRange(newEntryIndex + matchLength)
} else {
textState.selection
}
textState = textState.copy(
text = updatedText,
selection = newSelection
)
showCalendarOverlay = false
},
focusRequester = calendarFocusRequester
)
}
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.onGloballyPositioned { containerHeight = it.size.height }
.verticalScroll(scrollState)
.navigationBarsPadding()
.imePadding()
) {
BasicTextField(
value = textState,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.fillMaxSize()
.drawBehind {
val layout = textLayoutResult ?: return@drawBehind
val offset = nextTimeOffset ?: return@drawBehind
if (offset < layout.layoutInput.text.text.length) {
try {
val line = layout.getLineForOffset(offset)
val top = layout.getLineTop(line)
val bottom = layout.getLineBottom(line)
val left = layout.getLineLeft(line)
val right = layout.getLineRight(line)
drawRect(
color = parseColor(nextTimeBgColor),
topLeft = Offset(left, top),
size = Size(right - left, bottom - top)
)
} catch (_: Exception) {
}
}
},
onValueChange = { newValue ->
val result = WedgeEditorEngine.applyTextChange(
newValue,
textState,
undoHistory,
undoLineIndex,
lastDel
)
textState = result.text
undoHistory = result.undoHistory
undoLineIndex = result.undoLineIndex
lastDel = result.lastDel
},
onTextLayout = { textLayoutResult = it },
textStyle = editorStyle,
cursorBrush = SolidColor(editorStyle.color),
keyboardOptions = KeyboardOptions(
platformImeOptions = if (incognitoActive) PlatformImeOptions("privateImeOptions=true,com.google.android.inputmethod.latin.noPersonalizedLearning=true") else null
),
visualTransformation = { text ->
val annotated = buildAnnotatedString {
append(text.text)
WedgeRegex.BOLD_LINE.findAll(text.text).forEach { match ->
addStyle(
style = SpanStyle(
fontWeight = FontWeight.Bold,
color = focusColor
),
start = match.range.first,
end = match.range.last + 1
)
}
WedgeRegex.ITALIC_LINE.findAll(text.text).forEach { match ->
addStyle(
style = SpanStyle(
fontStyle = FontStyle.Italic
),
start = match.range.first,
end = match.range.last + 1
)
}
WedgeRegex.UNDERLINE_LINE.findAll(text.text).forEach { match ->
val contentGroup = match.groups[1]
if (contentGroup != null) {
addStyle(
style = SpanStyle(
textDecoration = TextDecoration.Underline
),
start = contentGroup.range.first,
end = contentGroup.range.last + 1
)
}
}
WedgeRegex.STRIKE_LINE.findAll(text.text).forEach { match ->
val contentMatch = match.groups[1]
if (contentMatch != null) {
addStyle(
style = SpanStyle(
textDecoration = TextDecoration.LineThrough
),
start = contentMatch.range.first,
end = contentMatch.range.last + 1
)
}
}
}
TransformedText(annotated, OffsetMapping.Identity)
}
)
}
}
val buttonSize = 54.dp
val cornerRadius = 14.dp
val paddingRight = 20.dp
val paddingBottom = if (isKeyboardOpen) 20.dp else 54.dp
val buttonOpacity by androidx.compose.animation.core.animateFloatAsState(
targetValue = if (isKeyboardOpen) 0.3f else 0.8f,
label = "buttonOpacity"
)
val iconSize = 32.dp
val gapBetweenButtons = 13.dp
Column(
modifier = Modifier
.align(Alignment.BottomEnd)
.imePadding()
.padding(end = paddingRight, bottom = paddingBottom),
verticalArrangement = Arrangement.spacedBy(gapBetweenButtons),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(buttonSize)
.graphicsLayer(alpha = buttonOpacity)
.clip(RoundedCornerShape(cornerRadius))
.background(colorResource(id = R.color.primary_color))
.clickable { showDatePicker = true }
.padding(10.dp),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Outlined.CalendarMonth,
contentDescription = "Calendar",
modifier = Modifier.size(iconSize),
tint = colorResource(id = R.color.title_color)
)
}
Box(
modifier = Modifier
.size(buttonSize)
.graphicsLayer(alpha = buttonOpacity)
.clip(RoundedCornerShape(cornerRadius))
.background(colorResource(id = R.color.primary_color))
.clickable { showCalc = !showCalc }
.padding(10.dp),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Default.Percent,
contentDescription = "Calculator",
modifier = Modifier.size(iconSize),
tint = colorResource(id = R.color.title_color)
)
}
}
}
}
VersionDialog(showVersionDialog, { showVersionDialog = false }, uriHandler)
}