Vibe-Coding mit ChatGPT: Ich baue meine Notiz-App aus 🚀

Stelle dir vor, du beschreibst eine Idee und nach einigen Minuten entsteht aus dieser Idee eine funktionierende Software, die du direkt auf deinem Smartphone oder Tablet ausführen kannst. Genau das ermöglicht Vibe-Coding. Mithilfe von KI kannst du ohne Programmierkenntnisse relativ schnell und einfach eine App erstellen. Nachdem ich längere Zeit mit diesem Gedanken gespielt habe, habe ich es vor ein paar Tagen endlich gewagt: Ich habe Vibe-Coding ausprobiert und meine erste kleine App für Android entwickelt, eine Notiz-App. Und was soll ich sagen? Ich bin begeistert. Ich hätte nicht gedacht, dass das Vibe-Coding mit ChatGPT dermaßen Spaß macht und man mit der KI in kürzester Zeit sichtbare Ergebnisse erzielt. Prompt-Engineering heißt das Zauberwort. Wie es mit meiner App weitergeht und wie ich diese mit KI ausbauen will, das erkläre ich in diesem Blogartikel.

KI kann genauso gut programmieren wie der Mensch

Künstliche Intelligenz entwickelt sich rasant weiter und übernimmt beim Programmieren immer anspruchsvollere Aufgaben. Moderne KI-Systeme vervollständigen nicht mehr nur einzelne Codezeilen, sondern können Funktionen planen, Fehler beheben, Tests schreiben und einfache Anwendungen weitgehend selbstständig erstellen. Bei klar beschriebenen und abgegrenzten Programmieraufgaben kann KI heute bereits Ergebnisse liefern, die genauso gut oder teilweise sogar besser sind als die eines Menschen.

Dadurch benötigen Unternehmen für bestimmte Routineaufgaben weniger menschliche Arbeitszeit und teilweise auch kleinere Entwicklerteams. Das bedeutet jedoch nicht, dass KI schon jeden Programmierer vollständig ersetzen kann. Vielmehr automatisiert sie immer mehr Tätigkeiten und verändert damit das Berufsbild: Entwickler schreiben weniger Code von Hand und beschäftigen sich stärker mit Anforderungen, Softwarearchitektur, Kontrolle und Sicherheit.

Parallel dazu wird das sogenannte Vibe-Coding bekannter. Dabei beschreibt eine Person in Alltagssprache, was ein Programm können soll, und lässt den Code größtenteils von einer KI erzeugen. So können auch Menschen ohne umfassende Programmierkenntnisse Webseiten, Prototypen oder kleinere Anwendungen entwickeln. In der professionellen Softwareentwicklung war Vibe-Coding laut einer Stack-Overflow-Umfrage 2025 allerdings noch keine Mehrheitsmethode: 72 Prozent der Befragten gaben an, es nicht einzusetzen.

Mit jeder neuen Modellgeneration dürfte der Anteil KI-generierter Software weiter wachsen. Bei größeren und komplexeren Projekten benötigt die KI jedoch weiterhin menschliche Unterstützung, weil sie Fehler, Sicherheitslücken oder ungeeignete Lösungen erzeugen kann. Programmierer werden daher wahrscheinlich nicht plötzlich verschwinden, aber ihre Zahl und ihre Aufgaben können sich langfristig deutlich verändern. KI kann beim eigentlichen Schreiben von Code bereits mit Menschen mithalten: Verantwortung, Projektverständnis und die abschließende Qualitätskontrolle bleiben vorerst menschliche Aufgaben.

Notiz-App: Der aktuelle Stand

Schauen wir uns kurz an, was ich bisher mit Vibe-Coding gebaut habe. Ich habe eine funktionierende Notiz-App, die ich bereits produktiv als APK auf meinem Samsung Galaxy S23 Plus nutze. Die Oberfläche der Notiz-App ist übersichtlich und schlicht gehalten, ich habe mich dabei etwas an der Notes-App von Samsung orientiert.

App-Oberfläche

Tippt man auf das Plus-Symbol, kann man eine neue Notiz samt Titel erstellen. Mit dem Zurück-Pfeil wird die neue Notiz automatisch gespeichert. Alternativ kann man auf das Drei-Punkte-Menü klicken und dort die Notiz speichern.

Notiz erstellen
Neue Notiz

Dort habe ich weitere Funktionen einbauen lassen: Als Text speichern, als PDF speichern, als Text teilen, als PDF teilen und Änderungen verwerfen. Nachdem die Notiz gespeichert wurde, kann man diese über das Drei-Punkte-Menü wieder löschen. Alle Funktionen habe ich erfolgreich getestet, besonders das Teilen ist praktisch, da ich eine Notiz in Sekunden per E-Mail oder WhatsApp weiterleiten kann.

Weitere Funktionen

Die App hat ein Suchfeld, das sowohl die Titel als auch die Inhalte der Notizen berücksichtigt. Die Ergebnisse werden bereits während der Eingabe gefiltert, sodass passende Einträge schnell gefunden werden können.

Als weitere Funktion habe ich Favoriten hinzugefügt. Jede Notiz lässt sich mit einem Stern markieren, wobei der Favoritenstatus dauerhaft in der Room-Datenbank gespeichert wird. Über einen Filter kann anschließend zwischen allen Notizen und ausschließlich den Favoriten gewechselt werden.

Zusätzlich kann die App die Notizen nach verschiedenen Kriterien sortieren. Nach dem Erstellungsdatum, von neu nach alt oder umgekehrt sowie alphabetisch nach Titel angeordnet. Suche, Favoritenfilter und Sortierung lassen sich dabei miteinander kombinieren.

Neue Funktionen in der Notiz-App

Persönlich bin ich mit dem Funktionsumfang der App sehr zufrieden und nutze diese bereits produktiv, wobei die Nutzung auch als Dauertest zu sehen ist. Ich will sehen, wie sich die App dauerhaft schlägt. Gleichzeitig bin ich begeistert darüber, wie aus einer einfachen Idee innerhalb von ein paar Tagen eine voll funktionierende Applikation entstehen kann.

Mir fehlen noch ein paar Funktionen, sodass ich wieder ChatGPT und Android Studio anschmeiße. Ich will, dass die App, die von mir ausgewählte Sortierung, z. B. zuletzt geändert, auch nach dem Schließen der App, beibehält. Das macht sie derzeit nicht, stattdessen wird immer die Sortierung nach neuesten Notizen priorisiert.

Also gebe ich den ChatGPT-Prompt ein:

Ich möchte eine weitere Optimierung in der Notiz-App. Ich möchte, dass die ausgewählte Sortierung auch dann bleibt, wenn die App geschlossen und wieder gestartet wurde. Das ist momentan nicht der Fall. Kannst du das programmieren?

ChatGPT macht sich an die Arbeit und gibt mir nach einer Minute und 18 Sekunden folgende Rückmeldung:

Die ausgewählte Sortierung wird jetzt dauerhaft gespeichert.

Wenn du beispielsweise „Zuletzt geändert“ auswählst, bleibt diese Einstellung auch nach dem vollständigen Schließen und erneuten Starten der App aktiv. Das gilt ebenso für:

Neueste

Älteste

A–Z

Zuletzt geändert

Die Einstellung wird über SharedPreferences gespeichert; dafür war keine Datenbankmigration nötig.

Geändert: MainActivity.kt

Der vollständige Build war erfolgreich.

Das ist Vibe-Coding. 🙂

Werde ich Programmieren lernen?

Die Frage, ob man nach eine paar Tagen Vibe-Coding gleich das Programmieren lernen und ein Software-Entwickler werden will, ist zu diesem Zeitpunkt vielleicht ein bisschen zu verfrüht. Es reizt mich dennoch, mehr über die Zusammenhänge im Programmieren und die Software-Architektur zu erfahren.

Während der Arbeit an meiner App im Android Studio ist mir aufgefallen, dass die Datei MainActivity.tk den meisten Code enthält. Also schaue ich mir diese Datei sowie die wichtigsten Dateien genauer an.

package com.example.meinenotizen

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.meinenotizen.ui.theme.MeineNotizenTheme
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MeineNotizenTheme {
                NotizenApp()
            }
        }
    }
}

@Composable
fun NotizenApp() {
    val context = LocalContext.current

    val database = remember {
        NoteDatabase.getDatabase(context)
    }

    val noteDao = database.noteDao()

    var notizen by remember {
        mutableStateOf<List<Note>>(emptyList())
    }

    var neueNotizOffen by remember {
        mutableStateOf(false)
    }

    var ausgewaehlteNotiz by remember {
        mutableStateOf<Note?>(null)
    }

    val coroutineScope = rememberCoroutineScope()

    LaunchedEffect(Unit) {
        notizen = noteDao.getAllNotes()
    }

    when {
        neueNotizOffen -> {
            NotizEditorBildschirm(
                notiz = null,

                onSpeichern = { notiz ->
                    coroutineScope.launch {
                        noteDao.insertNote(notiz)
                        notizen = noteDao.getAllNotes()
                        neueNotizOffen = false
                    }
                },

                onVerwerfen = {
                    neueNotizOffen = false
                },

                onLoeschen = null
            )
        }

        ausgewaehlteNotiz != null -> {
            NotizEditorBildschirm(
                notiz = ausgewaehlteNotiz,

                onSpeichern = { bearbeiteteNotiz ->
                    coroutineScope.launch {
                        val inhaltWurdeGeaendert =
                            bearbeiteteNotiz.title != ausgewaehlteNotiz?.title ||
                                    bearbeiteteNotiz.content != ausgewaehlteNotiz?.content

                        val zuSpeicherndeNotiz = bearbeiteteNotiz.copy(
                            updatedAt = if (inhaltWurdeGeaendert) {
                                System.currentTimeMillis()
                            } else {
                                bearbeiteteNotiz.updatedAt
                            }
                        )

                        noteDao.updateNote(zuSpeicherndeNotiz)
                        notizen = noteDao.getAllNotes()
                        ausgewaehlteNotiz = null
                    }
                },

                onVerwerfen = {
                    ausgewaehlteNotiz = null
                },

                onLoeschen = { notiz ->
                    coroutineScope.launch {
                        noteDao.deleteNote(notiz)
                        notizen = noteDao.getAllNotes()
                        ausgewaehlteNotiz = null
                    }
                }
            )
        }

        else -> {
            NotizenStartseite(
                notizen = notizen,

                onNeueNotiz = {
                    neueNotizOffen = true
                },

                onNotizAuswaehlen = { notiz ->
                    ausgewaehlteNotiz = notiz
                },

                onFavoritAendern = { notiz ->
                    coroutineScope.launch {
                        val geaenderteNotiz = notiz.copy(
                            isFavorite = !notiz.isFavorite,
                            updatedAt = System.currentTimeMillis()
                        )

                        noteDao.updateNote(geaenderteNotiz)
                        notizen = noteDao.getAllNotes()
                    }
                }
            )
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NotizenStartseite(
    notizen: List<Note>,
    onNeueNotiz: () -> Unit,
    onNotizAuswaehlen: (Note) -> Unit,
    onFavoritAendern: (Note) -> Unit
) {
    val context = LocalContext.current

    val einstellungen = remember(context) {
        context.getSharedPreferences(
            "notiora_einstellungen",
            android.content.Context.MODE_PRIVATE
        )
    }

    var suchText by remember {
        mutableStateOf("")
    }

    var nurFavoriten by remember {
        mutableStateOf(false)
    }

    var sortierung by remember(einstellungen) {
        mutableStateOf(
            einstellungen.getString(
                "notizen_sortierung",
                "Neueste"
            ) ?: "Neueste"
        )
    }

    var sortierMenueOffen by remember {
        mutableStateOf(false)
    }

    fun sortierungAuswaehlen(neueSortierung: String) {
        sortierung = neueSortierung
        einstellungen.edit()
            .putString("notizen_sortierung", neueSortierung)
            .apply()
        sortierMenueOffen = false
    }

    val gefilterteNotizen = notizen
        .filter { notiz ->
            val passtZurSuche =
                notiz.title.contains(
                    other = suchText,
                    ignoreCase = true
                ) ||
                        notiz.content.contains(
                            other = suchText,
                            ignoreCase = true
                        )

            val passtZumFavoritenFilter =
                !nurFavoriten || notiz.isFavorite

            passtZurSuche && passtZumFavoritenFilter
        }
        .let { liste ->
            when (sortierung) {
                "Zuletzt geändert" -> {
                    liste.sortedByDescending { it.updatedAt }
                }

                "Älteste" -> {
                    liste.sortedBy { it.createdAt }
                }

                "A–Z" -> {
                    liste.sortedBy {
                        it.title.lowercase(Locale.getDefault())
                    }
                }

                else -> {
                    liste.sortedByDescending { it.createdAt }
                }
            }
        }

    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(
                        text = "Notiora",
                        fontWeight = FontWeight.SemiBold
                    )
                }
            )
        },

        floatingActionButton = {
            FloatingActionButton(
                onClick = onNeueNotiz
            ) {
                Text(
                    text = "+",
                    style = MaterialTheme.typography.headlineMedium
                )
            }
        }
    ) { innerPadding ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding)
        ) {
            OutlinedTextField(
                value = suchText,

                onValueChange = {
                    suchText = it
                },

                label = {
                    Text("Notizen durchsuchen")
                },

                modifier = Modifier
                    .fillMaxWidth()
                    .padding(
                        start = 16.dp,
                        end = 16.dp,
                        top = 8.dp
                    ),

                singleLine = true
            )

            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(
                        start = 16.dp,
                        end = 16.dp,
                        top = 10.dp
                    ),

                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                FilterChip(
                    selected = !nurFavoriten,

                    onClick = {
                        nurFavoriten = false
                    },

                    label = {
                        Text("Alle")
                    }
                )

                FilterChip(
                    selected = nurFavoriten,

                    onClick = {
                        nurFavoriten = true
                    },

                    label = {
                        Text("★ Favoriten")
                    }
                )
            }

            Column(
                modifier = Modifier.padding(
                    start = 16.dp,
                    end = 16.dp,
                    bottom = 4.dp
                )
            ) {
                OutlinedButton(
                    onClick = {
                        sortierMenueOffen = true
                    }
                ) {
                    Text("Sortierung: $sortierung")
                }

                DropdownMenu(
                    expanded = sortierMenueOffen,

                    onDismissRequest = {
                        sortierMenueOffen = false
                    }
                ) {
                    DropdownMenuItem(
                        text = {
                            Text("Zuletzt geändert")
                        },

                        onClick = {
                            sortierungAuswaehlen("Zuletzt geändert")
                        }
                    )

                    DropdownMenuItem(
                        text = {
                            Text("Neueste zuerst")
                        },

                        onClick = {
                            sortierungAuswaehlen("Neueste")
                        }
                    )

                    DropdownMenuItem(
                        text = {
                            Text("Älteste zuerst")
                        },

                        onClick = {
                            sortierungAuswaehlen("Älteste")
                        }
                    )

                    DropdownMenuItem(
                        text = {
                            Text("Titel AZ")
                        },

                        onClick = {
                            sortierungAuswaehlen("AZ")
                        }
                    )
                }
            }

            when {
                notizen.isEmpty() -> {
                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(24.dp),

                        verticalArrangement = Arrangement.Center
                    ) {
                        Text(
                            text = "Noch keine Notizen",
                            style = MaterialTheme.typography.headlineSmall
                        )

                        Spacer(
                            modifier = Modifier.height(8.dp)
                        )

                        Text(
                            text = "Tippe auf +, um deine erste Notiz zu erstellen.",
                            style = MaterialTheme.typography.bodyLarge
                        )
                    }
                }

                gefilterteNotizen.isEmpty() -> {
                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(24.dp),

                        verticalArrangement = Arrangement.Center
                    ) {
                        Text(
                            text = if (nurFavoriten) {
                                "Keine Favoriten gefunden."
                            } else {
                                "Keine passende Notiz gefunden."
                            },

                            style = MaterialTheme.typography.bodyLarge
                        )
                    }
                }

                else -> {
                    LazyColumn(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(
                                start = 16.dp,
                                end = 16.dp,
                                top = 8.dp,
                                bottom = 96.dp
                            ),

                        verticalArrangement = Arrangement.spacedBy(12.dp)
                    ) {
                        items(
                            items = gefilterteNotizen,
                            key = { notiz ->
                                notiz.id
                            }
                        ) { notiz ->
                            NotizKarte(
                                notiz = notiz,

                                onClick = {
                                    onNotizAuswaehlen(notiz)
                                },

                                onFavoritClick = {
                                    onFavoritAendern(notiz)
                                }
                            )
                        }
                    }
                }
            }
        }
    }
}

@Composable
fun NotizKarte(
    notiz: Note,
    onClick: () -> Unit,
    onFavoritClick: () -> Unit
) {
    Card(
        onClick = onClick,

        modifier = Modifier.fillMaxWidth(),

        elevation = CardDefaults.cardElevation(
            defaultElevation = 2.dp
        )
    ) {
        Column(
            modifier = Modifier.padding(18.dp)
        ) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Text(
                    text = if (notiz.title.isBlank()) {
                        "Ohne Titel"
                    } else {
                        notiz.title
                    },

                    style = MaterialTheme.typography.titleMedium,
                    fontWeight = FontWeight.SemiBold,
                    modifier = Modifier.weight(1f)
                )

                TextButton(
                    onClick = onFavoritClick
                ) {
                    Text(
                        text = if (notiz.isFavorite) {
                            "★"
                        } else {
                            "☆"
                        },

                        style = MaterialTheme.typography.headlineSmall
                    )
                }
            }

            Text(
                text = formatDatum(notiz.createdAt),
                style = MaterialTheme.typography.bodySmall
            )

            if (notiz.content.isNotBlank()) {
                Spacer(
                    modifier = Modifier.height(8.dp)
                )

                Text(
                    text = notiz.content,
                    style = MaterialTheme.typography.bodyMedium,
                    maxLines = 4
                )
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NotizEditorBildschirm(
    notiz: Note?,
    onSpeichern: (Note) -> Unit,
    onVerwerfen: () -> Unit,
    onLoeschen: ((Note) -> Unit)?
) {
    val context = LocalContext.current
    val istNeueNotiz = notiz == null

    var titel by remember(notiz?.id) {
        mutableStateOf(notiz?.title.orEmpty())
    }

    var inhaltEingabe by remember(notiz?.id) {
        mutableStateOf(
            TextFieldValue(notiz?.content.orEmpty())
        )
    }

    val inhalt = inhaltEingabe.text

    var inhaltTextLayout by remember(notiz?.id) {
        mutableStateOf<TextLayoutResult?>(null)
    }

    var inhaltHatFokus by remember(notiz?.id) {
        mutableStateOf(false)
    }

    var menueGeoeffnet by remember {
        mutableStateOf(false)
    }

    var loeschDialogAnzeigen by remember {
        mutableStateOf(false)
    }

    val tastatur = LocalSoftwareKeyboardController.current

    val aktuelleNotiz = (notiz ?: Note(
        title = "",
        content = ""
    )).copy(
        title = titel,
        content = inhalt
    )

    fun speichernUndZurueck() {
        tastatur?.hide()

        if (titel.isBlank() && inhalt.isBlank()) {
            onVerwerfen()
        } else {
            onSpeichern(aktuelleNotiz)
        }
    }

    val textSpeichernLauncher =
        rememberLauncherForActivityResult(
            contract = ActivityResultContracts.CreateDocument(
                "text/plain"
            )
        ) { uri ->
            if (uri != null) {
                try {
                    speichereNotizAlsText(
                        context = context,
                        uri = uri,
                        notiz = aktuelleNotiz
                    )

                    Toast.makeText(
                        context,
                        "Textdatei wurde gespeichert.",
                        Toast.LENGTH_SHORT
                    ).show()
                } catch (fehler: Exception) {
                    Toast.makeText(
                        context,
                        "Textdatei konnte nicht gespeichert werden.",
                        Toast.LENGTH_LONG
                    ).show()
                }
            }
        }

    val pdfSpeichernLauncher =
        rememberLauncherForActivityResult(
            contract = ActivityResultContracts.CreateDocument(
                "application/pdf"
            )
        ) { uri ->
            if (uri != null) {
                try {
                    speichereNotizAlsPdf(
                        context = context,
                        uri = uri,
                        notiz = aktuelleNotiz
                    )

                    Toast.makeText(
                        context,
                        "PDF wurde gespeichert.",
                        Toast.LENGTH_SHORT
                    ).show()
                } catch (fehler: Exception) {
                    Toast.makeText(
                        context,
                        "PDF konnte nicht gespeichert werden.",
                        Toast.LENGTH_LONG
                    ).show()
                }
            }
        }

    BackHandler {
        speichernUndZurueck()
    }

    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(
                        text = if (istNeueNotiz) {
                            "Neue Notiz"
                        } else {
                            "Notiz"
                        }
                    )
                },

                navigationIcon = {
                    IconButton(
                        onClick = {
                            speichernUndZurueck()
                        }
                    ) {
                        Text(
                            text = "‹",
                            fontSize = 40.sp,
                            fontWeight = FontWeight.Light
                        )
                    }
                },

                actions = {
                    Box {
                        IconButton(
                            onClick = {
                                menueGeoeffnet = true
                            }
                        ) {
                            Text(
                                text = "⋮",
                                fontSize = 28.sp,
                                fontWeight = FontWeight.Bold
                            )
                        }

                        DropdownMenu(
                            expanded = menueGeoeffnet,

                            onDismissRequest = {
                                menueGeoeffnet = false
                            }
                        ) {
                            DropdownMenuItem(
                                text = {
                                    Text("Speichern und schließen")
                                },

                                onClick = {
                                    menueGeoeffnet = false
                                    speichernUndZurueck()
                                }
                            )

                            DropdownMenuItem(
                                text = {
                                    Text("Als Text speichern")
                                },

                                onClick = {
                                    menueGeoeffnet = false

                                    textSpeichernLauncher.launch(
                                        "${erstelleDateiname(titel)}.txt"
                                    )
                                }
                            )

                            DropdownMenuItem(
                                text = {
                                    Text("Als PDF speichern")
                                },

                                onClick = {
                                    menueGeoeffnet = false

                                    pdfSpeichernLauncher.launch(
                                        "${erstelleDateiname(titel)}.pdf"
                                    )
                                }
                            )

                            DropdownMenuItem(
                                text = {
                                    Text("Als Text teilen")
                                },

                                onClick = {
                                    menueGeoeffnet = false

                                    teileNotizAlsText(
                                        context = context,
                                        notiz = aktuelleNotiz
                                    )
                                }
                            )

                            DropdownMenuItem(
                                text = {
                                    Text("Als PDF teilen")
                                },

                                onClick = {
                                    menueGeoeffnet = false

                                    try {
                                        teileNotizAlsPdf(
                                            context = context,
                                            notiz = aktuelleNotiz
                                        )
                                    } catch (fehler: Exception) {
                                        Toast.makeText(
                                            context,
                                            "PDF konnte nicht geteilt werden.",
                                            Toast.LENGTH_LONG
                                        ).show()
                                    }
                                }
                            )

                            DropdownMenuItem(
                                text = {
                                    Text("Änderungen verwerfen")
                                },

                                onClick = {
                                    menueGeoeffnet = false
                                    tastatur?.hide()
                                    onVerwerfen()
                                }
                            )

                            if (
                                !istNeueNotiz &&
                                onLoeschen != null
                            ) {
                                DropdownMenuItem(
                                    text = {
                                        Text(
                                            text = "Notiz löschen",
                                            color =
                                                MaterialTheme.colorScheme.error
                                        )
                                    },

                                    onClick = {
                                        menueGeoeffnet = false
                                        loeschDialogAnzeigen = true
                                    }
                                )
                            }
                        }
                    }
                }
            )
        }
    ) { innerPadding ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding)
                .imePadding()
                .padding(
                    start = 18.dp,
                    end = 18.dp,
                    top = 10.dp,
                    bottom = 12.dp
                )
        ) {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(
                        top = 4.dp,
                        bottom = 10.dp
                    )
            ) {
                if (titel.isEmpty()) {
                    Text(
                        text = "Titel",
                        color =
                            MaterialTheme.colorScheme.onSurfaceVariant,
                        fontSize = 30.sp,
                        fontWeight = FontWeight.SemiBold
                    )
                }

                BasicTextField(
                    value = titel,

                    onValueChange = {
                        titel = it
                    },

                    singleLine = false,

                    textStyle = TextStyle(
                        color = MaterialTheme.colorScheme.onSurface,
                        fontSize = 30.sp,
                        lineHeight = 36.sp,
                        fontWeight = FontWeight.SemiBold
                    ),

                    cursorBrush = SolidColor(
                        MaterialTheme.colorScheme.primary
                    ),

                    modifier = Modifier.fillMaxWidth()
                )
            }

            Text(
                text = if (istNeueNotiz) {
                    "Neue Notiz"
                } else {
                    formatDatum(notiz.createdAt)
                },

                style = MaterialTheme.typography.bodySmall,
                color = MaterialTheme.colorScheme.onSurfaceVariant,

                modifier = Modifier.padding(
                    bottom = 14.dp
                )
            )

            BoxWithConstraints(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
            ) {
                val editorMindesthoehe = maxHeight
                val editorScrollState = rememberScrollState()
                val density = LocalDensity.current

                LaunchedEffect(
                    inhaltHatFokus,
                    inhaltEingabe.selection.end,
                    editorMindesthoehe
                ) {
                    if (inhaltHatFokus) {
                        delay(250)

                        val layout = inhaltTextLayout

                        if (layout != null) {
                            val cursorPosition =
                                inhaltEingabe.selection.end.coerceIn(
                                    0,
                                    inhalt.length
                                )

                            val cursorRechteck =
                                layout.getCursorRect(cursorPosition)

                            val sichtbareHoehePx = with(density) {
                                editorMindesthoehe.toPx()
                            }

                            val sicherheitsabstandPx = with(density) {
                                48.dp.toPx()
                            }

                            val scrollZiel = (
                                    cursorRechteck.bottom -
                                            sichtbareHoehePx +
                                            sicherheitsabstandPx
                                    )
                                .toInt()
                                .coerceIn(
                                    minimumValue = 0,
                                    maximumValue = editorScrollState.maxValue
                                )

                            editorScrollState.animateScrollTo(scrollZiel)
                        }
                    }
                }

                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .verticalScroll(editorScrollState)
                ) {
                    if (inhalt.isEmpty()) {
                        Text(
                            text = "Notiz eingeben",
                            style = MaterialTheme.typography.bodyLarge,
                            color = MaterialTheme.colorScheme.onSurfaceVariant
                        )
                    }

                    BasicTextField(
                        value = inhaltEingabe,

                        onValueChange = {
                            inhaltEingabe = it
                        },

                        onTextLayout = {
                            inhaltTextLayout = it
                        },

                        textStyle = TextStyle(
                            color = MaterialTheme.colorScheme.onSurface,
                            fontSize = 18.sp,
                            lineHeight = 27.sp
                        ),

                        cursorBrush = SolidColor(
                            MaterialTheme.colorScheme.primary
                        ),

                        modifier = Modifier
                            .fillMaxWidth()
                            .heightIn(min = editorMindesthoehe)
                            .padding(bottom = 72.dp)
                            .onFocusChanged {
                                inhaltHatFokus = it.isFocused
                            }
                    )
                }
            }
        }
    }

    if (
        loeschDialogAnzeigen &&
        notiz != null &&
        onLoeschen != null
    ) {
        AlertDialog(
            onDismissRequest = {
                loeschDialogAnzeigen = false
            },

            title = {
                Text("Notiz löschen?")
            },

            text = {
                Text(
                    "Möchtest du diese Notiz wirklich löschen? " +
                            "Dieser Vorgang kann nicht rückgängig gemacht werden."
                )
            },

            confirmButton = {
                Button(
                    onClick = {
                        loeschDialogAnzeigen = false
                        onLoeschen(notiz)
                    }
                ) {
                    Text("Löschen")
                }
            },

            dismissButton = {
                TextButton(
                    onClick = {
                        loeschDialogAnzeigen = false
                    }
                ) {
                    Text("Abbrechen")
                }
            }
        )
    }
}

fun formatDatum(timestamp: Long): String {
    val formatter = SimpleDateFormat(
        "dd.MM.yyyy · HH:mm",
        Locale.getDefault()
    )

    return formatter.format(Date(timestamp))
}

Die Datei MainActivity.kt ist der zentrale Einstiegspunkt und gleichzeitig die wichtigste Steuerungsdatei der App. Die Klasse MainActivity wird beim Start über das AndroidManifest.xml aufgerufen. In ihrer Methode onCreate() wird die Benutzeroberfläche mit Jetpack Compose gestartet, in das Design MeineNotizenTheme eingebettet und anschließend über die Funktion NotizenApp() aufgebaut.

NotizenApp() verbindet die Benutzeroberfläche mit der lokalen Room-Datenbank. Die Funktion lädt über NoteDatabase und NoteDao alle gespeicherten Notizen und hält sie als Compose-Zustand im Arbeitsspeicher. Sie entscheidet außerdem, ob die Startseite, der Editor für eine neue Notiz oder der Editor für eine bereits vorhandene Notiz angezeigt wird. Das Erstellen, Aktualisieren, Löschen und Markieren als Favorit erfolgt in Coroutines, damit die Datenbankarbeit die Benutzeroberfläche nicht blockiert. Nach jeder Änderung wird die Notizliste erneut aus der Datenbank geladen.

Die Funktion NotizenStartseite() stellt die Übersicht der App dar. Dort können Notizen durchsucht, nach Favoriten gefiltert und nach Datum oder Titel sortiert werden. Die ausgewählte Sortierreihenfolge wird in SharedPreferences gespeichert und bleibt dadurch nach einem Neustart erhalten. Jede Notiz wird über NotizKarte() als anklickbare Karte mit Titel, Datum, Inhaltsvorschau und Favoritenstern dargestellt. Über die schwebende Plus-Schaltfläche gelangt der Benutzer zum Editor.

Der NotizEditorBildschirm() verwaltet die Bearbeitung von Titel und Inhalt. Er kann neue Notizen speichern, bestehende Notizen aktualisieren oder löschen und behandelt auch die Zurück-Taste sowie die Bildschirmtastatur. Über das Menü lassen sich Änderungen verwerfen und Notizen als Textdatei oder PDF speichern beziehungsweise teilen. Die eigentliche Erzeugung dieser Exportdateien übernimmt NotizExport.kt; MainActivity.kt startet lediglich die entsprechenden Aktionen und zeigt Erfolgs- oder Fehlermeldungen an. Insgesamt enthält die Datei somit Benutzeroberfläche, Bildschirmsteuerung, UI-Zustand und die Verbindung zur Datenbank.

Vibe-Coding mit ChatGPT – Fazit

Vibe-Coding ermöglicht es, eine App schnell und intuitiv mithilfe künstlicher Intelligenz zu entwickeln, ohne jede technische Einzelheit von Anfang an vollständig beherrschen zu müssen. Besonders bei der Notizen-App hat dieser Ansatz geholfen, in kurzer Zeit eine funktionsfähige Anwendung mit Benutzeroberfläche, lokaler Datenbank, Such- und Sortierfunktionen sowie Text- und PDF-Export aufzubauen. Ideen konnten direkt beschrieben, ausprobiert und anschließend schrittweise verbessert werden.

Sladjan Lazic

Comments

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert