Gitnuro/src/main/kotlin/com/jetpackduba/gitnuro/viewmodels/CommitChangesViewModel.kt
2024-05-08 15:11:47 +02:00

177 lines
6.5 KiB
Kotlin

package com.jetpackduba.gitnuro.viewmodels
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.ui.text.input.TextFieldValue
import com.jetpackduba.gitnuro.extensions.delayedStateChange
import com.jetpackduba.gitnuro.extensions.filePath
import com.jetpackduba.gitnuro.extensions.fullData
import com.jetpackduba.gitnuro.extensions.lowercaseContains
import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.diff.GetCommitDiffEntriesUseCase
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
import com.jetpackduba.gitnuro.ui.tree_files.TreeItem
import com.jetpackduba.gitnuro.ui.tree_files.entriesToTreeEntry
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
private const val MIN_TIME_IN_MS_TO_SHOW_LOAD = 300L
class CommitChangesViewModel @Inject constructor(
private val tabState: TabState,
private val getCommitDiffEntriesUseCase: GetCommitDiffEntriesUseCase,
private val appSettingsRepository: AppSettingsRepository,
tabScope: CoroutineScope,
) {
private val _showSearch = MutableStateFlow(false)
val showSearch: StateFlow<Boolean> = _showSearch
private val _searchFilter = MutableStateFlow(TextFieldValue(""))
val searchFilter: StateFlow<TextFieldValue> = _searchFilter
val changesLazyListState = MutableStateFlow(
LazyListState(firstVisibleItemIndex = 0, firstVisibleItemScrollOffset = 0)
)
val textScroll = MutableStateFlow(ScrollState(0))
val showAsTree = appSettingsRepository.showChangesAsTreeFlow
private val treeContractedDirectories = MutableStateFlow(emptyList<String>())
private val _commitChangesState = MutableStateFlow<CommitChangesState>(CommitChangesState.Loading)
private val commitChangesFiltered =
combine(_commitChangesState, _showSearch, _searchFilter) { state, showSearch, filter ->
if (state is CommitChangesState.Loaded && showSearch && filter.text.isNotBlank()) {
state.copy(changes = state.changes.filter { it.filePath.lowercaseContains(filter.text) })
} else {
state
}
}
val commitChangesStateUi: StateFlow<CommitChangesStateUi> = combine(
commitChangesFiltered,
showAsTree,
treeContractedDirectories,
) { commitState, showAsTree, contractedDirs ->
when (commitState) {
CommitChangesState.Loading -> CommitChangesStateUi.Loading
is CommitChangesState.Loaded -> {
if (showAsTree) {
CommitChangesStateUi.TreeLoaded(
commit = commitState.commit,
changes = entriesToTreeEntry(commitState.changes, contractedDirs) { it.filePath }
)
} else {
CommitChangesStateUi.ListLoaded(
commit = commitState.commit,
changes = commitState.changes
)
}
}
}
}
.stateIn(
tabScope,
SharingStarted.Lazily,
CommitChangesStateUi.Loading
)
fun loadChanges(commit: RevCommit) = tabState.runOperation(
refreshType = RefreshType.NONE,
) { git ->
val state = _commitChangesState.value
// Check if it's a different commit before resetting everything
if (
state is CommitChangesState.Loading ||
state is CommitChangesState.Loaded && state.commit != commit
) {
delayedStateChange(
delayMs = MIN_TIME_IN_MS_TO_SHOW_LOAD,
onDelayTriggered = { _commitChangesState.value = CommitChangesState.Loading }
) {
val fullCommit = commit.fullData(git.repository)
if (fullCommit != null) {
val changes = getCommitDiffEntriesUseCase(git, fullCommit).toMutableList()
if (fullCommit.parentCount == 3) {
val untrackedFilesCommit =
fullCommit.parents?.firstOrNull {
val parentCommit = it.fullData(git.repository) ?: return@firstOrNull false
parentCommit.fullMessage.startsWith("untracked files on") && parentCommit.parentCount == 0
}
if (untrackedFilesCommit != null) {
val untrackedFilesChanges = getCommitDiffEntriesUseCase(git, untrackedFilesCommit)
if (untrackedFilesChanges.all { it.changeType == DiffEntry.ChangeType.ADD }) { // All files should be new
changes.addAll(untrackedFilesChanges)
}
}
}
_commitChangesState.value = CommitChangesState.Loaded(commit, changes)
}
}
_showSearch.value = false
_searchFilter.value = TextFieldValue("")
changesLazyListState.value = LazyListState(
0,
0
)
textScroll.value = ScrollState(0)
}
}
fun alternateShowAsTree() {
appSettingsRepository.showChangesAsTree = !appSettingsRepository.showChangesAsTree
}
fun onDirectoryClicked(directoryPath: String) {
val contractedDirectories = treeContractedDirectories.value
if (contractedDirectories.contains(directoryPath)) {
treeContractedDirectories.value -= directoryPath
} else {
treeContractedDirectories.value += directoryPath
}
}
fun onSearchFilterToggled(visible: Boolean) {
_showSearch.value = visible
}
fun onSearchFilterChanged(filter: TextFieldValue) {
_searchFilter.value = filter
}
}
private sealed interface CommitChangesState {
data object Loading : CommitChangesState
data class Loaded(val commit: RevCommit, val changes: List<DiffEntry>) :
CommitChangesState
}
sealed interface CommitChangesStateUi {
data object Loading : CommitChangesStateUi
sealed interface Loaded : CommitChangesStateUi {
val commit: RevCommit
}
data class ListLoaded(override val commit: RevCommit, val changes: List<DiffEntry>) :
Loaded
data class TreeLoaded(override val commit: RevCommit, val changes: List<TreeItem<DiffEntry>>) :
Loaded
}