Initial commit
This commit is contained in:
177
src/main/kotlin/QuickLaunchMain.kt
Normal file
177
src/main/kotlin/QuickLaunchMain.kt
Normal file
@@ -0,0 +1,177 @@
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.BiasAlignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.input.key.*
|
||||
import androidx.compose.ui.res.loadImageBitmap
|
||||
import androidx.compose.ui.res.useResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.*
|
||||
import notifications.NotificationCenter
|
||||
import theme.Minimize
|
||||
import theme.QuickLaunchTheme
|
||||
import viewModel.QuickLaunchInputCoordinator
|
||||
import viewModel.ShortCutHandler
|
||||
import windows.ShortcutEditWindow
|
||||
|
||||
|
||||
@Composable
|
||||
fun App(
|
||||
minimize: () -> Unit,
|
||||
openShortcuts: () -> Unit
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
QuickLaunchInputCoordinator.uiEvents.collect { event ->
|
||||
when (event) {
|
||||
is QuickLaunchInputCoordinator.QLUiEvent.HelpEvent -> {
|
||||
|
||||
}
|
||||
|
||||
is QuickLaunchInputCoordinator.QLUiEvent.OpenShortcutWindowEvent -> {
|
||||
openShortcuts()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val notification by NotificationCenter.message.collectAsState("")
|
||||
Column(
|
||||
verticalArrangement = Arrangement.SpaceAround,
|
||||
modifier = Modifier.background(color = Color(0f, 0f, 0f, 0.8f))
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
val text = remember { mutableStateOf("") }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
TextField(
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.Transparent,
|
||||
unfocusedContainerColor = Color.Transparent,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent
|
||||
),
|
||||
textStyle = MaterialTheme.typography.displayMedium.copy(
|
||||
fontWeight = FontWeight(200)
|
||||
),
|
||||
modifier = Modifier.focusRequester(focusRequester).fillMaxHeight(0.7f).fillMaxWidth(0.925f).onKeyEvent {
|
||||
if (it.type == KeyEventType.KeyUp && it.key == Key.Enter) {
|
||||
QuickLaunchInputCoordinator.enter(text.value)
|
||||
text.value = ""
|
||||
return@onKeyEvent true
|
||||
}
|
||||
return@onKeyEvent false
|
||||
},
|
||||
value = text.value,
|
||||
onValueChange = {
|
||||
text.value = it
|
||||
},
|
||||
maxLines = 1
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(0.dp)
|
||||
.background(Color.Transparent)
|
||||
.width(30.dp)
|
||||
.height(30.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { minimize() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Minimize,
|
||||
"minimize",
|
||||
tint = Color.White,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
val expanded by remember { derivedStateOf { notification.isNotEmpty() } }
|
||||
AnimatedVisibility(
|
||||
visible = expanded,
|
||||
enter = expandVertically(
|
||||
spring(
|
||||
stiffness = Spring.StiffnessLow,
|
||||
dampingRatio = Spring.DampingRatioHighBouncy
|
||||
)
|
||||
)
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = notification,
|
||||
transitionSpec = {
|
||||
fadeIn() + slideInHorizontally() togetherWith fadeOut() + slideOutHorizontally(
|
||||
targetOffsetX = { it / 2 },
|
||||
)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
it,
|
||||
color = Color.Yellow,
|
||||
modifier = Modifier.padding(start = 16.dp, bottom = 8.dp, top = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun main() {
|
||||
application {
|
||||
val windowState =
|
||||
rememberWindowState(position = WindowPosition(BiasAlignment(0f, -0.4f)), size = DpSize(400.dp, 104.dp))
|
||||
|
||||
QuickLaunchTheme {
|
||||
Window(
|
||||
icon = BitmapPainter(useResource("icon.png", ::loadImageBitmap)),
|
||||
transparent = true,
|
||||
undecorated = true,
|
||||
resizable = false,
|
||||
title = "QuickLaunch",
|
||||
state = windowState,
|
||||
onCloseRequest = ::exitApplication
|
||||
) {
|
||||
val openHelpWindow = remember { mutableStateOf(false) }
|
||||
App(
|
||||
minimize = { windowState.isMinimized = true },
|
||||
openShortcuts = { openHelpWindow.value = true }
|
||||
)
|
||||
if (openHelpWindow.value) {
|
||||
DialogWindow(
|
||||
title = "Edit shortcuts",
|
||||
onCloseRequest = {
|
||||
openHelpWindow.value = false
|
||||
ShortCutHandler.persistToStorage()
|
||||
},
|
||||
state = rememberDialogState(
|
||||
size = DpSize(800.dp, 600.dp),
|
||||
position = WindowPosition(Alignment.Center)
|
||||
)
|
||||
) {
|
||||
ShortcutEditWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
src/main/kotlin/notifications/NotificationCenter.kt
Normal file
44
src/main/kotlin/notifications/NotificationCenter.kt
Normal file
@@ -0,0 +1,44 @@
|
||||
package notifications
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
|
||||
object NotificationCenter {
|
||||
|
||||
private val queue : BlockingQueue<Notification> = LinkedBlockingQueue()
|
||||
|
||||
val message: Flow<String> = flow {
|
||||
while (true) {
|
||||
val notification = queue.take()
|
||||
emit(notification.message)
|
||||
delay(notification.duration.ms)
|
||||
emit("")
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
|
||||
fun addNotification(message: String, duration: NotificationDuration){
|
||||
if (message.isNotEmpty()) {
|
||||
queue.put(
|
||||
Notification(
|
||||
message,
|
||||
duration
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class Notification(
|
||||
val message: String,
|
||||
val duration: NotificationDuration
|
||||
)
|
||||
|
||||
enum class NotificationDuration(val ms : Long) {
|
||||
SHORT(3000L),
|
||||
LONG(5000L)
|
||||
}
|
||||
67
src/main/kotlin/theme/Color.kt
Normal file
67
src/main/kotlin/theme/Color.kt
Normal file
@@ -0,0 +1,67 @@
|
||||
package theme
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val md_theme_light_primary = Color(0xFF006D3E)
|
||||
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_primaryContainer = Color(0xFF96F7B8)
|
||||
val md_theme_light_onPrimaryContainer = Color(0xFF00210F)
|
||||
val md_theme_light_secondary = Color(0xFF4F6354)
|
||||
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_secondaryContainer = Color(0xFFD1E8D5)
|
||||
val md_theme_light_onSecondaryContainer = Color(0xFF0C1F13)
|
||||
val md_theme_light_tertiary = Color(0xFF3B6470)
|
||||
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_tertiaryContainer = Color(0xFFBEEAF7)
|
||||
val md_theme_light_onTertiaryContainer = Color(0xFF001F26)
|
||||
val md_theme_light_error = Color(0xFFBA1A1A)
|
||||
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||
val md_theme_light_background = Color(0xFFFBFDF8)
|
||||
val md_theme_light_onBackground = Color(0xFF191C1A)
|
||||
val md_theme_light_surface = Color(0xFFFBFDF8)
|
||||
val md_theme_light_onSurface = Color(0xFF191C1A)
|
||||
val md_theme_light_surfaceVariant = Color(0xFFDCE5DB)
|
||||
val md_theme_light_onSurfaceVariant = Color(0xFF414942)
|
||||
val md_theme_light_outline = Color(0xFF717971)
|
||||
val md_theme_light_inverseOnSurface = Color(0xFFF0F1EC)
|
||||
val md_theme_light_inverseSurface = Color(0xFF2E312E)
|
||||
val md_theme_light_inversePrimary = Color(0xFF7ADA9D)
|
||||
val md_theme_light_shadow = Color(0xFF000000)
|
||||
val md_theme_light_surfaceTint = Color(0xFF006D3E)
|
||||
val md_theme_light_outlineVariant = Color(0xFFC0C9C0)
|
||||
val md_theme_light_scrim = Color(0xFF000000)
|
||||
|
||||
val md_theme_dark_primary = Color(0xFF7ADA9D)
|
||||
val md_theme_dark_onPrimary = Color(0xFF00391E)
|
||||
val md_theme_dark_primaryContainer = Color(0xFF00522D)
|
||||
val md_theme_dark_onPrimaryContainer = Color(0xFF96F7B8)
|
||||
val md_theme_dark_secondary = Color(0xFFB6CCB9)
|
||||
val md_theme_dark_onSecondary = Color(0xFF213527)
|
||||
val md_theme_dark_secondaryContainer = Color(0xFF384B3D)
|
||||
val md_theme_dark_onSecondaryContainer = Color(0xFFD1E8D5)
|
||||
val md_theme_dark_tertiary = Color(0xFFA3CDDB)
|
||||
val md_theme_dark_onTertiary = Color(0xFF023640)
|
||||
val md_theme_dark_tertiaryContainer = Color(0xFF214C57)
|
||||
val md_theme_dark_onTertiaryContainer = Color(0xFFBEEAF7)
|
||||
val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||
val md_theme_dark_onError = Color(0xFF690005)
|
||||
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_dark_background = Color(0xFF191C1A)
|
||||
val md_theme_dark_onBackground = Color(0xFFE1E3DE)
|
||||
val md_theme_dark_surface = Color(0xFF191C1A)
|
||||
val md_theme_dark_onSurface = Color(0xFFE1E3DE)
|
||||
val md_theme_dark_surfaceVariant = Color(0xFF414942)
|
||||
val md_theme_dark_onSurfaceVariant = Color(0xFFC0C9C0)
|
||||
val md_theme_dark_outline = Color(0xFF8A938B)
|
||||
val md_theme_dark_inverseOnSurface = Color(0xFF191C1A)
|
||||
val md_theme_dark_inverseSurface = Color(0xFFE1E3DE)
|
||||
val md_theme_dark_inversePrimary = Color(0xFF006D3E)
|
||||
val md_theme_dark_shadow = Color(0xFF000000)
|
||||
val md_theme_dark_surfaceTint = Color(0xFF7ADA9D)
|
||||
val md_theme_dark_outlineVariant = Color(0xFF414942)
|
||||
val md_theme_dark_scrim = Color(0xFF000000)
|
||||
|
||||
|
||||
val seed = Color(0xFF006D3E)
|
||||
25
src/main/kotlin/theme/QLIcons.kt
Normal file
25
src/main/kotlin/theme/QLIcons.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package theme
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
public val Icons.Filled.Minimize: ImageVector
|
||||
get() {
|
||||
if (_minimize != null) {
|
||||
return _minimize!!
|
||||
}
|
||||
_minimize = materialIcon(name = "Filled.Minimize") {
|
||||
materialPath {
|
||||
moveTo(6.0f, 10.0f)
|
||||
horizontalLineToRelative(12.0f)
|
||||
verticalLineToRelative(2.0f)
|
||||
horizontalLineTo(6.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
return _minimize!!
|
||||
}
|
||||
|
||||
private var _minimize: ImageVector? = null
|
||||
91
src/main/kotlin/theme/QuickLaunchTheme.kt
Normal file
91
src/main/kotlin/theme/QuickLaunchTheme.kt
Normal file
@@ -0,0 +1,91 @@
|
||||
package theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
|
||||
private val LightColors = lightColorScheme(
|
||||
primary = md_theme_light_primary,
|
||||
onPrimary = md_theme_light_onPrimary,
|
||||
primaryContainer = md_theme_light_primaryContainer,
|
||||
onPrimaryContainer = md_theme_light_onPrimaryContainer,
|
||||
secondary = md_theme_light_secondary,
|
||||
onSecondary = md_theme_light_onSecondary,
|
||||
secondaryContainer = md_theme_light_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_light_onSecondaryContainer,
|
||||
tertiary = md_theme_light_tertiary,
|
||||
onTertiary = md_theme_light_onTertiary,
|
||||
tertiaryContainer = md_theme_light_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
||||
error = md_theme_light_error,
|
||||
errorContainer = md_theme_light_errorContainer,
|
||||
onError = md_theme_light_onError,
|
||||
onErrorContainer = md_theme_light_onErrorContainer,
|
||||
background = md_theme_light_background,
|
||||
onBackground = md_theme_light_onBackground,
|
||||
surface = md_theme_light_surface,
|
||||
onSurface = md_theme_light_onSurface,
|
||||
surfaceVariant = md_theme_light_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
||||
outline = md_theme_light_outline,
|
||||
inverseOnSurface = md_theme_light_inverseOnSurface,
|
||||
inverseSurface = md_theme_light_inverseSurface,
|
||||
inversePrimary = md_theme_light_inversePrimary,
|
||||
surfaceTint = md_theme_light_surfaceTint,
|
||||
outlineVariant = md_theme_light_outlineVariant,
|
||||
scrim = md_theme_light_scrim,
|
||||
)
|
||||
|
||||
|
||||
private val DarkColors = darkColorScheme(
|
||||
primary = md_theme_dark_primary,
|
||||
onPrimary = md_theme_dark_onPrimary,
|
||||
primaryContainer = md_theme_dark_primaryContainer,
|
||||
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
|
||||
secondary = md_theme_dark_secondary,
|
||||
onSecondary = md_theme_dark_onSecondary,
|
||||
secondaryContainer = md_theme_dark_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
|
||||
tertiary = md_theme_dark_tertiary,
|
||||
onTertiary = md_theme_dark_onTertiary,
|
||||
tertiaryContainer = md_theme_dark_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
|
||||
error = md_theme_dark_error,
|
||||
errorContainer = md_theme_dark_errorContainer,
|
||||
onError = md_theme_dark_onError,
|
||||
onErrorContainer = md_theme_dark_onErrorContainer,
|
||||
background = md_theme_dark_background,
|
||||
onBackground = md_theme_dark_onBackground,
|
||||
surface = md_theme_dark_surface,
|
||||
onSurface = md_theme_dark_onSurface,
|
||||
surfaceVariant = md_theme_dark_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
|
||||
outline = md_theme_dark_outline,
|
||||
inverseOnSurface = md_theme_dark_inverseOnSurface,
|
||||
inverseSurface = md_theme_dark_inverseSurface,
|
||||
inversePrimary = md_theme_dark_inversePrimary,
|
||||
surfaceTint = md_theme_dark_surfaceTint,
|
||||
outlineVariant = md_theme_dark_outlineVariant,
|
||||
scrim = md_theme_dark_scrim,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun QuickLaunchTheme(
|
||||
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable() () -> Unit
|
||||
) {
|
||||
val colors = if (!useDarkTheme) {
|
||||
LightColors
|
||||
} else {
|
||||
DarkColors
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colors,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
53
src/main/kotlin/viewModel/QuickLaunchInputCoordinator.kt
Normal file
53
src/main/kotlin/viewModel/QuickLaunchInputCoordinator.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
package viewModel
|
||||
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import notifications.NotificationCenter
|
||||
import notifications.NotificationDuration
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
object QuickLaunchInputCoordinator {
|
||||
|
||||
private val _uiEvents = MutableSharedFlow<QLUiEvent>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
||||
val uiEvents : Flow<QLUiEvent> = _uiEvents
|
||||
|
||||
fun enter(userInput: String) {
|
||||
if (userInput.isNotEmpty()) {
|
||||
if (userInput.startsWith("/")) {
|
||||
command(userInput.substring(1))
|
||||
} else {
|
||||
shortCut(userInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun command(cmd: String) {
|
||||
when(cmd){
|
||||
"x" -> {
|
||||
exitProcess(0)
|
||||
}
|
||||
"v" -> {
|
||||
NotificationCenter.addNotification("version c1.0", NotificationDuration.SHORT)
|
||||
}
|
||||
"s" -> {
|
||||
_uiEvents.tryEmit(QLUiEvent.OpenShortcutWindowEvent)
|
||||
}
|
||||
else -> {
|
||||
_uiEvents.tryEmit(QLUiEvent.HelpEvent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun shortCut(shortCut: String) {
|
||||
ShortCutHandler.executeShortcut(shortCut)
|
||||
}
|
||||
|
||||
|
||||
sealed class QLUiEvent {
|
||||
object OpenShortcutWindowEvent : QLUiEvent()
|
||||
object HelpEvent : QLUiEvent()
|
||||
}
|
||||
}
|
||||
80
src/main/kotlin/viewModel/ShortCutHandler.kt
Normal file
80
src/main/kotlin/viewModel/ShortCutHandler.kt
Normal file
@@ -0,0 +1,80 @@
|
||||
package viewModel
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.*
|
||||
|
||||
object ShortCutHandler {
|
||||
private val currentPath: File =
|
||||
File(System.getProperty("user.home"), ".QuickLaunch")
|
||||
private val shortcutsFile = File(currentPath,"shortcuts.json")
|
||||
private val _shortcuts = MutableStateFlow<Map<String, String>>(emptyMap())
|
||||
val shortcuts: StateFlow<Map<String, String>> = _shortcuts
|
||||
|
||||
|
||||
init {
|
||||
if (!currentPath.exists()){ //TODO this is for config somewhere else maybe
|
||||
currentPath.mkdir()
|
||||
}
|
||||
readFromStorage()
|
||||
// BufferedReader(FileReader(File("C:\\Users\\Paul\\AppData\\Roaming\\QuickLaunch\\File.txt"))).use {
|
||||
// _shortcuts.value = buildMap {
|
||||
// var line = it.readLine()
|
||||
// while (line != null){
|
||||
// put(line, it.readLine())
|
||||
// line = it.readLine()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// persistToStorage()
|
||||
}
|
||||
|
||||
fun addShortcut(short: String, path: String) {
|
||||
_shortcuts.value = buildMap {
|
||||
putAll(shortcuts.value)
|
||||
put(short, path)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeShortcut(short: String) {
|
||||
_shortcuts.value = buildMap {
|
||||
putAll(shortcuts.value.filterNot { entry -> entry.key == short })
|
||||
}
|
||||
}
|
||||
|
||||
fun executeShortcut(short: String) {
|
||||
shortcuts.value[short]?.let {
|
||||
ProcessBuilder(it.split(";")).start()
|
||||
}
|
||||
}
|
||||
|
||||
fun persistToStorage() {
|
||||
_shortcuts.value = buildMap {
|
||||
putAll(shortcuts.value.filter { entry -> entry.key.isNotEmpty() })
|
||||
}
|
||||
BufferedWriter(FileWriter(shortcutsFile)).use {
|
||||
it.write(
|
||||
Json.Default.encodeToString(shortcuts.value)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readFromStorage() {
|
||||
println(shortcutsFile.path)
|
||||
if (!shortcutsFile.exists()) {
|
||||
shortcutsFile.createNewFile()
|
||||
} else {
|
||||
try {
|
||||
BufferedReader(FileReader(shortcutsFile)).use {
|
||||
_shortcuts.value = Json.Default.decodeFromString(it.readText())
|
||||
}
|
||||
} catch (j : SerializationException){
|
||||
println("Empty configuration")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
103
src/main/kotlin/windows/ShortcutEditDialogWindow.kt
Normal file
103
src/main/kotlin/windows/ShortcutEditDialogWindow.kt
Normal file
@@ -0,0 +1,103 @@
|
||||
package windows
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import viewModel.ShortCutHandler
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ShortcutEditWindow() {
|
||||
Scaffold(
|
||||
floatingActionButton = { AddFab() }
|
||||
) {
|
||||
val entries by ShortCutHandler.shortcuts.collectAsState()
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(8.dp).animateContentSize()
|
||||
) {
|
||||
items(entries.toList()) {
|
||||
ShortcutRow(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun ShortcutRow(entry: Pair<String, String>) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
val shState = remember(entry.first) { mutableStateOf(entry.first) }
|
||||
val cmdState = remember(entry.second) { mutableStateOf(entry.second) }
|
||||
TextField(
|
||||
textStyle = MaterialTheme.typography.bodyMedium,
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(0.1f),
|
||||
value = shState.value,
|
||||
onValueChange = {
|
||||
ShortCutHandler.removeShortcut(shState.value)
|
||||
ShortCutHandler.addShortcut(it, cmdState.value)
|
||||
}
|
||||
)
|
||||
|
||||
TextField(
|
||||
textStyle = MaterialTheme.typography.bodyMedium,
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(0.85f),
|
||||
value = cmdState.value,
|
||||
onValueChange = {
|
||||
ShortCutHandler.addShortcut(shState.value, it)
|
||||
},
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.width(30.dp).height(30.dp).clickable (
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
ShortCutHandler.removeShortcut(shState.value)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
"remove",
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AddFab() {
|
||||
Surface (
|
||||
shadowElevation = 6.dp,
|
||||
color = FloatingActionButtonDefaults.containerColor,
|
||||
shape = FloatingActionButtonDefaults.shape,
|
||||
modifier = Modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
){
|
||||
ShortCutHandler.addShortcut("", "")
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Default.Add, "add", modifier = Modifier.width(56.dp).height(56.dp).padding(12.dp))
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/icon.ico
Normal file
BIN
src/main/resources/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
BIN
src/main/resources/icon.png
Normal file
BIN
src/main/resources/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
Reference in New Issue
Block a user