Pith - kerf
kerf/app/src/main/java/com/vgmlr/kerf/NoteViewModel.kt [6.9 kb]
Modified: 23:09:03 55 026 (13 May 026)
17 Days Ago
package com.vgmlr.kerf

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.json.JSONObject

class NoteViewModel(application: Application) : AndroidViewModel(application) {
    private val dao = AppDb.getDatabase(application).dao()
    private var saveJob: Job? = null

    private val _sharedText = MutableStateFlow<String?>(null)
    val sharedText: StateFlow<String?> = _sharedText.asStateFlow()

    private val _sharedFile = MutableStateFlow<Pair<String, String>?>(null)
    val sharedFile: StateFlow<Pair<String, String>?> = _sharedFile.asStateFlow()

    private val _kerfImport = MutableStateFlow<Pair<String, String>?>(null)
    val kerfImport: StateFlow<Pair<String, String>?> = _kerfImport.asStateFlow()

    val allNotes: StateFlow<List<Note>> = dao.getAll()
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

    fun observeNote(id: Int): Flow<Note?> = dao.getByIdFlow(id)

    fun setSharedText(text: String?) { _sharedText.value = text }

    fun setKerfImport(data: Pair<String, String>?) { _kerfImport.value = data }

    fun setSharedFile(file: Pair<String, String>?) {
        if (file == null) {
            _sharedFile.value = null
            return
        }
        viewModelScope.launch {
            val currentNotes = allNotes.value
            var finalTitle = file.first
            var count = 1
            while (currentNotes.any { it.title.equals(finalTitle, ignoreCase = true) }) {
                count++
                finalTitle = "${file.first} ($count)"
            }
            _sharedFile.value = finalTitle to file.second
        }
    }

    fun toggleReplyMode(id: Int) {
        viewModelScope.launch {
            val current = dao.getById(id) ?: return@launch
            if (current.replies == null) {
                dao.update(current.copy(replies = JSONObject().toString()))
            } else {
                dao.update(current.copy(replies = null))
            }
        }
    }

    fun updateReply(id: Int, itemNum: Int, text: String) {
        viewModelScope.launch {
            val current = dao.getById(id) ?: return@launch
            val json = if (current.replies != null) JSONObject(current.replies) else JSONObject()
            json.put(itemNum.toString(), text)
            dao.update(current.copy(replies = json.toString(), updatedAt = System.currentTimeMillis()))
        }
    }

    fun deleteLineWithReplySync(id: Int, title: String, content: String, lineIndex: Int) {
        saveJob?.cancel()
        viewModelScope.launch {
            val note = dao.getById(id) ?: return@launch
            val json = if (note.replies != null) JSONObject(note.replies) else null

            val newContent = KerfUtils.deleteAndReindex(content, lineIndex)

            val newJson = if (json != null) {
                val updated = JSONObject()
                val lines = content.lines()
                val deletedItemMatch = KerfUtils.ItemRegex.find(lines[lineIndex].trim())
                val deletedNum = deletedItemMatch?.groupValues?.get(1)?.toIntOrNull() ?: -1

                val it = json.keys()
                while (it.hasNext()) {
                    val keyStr = it.next()
                    val key = keyStr.toInt()
                    val value = json.getString(keyStr)
                    if (key < deletedNum) {
                        updated.put(key.toString(), value)
                    } else if (key > deletedNum) {
                        updated.put((key - 1).toString(), value)
                    }
                }
                updated.toString()
            } else null

            dao.update(note.copy(title = title, content = newContent, replies = newJson, updatedAt = System.currentTimeMillis()))
        }
    }

    fun addNote(isImported: Boolean = false, onDone: (Int) -> Unit) {
        viewModelScope.launch {
            val now = System.currentTimeMillis()
            val id = dao.insert(Note(title = "", content = KerfUtils.DEFAULT_PREFIX, isImported = isImported, createdAt = now, updatedAt = now))
            onDone(id.toInt())
        }
    }

    fun updateNote(id: Int, title: String, content: String) {
        saveJob?.cancel()
        saveJob = viewModelScope.launch { delay(2000); performSave(id, title, content) }
    }

    fun updateNoteImmediate(id: Int, title: String, content: String, isImported: Boolean? = null) {
        saveJob?.cancel()
        viewModelScope.launch { performSave(id, title, content, isImported) }
    }

    private suspend fun performSave(id: Int, title: String, content: String, isImported: Boolean? = null) {
        val current = dao.getById(id)
        if (current != null) {
            val nextImported = isImported ?: current.isImported
            if (current.title != title || current.content != content || current.isImported != nextImported) {
                dao.update(current.copy(title = title, content = content, isImported = nextImported, updatedAt = System.currentTimeMillis()))
            }
        }
    }

    fun togglePin(id: Int) {
        viewModelScope.launch {
            dao.getById(id)?.let {
                dao.update(it.copy(isPinned = !it.isPinned))
            }
        }
    }

    fun updateLastShared(id: Int) {
        viewModelScope.launch { dao.getById(id)?.let { dao.update(it.copy(lastShared = System.currentTimeMillis())) } }
    }

    fun updateLastCopied(id: Int) {
        viewModelScope.launch { dao.getById(id)?.let { dao.update(it.copy(lastCopied = System.currentTimeMillis())) } }
    }

    fun clearNoteAndSharedDate(id: Int) {
        saveJob?.cancel()
        viewModelScope.launch {
            dao.getById(id)?.let { current ->
                dao.update(current.copy(content = KerfUtils.DEFAULT_PREFIX, replies = null, lastShared = null, lastCopied = null, updatedAt = System.currentTimeMillis()))
            }
        }
    }

    fun deleteNote(id: Int) = viewModelScope.launch { dao.deleteById(id) }

    fun appendContent(id: Int, text: String) {
        viewModelScope.launch {
            val note = dao.getById(id) ?: return@launch
            val lines = note.content.lines()
            var lastNum = 0

            lines.forEach { line ->
                KerfUtils.ItemRegex.find(line.trim())?.let { match ->
                    val num = match.groupValues[1].toIntOrNull() ?: 0
                    if (num > lastNum) lastNum = num
                }
            }

            val prefix = KerfUtils.DEFAULT_PREFIX
            val isInitial = note.content.isBlank() || note.content.trim() == prefix.trim()

            val newContent = if (isInitial) {
                "$prefix$text"
            } else {
                val cleanBase = note.content.trimEnd()
                "$cleanBase\n\n${lastNum + 1}. $text"
            }
            dao.update(note.copy(content = newContent, updatedAt = System.currentTimeMillis()))
        }
    }
}
Updates
Shim - Android 70.026.1
Wedge - Linux 68.026.1
Wedge - Android 68.026.1
Taper - Linux 64.026.1
Ayh Extension - Chrome 63.026.1
Dev
TVShow (227) 'CSA'
TVShow (228) 'APT'
TVProgram (83) 'BXT'
Miter Update(s)
Shim (Dictation)

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)