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 _isMultiSelectMode = MutableStateFlow(false)53 val isMultiSelectMode: StateFlow<Boolean> = _isMultiSelectMode.asStateFlow()5455 private val _selectedIds = MutableStateFlow<Set<String>>(emptySet())56 val selectedIds: StateFlow<Set<String>> = _selectedIds.asStateFlow()5758 private val _savedPulse = MutableStateFlow(0L)59 60 val savedPulse: StateFlow<Long> = _savedPulse.asStateFlow()6162 private var saveJob: Job? = null6364 65 fun onContentChanged(plainText: String) =66 scheduleSave { it.copy(notes = plainText.ifBlank { null }) }6768 69707172 fun onTitleChanged(title: String) =73 scheduleSave { it.copy(title = title) }7475 76 fun saveTitleNow(title: String) {77 saveJob?.cancel()78 commit { it.copy(title = title) }79 }8081 fun openNote(noteId: String) {82 saveJob?.cancel()83 exitMultiSelect()84 _activeNoteId.value = noteId85 }8687 fun showToc() {88 saveJob?.cancel()89 exitMultiSelect()90 _activeNoteId.value = null91 }9293 fun createNote() {94 saveJob?.cancel()95 exitMultiSelect()96 viewModelScope.launch {97 val created = vault.addEntry(98 VaultEntry(99 id = "",100 type = "note",101 title = "Untitled",102 notes = null,103 createdAt = 0L,104 modifiedAt = 0L,105 ),106 )107 _activeNoteId.value = created.id108 }109 }110111 112 fun saveNow(title: String, plainText: String) {113 saveJob?.cancel()114 commit {115 it.copy(116 title = title.ifBlank { "Untitled" },117 notes = plainText.ifBlank { null },118 )119 }120 }121122 fun delete(onDone: () -> Unit) {123 viewModelScope.launch {124 _activeNoteId.value?.let { runCatching { vault.deleteEntry(it) } }125 _activeNoteId.value = null126 onDone()127 }128 }129130 fun enterMultiSelect(noteId: String) {131 _isMultiSelectMode.value = true132 _selectedIds.value = setOf(noteId)133 }134135 fun toggleSelected(noteId: String) {136 val next = _selectedIds.value.toMutableSet()137 if (!next.add(noteId)) next.remove(noteId)138 _selectedIds.value = next139 }140141 fun exitMultiSelect() {142 _isMultiSelectMode.value = false143 _selectedIds.value = emptySet()144 }145146 fun batchDelete() {147 val ids = _selectedIds.value.toList()148 if (ids.isEmpty()) return149 viewModelScope.launch {150 ids.forEach { id -> runCatching { vault.deleteEntry(id) } }151 exitMultiSelect()152 }153 }154155 fun lock() = vault.lock()156157 private fun scheduleSave(patch: (VaultEntry) -> VaultEntry) {158 saveJob?.cancel()159 saveJob = viewModelScope.launch {160 delay(800)161 commit(patch)162 }163 }164165 private fun commit(patch: (VaultEntry) -> VaultEntry) {166 val current = entry.value ?: return167 viewModelScope.launch {168 runCatching { vault.updateEntry(patch(current)) }169 .onSuccess { _savedPulse.value = System.currentTimeMillis() }170 }171 }172}