Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce a view model in ManageNotetypes #17804

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.ichi2.utils.positiveButton

class AddNewNotesType(
private val activity: ManageNotetypes,
private val viewModel: ManageNotetypeViewModel,
) {
private lateinit var dialogView: View

Expand Down Expand Up @@ -152,14 +153,15 @@ class AddNewNotesType(
selectedOption: AddNotetypeUiModel,
) {
activity.launchCatchingTask {
activity.runAndRefreshAfter {
withCol {
val kind = StockNotetype.Kind.forNumber(selectedOption.id.toInt())
val updatedStandardNotetype =
BackendUtils.fromJsonBytes(getStockNotetypeLegacy(kind)).apply {
set("name", newName)
}
addNotetypeLegacy(BackendUtils.toJsonBytes(updatedStandardNotetype))
}
viewModel.refresh()
}
}

Expand All @@ -168,7 +170,7 @@ class AddNewNotesType(
model: AddNotetypeUiModel,
) {
activity.launchCatchingTask {
activity.runAndRefreshAfter {
withCol {
val targetNotetype = getNotetype(model.id)
val newNotetype =
targetNotetype.copy {
Expand All @@ -177,6 +179,7 @@ class AddNewNotesType(
}
addNotetype(newNotetype)
}
viewModel.refresh()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/****************************************************************************************
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.anki.notetype

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import anki.notetypes.copy
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.libanki.NoteTypeId
import com.ichi2.libanki.getNotetype
import com.ichi2.libanki.getNotetypeNameIdUseCount
import com.ichi2.libanki.removeNotetype
import com.ichi2.libanki.updateNotetype
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

/**
* @see ManageNotetypes
*/
class ManageNotetypeViewModel : ViewModel() {
private val _uiState = MutableStateFlow(NotetypesUiState())
val uiState: StateFlow<NotetypesUiState> = _uiState.asStateFlow()
Comment on lines +36 to +37
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[no action needed, comment only]

I prefer exposing the mutable Flow, instead of the additional boilerplate


/**
* Reference to the coroutine started in response to a filter request. As every change in the
* query makes a request, this job can be used to cancel previous coroutines to avoid useless
* work.
*/
private var filterJob: Job? = null

fun refresh() {
viewModelScope.launch {
_uiState.update { state -> state.copy(isProcessing = true) }
refreshNotetypes()
}
}

/**
* Refreshes the list of notetypes, additionally filtering the list. Also calls to emit the new
Copy link
Member

@david-allison david-allison Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'emit the new' feels like it's a truncated sentence

* state.
* @param query the option query string to use to filter the list of notetypes
lukstbit marked this conversation as resolved.
Show resolved Hide resolved
*/
private suspend fun refreshNotetypes(query: String = "") {
val notetypes =
withCol {
getNotetypeNameIdUseCount()
.filter { backendNotetype ->
if (query.isEmpty()) {
true
} else {
backendNotetype.name.lowercase().contains(query.lowercase())
}
}.map { backendNotetype ->
NotetypeItemUiState(
id = backendNotetype.id,
name = backendNotetype.name,
useCount = backendNotetype.useCount,
onNavigateTo = { destination ->
_uiState.update { state ->
state.copy(destination = destination)
}
},
)
}
}
_uiState.update { state ->
Copy link
Member

@david-allison david-allison Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ An error appears to block the UI in a processing state.

Either an error in this method, or if callers don't call this method

state.copy(
notetypes = notetypes,
isProcessing = false,
)
}
}

fun rename(
noteTypeId: NoteTypeId,
newName: String,
) {
viewModelScope.launch {
_uiState.update { state -> state.copy(isProcessing = true) }
withCol {
val initialNotetype = getNotetype(noteTypeId)
val renamedNotetype = initialNotetype.copy { this.name = newName }
updateNotetype(renamedNotetype)
}
refreshNotetypes()
}
}

fun delete(noteTypeId: NoteTypeId) {
viewModelScope.launch {
_uiState.update { state -> state.copy(isProcessing = true) }
withCol {
removeNotetype(noteTypeId)
}
refreshNotetypes()
}
}

fun filter(query: String) {
filterJob?.cancel()
filterJob =
viewModelScope.launch {
refreshNotetypes(query)
}
}

/**
* Mark the previously sent [NotetypesDestination] as consumed by the ui. Done to prevent
* situations when we would come back to the screen and the navigation requests is again seen
lukstbit marked this conversation as resolved.
Show resolved Hide resolved
* and we would enter in a loop.
*/
fun markNavigationRequestAsDone() {
_uiState.update { state -> state.copy(destination = null) }
}
}
Loading
Loading