1package com.peachpasswords.ui.vault23import androidx.lifecycle.SavedStateHandle4import androidx.lifecycle.ViewModel5import androidx.lifecycle.viewModelScope6import com.peachpasswords.vault.VaultEntry7import com.peachpasswords.vault.VaultRepository8import com.peachpasswords.vault.isNote9import dagger.hilt.android.lifecycle.HiltViewModel10import kotlinx.coroutines.Job11import kotlinx.coroutines.delay12import kotlinx.coroutines.flow.MutableStateFlow13import kotlinx.coroutines.flow.SharingStarted14import kotlinx.coroutines.flow.StateFlow15import kotlinx.coroutines.flow.asStateFlow16import kotlinx.coroutines.flow.combine17import kotlinx.coroutines.flow.map18import kotlinx.coroutines.flow.stateIn19import kotlinx.coroutines.launch20import javax.inject.Inject2122232425262728293031@HiltViewModel32class NotesModeViewModel @Inject constructor(33 private val vault: VaultRepository,34 savedStateHandle: SavedStateHandle,35) : ViewModel() {36 private val id: String = savedStateHandle.get<String>("id").orEmpty()37 private val _activeNoteId = MutableStateFlow(id.takeIf { it.isNotBlank() })3839 val entry: StateFlow<VaultEntry?> =40 combine(vault.entries, _activeNoteId) { list, activeId ->41 activeId?.let { id -> list.firstOrNull { it.id == id && it.isNote() } }42 }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), vault.getEntry(id)?.takeIf { it.isNote() })4344 val notes: StateFlow<List<VaultEntry>> =45 vault.entries.map { list ->46 list.filter { it.isNote() }47 .sortedByDescending { it.lastUsedAt ?: it.modifiedAt }48 }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), vault.getEntries().filter { it.isNote() })4950 val activeNoteId: StateFlow<String?> = _activeNoteId.asStateFlow()5152 private val _savedPulse = MutableStateFlow(0L)53 54 val savedPulse: StateFlow<Long> = _savedPulse.asStateFlow()5556 private var saveJob: Job? = null5758 59 fun onContentChanged(plainText: String) =60 scheduleSave { it.copy(notes = plainText.ifBlank { null }) }6162 63646566 fun onTitleChanged(title: String) =67 scheduleSave { it.copy(title = title) }6869 70 fun saveTitleNow(title: String) {71 saveJob?.cancel()72 commit { it.copy(title = title) }73 }7475 fun openNote(noteId: String) {76 saveJob?.cancel()77 _activeNoteId.value = noteId78 }7980 fun showToc() {81 saveJob?.cancel()82 _activeNoteId.value = null83 }8485 fun createNote() {86 saveJob?.cancel()87 viewModelScope.launch {88 val created = vault.addEntry(89 VaultEntry(90 id = "",91 type = "note",92 title = "Untitled",93 notes = null,94 createdAt = 0L,95 modifiedAt = 0L,96 ),97 )98 _activeNoteId.value = created.id99 }100 }101102 103 fun saveNow(title: String, plainText: String) {104 saveJob?.cancel()105 commit {106 it.copy(107 title = title.ifBlank { "Untitled" },108 notes = plainText.ifBlank { null },109 )110 }111 }112113 fun delete(onDone: () -> Unit) {114 viewModelScope.launch {115 _activeNoteId.value?.let { runCatching { vault.deleteEntry(it) } }116 _activeNoteId.value = null117 onDone()118 }119 }120121 private fun scheduleSave(patch: (VaultEntry) -> VaultEntry) {122 saveJob?.cancel()123 saveJob = viewModelScope.launch {124 delay(800)125 commit(patch)126 }127 }128129 private fun commit(patch: (VaultEntry) -> VaultEntry) {130 val current = entry.value ?: return131 viewModelScope.launch {132 runCatching { vault.updateEntry(patch(current)) }133 .onSuccess { _savedPulse.value = System.currentTimeMillis() }134 }135 }136}