Completed arch refactor

This commit is contained in:
Abdelilah El Aissaoui 2022-01-04 19:54:56 +01:00
parent e7de563b28
commit 97a082bc47
19 changed files with 335 additions and 294 deletions

View File

@ -36,7 +36,7 @@ dependencies {
tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "11"
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.allWarningsAsErrors = false
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}

View File

@ -149,6 +149,14 @@ class App {
}
private fun removeTab(tabs: List<TabInformation>, key: Int) = appScope.launch(Dispatchers.IO) {
// Stop any running jobs
val tabToRemove = tabs.firstOrNull { it.key == key } ?: return@launch
tabToRemove.tabViewModel.dispose()
// Remove tab from persistent tabs storage
appStateManager.repositoryTabRemoved(key)
// Remove from tabs flow
tabsFlow.value = tabs.filter { tab -> tab.key != key }
}
@ -187,10 +195,7 @@ class App {
onAddedTab(newAppTab)
newAppTab
},
onTabClosed = { key ->
appStateManager.repositoryTabRemoved(key)
onRemoveTab(key)
}
onTabClosed = onRemoveTab
)
IconButton(
modifier = Modifier

View File

@ -5,21 +5,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
class StashManager @Inject constructor() {
private val _stashStatus = MutableStateFlow<StashStatus>(StashStatus.Loaded(listOf()))
val stashStatus: StateFlow<StashStatus>
get() = _stashStatus
suspend fun stash(git: Git) = withContext(Dispatchers.IO) {
git
.stashCreate()
.setIncludeUntracked(true)
.call()
loadStashList(git)
}
suspend fun popStash(git: Git) = withContext(Dispatchers.IO) {
@ -29,23 +22,11 @@ class StashManager @Inject constructor() {
git.stashDrop()
.call()
loadStashList(git)
}
suspend fun loadStashList(git: Git) = withContext(Dispatchers.IO) {
_stashStatus.value = StashStatus.Loading
val stashList = git
suspend fun getStashList(git: Git) = withContext(Dispatchers.IO) {
return@withContext git
.stashList()
.call()
_stashStatus.value = StashStatus.Loaded(stashList.toList())
}
}
sealed class StashStatus {
object Loading : StashStatus()
data class Loaded(val stashes: List<RevCommit>) : StashStatus()
}

View File

@ -4,6 +4,7 @@ import app.app.Error
import app.app.newErrorNow
import app.di.TabScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@ -46,13 +47,11 @@ class TabState @Inject constructor() {
@set:Synchronized
var operationRunning = false
private val _processing = MutableStateFlow(false)
val processing: StateFlow<Boolean>
get() = _processing
val processing: StateFlow<Boolean> = _processing
fun safeProcessing(showError: Boolean = true, callback: suspend (git: Git) -> RefreshType) =
managerScope.launch {
managerScope.launch(Dispatchers.IO) {
mutex.withLock {
_processing.value = true
operationRunning = true
@ -74,7 +73,30 @@ class TabState @Inject constructor() {
}
}
fun runOperation(block: suspend (git: Git) -> RefreshType) = managerScope.launch {
fun safeProcessingWihoutGit(showError: Boolean = true, callback: suspend () -> RefreshType) =
managerScope.launch(Dispatchers.IO) {
mutex.withLock {
_processing.value = true
operationRunning = true
try {
val refreshType = callback()
if (refreshType != RefreshType.NONE)
_refreshData.emit(refreshType)
} catch (ex: Exception) {
ex.printStackTrace()
if (showError)
_errors.emit(newErrorNow(ex, ex.localizedMessage))
} finally {
_processing.value = false
operationRunning = false
}
}
}
fun runOperation(block: suspend (git: Git) -> RefreshType) = managerScope.launch(Dispatchers.IO) {
operationRunning = true
try {
val refreshType = block(safeGit)

View File

@ -27,33 +27,15 @@ import app.ui.dialogs.PasswordDialog
import app.ui.dialogs.UserPasswordDialog
import kotlinx.coroutines.delay
// TODO onDispose sometimes is called when changing tabs, therefore losing the tab state
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AppTab(
gitManager: TabViewModel,
repositoryPath: String?,
tabName: MutableState<String>
tabViewModel: TabViewModel,
) {
DisposableEffect(gitManager) {
if (repositoryPath != null)
gitManager.openRepository(repositoryPath)
// TODO onDispose sometimes is called when changing tabs, therefore losing the tab state
onDispose {
println("onDispose called for $tabName")
gitManager.dispose()
}
}
val errorManager = remember(gitManager) { // TODO Is remember here necessary?
gitManager.errorsManager
}
val errorManager = tabViewModel.errorsManager
val lastError by errorManager.lastError.collectAsState()
var showError by remember {
mutableStateOf(false)
}
var showError by remember { mutableStateOf(false) }
if (lastError != null)
LaunchedEffect(lastError) {
@ -62,13 +44,8 @@ fun AppTab(
showError = false
}
val repositorySelectionStatus by gitManager.repositorySelectionStatus.collectAsState()
val isProcessing by gitManager.processing.collectAsState()
if (repositorySelectionStatus is RepositorySelectionStatus.Open) {
tabName.value = gitManager.repositoryName
}
val repositorySelectionStatus by tabViewModel.repositorySelectionStatus.collectAsState()
val isProcessing by tabViewModel.processing.collectAsState()
Box {
Column(
@ -87,7 +64,7 @@ fun AppTab(
.alpha(linearProgressAlpha)
)
CredentialsDialog(gitManager)
CredentialsDialog(tabViewModel)
Box(modifier = Modifier.fillMaxSize()) {
Crossfade(targetState = repositorySelectionStatus) {
@ -95,13 +72,13 @@ fun AppTab(
@Suppress("UnnecessaryVariable") // Don't inline it because smart cast won't work
when (repositorySelectionStatus) {
RepositorySelectionStatus.None -> {
WelcomePage(gitManager = gitManager)
WelcomePage(tabViewModel = tabViewModel)
}
RepositorySelectionStatus.Loading -> {
LoadingRepository()
}
is RepositorySelectionStatus.Open -> {
RepositoryOpenPage(tabViewModel = gitManager)
RepositoryOpenPage(tabViewModel = tabViewModel)
}
}
}

View File

@ -4,10 +4,7 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -25,20 +22,38 @@ import app.theme.secondaryTextColor
import app.ui.components.AvatarImage
import app.ui.components.ScrollableLazyColumn
import app.ui.components.TooltipText
import app.viewmodels.CommitChangesStatus
import app.viewmodels.CommitChangesViewModel
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.revwalk.RevCommit
@Composable
fun CommitChanges(
gitManager: TabViewModel,
commit: RevCommit,
commitChangesViewModel: CommitChangesViewModel,
onDiffSelected: (DiffEntry) -> Unit
) {
var diff by remember { mutableStateOf(emptyList<DiffEntry>()) }
LaunchedEffect(commit) {
diff = gitManager.diffListFromCommit(commit)
}
val commitChangesStatusState = commitChangesViewModel.commitChangesStatus.collectAsState()
when(val commitChangesStatus = commitChangesStatusState.value) {
CommitChangesStatus.Loading -> {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}
is CommitChangesStatus.Loaded -> {
CommitChangesView(
commit = commitChangesStatus.commit,
changes = commitChangesStatus.changes,
onDiffSelected = onDiffSelected,
)
}
}
}
@Composable
fun CommitChangesView(
commit: RevCommit,
changes: List<DiffEntry>,
onDiffSelected: (DiffEntry) -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize(),
@ -90,7 +105,7 @@ fun CommitChanges(
)
CommitLogChanges(diff, onDiffSelected = onDiffSelected)
CommitLogChanges(changes, onDiffSelected = onDiffSelected)
}
}
}

View File

@ -17,14 +17,12 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import app.theme.primaryTextColor
import app.viewmodels.MenuViewModel
@Composable
fun GMenu(
fun Menu(
menuViewModel: MenuViewModel,
onRepositoryOpen: () -> Unit,
onPull: () -> Unit,
onPush: () -> Unit,
onStash: () -> Unit,
onPopStash: () -> Unit,
onCreateBranch: () -> Unit,
) {
Row(
@ -47,17 +45,13 @@ fun GMenu(
MenuButton(
title = "Pull",
icon = painterResource("download.svg"),
onClick = {
onPull()
},
onClick = { menuViewModel.pull() },
)
MenuButton(
title = "Push",
icon = painterResource("upload.svg"),
onClick = {
onPush()
},
onClick = { menuViewModel.push() },
)
Spacer(modifier = Modifier.width(16.dp))
@ -76,12 +70,12 @@ fun GMenu(
MenuButton(
title = "Stash",
icon = painterResource("stash.svg"),
onClick = onStash,
onClick = { menuViewModel.stash() },
)
MenuButton(
title = "Pop",
icon = painterResource("apply_stash.svg"),
onClick = onPopStash,
onClick = { menuViewModel.popStash() },
)
Spacer(modifier = Modifier.weight(1f))

View File

@ -20,10 +20,9 @@ import org.jetbrains.compose.splitpane.rememberSplitPaneState
fun RepositoryOpenPage(tabViewModel: TabViewModel) {
val repositoryState by tabViewModel.repositoryState.collectAsState()
val diffSelected by tabViewModel.diffSelected.collectAsState()
val selectedItem by tabViewModel.selectedItem.collectAsState()
var showNewBranchDialog by remember { mutableStateOf(false) }
val (selectedItem, setSelectedItem) = remember { mutableStateOf<SelectedItem>(SelectedItem.None) }
LaunchedEffect(selectedItem) {
tabViewModel.newDiffSelected = null
}
@ -41,14 +40,11 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
}
Column {
GMenu(
Menu(
menuViewModel = tabViewModel.menuViewModel,
onRepositoryOpen = {
openRepositoryDialog(gitManager = tabViewModel)
},
onPull = { tabViewModel.pull() },
onPush = { tabViewModel.push() },
onStash = { tabViewModel.stash() },
onPopStash = { tabViewModel.popStash() },
onCreateBranch = { showNewBranchDialog = true }
)
@ -64,22 +60,20 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
Branches(
branchesViewModel = tabViewModel.branchesViewModel,
onBranchClicked = {
val commit = tabViewModel.findCommit(it.objectId)
setSelectedItem(SelectedItem.Ref(commit))
tabViewModel.newSelectedRef(it.objectId)
}
)
Remotes(remotesViewModel = tabViewModel.remotesViewModel)
Tags(
tagsViewModel = tabViewModel.tagsViewModel,
onTagClicked = {
val commit = tabViewModel.findCommit(it.objectId)
setSelectedItem(SelectedItem.Ref(commit))
tabViewModel.newSelectedRef(it.objectId)
}
)
Stashes(
gitManager = tabViewModel,
stashesViewModel = tabViewModel.stashesViewModel,
onStashSelected = { stash ->
setSelectedItem(SelectedItem.Stash(stash))
tabViewModel.newSelectedStash(stash)
}
)
}
@ -102,7 +96,7 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
logViewModel = tabViewModel.logViewModel,
selectedItem = selectedItem,
onItemSelected = {
setSelectedItem(it)
tabViewModel.newSelectedItem(it)
},
)
}
@ -120,7 +114,8 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
modifier = Modifier
.fillMaxHeight()
) {
if (selectedItem == SelectedItem.UncommitedChanges) {
val safeSelectedItem = selectedItem
if (safeSelectedItem == SelectedItem.UncommitedChanges) {
UncommitedChanges(
statusViewModel = tabViewModel.statusViewModel,
selectedEntryType = diffSelected,
@ -135,10 +130,9 @@ fun RepositoryOpenPage(tabViewModel: TabViewModel) {
tabViewModel.newDiffSelected = DiffEntryType.UnstagedDiff(diffEntry)
}
)
} else if (selectedItem is SelectedItem.CommitBasedItem) {
} else if (safeSelectedItem is SelectedItem.CommitBasedItem) {
CommitChanges(
gitManager = tabViewModel,
commit = selectedItem.revCommit,
commitChangesViewModel = tabViewModel.commitChangesViewModel,
onDiffSelected = { diffEntry ->
tabViewModel.newDiffSelected = DiffEntryType.CommitDiff(diffEntry)
}

View File

@ -6,19 +6,19 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import app.viewmodels.TabViewModel
import app.git.StashStatus
import app.ui.components.ScrollableLazyColumn
import app.ui.components.SideMenuEntry
import app.ui.components.SideMenuSubentry
import app.viewmodels.StashStatus
import app.viewmodels.StashesViewModel
import org.eclipse.jgit.revwalk.RevCommit
@Composable
fun Stashes(
gitManager: TabViewModel,
stashesViewModel: StashesViewModel,
onStashSelected: (commit: RevCommit) -> Unit,
) {
val stashStatusState = gitManager.stashStatus.collectAsState()
val stashStatusState = stashesViewModel.stashStatus.collectAsState()
val stashStatus = stashStatusState.value
val stashList = if (stashStatus is StashStatus.Loaded)

View File

@ -29,7 +29,7 @@ fun openRepositoryDialog(gitManager: TabViewModel) {
}
private fun openRepositoryDialog(
gitManager: TabViewModel,
tabViewModel: TabViewModel,
latestDirectoryOpened: String
) {
@ -42,5 +42,5 @@ private fun openRepositoryDialog(
fileChooser.showSaveDialog(null)
if (fileChooser.selectedFile != null)
gitManager.openRepository(fileChooser.selectedFile)
tabViewModel.openRepository(fileChooser.selectedFile)
}

View File

@ -33,9 +33,9 @@ import java.net.URI
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun WelcomePage(
gitManager: TabViewModel,
tabViewModel: TabViewModel,
) {
val appStateManager = gitManager.appStateManager
val appStateManager = tabViewModel.appStateManager
var showCloneView by remember { mutableStateOf(false) }
// Crossfade(showCloneView) {
@ -69,7 +69,7 @@ fun WelcomePage(
.padding(bottom = 8.dp),
title = "Open a repository",
painter = painterResource("open.svg"),
onClick = { openRepositoryDialog(gitManager) }
onClick = { openRepositoryDialog(tabViewModel) }
)
ButtonTile(
@ -136,7 +136,7 @@ fun WelcomePage(
) {
TextButton(
onClick = {
gitManager.openRepository(repo)
tabViewModel.openRepository(repo)
}
) {
Text(
@ -161,7 +161,7 @@ fun WelcomePage(
if (showCloneView)
MaterialDialog {
CloneDialog(gitManager, onClose = { showCloneView = false })
CloneDialog(tabViewModel, onClose = { showCloneView = false })
}
// Popup(focusable = true, onDismissRequest = { showCloneView = false }, alignment = Alignment.Center) {
//

View File

@ -28,6 +28,8 @@ import app.theme.tabColorActive
import app.theme.tabColorInactive
import app.ui.AppTab
import javax.inject.Inject
import kotlin.io.path.Path
import kotlin.io.path.name
@Composable
@ -166,7 +168,7 @@ class TabInformation(
appComponent: AppComponent,
) {
@Inject
lateinit var gitManager: TabViewModel
lateinit var tabViewModel: TabViewModel
@Inject
lateinit var appStateManager: AppStateManager
@ -180,14 +182,18 @@ class TabInformation(
tabComponent.inject(this)
//TODO: This shouldn't be here, should be in the parent method
gitManager.onRepositoryChanged = { path ->
tabViewModel.onRepositoryChanged = { path ->
if (path == null) {
appStateManager.repositoryTabRemoved(key)
} else
} else {
tabName.value = Path(path).name
appStateManager.repositoryTabChanged(key, path)
}
}
if(path != null)
tabViewModel.openRepository(path)
content = {
AppTab(gitManager, path, tabName)
AppTab(tabViewModel)
}
}
}

View File

@ -0,0 +1,35 @@
package app.viewmodels
import app.git.DiffManager
import app.git.RefreshType
import app.git.TabState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
class CommitChangesViewModel @Inject constructor(
private val tabState: TabState,
private val diffManager: DiffManager,
) {
private val _commitChangesStatus = MutableStateFlow<CommitChangesStatus>(CommitChangesStatus.Loading)
val commitChangesStatus: StateFlow<CommitChangesStatus> = _commitChangesStatus
fun loadChanges(commit: RevCommit) = tabState.runOperation { git ->
_commitChangesStatus.value = CommitChangesStatus.Loading
val changes = diffManager.commitDiffEntries(git, commit)
_commitChangesStatus.value = CommitChangesStatus.Loaded(commit, changes)
return@runOperation RefreshType.NONE
}
}
sealed class CommitChangesStatus {
object Loading : CommitChangesStatus()
data class Loaded(val commit: RevCommit, val changes: List<DiffEntry>) : CommitChangesStatus()
}

View File

@ -1,17 +1,10 @@
package app.viewmodels
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import app.git.*
import app.git.diff.Hunk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.diff.DiffEntry
import javax.inject.Inject
@ -31,7 +24,7 @@ class DiffViewModel @Inject constructor(
)
)
suspend fun updateDiff(git: Git, diffEntryType: DiffEntryType) = withContext(Dispatchers.IO) {
fun updateDiff(diffEntryType: DiffEntryType) = tabState.runOperation { git ->
val oldDiffEntryType = _diffResult.value?.diffEntryType
_diffResult.value = null
@ -51,6 +44,8 @@ class DiffViewModel @Inject constructor(
val hunks = diffManager.diffFormat(git, diffEntryType)
_diffResult.value = DiffResult(diffEntryType, hunks)
return@runOperation RefreshType.NONE
}
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation { git ->

View File

@ -0,0 +1,41 @@
package app.viewmodels
import app.git.RefreshType
import app.git.RemoteOperationsManager
import app.git.StashManager
import app.git.TabState
import javax.inject.Inject
class MenuViewModel @Inject constructor(
private val tabState: TabState,
private val remoteOperationsManager: RemoteOperationsManager,
private val stashManager: StashManager,
) {
fun pull() = tabState.safeProcessing { git ->
remoteOperationsManager.pull(git)
return@safeProcessing RefreshType.ONLY_LOG
}
fun push() = tabState.safeProcessing { git ->
try {
remoteOperationsManager.push(git)
} catch (ex: Exception) {
ex.printStackTrace()
}
return@safeProcessing RefreshType.ONLY_LOG
}
fun stash() = tabState.safeProcessing { git ->
stashManager.stash(git)
return@safeProcessing RefreshType.UNCOMMITED_CHANGES
}
fun popStash() = tabState.safeProcessing { git ->
stashManager.popStash(git)
return@safeProcessing RefreshType.UNCOMMITED_CHANGES
}
}

View File

@ -0,0 +1,32 @@
package app.viewmodels
import app.git.StashManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
class StashesViewModel @Inject constructor(
private val stashManager: StashManager,
) {
private val _stashStatus = MutableStateFlow<StashStatus>(StashStatus.Loaded(listOf()))
val stashStatus: StateFlow<StashStatus>
get() = _stashStatus
suspend fun loadStashes(git: Git) {
_stashStatus.value = StashStatus.Loading
val stashList = stashManager.getStashList(git)
_stashStatus.value = StashStatus.Loaded(stashList.toList()) // TODO: Is the list cast necessary?
}
suspend fun refresh(git: Git) {
loadStashes(git)
}
}
sealed class StashStatus {
object Loading : StashStatus()
data class Loaded(val stashes: List<RevCommit>) : StashStatus()
}

View File

@ -26,9 +26,7 @@ class StatusViewModel @Inject constructor(
_commitMessage.value = value
}
private val _hasUncommitedChanges = MutableStateFlow<Boolean>(false)
val hasUncommitedChanges: StateFlow<Boolean>
get() = _hasUncommitedChanges
private var lastUncommitedChangesState = false
fun stage(diffEntry: DiffEntry) = tabState.runOperation { git ->
statusManager.stage(git, diffEntry)
@ -68,7 +66,7 @@ class StatusViewModel @Inject constructor(
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
suspend fun loadStatus(git: Git) {
private suspend fun loadStatus(git: Git) {
val previousStatus = _stageStatus.value
try {
@ -85,8 +83,8 @@ class StatusViewModel @Inject constructor(
}
}
suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
_hasUncommitedChanges.value = statusManager.hasUncommitedChanges(git)
private suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
lastUncommitedChangesState = statusManager.hasUncommitedChanges(git)
}
fun commit(message: String) = tabState.safeProcessing { git ->
@ -95,7 +93,6 @@ class StatusViewModel @Inject constructor(
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {
loadStatus(git)
loadHasUncommitedChanges(git)
@ -105,11 +102,12 @@ class StatusViewModel @Inject constructor(
* Checks if there are uncommited changes and returns if the state has changed (
*/
suspend fun updateHasUncommitedChanges(git: Git): Boolean {
val hadUncommitedChanges = hasUncommitedChanges.value
val hadUncommitedChanges = this.lastUncommitedChangesState
loadStatus(git)
loadHasUncommitedChanges(git)
val hasNowUncommitedChanges = hasUncommitedChanges.value
val hasNowUncommitedChanges = this.lastUncommitedChangesState
// Return true to update the log only if the uncommitedChanges status has changed
return (hasNowUncommitedChanges != hadUncommitedChanges)

View File

@ -6,12 +6,12 @@ import app.app.newErrorNow
import app.credentials.CredentialsState
import app.credentials.CredentialsStateManager
import app.git.*
import app.ui.SelectedItem
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.lib.RepositoryState
@ -28,40 +28,35 @@ class TabViewModel @Inject constructor(
val remotesViewModel: RemotesViewModel,
val statusViewModel: StatusViewModel,
val diffViewModel: DiffViewModel,
val menuViewModel: MenuViewModel,
val stashesViewModel: StashesViewModel,
val commitChangesViewModel: CommitChangesViewModel,
private val repositoryManager: RepositoryManager,
private val remoteOperationsManager: RemoteOperationsManager,
private val stashManager: StashManager,
private val diffManager: DiffManager,
private val tabState: TabState,
val errorsManager: ErrorsManager,
val appStateManager: AppStateManager,
private val fileChangesWatcher: FileChangesWatcher,
) {
val repositoryName: String
get() = safeGit.repository.directory.parentFile.name
private val _selectedItem = MutableStateFlow<SelectedItem>(SelectedItem.None)
val selectedItem: StateFlow<SelectedItem> = _selectedItem
private val credentialsStateManager = CredentialsStateManager
private val managerScope = CoroutineScope(SupervisorJob())
private val _repositorySelectionStatus = MutableStateFlow<RepositorySelectionStatus>(RepositorySelectionStatus.None)
val repositorySelectionStatus: StateFlow<RepositorySelectionStatus>
get() = _repositorySelectionStatus
private val _processing = MutableStateFlow(false)
val processing: StateFlow<Boolean>
get() = _processing
val processing: StateFlow<Boolean> = tabState.processing
val stashStatus: StateFlow<StashStatus> = stashManager.stashStatus
val credentialsState: StateFlow<CredentialsState> = credentialsStateManager.credentialsState
val cloneStatus: StateFlow<CloneStatus> = remoteOperationsManager.cloneStatus
private val _diffSelected = MutableStateFlow<DiffEntryType?>(null)
val diffSelected : StateFlow<DiffEntryType?> = _diffSelected
val diffSelected: StateFlow<DiffEntryType?> = _diffSelected
var newDiffSelected: DiffEntryType?
get() = diffSelected.value
set(value){
set(value) {
_diffSelected.value = value
updateDiffEntry()
@ -71,13 +66,13 @@ class TabViewModel @Inject constructor(
val repositoryState: StateFlow<RepositoryState> = _repositoryState
init {
managerScope.launch {
tabState.managerScope.launch {
tabState.refreshData.collect { refreshType ->
when (refreshType) {
RefreshType.NONE -> println("Not refreshing...")
RefreshType.ALL_DATA -> refreshRepositoryInfo()
RefreshType.ONLY_LOG -> refreshLog()
RefreshType.UNCOMMITED_CHANGES -> checkUncommitedChanges()
RefreshType.UNCOMMITED_CHANGES -> checkUncommitedChanges(false)
}
}
}
@ -89,146 +84,94 @@ class TabViewModel @Inject constructor(
return@runOperation RefreshType.NONE
}
/**
* Property that indicates if a git operation is running
*/
@set:Synchronized private var operationRunning = false
private val safeGit: Git
get() {
val git = this.tabState.git
if (git == null) {
_repositorySelectionStatus.value = RepositorySelectionStatus.None
throw CancellationException()
} else
return git
}
fun openRepository(directory: String) {
openRepository(File(directory))
}
fun openRepository(directory: File) = managerScope.launch(Dispatchers.IO) {
safeProcessing {
println("Trying to open repository ${directory.absoluteFile}")
fun openRepository(directory: File) = tabState.safeProcessingWihoutGit {
println("Trying to open repository ${directory.absoluteFile}")
val gitDirectory = if (directory.name == ".git") {
val gitDirectory = if (directory.name == ".git") {
directory
} else {
val gitDir = File(directory, ".git")
if (gitDir.exists() && gitDir.isDirectory) {
gitDir
} else
directory
} else {
val gitDir = File(directory, ".git")
if (gitDir.exists() && gitDir.isDirectory) {
gitDir
} else
directory
}
val builder = FileRepositoryBuilder()
val repository: Repository = builder.setGitDir(gitDirectory)
.readEnvironment() // scan environment GIT_* variables
.findGitDir() // scan up the file system tree
.build()
try {
repository.workTree // test if repository is valid
_repositorySelectionStatus.value = RepositorySelectionStatus.Open(repository)
tabState.git = Git(repository)
onRepositoryChanged(repository.directory.parent)
refreshRepositoryInfo()
launch {
watchRepositoryChanges()
}
println("AppStateManagerReference $appStateManager")
} catch (ex: Exception) {
ex.printStackTrace()
onRepositoryChanged(null)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
}
}
val builder = FileRepositoryBuilder()
val repository: Repository = builder.setGitDir(gitDirectory)
.readEnvironment() // scan environment GIT_* variables
.findGitDir() // scan up the file system tree
.build()
try {
repository.workTree // test if repository is valid
_repositorySelectionStatus.value = RepositorySelectionStatus.Open(repository)
val git = Git(repository)
tabState.git = git
onRepositoryChanged(repository.directory.parent)
refreshRepositoryInfo()
watchRepositoryChanges(git)
} catch (ex: Exception) {
ex.printStackTrace()
onRepositoryChanged(null)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
}
return@safeProcessingWihoutGit RefreshType.NONE
}
suspend fun loadRepositoryState(git: Git) = withContext(Dispatchers.IO) {
private suspend fun loadRepositoryState(git: Git) = withContext(Dispatchers.IO) {
_repositoryState.value = repositoryManager.getRepositoryState(git)
}
private suspend fun watchRepositoryChanges() {
val ignored = safeGit.status().call().ignoredNotInIndex.toList()
private suspend fun watchRepositoryChanges(git: Git) = tabState.managerScope.launch(Dispatchers.IO) {
val ignored = git.status().call().ignoredNotInIndex.toList()
fileChangesWatcher.watchDirectoryPath(
pathStr = safeGit.repository.directory.parent,
pathStr = git.repository.directory.parent,
ignoredDirsPath = ignored,
).collect {
if (!operationRunning) { // Only update if there isn't any process running
safeProcessing(showError = false) {
println("Changes detected, loading status")
statusViewModel.refresh(safeGit)
checkUncommitedChanges()
if (!tabState.operationRunning) { // Only update if there isn't any process running
println("Changes detected, loading status")
checkUncommitedChanges(isFsChange = true)
updateDiffEntry()
}
updateDiffEntry()
}
}
}
private suspend fun loadLog() {
logViewModel.loadLog(safeGit)
}
suspend fun checkUncommitedChanges() {
val uncommitedChangesStateChanged = statusViewModel.updateHasUncommitedChanges(safeGit)
private suspend fun checkUncommitedChanges(isFsChange: Boolean = false) = tabState.runOperation { git ->
val uncommitedChangesStateChanged = statusViewModel.updateHasUncommitedChanges(git)
// Update the log only if the uncommitedChanges status has changed
if (uncommitedChangesStateChanged)
loadLog()
if ((uncommitedChangesStateChanged && isFsChange) || !isFsChange)
logViewModel.refresh(git)
updateDiffEntry()
// Stashes list should only be updated if we are doing a stash operation, however it's a small operation
// that we can afford to do when doing other operations
stashesViewModel.refresh(git)
return@runOperation RefreshType.NONE
}
fun pull() = managerScope.launch {
safeProcessing {
remoteOperationsManager.pull(safeGit)
loadLog()
}
}
private suspend fun refreshRepositoryInfo() = tabState.safeProcessing { git ->
logViewModel.refresh(git)
branchesViewModel.refresh(git)
remotesViewModel.refresh(git)
tagsViewModel.refresh(git)
statusViewModel.refresh(git)
stashesViewModel.refresh(git)
loadRepositoryState(git)
fun push() = managerScope.launch {
safeProcessing {
try {
remoteOperationsManager.push(safeGit)
} finally {
loadLog()
}
}
}
private suspend fun refreshRepositoryInfo() {
logViewModel.refresh(safeGit)
branchesViewModel.refresh(safeGit)
remotesViewModel.refresh(safeGit)
tagsViewModel.refresh(safeGit)
statusViewModel.refresh(safeGit)
loadRepositoryState(safeGit)
stashManager.loadStashList(safeGit)
loadLog()
}
fun stash() = managerScope.launch {
safeProcessing {
stashManager.stash(safeGit)
checkUncommitedChanges()
loadLog()
}
}
fun popStash() = managerScope.launch {
safeProcessing {
stashManager.popStash(safeGit)
checkUncommitedChanges()
loadLog()
}
return@safeProcessing RefreshType.NONE
}
fun credentialsDenied() {
@ -243,51 +186,54 @@ class TabViewModel @Inject constructor(
credentialsStateManager.updateState(CredentialsState.SshCredentialsAccepted(password))
}
suspend fun diffListFromCommit(commit: RevCommit): List<DiffEntry> {
return diffManager.commitDiffEntries(safeGit, commit)
}
var onRepositoryChanged: (path: String?) -> Unit = {}
fun dispose() {
managerScope.cancel()
tabState.managerScope.cancel()
}
fun clone(directory: File, url: String) = managerScope.launch {
fun clone(directory: File, url: String) = tabState.safeProcessingWihoutGit {
remoteOperationsManager.clone(directory, url)
return@safeProcessingWihoutGit RefreshType.NONE
}
fun findCommit(objectId: ObjectId): RevCommit {
return safeGit.repository.parseCommit(objectId)
private fun findCommit(git: Git, objectId: ObjectId): RevCommit {
return git.repository.parseCommit(objectId)
}
private suspend fun safeProcessing(showError: Boolean = true, callback: suspend () -> Unit) {
_processing.value = true
operationRunning = true
try {
callback()
} catch (ex: Exception) {
ex.printStackTrace()
if (showError)
errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
} finally {
_processing.value = false
operationRunning = false
}
}
fun updateDiffEntry() = tabState.runOperation { git ->
private fun updateDiffEntry() {
val diffSelected = diffSelected.value
if(diffSelected != null) {
diffViewModel.updateDiff(git, diffSelected)
if (diffSelected != null) {
diffViewModel.updateDiff(diffSelected)
}
}
fun newSelectedRef(objectId: ObjectId?) = tabState.runOperation { git ->
if(objectId == null) {
newSelectedItem(SelectedItem.None)
return@runOperation RefreshType.NONE
}
val commit = findCommit(git, objectId)
newSelectedItem(SelectedItem.Ref(commit))
return@runOperation RefreshType.NONE
}
fun newSelectedStash(stash: RevCommit) {
newSelectedItem(SelectedItem.Stash(stash))
}
fun newSelectedItem(selectedItem: SelectedItem) {
_selectedItem.value = selectedItem
if(selectedItem is SelectedItem.CommitBasedItem) {
commitChangesViewModel.loadChanges(selectedItem.revCommit)
}
}
}

View File

@ -21,7 +21,7 @@ class TagsViewModel @Inject constructor(
val tags: StateFlow<List<Ref>>
get() = _tags
suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) {
private suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) {
val tagsList = tagsManager.getTags(git)
_tags.value = tagsList