From d2048ea5b76c5b80ecc8f7be9fc6bf5329fb9c0c Mon Sep 17 00:00:00 2001 From: Thomas Boksberger Date: Fri, 3 Apr 2026 11:33:29 +0200 Subject: [PATCH] first workting version --- .gitignore | 3 + .idea/compiler.xml | 6 ++ .idea/gradle.xml | 15 ++++ .idea/kotlinc.xml | 7 ++ .idea/misc.xml | 6 +- .idea/modules.xml | 2 +- .idea/modules/AnyTask.main.iml | 8 ++ CLAUDE.md | 38 ++++++---- build.gradle.kts | 7 ++ httpClient/anyType.http | 14 ++++ .../http-client.private.env.template.json | 6 ++ src/main/kotlin/ch/ergon/anytask/Main.kt | 31 ++++++-- .../kotlin/ch/ergon/anytask/api/AnyTypeApi.kt | 75 +++++++++++++++++++ .../ch/ergon/anytask/api/AnyTypeClient.kt | 53 ------------- .../anytask/api/dto/CreateObjectRequest.kt | 11 +++ .../anytask/api/dto/CreateObjectResponse.kt | 8 ++ .../kotlin/ch/ergon/anytask/api/dto/Space.kt | 9 +++ .../ergon/anytask/api/dto/SpacesResponse.kt | 8 ++ .../ch/ergon/anytask/config/AppConfig.kt | 32 ++++++++ .../ch/ergon/anytask/hotkey/HotkeyManager.kt | 12 +-- .../kotlin/ch/ergon/anytask/ui/TaskWindow.kt | 12 +-- src/main/resources/anytask.yaml | 2 + src/main/resources/log4j2.xml | 13 ++++ 23 files changed, 292 insertions(+), 86 deletions(-) create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/modules/AnyTask.main.iml create mode 100644 httpClient/anyType.http create mode 100644 httpClient/http-client.private.env.template.json create mode 100644 src/main/kotlin/ch/ergon/anytask/api/AnyTypeApi.kt delete mode 100644 src/main/kotlin/ch/ergon/anytask/api/AnyTypeClient.kt create mode 100644 src/main/kotlin/ch/ergon/anytask/api/dto/CreateObjectRequest.kt create mode 100644 src/main/kotlin/ch/ergon/anytask/api/dto/CreateObjectResponse.kt create mode 100644 src/main/kotlin/ch/ergon/anytask/api/dto/Space.kt create mode 100644 src/main/kotlin/ch/ergon/anytask/api/dto/SpacesResponse.kt create mode 100644 src/main/kotlin/ch/ergon/anytask/config/AppConfig.kt create mode 100644 src/main/resources/anytask.yaml create mode 100644 src/main/resources/log4j2.xml diff --git a/.gitignore b/.gitignore index 0795f55..d534b33 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ out/ # macOS .DS_Store + +# Http Client +httpClient/http-client.private.env.json diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..f9163b4 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..360d66b --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 159fe23..2758df8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,10 @@ - + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index b205e4b..4ed22e1 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/modules/AnyTask.main.iml b/.idea/modules/AnyTask.main.iml new file mode 100644 index 0000000..fb979ed --- /dev/null +++ b/.idea/modules/AnyTask.main.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 13685d4..2b8c39d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,37 +2,47 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Git + +Always stage newly created files that permanently belong to the project with `git add `. + ## Commands ```bash -./gradlew run # App starten -./gradlew compileKotlin # Nur kompilieren -./gradlew packageDmg # macOS DMG bauen +./gradlew run # Run the app +./gradlew compileKotlin # Compile only +./gradlew packageDmg # Build macOS DMG ``` ## Architecture -**AnyTask** ist eine Kotlin/Compose-Desktop-App, die im Hintergrund läuft und über eine globale Tastenkombination (Ctrl+Shift+Space) ein Fenster zum Erstellen von AnyType-Tasks zeigt. +**AnyTask** is a Kotlin/Compose desktop app that runs in the background and shows a task creation window via a global hotkey (Ctrl+Shift+Space). -### Komponenten +### Components -- **`Main.kt`** — Einstiegspunkt. Setzt `apple.awt.UIElement=true` (kein Dock-Icon), startet die Compose-`application {}`, richtet System-Tray und HotkeyManager ein. -- **`hotkey/HotkeyManager.kt`** — JNativeHook-Wrapper. Registriert Ctrl+Shift+Space als systemweiten Hotkey und dispatcht den Callback auf den AWT-EDT. -- **`ui/TaskWindow.kt`** — Compose-UI mit Formular (Titel, Details). Escape schliesst, ⌘↵ erstellt. -- **`api/AnyTypeClient.kt`** — Ktor-HTTP-Client für die lokale AnyType-API (`localhost:31007`). Momentan Skeleton — API-Details müssen noch verifiziert werden. +- **`Main.kt`** — Entry point. Sets `apple.awt.UIElement=true` (no dock icon), starts the Compose `application {}`, sets up system tray and HotkeyManager. +- **`hotkey/HotkeyManager.kt`** — JNativeHook wrapper. Registers Ctrl+Shift+Space as a system-wide hotkey and dispatches the callback on the AWT EDT. +- **`ui/TaskWindow.kt`** — Compose UI with a form (title, details). Escape closes, ⌘↵ creates. +- **`api/AnyTypeApi.kt`** — Ktor HTTP client for the local AnyType API (`localhost:31009`). Currently a skeleton — API details still need to be verified. ### Stack - Kotlin 2.1.0 / Compose Multiplatform 1.7.1 -- JNativeHook 2.2.2 (globale Hotkeys) +- JNativeHook 2.2.2 (global hotkeys) - Ktor 3.0.0 + kotlinx.serialization (AnyType API) - Gradle 9.2.1 -## macOS-Hinweise +## Code Style -- **Accessibility-Berechtigung** für JNativeHook: System Settings → Privacy & Security → Accessibility → App hinzufügen. Ohne diese Berechtigung funktioniert der globale Hotkey nicht. -- Das Tray-Icon erscheint in der Menüleiste (kein Dock-Icon, da `apple.awt.UIElement=true`). +- One class per file — file name matches the class name. +- No wildcard imports (`import foo.*`). +- Code, comments, and documentation in English. + +## macOS Notes + +- **Accessibility permission** for JNativeHook: System Settings → Privacy & Security → Accessibility → add the app. Without this permission the global hotkey will not work. +- The tray icon appears in the menu bar (no dock icon, since `apple.awt.UIElement=true`). ## AnyType API -Die lokale AnyType-API läuft via `anytype-heart` auf `http://localhost:31007`. Der `appKey` und die `spaceId` in `AnyTypeClient` müssen noch konfigurierbar gemacht werden. +The local AnyType API runs via `anytype-heart` on `http://localhost:31007`. `apiKey` and `spaceId` are configured via `anytask.yaml`. diff --git a/build.gradle.kts b/build.gradle.kts index 2ce0b24..62a6c24 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,6 +28,13 @@ dependencies { // Coroutines (Swing/AWT dispatcher) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.9.0") + + // YAML config + implementation("com.charleskorn.kaml:kaml:0.67.0") + + // Logging + implementation("org.apache.logging.log4j:log4j-api:2.24.3") + implementation("org.apache.logging.log4j:log4j-core:2.24.3") } compose.desktop { diff --git a/httpClient/anyType.http b/httpClient/anyType.http new file mode 100644 index 0000000..dbe5a2c --- /dev/null +++ b/httpClient/anyType.http @@ -0,0 +1,14 @@ +### Get Spaces +GET http://localhost:31009/v1/spaces +Authorization: Bearer {{apiKey}} + +### Create Task +POST http://localhost:31009/v1/spaces/{{spaceId}}/objects +Authorization: Bearer {{apiKey}} +Content-Type: application/json + +{ + "name": "Test Task II", + "body": "Created via HTTP client", + "type_key": "ot-task" +} diff --git a/httpClient/http-client.private.env.template.json b/httpClient/http-client.private.env.template.json new file mode 100644 index 0000000..2b32c82 --- /dev/null +++ b/httpClient/http-client.private.env.template.json @@ -0,0 +1,6 @@ +{ + "local": { + "apiKey": "", + "spaceId": "" + } +} diff --git a/src/main/kotlin/ch/ergon/anytask/Main.kt b/src/main/kotlin/ch/ergon/anytask/Main.kt index e7b4193..08e3b66 100644 --- a/src/main/kotlin/ch/ergon/anytask/Main.kt +++ b/src/main/kotlin/ch/ergon/anytask/Main.kt @@ -5,13 +5,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.* -import ch.ergon.anytask.api.AnyTypeClient +import ch.ergon.anytask.api.AnyTypeApi +import ch.ergon.anytask.config.loadConfig import ch.ergon.anytask.hotkey.HotkeyManager import ch.ergon.anytask.ui.TaskWindow +import org.apache.logging.log4j.LogManager import java.awt.* +import kotlin.system.exitProcess + +private val logger = LogManager.getLogger("Main") fun main() { - // Kein Dock-Icon auf macOS (Agent-App) + // No dock icon on macOS (agent app) System.setProperty("apple.awt.UIElement", "true") application { @@ -22,11 +27,25 @@ fun main() { size = DpSize(480.dp, 320.dp) ) - val client = remember { AnyTypeClient() } + val config = remember { loadConfig() } + val client = remember { AnyTypeApi(apiKey = config.apiKey, spaceId = config.spaceId) } LaunchedEffect(Unit) { HotkeyManager.start(onHotkey = { isVisible = !isVisible }) setupSystemTray(onShow = { isVisible = true }, onExit = { exitApplication() }) + client.getSpaces() + .onSuccess { spaces -> + logger.info("Available spaces:") + spaces.forEach { logger.info("Space: ${it.id} ${it.name}") } + val space = spaces.find { space -> space.id == config.spaceId } + if (space == null) { + logger.error("Selected space not found: ${config.spaceId}") + exitProcess(-1) + } else { + logger.info("Selected space: ${space.name} (${space.id})") + } + } + .onFailure { logger.error("Failed to load spaces: ${it.message}") } } DisposableEffect(Unit) { @@ -57,14 +76,14 @@ private fun setupSystemTray(onShow: () -> Unit, onExit: () -> Unit) { val icon = createTrayIcon(size) val menu = PopupMenu().apply { - add(MenuItem("Öffnen").also { it.addActionListener { onShow() } }) + add(MenuItem("Open").also { it.addActionListener { onShow() } }) addSeparator() - add(MenuItem("Beenden").also { it.addActionListener { onExit() } }) + add(MenuItem("Quit").also { it.addActionListener { onExit() } }) } val trayIcon = TrayIcon(icon, "AnyTask", menu).apply { isImageAutoSize = true - addActionListener { onShow() } // Doppelklick + addActionListener { onShow() } // double click } SystemTray.getSystemTray().add(trayIcon) diff --git a/src/main/kotlin/ch/ergon/anytask/api/AnyTypeApi.kt b/src/main/kotlin/ch/ergon/anytask/api/AnyTypeApi.kt new file mode 100644 index 0000000..c62f528 --- /dev/null +++ b/src/main/kotlin/ch/ergon/anytask/api/AnyTypeApi.kt @@ -0,0 +1,75 @@ +package ch.ergon.anytask.api + +import ch.ergon.anytask.api.dto.CreateObjectRequest +import ch.ergon.anytask.api.dto.CreateObjectResponse +import ch.ergon.anytask.api.dto.Space +import ch.ergon.anytask.api.dto.SpacesResponse +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.serialization.json.Json +import org.apache.logging.log4j.LogManager +import kotlin.time.DurationUnit.MILLISECONDS +import kotlin.time.toDuration + +/** + * Client for the local AnyType API (anytype-heart middleware). + * The API runs on http://localhost:31007 by default. + * apiKey and spaceId are loaded from anytask.yaml. + */ +class AnyTypeApi( + private val baseUrl: String = "http://localhost:31009", + private val apiKey: String = "", + private val spaceId: String = "", +) { + private val logger = LogManager.getLogger(AnyTypeApi::class.java) + + private val http = HttpClient(CIO) { + install(ContentNegotiation) { + json(Json { ignoreUnknownKeys = true }) + } + }.also { client -> + client.plugin(HttpSend).intercept { request -> + val method = request.method.value + val url = request.url.buildString() + logger.info("→ $method $url") + val start = System.currentTimeMillis() + val call = execute(request) + val duration = (System.currentTimeMillis() - start).toDuration(MILLISECONDS) + val status = call.response.status + logger.info("← ${status.value} (${status.description}) $duration") + if (!status.isSuccess()) { + logger.error("Response body: ${call.response.bodyAsText()}") + } + call + } + } + + suspend fun getSpaces(): Result> = + runCatching { + val response = http.get("$baseUrl/v1/spaces") { + header("Anytype-Version", "0.1") + bearerAuth(apiKey) + } + response.body().data + } + + suspend fun createTask(title: String, details: String = ""): Result = + runCatching { + val response = http.post("$baseUrl/v1/spaces/$spaceId/objects") { + contentType(ContentType.Application.Json) + header("Anytype-Version", "0.1") + bearerAuth(apiKey) + setBody(CreateObjectRequest(name = title, body = details, typeKey = "ot-task")) + } + response.body().objectId + } + + fun close() = http.close() +} diff --git a/src/main/kotlin/ch/ergon/anytask/api/AnyTypeClient.kt b/src/main/kotlin/ch/ergon/anytask/api/AnyTypeClient.kt deleted file mode 100644 index 138418f..0000000 --- a/src/main/kotlin/ch/ergon/anytask/api/AnyTypeClient.kt +++ /dev/null @@ -1,53 +0,0 @@ -package ch.ergon.anytask.api - -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -/** - * Client für die lokale AnyType-API (anytype-heart Middleware). - * Die API läuft standardmässig auf http://localhost:31007. - * - * TODO: App-Key via Einstellungen konfigurierbar machen. - */ -class AnyTypeClient( - private val baseUrl: String = "http://localhost:31007", - private val appKey: String = "", // wird nach erstem Login gesetzt -) { - private val http = HttpClient(CIO) { - install(ContentNegotiation) { - json(Json { ignoreUnknownKeys = true }) - } - } - - suspend fun createTask(title: String, details: String = "", spaceId: String = ""): Result = - runCatching { - val response = http.post("$baseUrl/v1/spaces/$spaceId/objects") { - contentType(ContentType.Application.Json) - header("Anytype-Version", "0.1") - bearerAuth(appKey) - setBody(CreateObjectRequest(title = title, details = details, typeKey = "ot-task")) - } - response.body().objectId - } - - fun close() = http.close() -} - -@Serializable -private data class CreateObjectRequest( - val title: String, - val details: String, - val typeKey: String, -) - -@Serializable -private data class CreateObjectResponse( - val objectId: String, -) diff --git a/src/main/kotlin/ch/ergon/anytask/api/dto/CreateObjectRequest.kt b/src/main/kotlin/ch/ergon/anytask/api/dto/CreateObjectRequest.kt new file mode 100644 index 0000000..aba55ce --- /dev/null +++ b/src/main/kotlin/ch/ergon/anytask/api/dto/CreateObjectRequest.kt @@ -0,0 +1,11 @@ +package ch.ergon.anytask.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CreateObjectRequest( + val name: String, + val body: String, + @SerialName("type_key") val typeKey: String, +) \ No newline at end of file diff --git a/src/main/kotlin/ch/ergon/anytask/api/dto/CreateObjectResponse.kt b/src/main/kotlin/ch/ergon/anytask/api/dto/CreateObjectResponse.kt new file mode 100644 index 0000000..c0128fd --- /dev/null +++ b/src/main/kotlin/ch/ergon/anytask/api/dto/CreateObjectResponse.kt @@ -0,0 +1,8 @@ +package ch.ergon.anytask.api.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class CreateObjectResponse( + val objectId: String, +) \ No newline at end of file diff --git a/src/main/kotlin/ch/ergon/anytask/api/dto/Space.kt b/src/main/kotlin/ch/ergon/anytask/api/dto/Space.kt new file mode 100644 index 0000000..e2b42c3 --- /dev/null +++ b/src/main/kotlin/ch/ergon/anytask/api/dto/Space.kt @@ -0,0 +1,9 @@ +package ch.ergon.anytask.api.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class Space( + val id: String, + val name: String, +) \ No newline at end of file diff --git a/src/main/kotlin/ch/ergon/anytask/api/dto/SpacesResponse.kt b/src/main/kotlin/ch/ergon/anytask/api/dto/SpacesResponse.kt new file mode 100644 index 0000000..54a1ce3 --- /dev/null +++ b/src/main/kotlin/ch/ergon/anytask/api/dto/SpacesResponse.kt @@ -0,0 +1,8 @@ +package ch.ergon.anytask.api.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class SpacesResponse( + val data: List, +) \ No newline at end of file diff --git a/src/main/kotlin/ch/ergon/anytask/config/AppConfig.kt b/src/main/kotlin/ch/ergon/anytask/config/AppConfig.kt new file mode 100644 index 0000000..9939595 --- /dev/null +++ b/src/main/kotlin/ch/ergon/anytask/config/AppConfig.kt @@ -0,0 +1,32 @@ +package ch.ergon.anytask.config + +import com.charleskorn.kaml.Yaml +import kotlinx.serialization.Serializable +import java.io.File + +@Serializable +data class AppConfig( + val apiKey: String = "", + val spaceId: String = "", +) + +fun loadConfig(): AppConfig { + val yaml = Yaml.default + val serializer = AppConfig.serializer() + + // External files take precedence (production use) + val externalLocations = listOf( + File(System.getProperty("user.home"), ".anytask/anytask.yaml"), + File("anytask.yaml"), + ) + externalLocations.firstOrNull { it.exists() }?.let { + return yaml.decodeFromString(serializer, it.readText()) + } + + // Fallback: classpath resource (development) + AppConfig::class.java.getResourceAsStream("/anytask.yaml")?.let { + return yaml.decodeFromString(serializer, it.bufferedReader().readText()) + } + + return AppConfig() +} diff --git a/src/main/kotlin/ch/ergon/anytask/hotkey/HotkeyManager.kt b/src/main/kotlin/ch/ergon/anytask/hotkey/HotkeyManager.kt index 8f0bc20..0643d7f 100644 --- a/src/main/kotlin/ch/ergon/anytask/hotkey/HotkeyManager.kt +++ b/src/main/kotlin/ch/ergon/anytask/hotkey/HotkeyManager.kt @@ -4,20 +4,22 @@ import com.github.kwhat.jnativehook.GlobalScreen import com.github.kwhat.jnativehook.NativeHookException import com.github.kwhat.jnativehook.keyboard.NativeKeyEvent import com.github.kwhat.jnativehook.keyboard.NativeKeyListener +import org.apache.logging.log4j.LogManager import java.awt.EventQueue import java.util.logging.Level import java.util.logging.Logger /** - * Lauscht auf eine systemweite Tastenkombination: Ctrl+Shift+Space - * Erfordert auf macOS die Berechtigung "Eingabegeräte überwachen" (Accessibility). + * Listens for a system-wide hotkey: Ctrl+Shift+Space. + * Requires the Accessibility permission on macOS. */ object HotkeyManager { + private val logger = LogManager.getLogger(HotkeyManager::class.java) private var listener: NativeKeyListener? = null fun start(onHotkey: () -> Unit) { - // JNativeHook-Logging unterdrücken + // Suppress JNativeHook logging Logger.getLogger(GlobalScreen::class.java.`package`.name).apply { level = Level.OFF useParentHandlers = false @@ -26,14 +28,14 @@ object HotkeyManager { try { GlobalScreen.registerNativeHook() } catch (e: NativeHookException) { - System.err.println("JNativeHook konnte nicht registriert werden: ${e.message}") + logger.error("Failed to register JNativeHook: ${e.message}") return } listener = object : NativeKeyListener { override fun nativeKeyPressed(e: NativeKeyEvent) { if (isHotkey(e)) { - // Callback auf den AWT-EDT dispatchen (Compose läuft dort) + // Dispatch callback on the AWT EDT (where Compose runs) EventQueue.invokeLater(onHotkey) } } diff --git a/src/main/kotlin/ch/ergon/anytask/ui/TaskWindow.kt b/src/main/kotlin/ch/ergon/anytask/ui/TaskWindow.kt index a153840..c7c02aa 100644 --- a/src/main/kotlin/ch/ergon/anytask/ui/TaskWindow.kt +++ b/src/main/kotlin/ch/ergon/anytask/ui/TaskWindow.kt @@ -9,11 +9,11 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.input.key.* import androidx.compose.ui.unit.dp -import ch.ergon.anytask.api.AnyTypeClient +import ch.ergon.anytask.api.AnyTypeApi import kotlinx.coroutines.launch @Composable -fun TaskWindow(client: AnyTypeClient, onDismiss: () -> Unit) { +fun TaskWindow(client: AnyTypeApi, onDismiss: () -> Unit) { val scope = rememberCoroutineScope() var title by remember { mutableStateOf("") } var details by remember { mutableStateOf("") } @@ -58,12 +58,12 @@ fun TaskWindow(client: AnyTypeClient, onDismiss: () -> Unit) { }, verticalArrangement = Arrangement.spacedBy(12.dp), ) { - Text("Neue Aufgabe", style = MaterialTheme.typography.h6) + Text("New Task", style = MaterialTheme.typography.h6) OutlinedTextField( value = title, onValueChange = { title = it }, - label = { Text("Titel") }, + label = { Text("Title") }, singleLine = true, modifier = Modifier .fillMaxWidth() @@ -86,10 +86,10 @@ fun TaskWindow(client: AnyTypeClient, onDismiss: () -> Unit) { modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), ) { - TextButton(onClick = onDismiss) { Text("Abbrechen") } + TextButton(onClick = onDismiss) { Text("Cancel") } Button(onClick = ::submit, enabled = title.isNotBlank() && !isLoading) { if (isLoading) CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp) - else Text("Erstellen ⌘↵") + else Text("Create ⌘↵") } } } diff --git a/src/main/resources/anytask.yaml b/src/main/resources/anytask.yaml new file mode 100644 index 0000000..be13d17 --- /dev/null +++ b/src/main/resources/anytask.yaml @@ -0,0 +1,2 @@ +apiKey: 2tkMSzFegWwzsQ+n9SpGwVjychEkCESWjV1/ftytXG4= +spaceId: bafyreiafzvfsrurvx4gvr2wwysgp5uugujzmbdg5hxnjlahfmx3ww6kxfy.172mmtcppmjtt diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..35a6a3c --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file