Creation of multiple viewmodels that hold data state

This commit is contained in:
Abdelilah El Aissaoui 2022-01-03 21:39:53 +01:00
parent 997a651faf
commit 9c53ce726e
33 changed files with 846 additions and 485 deletions

View File

@ -44,7 +44,7 @@ tasks.withType<KotlinCompile>() {
compose.desktop { compose.desktop {
application { application {
mainClass = "MainKt" mainClass = "MainKt"
//
nativeDistributions { nativeDistributions {
includeAllModules = true includeAllModules = true
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.AppImage) targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.AppImage)

View File

@ -24,21 +24,15 @@ import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import app.di.DaggerAppComponent import app.di.DaggerAppComponent
import app.git.GitManager
import app.theme.AppTheme import app.theme.AppTheme
import app.ui.AppTab
import app.ui.components.RepositoriesTabPanel import app.ui.components.RepositoriesTabPanel
import app.ui.components.TabInformation import app.ui.components.TabInformation
import app.ui.dialogs.SettingsDialog import app.ui.dialogs.SettingsDialog
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
class Main { class Main {
private val appComponent = DaggerAppComponent.create() private val appComponent = DaggerAppComponent.create()
@Inject
lateinit var gitManagerProvider: Provider<GitManager>
@Inject @Inject
lateinit var appStateManager: AppStateManager lateinit var appStateManager: AppStateManager
@ -47,7 +41,7 @@ class Main {
init { init {
appComponent.inject(this) appComponent.inject(this)
println("AppStateManagerReference $appStateManager")
appStateManager.loadRepositoriesTabs() appStateManager.loadRepositoriesTabs()
} }
@ -200,19 +194,11 @@ class Main {
): TabInformation { ): TabInformation {
return TabInformation( return TabInformation(
title = tabName, tabName = tabName,
key = key key = key,
) { path = path,
val gitManager = remember { gitManagerProvider.get() } appComponent = appComponent,
gitManager.onRepositoryChanged = { path -> )
if (path == null) {
appStateManager.repositoryTabRemoved(key)
} else
appStateManager.repositoryTabChanged(key, path)
}
AppTab(gitManager, path, tabName)
}
} }
} }

View File

@ -1,5 +1,6 @@
package app.di package app.di
import app.AppStateManager
import app.Main import app.Main
import dagger.Component import dagger.Component
import javax.inject.Singleton import javax.inject.Singleton
@ -8,4 +9,5 @@ import javax.inject.Singleton
@Component @Component
interface AppComponent { interface AppComponent {
fun inject(main: Main) fun inject(main: Main)
fun appStateManager(): AppStateManager
} }

View File

@ -0,0 +1,10 @@
package app.di
import app.ui.components.TabInformation
import dagger.Component
@TabScope
@Component(dependencies = [ AppComponent::class ])
interface TabComponent {
fun inject(tabInformation: TabInformation)
}

View File

@ -0,0 +1,7 @@
package app.di
import javax.inject.Scope
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class TabScope

View File

@ -1,9 +1,12 @@
package app.git package app.git
import app.extensions.isBranch
import app.extensions.simpleName
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.CreateBranchCommand
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.ListBranchCommand import org.eclipse.jgit.api.ListBranchCommand
import org.eclipse.jgit.api.MergeCommand import org.eclipse.jgit.api.MergeCommand
@ -32,17 +35,6 @@ class BranchesManager @Inject constructor() {
return branchList.firstOrNull { it.name == branchName } return branchList.firstOrNull { it.name == branchName }
} }
suspend fun loadBranches(git: Git) = withContext(Dispatchers.IO) {
val branchList = getBranches(git)
val branchName = git
.repository
.fullBranch
_branches.value = branchList
_currentBranch.value = branchName
}
suspend fun getBranches(git: Git) = withContext(Dispatchers.IO) { suspend fun getBranches(git: Git) = withContext(Dispatchers.IO) {
return@withContext git return@withContext git
.branchList() .branchList()
@ -55,8 +47,6 @@ class BranchesManager @Inject constructor() {
.setCreateBranch(true) .setCreateBranch(true)
.setName(branchName) .setName(branchName)
.call() .call()
loadBranches(git)
} }
suspend fun createBranchOnCommit(git: Git, branch: String, revCommit: RevCommit) = withContext(Dispatchers.IO) { suspend fun createBranchOnCommit(git: Git, branch: String, revCommit: RevCommit) = withContext(Dispatchers.IO) {
@ -95,4 +85,17 @@ class BranchesManager @Inject constructor() {
.setListMode(ListBranchCommand.ListMode.REMOTE) .setListMode(ListBranchCommand.ListMode.REMOTE)
.call() .call()
} }
suspend fun checkoutRef(git: Git, ref: Ref) = withContext(Dispatchers.IO) {
git.checkout().apply {
setName(ref.name)
if (ref.isBranch && ref.name.startsWith("refs/remotes/")) {
setCreateBranch(true)
setName(ref.simpleName)
setStartPoint(ref.objectId.name)
setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
}
call()
}
}
} }

View File

@ -21,17 +21,8 @@ import javax.inject.Inject
class LogManager @Inject constructor( class LogManager @Inject constructor(
private val statusManager: StatusManager, private val statusManager: StatusManager,
private val branchesManager: BranchesManager,
) { ) {
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading) suspend fun loadLog(git: Git, currentBranch: Ref?) = withContext(Dispatchers.IO) {
val logStatus: StateFlow<LogStatus>
get() = _logStatus
suspend fun loadLog(git: Git) = withContext(Dispatchers.IO) {
_logStatus.value = LogStatus.Loading
val currentBranch = branchesManager.currentBranchRef(git)
val commitList = GraphCommitList() val commitList = GraphCommitList()
val repositoryState = git.repository.repositoryState val repositoryState = git.repository.repositoryState
@ -45,7 +36,7 @@ class LogManager @Inject constructor(
walk.markStartAllRefs(Constants.R_REMOTES) walk.markStartAllRefs(Constants.R_REMOTES)
walk.markStartAllRefs(Constants.R_TAGS) walk.markStartAllRefs(Constants.R_TAGS)
if (statusManager.checkHasUncommitedChanges(git)) if (statusManager.hasUncommitedChanges(git))
commitList.addUncommitedChangesGraphCommit(logList.first()) commitList.addUncommitedChangesGraphCommit(logList.first())
commitList.source(walk) commitList.source(walk)
@ -55,9 +46,8 @@ class LogManager @Inject constructor(
ensureActive() ensureActive()
} }
val loadedStatus = LogStatus.Loaded(commitList, currentBranch)
_logStatus.value = loadedStatus return@withContext commitList
} }
suspend fun checkoutCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) { suspend fun checkoutCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) {
@ -67,19 +57,6 @@ class LogManager @Inject constructor(
.call() .call()
} }
suspend fun checkoutRef(git: Git, ref: Ref) = withContext(Dispatchers.IO) {
git.checkout().apply {
setName(ref.name)
if (ref.isBranch && ref.name.startsWith("refs/remotes/")) {
setCreateBranch(true)
setName(ref.simpleName)
setStartPoint(ref.objectId.name)
setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
}
call()
}
}
suspend fun revertCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) { suspend fun revertCommit(git: Git, revCommit: RevCommit) = withContext(Dispatchers.IO) {
git git
.revert() .revert()
@ -107,8 +84,3 @@ enum class ResetType {
MIXED, MIXED,
HARD, HARD,
} }
sealed class LogStatus {
object Loading : LogStatus()
class Loaded(val plotCommitList: GraphCommitList, val currentBranch: Ref?) : LogStatus()
}

View File

@ -10,22 +10,18 @@ import org.eclipse.jgit.transport.RemoteConfig
import javax.inject.Inject import javax.inject.Inject
class RemotesManager @Inject constructor() { class RemotesManager @Inject constructor() {
private val _remotes = MutableStateFlow<List<RemoteInfo>>(listOf())
val remotes: StateFlow<List<RemoteInfo>>
get() = _remotes
suspend fun loadRemotes(git: Git, allRemoteBranches: List<Ref>) = withContext(Dispatchers.IO) { suspend fun loadRemotes(git: Git, allRemoteBranches: List<Ref>) = withContext(Dispatchers.IO) {
val remotes = git.remoteList() val remotes = git.remoteList()
.call() .call()
val remoteInfoList = remotes.map { remoteConfig -> return@withContext remotes.map { remoteConfig ->
val remoteBranches = allRemoteBranches.filter { branch -> val remoteBranches = allRemoteBranches.filter { branch ->
branch.name.startsWith("refs/remotes/${remoteConfig.name}") branch.name.startsWith("refs/remotes/${remoteConfig.name}")
} }
RemoteInfo(remoteConfig, remoteBranches) RemoteInfo(remoteConfig, remoteBranches)
} }
_remotes.value = remoteInfoList
} }
} }

View File

@ -0,0 +1,12 @@
package app.git
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import javax.inject.Inject
class RepositoryManager @Inject constructor() {
suspend fun getRepositoryState(git: Git) = withContext(Dispatchers.IO) {
return@withContext git.repository.repositoryState
}
}

View File

@ -12,7 +12,6 @@ import app.git.diff.Hunk
import app.git.diff.LineType import app.git.diff.LineType
import app.theme.conflictFile import app.theme.conflictFile
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -31,24 +30,9 @@ import javax.inject.Inject
class StatusManager @Inject constructor( class StatusManager @Inject constructor(
private val branchesManager: BranchesManager,
private val rawFileManagerFactory: RawFileManagerFactory, private val rawFileManagerFactory: RawFileManagerFactory,
) { ) {
private val _stageStatus = MutableStateFlow<StageStatus>(StageStatus.Loaded(listOf(), listOf())) suspend fun hasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
val stageStatus: StateFlow<StageStatus> = _stageStatus
private val _repositoryState = MutableStateFlow(RepositoryState.SAFE)
val repositoryState: StateFlow<RepositoryState> = _repositoryState
private val _hasUncommitedChanges = MutableStateFlow<Boolean>(false)
val hasUncommitedChanges: StateFlow<Boolean>
get() = _hasUncommitedChanges
suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
_hasUncommitedChanges.value = checkHasUncommitedChanges(git)
}
suspend fun checkHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
val status = git val status = git
.status() .status()
.call() .call()
@ -56,77 +40,6 @@ class StatusManager @Inject constructor(
return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges() return@withContext status.hasUncommittedChanges() || status.hasUntrackedChanges()
} }
suspend fun loadRepositoryStatus(git: Git) = withContext(Dispatchers.IO) {
_repositoryState.value = git.repository.repositoryState
}
suspend fun loadStatus(git: Git) = withContext(Dispatchers.IO) {
val previousStatus = _stageStatus.value
_stageStatus.value = StageStatus.Loading
try {
loadRepositoryStatus(git)
loadHasUncommitedChanges(git)
val currentBranch = branchesManager.currentBranchRef(git)
val repositoryState = git.repository.repositoryState
val staged = git
.diff()
.setShowNameAndStatusOnly(true).apply {
if (currentBranch == null && !repositoryState.isMerging && !repositoryState.isRebasing)
setOldTree(EmptyTreeIterator()) // Required if the repository is empty
setCached(true)
}
.call()
// TODO: Grouping and fitlering allows us to remove duplicates when conflicts appear, requires more testing (what happens in windows? /dev/null is a unix thing)
// TODO: Test if we should group by old path or new path
.groupBy {
if(it.newPath != "/dev/null")
it.newPath
else
it.oldPath
}
.map {
val entries = it.value
val hasConflicts =
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
StatusEntry(entries.first(), isConflict = hasConflicts)
}
ensureActive()
val unstaged = git
.diff()
.setShowNameAndStatusOnly(true)
.call()
.groupBy {
if(it.oldPath != "/dev/null")
it.oldPath
else
it.newPath
}
.map {
val entries = it.value
val hasConflicts =
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
StatusEntry(entries.first(), isConflict = hasConflicts)
}
ensureActive()
_stageStatus.value = StageStatus.Loaded(staged, unstaged)
} catch (ex: Exception) {
_stageStatus.value = previousStatus
throw ex
}
}
suspend fun stage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) { suspend fun stage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) {
if (diffEntry.changeType == DiffEntry.ChangeType.DELETE) { if (diffEntry.changeType == DiffEntry.ChangeType.DELETE) {
git.rm() git.rm()
@ -137,8 +50,6 @@ class StatusManager @Inject constructor(
.addFilepattern(diffEntry.filePath) .addFilepattern(diffEntry.filePath)
.call() .call()
} }
loadStatus(git)
} }
suspend fun stageHunk(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) { suspend fun stageHunk(git: Git, diffEntry: DiffEntry, hunk: Hunk) = withContext(Dispatchers.IO) {
@ -175,7 +86,7 @@ class StatusManager @Inject constructor(
completedWithErrors = false completedWithErrors = false
loadStatus(git) // loadStatus(git)
} finally { } finally {
if (completedWithErrors) if (completedWithErrors)
dirCache.unlock() dirCache.unlock()
@ -226,7 +137,7 @@ class StatusManager @Inject constructor(
completedWithErrors = false completedWithErrors = false
loadStatus(git) // loadStatus(git)
} finally { } finally {
if (completedWithErrors) if (completedWithErrors)
dirCache.unlock() dirCache.unlock()
@ -271,8 +182,6 @@ class StatusManager @Inject constructor(
git.reset() git.reset()
.addPath(diffEntry.filePath) .addPath(diffEntry.filePath)
.call() .call()
loadStatus(git)
} }
suspend fun commit(git: Git, message: String) = withContext(Dispatchers.IO) { suspend fun commit(git: Git, message: String) = withContext(Dispatchers.IO) {
@ -280,8 +189,6 @@ class StatusManager @Inject constructor(
.setMessage(message) .setMessage(message)
.setAllowEmpty(false) .setAllowEmpty(false)
.call() .call()
loadStatus(git)
} }
suspend fun reset(git: Git, diffEntry: DiffEntry, staged: Boolean) = withContext(Dispatchers.IO) { suspend fun reset(git: Git, diffEntry: DiffEntry, staged: Boolean) = withContext(Dispatchers.IO) {
@ -297,15 +204,13 @@ class StatusManager @Inject constructor(
.addPath(diffEntry.filePath) .addPath(diffEntry.filePath)
.call() .call()
loadStatus(git) // loadStatus(git)
} }
suspend fun unstageAll(git: Git) = withContext(Dispatchers.IO) { suspend fun unstageAll(git: Git) = withContext(Dispatchers.IO) {
git git
.reset() .reset()
.call() .call()
loadStatus(git)
} }
suspend fun stageAll(git: Git) = withContext(Dispatchers.IO) { suspend fun stageAll(git: Git) = withContext(Dispatchers.IO) {
@ -313,15 +218,58 @@ class StatusManager @Inject constructor(
.add() .add()
.addFilepattern(".") .addFilepattern(".")
.call() .call()
}
loadStatus(git) suspend fun getStaged(git: Git, currentBranch: Ref?, repositoryState: RepositoryState) = withContext(Dispatchers.IO) {
return@withContext git
.diff()
.setShowNameAndStatusOnly(true).apply {
if (currentBranch == null && !repositoryState.isMerging && !repositoryState.isRebasing)
setOldTree(EmptyTreeIterator()) // Required if the repository is empty
setCached(true)
}
.call()
// TODO: Grouping and fitlering allows us to remove duplicates when conflicts appear, requires more testing (what happens in windows? /dev/null is a unix thing)
// TODO: Test if we should group by old path or new path
.groupBy {
if(it.newPath != "/dev/null")
it.newPath
else
it.oldPath
}
.map {
val entries = it.value
val hasConflicts =
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
StatusEntry(entries.first(), isConflict = hasConflicts)
} }
} }
sealed class StageStatus { suspend fun getUnstaged(git: Git, repositoryState: RepositoryState) = withContext(Dispatchers.IO) {
object Loading : StageStatus() return@withContext git
data class Loaded(val staged: List<StatusEntry>, val unstaged: List<StatusEntry>) : StageStatus() .diff()
.setShowNameAndStatusOnly(true)
.call()
.groupBy {
if(it.oldPath != "/dev/null")
it.oldPath
else
it.newPath
} }
.map {
val entries = it.value
val hasConflicts =
(entries.count() > 1 && (repositoryState.isMerging || repositoryState.isRebasing))
StatusEntry(entries.first(), isConflict = hasConflicts)
}
}
}
data class StatusEntry(val diffEntry: DiffEntry, val isConflict: Boolean) { data class StatusEntry(val diffEntry: DiffEntry, val isConflict: Boolean) {
val icon: ImageVector val icon: ImageVector

View File

@ -0,0 +1,100 @@
package app.git
import app.app.Error
import app.app.newErrorNow
import app.di.TabScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.eclipse.jgit.api.Git
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
@TabScope
class TabState @Inject constructor() {
var git: Git? = null
val safeGit: Git
get() {
val git = this.git
if (git == null) {
// _repositorySelectionStatus.value = RepositorySelectionStatus.None
throw CancellationException("Null git object")
} else
return git
}
val mutex = Mutex()
private val _refreshData = MutableSharedFlow<RefreshType>()
val refreshData: Flow<RefreshType> = _refreshData
suspend fun refreshData(refreshType: RefreshType) = _refreshData.emit(refreshType)
private val _errors = MutableSharedFlow<Error>()
val errors: Flow<Error> = _errors
val managerScope = CoroutineScope(SupervisorJob())
/**
* Property that indicates if a git operation is running
*/
@set:Synchronized
var operationRunning = false
private val _processing = MutableStateFlow(false)
val processing: StateFlow<Boolean>
get() = _processing
fun safeProcessing(showError: Boolean = true, callback: suspend (git: Git) -> RefreshType) =
managerScope.launch {
mutex.withLock {
_processing.value = true
operationRunning = true
try {
val refreshType = callback(safeGit)
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 {
operationRunning = true
try {
val refreshType = block(safeGit)
if (refreshType != RefreshType.NONE)
_refreshData.emit(refreshType)
} finally {
operationRunning = false
}
}
}
enum class RefreshType {
NONE,
ALL_DATA,
ONLY_LOG,
/**
* Requires to update the status if currently selected and update the log if there has been a change
* in the "uncommited changes" state (if there were changes before but not anymore and vice-versa)
*/
UNCOMMITED_CHANGES,
}

View File

@ -10,16 +10,8 @@ import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject import javax.inject.Inject
class TagsManager @Inject constructor() { class TagsManager @Inject constructor() {
suspend fun getTags(git: Git) = withContext(Dispatchers.IO) {
private val _tags = MutableStateFlow<List<Ref>>(listOf()) return@withContext git.tagList().call()
val tags: StateFlow<List<Ref>>
get() = _tags
suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) {
val branchList = git.tagList().call()
_tags.value = branchList
} }
suspend fun createTagOnCommit(git: Git, tag: String, revCommit: RevCommit) = withContext(Dispatchers.IO) { suspend fun createTagOnCommit(git: Git, tag: String, revCommit: RevCommit) = withContext(Dispatchers.IO) {

View File

@ -21,7 +21,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.LoadingRepository import app.LoadingRepository
import app.credentials.CredentialsState import app.credentials.CredentialsState
import app.git.GitManager import app.git.TabViewModel
import app.git.RepositorySelectionStatus import app.git.RepositorySelectionStatus
import app.ui.dialogs.PasswordDialog import app.ui.dialogs.PasswordDialog
import app.ui.dialogs.UserPasswordDialog import app.ui.dialogs.UserPasswordDialog
@ -30,7 +30,7 @@ import kotlinx.coroutines.delay
@OptIn(ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
@Composable @Composable
fun AppTab( fun AppTab(
gitManager: GitManager, gitManager: TabViewModel,
repositoryPath: String?, repositoryPath: String?,
tabName: MutableState<String> tabName: MutableState<String>
) { ) {
@ -101,7 +101,7 @@ fun AppTab(
LoadingRepository() LoadingRepository()
} }
is RepositorySelectionStatus.Open -> { is RepositorySelectionStatus.Open -> {
RepositoryOpenPage(gitManager = gitManager) RepositoryOpenPage(tabViewModel = gitManager)
} }
} }
} }
@ -158,7 +158,7 @@ fun AppTab(
} }
@Composable @Composable
fun CredentialsDialog(gitManager: GitManager) { fun CredentialsDialog(gitManager: TabViewModel) {
val credentialsState by gitManager.credentialsState.collectAsState() val credentialsState by gitManager.credentialsState.collectAsState()
if (credentialsState == CredentialsState.HttpCredentialsRequested) { if (credentialsState == CredentialsState.HttpCredentialsRequested) {

View File

@ -13,23 +13,23 @@ import androidx.compose.ui.unit.dp
import app.MAX_SIDE_PANEL_ITEMS_HEIGHT import app.MAX_SIDE_PANEL_ITEMS_HEIGHT
import app.extensions.isLocal import app.extensions.isLocal
import app.extensions.simpleName import app.extensions.simpleName
import app.git.GitManager import app.git.TabViewModel
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.ui.components.SideMenuEntry import app.ui.components.SideMenuEntry
import app.ui.components.SideMenuSubentry import app.ui.components.SideMenuSubentry
import app.ui.components.entryHeight import app.ui.components.entryHeight
import app.ui.context_menu.branchContextMenuItems import app.ui.context_menu.branchContextMenuItems
import app.ui.dialogs.MergeDialog import app.ui.dialogs.MergeDialog
import app.viewmodels.BranchesViewModel
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
@Composable @Composable
fun Branches( fun Branches(
gitManager: GitManager, branchesViewModel: BranchesViewModel,
onBranchClicked: (Ref) -> Unit, onBranchClicked: (Ref) -> Unit,
) { ) {
val branches by gitManager.branches.collectAsState() val branches by branchesViewModel.branches.collectAsState()
val currentBranch by gitManager.currentBranch.collectAsState() val currentBranch by branchesViewModel.currentBranch.collectAsState()
val (mergeBranch, setMergeBranch) = remember { mutableStateOf<Ref?>(null) } val (mergeBranch, setMergeBranch) = remember { mutableStateOf<Ref?>(null) }
Column { Column {
@ -48,9 +48,9 @@ fun Branches(
branch = branch, branch = branch,
isCurrentBranch = currentBranch == branch.name, isCurrentBranch = currentBranch == branch.name,
onBranchClicked = { onBranchClicked(branch) }, onBranchClicked = { onBranchClicked(branch) },
onCheckoutBranch = { gitManager.checkoutRef(branch) }, onCheckoutBranch = { branchesViewModel.checkoutRef(branch) },
onMergeBranch = { setMergeBranch(branch) }, onMergeBranch = { setMergeBranch(branch) },
onDeleteBranch = { gitManager.deleteBranch(branch) }, onDeleteBranch = { branchesViewModel.deleteBranch(branch) },
) )
} }
} }
@ -62,7 +62,7 @@ fun Branches(
currentBranch, currentBranch,
mergeBranchName = mergeBranch.name, mergeBranchName = mergeBranch.name,
onReject = { setMergeBranch(null) }, onReject = { setMergeBranch(null) },
onAccept = { ff -> gitManager.mergeBranch(mergeBranch, ff) } onAccept = { ff -> branchesViewModel.mergeBranch(mergeBranch, ff) }
) )
} }
} }

View File

@ -3,7 +3,6 @@ package app.ui
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Divider import androidx.compose.material.Divider
import androidx.compose.material.Icon import androidx.compose.material.Icon
@ -12,14 +11,13 @@ import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.extensions.* import app.extensions.*
import app.git.GitManager import app.git.TabViewModel
import app.theme.headerBackground import app.theme.headerBackground
import app.theme.headerText import app.theme.headerText
import app.theme.primaryTextColor import app.theme.primaryTextColor
@ -32,7 +30,7 @@ import org.eclipse.jgit.revwalk.RevCommit
@Composable @Composable
fun CommitChanges( fun CommitChanges(
gitManager: GitManager, gitManager: TabViewModel,
commit: RevCommit, commit: RevCommit,
onDiffSelected: (DiffEntry) -> Unit onDiffSelected: (DiffEntry) -> Unit
) { ) {

View File

@ -16,24 +16,25 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.git.DiffEntryType import app.git.DiffEntryType
import app.git.GitManager import app.git.TabViewModel
import app.git.diff.Hunk import app.git.diff.Hunk
import app.git.diff.LineType import app.git.diff.LineType
import app.theme.primaryTextColor import app.theme.primaryTextColor
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.ui.components.SecondaryButton import app.ui.components.SecondaryButton
import app.viewmodels.DiffViewModel
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
@Composable @Composable
fun Diff(gitManager: GitManager, diffEntryType: DiffEntryType, onCloseDiffView: () -> Unit) { fun Diff(
var text by remember { mutableStateOf(listOf<Hunk>()) } diffViewModel: DiffViewModel,
onCloseDiffView: () -> Unit,
) {
val diffResultState = diffViewModel.diffResult.collectAsState()
val diffResult = diffResultState.value ?: return
LaunchedEffect(Unit) { val diffEntryType = diffResult.diffEntryType
text = gitManager.diffFormat(diffEntryType) val hunks = diffResult.hunks
if (text.isEmpty()) onCloseDiffView()
}
Column( Column(
modifier = Modifier modifier = Modifier
@ -59,7 +60,7 @@ fun Diff(gitManager: GitManager, diffEntryType: DiffEntryType, onCloseDiffView:
.fillMaxSize() .fillMaxSize()
// .padding(16.dp) // .padding(16.dp)
) { ) {
itemsIndexed(text) { index, hunk -> itemsIndexed(hunks) { index, hunk ->
val hunksSeparation = if (index == 0) val hunksSeparation = if (index == 0)
0.dp 0.dp
else else
@ -96,9 +97,9 @@ fun Diff(gitManager: GitManager, diffEntryType: DiffEntryType, onCloseDiffView:
backgroundButton = color, backgroundButton = color,
onClick = { onClick = {
if (diffEntryType is DiffEntryType.StagedDiff) { if (diffEntryType is DiffEntryType.StagedDiff) {
gitManager.unstageHunk(diffEntryType.diffEntry, hunk) diffViewModel.unstageHunk(diffEntryType.diffEntry, hunk)
} else { } else {
gitManager.stageHunk(diffEntryType.diffEntry, hunk) diffViewModel.stageHunk(diffEntryType.diffEntry, hunk)
} }
} }
) )

View File

@ -12,16 +12,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.MAX_SIDE_PANEL_ITEMS_HEIGHT import app.MAX_SIDE_PANEL_ITEMS_HEIGHT
import app.extensions.simpleVisibleName import app.extensions.simpleVisibleName
import app.git.GitManager import app.git.TabViewModel
import app.git.RemoteInfo import app.git.RemoteInfo
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.ui.components.SideMenuEntry import app.ui.components.SideMenuEntry
import app.ui.components.SideMenuSubentry import app.ui.components.SideMenuSubentry
import app.ui.components.entryHeight import app.ui.components.entryHeight
import app.viewmodels.RemotesViewModel
@Composable @Composable
fun Remotes(gitManager: GitManager) { fun Remotes(remotesViewModel: RemotesViewModel) {
val remotes by gitManager.remotes.collectAsState() val remotes by remotesViewModel.remotes.collectAsState()
Column { Column {
SideMenuEntry("Remotes") SideMenuEntry("Remotes")

View File

@ -6,7 +6,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.git.DiffEntryType import app.git.DiffEntryType
import app.git.GitManager import app.git.TabViewModel
import app.ui.dialogs.NewBranchDialog import app.ui.dialogs.NewBranchDialog
import app.ui.log.Log import app.ui.log.Log
import openRepositoryDialog import openRepositoryDialog
@ -18,15 +18,22 @@ import org.jetbrains.compose.splitpane.rememberSplitPaneState
@OptIn(ExperimentalSplitPaneApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class) @OptIn(ExperimentalSplitPaneApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class)
@Composable @Composable
fun RepositoryOpenPage(gitManager: GitManager) { fun RepositoryOpenPage(tabViewModel: TabViewModel) {
val repositoryState by tabViewModel.repositoryState.collectAsState()
var diffSelected by remember { var diffSelected by remember {
mutableStateOf<DiffEntryType?>(null) mutableStateOf<DiffEntryType?>(null)
} }
LaunchedEffect(diffSelected) {
diffSelected?.let { safeDiffSelected ->
tabViewModel.updatedDiffEntry(safeDiffSelected)
}
}
var showNewBranchDialog by remember { mutableStateOf(false) } var showNewBranchDialog by remember { mutableStateOf(false) }
val (selectedItem, setSelectedItem) = remember { mutableStateOf<SelectedItem>(SelectedItem.None) } val (selectedItem, setSelectedItem) = remember { mutableStateOf<SelectedItem>(SelectedItem.None) }
LaunchedEffect(selectedItem) { LaunchedEffect(selectedItem) {
diffSelected = null diffSelected = null
} }
@ -37,7 +44,7 @@ fun RepositoryOpenPage(gitManager: GitManager) {
showNewBranchDialog = false showNewBranchDialog = false
}, },
onAccept = { branchName -> onAccept = { branchName ->
gitManager.createBranch(branchName) tabViewModel.branchesViewModel.createBranch(branchName)
showNewBranchDialog = false showNewBranchDialog = false
} }
) )
@ -46,12 +53,12 @@ fun RepositoryOpenPage(gitManager: GitManager) {
Column { Column {
GMenu( GMenu(
onRepositoryOpen = { onRepositoryOpen = {
openRepositoryDialog(gitManager = gitManager) openRepositoryDialog(gitManager = tabViewModel)
}, },
onPull = { gitManager.pull() }, onPull = { tabViewModel.pull() },
onPush = { gitManager.push() }, onPush = { tabViewModel.push() },
onStash = { gitManager.stash() }, onStash = { tabViewModel.stash() },
onPopStash = { gitManager.popStash() }, onPopStash = { tabViewModel.popStash() },
onCreateBranch = { showNewBranchDialog = true } onCreateBranch = { showNewBranchDialog = true }
) )
@ -65,22 +72,22 @@ fun RepositoryOpenPage(gitManager: GitManager) {
.fillMaxHeight() .fillMaxHeight()
) { ) {
Branches( Branches(
gitManager = gitManager, branchesViewModel = tabViewModel.branchesViewModel,
onBranchClicked = { onBranchClicked = {
val commit = gitManager.findCommit(it.objectId) val commit = tabViewModel.findCommit(it.objectId)
setSelectedItem(SelectedItem.Ref(commit)) setSelectedItem(SelectedItem.Ref(commit))
} }
) )
Remotes(gitManager = gitManager) Remotes(remotesViewModel = tabViewModel.remotesViewModel)
Tags( Tags(
gitManager = gitManager, tagsViewModel = tabViewModel.tagsViewModel,
onTagClicked = { onTagClicked = {
val commit = gitManager.findCommit(it.objectId) val commit = tabViewModel.findCommit(it.objectId)
setSelectedItem(SelectedItem.Ref(commit)) setSelectedItem(SelectedItem.Ref(commit))
} }
) )
Stashes( Stashes(
gitManager = gitManager, gitManager = tabViewModel,
onStashSelected = { stash -> onStashSelected = { stash ->
setSelectedItem(SelectedItem.Stash(stash)) setSelectedItem(SelectedItem.Stash(stash))
} }
@ -97,11 +104,13 @@ fun RepositoryOpenPage(gitManager: GitManager) {
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { ) {
Crossfade(targetState = diffSelected) { diffEntry -> // Crossfade(targetState = diffSelected) { diffEntry ->
when (diffEntry) { when (diffSelected) {
null -> { null -> {
Log( Log(
gitManager = gitManager, tabViewModel = tabViewModel,
repositoryState = repositoryState,
logViewModel = tabViewModel.logViewModel,
selectedItem = selectedItem, selectedItem = selectedItem,
onItemSelected = { onItemSelected = {
setSelectedItem(it) setSelectedItem(it)
@ -110,12 +119,11 @@ fun RepositoryOpenPage(gitManager: GitManager) {
} }
else -> { else -> {
Diff( Diff(
gitManager = gitManager, diffViewModel = tabViewModel.diffViewModel,
diffEntryType = diffEntry,
onCloseDiffView = { diffSelected = null }) onCloseDiffView = { diffSelected = null })
} }
} }
} // }
} }
} }
@ -126,8 +134,9 @@ fun RepositoryOpenPage(gitManager: GitManager) {
) { ) {
if (selectedItem == SelectedItem.UncommitedChanges) { if (selectedItem == SelectedItem.UncommitedChanges) {
UncommitedChanges( UncommitedChanges(
gitManager = gitManager, statusViewModel = tabViewModel.statusViewModel,
selectedEntryType = diffSelected, selectedEntryType = diffSelected,
repositoryState = repositoryState,
onStagedDiffEntrySelected = { diffEntry -> onStagedDiffEntrySelected = { diffEntry ->
diffSelected = if (diffEntry != null) diffSelected = if (diffEntry != null)
DiffEntryType.StagedDiff(diffEntry) DiffEntryType.StagedDiff(diffEntry)
@ -140,7 +149,7 @@ fun RepositoryOpenPage(gitManager: GitManager) {
) )
} else if (selectedItem is SelectedItem.CommitBasedItem) { } else if (selectedItem is SelectedItem.CommitBasedItem) {
CommitChanges( CommitChanges(
gitManager = gitManager, gitManager = tabViewModel,
commit = selectedItem.revCommit, commit = selectedItem.revCommit,
onDiffSelected = { diffEntry -> onDiffSelected = { diffEntry ->
diffSelected = DiffEntryType.CommitDiff(diffEntry) diffSelected = DiffEntryType.CommitDiff(diffEntry)

View File

@ -6,7 +6,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import app.git.GitManager import app.git.TabViewModel
import app.git.StashStatus import app.git.StashStatus
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.ui.components.SideMenuEntry import app.ui.components.SideMenuEntry
@ -15,7 +15,7 @@ import org.eclipse.jgit.revwalk.RevCommit
@Composable @Composable
fun Stashes( fun Stashes(
gitManager: GitManager, gitManager: TabViewModel,
onStashSelected: (commit: RevCommit) -> Unit, onStashSelected: (commit: RevCommit) -> Unit,
) { ) {
val stashStatusState = gitManager.stashStatus.collectAsState() val stashStatusState = gitManager.stashStatus.collectAsState()

View File

@ -1,9 +1,9 @@
import app.extensions.runCommand import app.extensions.runCommand
import app.git.GitManager import app.git.TabViewModel
import javax.swing.JFileChooser import javax.swing.JFileChooser
fun openRepositoryDialog(gitManager: GitManager) { fun openRepositoryDialog(gitManager: TabViewModel) {
val os = System.getProperty("os.name") val os = System.getProperty("os.name")
val appStateManager = gitManager.appStateManager val appStateManager = gitManager.appStateManager
val latestDirectoryOpened = appStateManager.latestOpenedRepositoryPath val latestDirectoryOpened = appStateManager.latestOpenedRepositoryPath
@ -29,7 +29,7 @@ fun openRepositoryDialog(gitManager: GitManager) {
} }
private fun openRepositoryDialog( private fun openRepositoryDialog(
gitManager: GitManager, gitManager: TabViewModel,
latestDirectoryOpened: String latestDirectoryOpened: String
) { ) {

View File

@ -13,20 +13,21 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.MAX_SIDE_PANEL_ITEMS_HEIGHT import app.MAX_SIDE_PANEL_ITEMS_HEIGHT
import app.extensions.simpleName import app.extensions.simpleName
import app.git.GitManager import app.git.TabViewModel
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.ui.components.SideMenuEntry import app.ui.components.SideMenuEntry
import app.ui.components.SideMenuSubentry import app.ui.components.SideMenuSubentry
import app.ui.components.entryHeight import app.ui.components.entryHeight
import app.ui.context_menu.tagContextMenuItems import app.ui.context_menu.tagContextMenuItems
import app.viewmodels.TagsViewModel
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
@Composable @Composable
fun Tags( fun Tags(
gitManager: GitManager, tagsViewModel: TagsViewModel,
onTagClicked: (Ref) -> Unit, onTagClicked: (Ref) -> Unit,
) { ) {
val tagsState = gitManager.tags.collectAsState() val tagsState = tagsViewModel.tags.collectAsState()
val tags = tagsState.value val tags = tagsState.value
Column { Column {
@ -46,8 +47,8 @@ fun Tags(
TagRow( TagRow(
tag = tag, tag = tag,
onTagClicked = { onTagClicked(tag) }, onTagClicked = { onTagClicked(tag) },
onCheckoutTag = { gitManager.checkoutRef(tag) }, onCheckoutTag = { tagsViewModel.checkoutRef(tag) },
onDeleteTag = { gitManager.deleteTag(tag) } onDeleteTag = { tagsViewModel.deleteTag(tag) }
) )
} }
} }

View File

@ -28,36 +28,30 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.extensions.filePath import app.extensions.filePath
import app.extensions.icon
import app.extensions.iconColor
import app.extensions.isMerging import app.extensions.isMerging
import app.git.DiffEntryType import app.git.DiffEntryType
import app.git.GitManager
import app.git.StageStatus
import app.git.StatusEntry import app.git.StatusEntry
import app.theme.headerBackground import app.theme.headerBackground
import app.theme.headerText import app.theme.headerText
import app.theme.primaryTextColor import app.theme.primaryTextColor
import app.ui.components.ScrollableLazyColumn import app.ui.components.ScrollableLazyColumn
import app.ui.components.SecondaryButton import app.ui.components.SecondaryButton
import app.viewmodels.StageStatus
import app.viewmodels.StatusViewModel
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.lib.RepositoryState
@OptIn(ExperimentalAnimationApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class) @OptIn(ExperimentalAnimationApi::class, androidx.compose.ui.ExperimentalComposeUiApi::class)
@Composable @Composable
fun UncommitedChanges( fun UncommitedChanges(
gitManager: GitManager, statusViewModel: StatusViewModel,
selectedEntryType: DiffEntryType?, selectedEntryType: DiffEntryType?,
repositoryState: RepositoryState,
onStagedDiffEntrySelected: (DiffEntry?) -> Unit, onStagedDiffEntrySelected: (DiffEntry?) -> Unit,
onUnstagedDiffEntrySelected: (DiffEntry) -> Unit, onUnstagedDiffEntrySelected: (DiffEntry) -> Unit,
) { ) {
val stageStatusState = gitManager.stageStatus.collectAsState() val stageStatusState = statusViewModel.stageStatus.collectAsState()
val stageStatus = stageStatusState.value val stageStatus = stageStatusState.value
val lastCheck by gitManager.lastTimeChecked.collectAsState()
val repositoryState by gitManager.repositoryState.collectAsState()
LaunchedEffect(lastCheck) {
gitManager.loadStatus()
}
val staged: List<StatusEntry> val staged: List<StatusEntry>
val unstaged: List<StatusEntry> val unstaged: List<StatusEntry>
@ -83,7 +77,7 @@ fun UncommitedChanges(
var commitMessage by remember { mutableStateOf("") } var commitMessage by remember { mutableStateOf("") }
val doCommit = { val doCommit = {
gitManager.commit(commitMessage) statusViewModel.commit(commitMessage)
onStagedDiffEntrySelected(null) onStagedDiffEntrySelected(null)
commitMessage = "" commitMessage = ""
} }
@ -111,13 +105,13 @@ fun UncommitedChanges(
diffEntries = staged, diffEntries = staged,
onDiffEntrySelected = onStagedDiffEntrySelected, onDiffEntrySelected = onStagedDiffEntrySelected,
onDiffEntryOptionSelected = { onDiffEntryOptionSelected = {
gitManager.unstage(it) statusViewModel.unstage(it)
}, },
onReset = { diffEntry -> onReset = { diffEntry ->
gitManager.resetStaged(diffEntry) statusViewModel.resetStaged(diffEntry)
}, },
onAllAction = { onAllAction = {
gitManager.unstageAll() statusViewModel.unstageAll()
} }
) )
@ -132,13 +126,13 @@ fun UncommitedChanges(
diffEntries = unstaged, diffEntries = unstaged,
onDiffEntrySelected = onUnstagedDiffEntrySelected, onDiffEntrySelected = onUnstagedDiffEntrySelected,
onDiffEntryOptionSelected = { onDiffEntryOptionSelected = {
gitManager.stage(it) statusViewModel.stage(it)
}, },
onReset = { diffEntry -> onReset = { diffEntry ->
gitManager.resetUnstaged(diffEntry) statusViewModel.resetUnstaged(diffEntry)
}, },
{ {
gitManager.stageAll() statusViewModel.stageAll()
}, },
allActionTitle = "Stage all" allActionTitle = "Stage all"
) )

View File

@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.extensions.dirName import app.extensions.dirName
import app.extensions.dirPath import app.extensions.dirPath
import app.git.GitManager import app.git.TabViewModel
import app.theme.primaryTextColor import app.theme.primaryTextColor
import app.theme.secondaryTextColor import app.theme.secondaryTextColor
import app.ui.dialogs.CloneDialog import app.ui.dialogs.CloneDialog
@ -33,7 +33,7 @@ import java.net.URI
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun WelcomePage( fun WelcomePage(
gitManager: GitManager, gitManager: TabViewModel,
) { ) {
val appStateManager = gitManager.appStateManager val appStateManager = gitManager.appStateManager
var showCloneView by remember { mutableStateOf(false) } var showCloneView by remember { mutableStateOf(false) }

View File

@ -20,8 +20,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.AppStateManager
import app.di.AppComponent
import app.di.DaggerTabComponent
import app.git.TabViewModel
import app.theme.tabColorActive import app.theme.tabColorActive
import app.theme.tabColorInactive import app.theme.tabColorInactive
import app.ui.AppTab
import javax.inject.Inject
@Composable @Composable
@ -48,7 +54,7 @@ fun RepositoriesTabPanel(
) { ) {
items(items = tabs) { tab -> items(items = tabs) { tab ->
Tab( Tab(
title = tab.title, title = tab.tabName,
selected = tab.key == selectedTabKey, selected = tab.key == selectedTabKey,
onClick = { onClick = {
onTabSelected(tab.key) onTabSelected(tab.key)
@ -154,7 +160,33 @@ fun Tab(title: MutableState<String>, selected: Boolean, onClick: () -> Unit, onC
} }
class TabInformation( class TabInformation(
val title: MutableState<String>, val tabName: MutableState<String>,
val key: Int, val key: Int,
val path: String?,
appComponent: AppComponent,
) {
@Inject
lateinit var gitManager: TabViewModel
@Inject
lateinit var appStateManager: AppStateManager
val content: @Composable (TabInformation) -> Unit val content: @Composable (TabInformation) -> Unit
)
init {
val tabComponent = DaggerTabComponent.builder()
.appComponent(appComponent)
.build()
tabComponent.inject(this)
gitManager.onRepositoryChanged = { path ->
if (path == null) {
appStateManager.repositoryTabRemoved(key)
} else
appStateManager.repositoryTabChanged(key, path)
}
content = {
AppTab(gitManager, path, tabName)
}
}
}

View File

@ -12,13 +12,13 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.git.CloneStatus import app.git.CloneStatus
import app.git.GitManager import app.git.TabViewModel
import app.theme.primaryTextColor import app.theme.primaryTextColor
import java.io.File import java.io.File
@Composable @Composable
fun CloneDialog( fun CloneDialog(
gitManager: GitManager, gitManager: TabViewModel,
onClose: () -> Unit onClose: () -> Unit
) { ) {
val cloneStatus = gitManager.cloneStatus.collectAsState() val cloneStatus = gitManager.cloneStatus.collectAsState()

View File

@ -34,8 +34,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.extensions.* import app.extensions.*
import app.git.GitManager import app.git.TabViewModel
import app.git.LogStatus
import app.git.graph.GraphNode import app.git.graph.GraphNode
import app.theme.* import app.theme.*
import app.ui.SelectedItem import app.ui.SelectedItem
@ -47,6 +46,8 @@ import app.ui.dialogs.MergeDialog
import app.ui.dialogs.NewBranchDialog import app.ui.dialogs.NewBranchDialog
import app.ui.dialogs.NewTagDialog import app.ui.dialogs.NewTagDialog
import app.ui.dialogs.ResetBranchDialog import app.ui.dialogs.ResetBranchDialog
import app.viewmodels.LogStatus
import app.viewmodels.LogViewModel
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.lib.RepositoryState import org.eclipse.jgit.lib.RepositoryState
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit
@ -70,13 +71,14 @@ private const val CANVAS_MIN_WIDTH = 100
) )
@Composable @Composable
fun Log( fun Log(
gitManager: GitManager, tabViewModel: TabViewModel,
logViewModel: LogViewModel,
selectedItem: SelectedItem, selectedItem: SelectedItem,
onItemSelected: (SelectedItem) -> Unit, onItemSelected: (SelectedItem) -> Unit,
repositoryState: RepositoryState,
) { ) {
val logStatusState = gitManager.logStatus.collectAsState() val logStatusState = logViewModel.logStatus.collectAsState()
val logStatus = logStatusState.value val logStatus = logStatusState.value
val repositoryState by gitManager.repositoryState.collectAsState()
val showLogDialog = remember { mutableStateOf<LogDialog>(LogDialog.None) } val showLogDialog = remember { mutableStateOf<LogDialog>(LogDialog.None) }
val selectedCommit = if (selectedItem is SelectedItem.CommitBasedItem) { val selectedCommit = if (selectedItem is SelectedItem.CommitBasedItem) {
@ -86,6 +88,7 @@ fun Log(
} }
if (logStatus is LogStatus.Loaded) { if (logStatus is LogStatus.Loaded) {
val hasUncommitedChanges = logStatus.hasUncommitedChanges
val commitList = logStatus.plotCommitList val commitList = logStatus.plotCommitList
val scrollState = rememberLazyListState() val scrollState = rememberLazyListState()
@ -102,7 +105,7 @@ fun Log(
} }
LogDialogs( LogDialogs(
gitManager, logViewModel,
currentBranch = logStatus.currentBranch, currentBranch = logStatus.currentBranch,
onResetShowLogDialog = { showLogDialog.value = LogDialog.None }, onResetShowLogDialog = { showLogDialog.value = LogDialog.None },
showLogDialog = showLogDialog.value, showLogDialog = showLogDialog.value,
@ -114,7 +117,7 @@ fun Log(
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
.fillMaxSize() .fillMaxSize()
) { ) {
val hasUncommitedChanges by gitManager.hasUncommitedChanges.collectAsState() // val hasUncommitedChanges by tabViewModel.hasUncommitedChanges.collectAsState()
val weightMod = remember { mutableStateOf(0f) } val weightMod = remember { mutableStateOf(0f) }
var graphWidth = (CANVAS_MIN_WIDTH + weightMod.value).dp var graphWidth = (CANVAS_MIN_WIDTH + weightMod.value).dp
@ -131,11 +134,12 @@ fun Log(
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
.fillMaxSize(), .fillMaxSize(),
) { ) {
//TODO: Shouldn't this be an item of the graph?
if (hasUncommitedChanges) if (hasUncommitedChanges)
item { item {
UncommitedChangesLine( UncommitedChangesLine(
selected = selectedItem == SelectedItem.UncommitedChanges, selected = selectedItem == SelectedItem.UncommitedChanges,
hasPreviousCommits = commitList.count() > 0, hasPreviousCommits = commitList.isNotEmpty(),
graphWidth = graphWidth, graphWidth = graphWidth,
weightMod = weightMod, weightMod = weightMod,
repositoryState = repositoryState, repositoryState = repositoryState,
@ -146,7 +150,7 @@ fun Log(
} }
items(items = commitList) { graphNode -> items(items = commitList) { graphNode ->
CommitLine( CommitLine(
gitManager = gitManager, logViewModel = logViewModel,
graphNode = graphNode, graphNode = graphNode,
selected = selectedCommit?.name == graphNode.name, selected = selectedCommit?.name == graphNode.name,
weightMod = weightMod, weightMod = weightMod,
@ -169,7 +173,7 @@ fun Log(
@Composable @Composable
fun LogDialogs( fun LogDialogs(
gitManager: GitManager, logViewModel: LogViewModel,
onResetShowLogDialog: () -> Unit, onResetShowLogDialog: () -> Unit,
showLogDialog: LogDialog, showLogDialog: LogDialog,
currentBranch: Ref?, currentBranch: Ref?,
@ -179,7 +183,7 @@ fun LogDialogs(
NewBranchDialog( NewBranchDialog(
onReject = onResetShowLogDialog, onReject = onResetShowLogDialog,
onAccept = { branchName -> onAccept = { branchName ->
gitManager.createBranchOnCommit(branchName, showLogDialog.graphNode) logViewModel.createBranchOnCommit(branchName, showLogDialog.graphNode)
onResetShowLogDialog() onResetShowLogDialog()
} }
) )
@ -188,7 +192,7 @@ fun LogDialogs(
NewTagDialog( NewTagDialog(
onReject = onResetShowLogDialog, onReject = onResetShowLogDialog,
onAccept = { tagName -> onAccept = { tagName ->
gitManager.createTagOnCommit(tagName, showLogDialog.graphNode) logViewModel.createTagOnCommit(tagName, showLogDialog.graphNode)
onResetShowLogDialog() onResetShowLogDialog()
} }
) )
@ -200,7 +204,7 @@ fun LogDialogs(
mergeBranchName = showLogDialog.ref.simpleName, mergeBranchName = showLogDialog.ref.simpleName,
onReject = onResetShowLogDialog, onReject = onResetShowLogDialog,
onAccept = { ff -> onAccept = { ff ->
gitManager.mergeBranch(showLogDialog.ref, ff) logViewModel.mergeBranch(showLogDialog.ref, ff)
onResetShowLogDialog() onResetShowLogDialog()
} }
) )
@ -208,7 +212,7 @@ fun LogDialogs(
is LogDialog.ResetBranch -> ResetBranchDialog( is LogDialog.ResetBranch -> ResetBranchDialog(
onReject = onResetShowLogDialog, onReject = onResetShowLogDialog,
onAccept = { resetType -> onAccept = { resetType ->
gitManager.resetToCommit(showLogDialog.graphNode, resetType) logViewModel.resetToCommit(showLogDialog.graphNode, resetType)
onResetShowLogDialog() onResetShowLogDialog()
} }
) )
@ -324,7 +328,7 @@ fun UncommitedChangesLine(
@Composable @Composable
fun CommitLine( fun CommitLine(
gitManager: GitManager, logViewModel: LogViewModel,
graphNode: GraphNode, graphNode: GraphNode,
selected: Boolean, selected: Boolean,
weightMod: MutableState<Float>, weightMod: MutableState<Float>,
@ -348,9 +352,7 @@ fun CommitLine(
listOf( listOf(
ContextMenuItem( ContextMenuItem(
label = "Checkout commit", label = "Checkout commit",
onClick = { onClick = { logViewModel.checkoutCommit(graphNode) }),
gitManager.checkoutCommit(graphNode)
}),
ContextMenuItem( ContextMenuItem(
label = "Create branch", label = "Create branch",
onClick = showCreateNewBranch onClick = showCreateNewBranch
@ -361,7 +363,7 @@ fun CommitLine(
), ),
ContextMenuItem( ContextMenuItem(
label = "Revert commit", label = "Revert commit",
onClick = { gitManager.revertCommit(graphNode) } onClick = { logViewModel.revertCommit(graphNode) }
), ),
ContextMenuItem( ContextMenuItem(
@ -403,10 +405,10 @@ fun CommitLine(
refs = commitRefs, refs = commitRefs,
nodeColor = nodeColor, nodeColor = nodeColor,
currentBranch = currentBranch, currentBranch = currentBranch,
onCheckoutRef = { ref -> gitManager.checkoutRef(ref) }, onCheckoutRef = { ref -> logViewModel.checkoutRef(ref) },
onMergeBranch = { ref -> onMergeBranch(ref) }, onMergeBranch = { ref -> onMergeBranch(ref) },
onDeleteBranch = { ref -> gitManager.deleteBranch(ref) }, onDeleteBranch = { ref -> logViewModel.deleteBranch(ref) },
onDeleteTag = { ref -> gitManager.deleteTag(ref) }, onDeleteTag = { ref -> logViewModel.deleteTag(ref) },
) )
} }
} }

View File

@ -0,0 +1,60 @@
package app.viewmodels
import app.git.BranchesManager
import app.git.RefreshType
import app.git.TabState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
class BranchesViewModel @Inject constructor(
private val branchesManager: BranchesManager,
private val tabState: TabState,
) {
private val _branches = MutableStateFlow<List<Ref>>(listOf())
val branches: StateFlow<List<Ref>>
get() = _branches
private val _currentBranch = MutableStateFlow<String>("")
val currentBranch: StateFlow<String>
get() = _currentBranch
suspend fun loadBranches(git: Git) {
val branchesList = branchesManager.getBranches(git)
_branches.value = branchesList
_currentBranch.value = branchesManager.currentBranchRef(git)?.name ?: ""
}
fun createBranch(branchName: String) = tabState.safeProcessing { git ->
branchesManager.createBranch(git, branchName)
this.loadBranches(git)
return@safeProcessing RefreshType.NONE
}
fun mergeBranch(ref: Ref, fastForward: Boolean) = tabState.safeProcessing { git ->
branchesManager.mergeBranch(git, ref, fastForward)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteBranch(branch: Ref) =tabState.safeProcessing { git ->
branchesManager.deleteBranch(git, branch)
return@safeProcessing RefreshType.ALL_DATA
}
fun checkoutRef(ref: Ref) = tabState.safeProcessing { git ->
branchesManager.checkoutRef(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) {
loadBranches(git)
}
}

View File

@ -0,0 +1,44 @@
package app.viewmodels
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
class DiffViewModel @Inject constructor(
private val tabState: TabState,
private val diffManager: DiffManager,
private val statusManager: StatusManager,
) {
// TODO Maybe use a sealed class instead of a null to represent that a diff is not selected?
private val _diffResult = MutableStateFlow<DiffResult?>(null)
val diffResult: StateFlow<DiffResult?> = _diffResult
suspend fun updateDiff(git: Git, diffEntryType: DiffEntryType) = withContext(Dispatchers.IO) {
_diffResult.value = null
val hunks = diffManager.diffFormat(git, diffEntryType)
_diffResult.value = DiffResult(diffEntryType, hunks)
}
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation { git ->
statusManager.stageHunk(git, diffEntry, hunk)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = tabState.runOperation { git ->
statusManager.unstageHunk(git, diffEntry, hunk)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
}
data class DiffResult(val diffEntryType: DiffEntryType, val hunks: List<Hunk>)

View File

@ -0,0 +1,97 @@
package app.viewmodels
import app.git.*
import app.git.graph.GraphCommitList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.revwalk.RevCommit
import javax.inject.Inject
class LogViewModel @Inject constructor(
private val logManager: LogManager,
private val statusManager: StatusManager,
private val branchesManager: BranchesManager,
private val tagsManager: TagsManager,
private val tabState: TabState,
) {
private val _logStatus = MutableStateFlow<LogStatus>(LogStatus.Loading)
val logStatus: StateFlow<LogStatus>
get() = _logStatus
suspend fun loadLog(git: Git) {
_logStatus.value = LogStatus.Loading
val currentBranch = branchesManager.currentBranchRef(git)
val log = logManager.loadLog(git, currentBranch)
val hasUncommitedChanges = statusManager.hasUncommitedChanges(git)
_logStatus.value = LogStatus.Loaded(hasUncommitedChanges, log, currentBranch)
}
fun checkoutCommit(revCommit: RevCommit) = tabState.safeProcessing { git ->
logManager.checkoutCommit(git, revCommit)
return@safeProcessing RefreshType.ALL_DATA
}
fun revertCommit(revCommit: RevCommit) = tabState.safeProcessing { git ->
logManager.revertCommit(git, revCommit)
return@safeProcessing RefreshType.ALL_DATA
}
fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = tabState.safeProcessing { git ->
logManager.resetToCommit(git, revCommit, resetType = resetType)
return@safeProcessing RefreshType.ALL_DATA
}
fun checkoutRef(ref: Ref) = tabState.safeProcessing { git ->
branchesManager.checkoutRef(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing { git ->
branchesManager.createBranchOnCommit(git, branch, revCommit)
return@safeProcessing RefreshType.ALL_DATA
}
fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing { git ->
tagsManager.createTagOnCommit(git, tag, revCommit)
return@safeProcessing RefreshType.ALL_DATA
}
fun mergeBranch(ref: Ref, fastForward: Boolean) = tabState.safeProcessing { git ->
branchesManager.mergeBranch(git, ref, fastForward)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteBranch(branch: Ref) =tabState.safeProcessing { git ->
branchesManager.deleteBranch(git, branch)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteTag(tag: Ref) = tabState.safeProcessing { git ->
tagsManager.deleteTag(git, tag)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) {
loadLog(git)
}
}
sealed class LogStatus {
object Loading : LogStatus()
class Loaded(val hasUncommitedChanges: Boolean, val plotCommitList: GraphCommitList, val currentBranch: Ref?) : LogStatus()
}

View File

@ -0,0 +1,44 @@
package app.viewmodels
import app.git.BranchesManager
import app.git.RemoteInfo
import app.git.RemotesManager
import app.git.TabState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.transport.RemoteConfig
import javax.inject.Inject
class RemotesViewModel @Inject constructor(
private val remotesManager: RemotesManager,
private val branchesManager: BranchesManager,
) {
private val _remotes = MutableStateFlow<List<RemoteInfo>>(listOf())
val remotes: StateFlow<List<RemoteInfo>>
get() = _remotes
suspend fun loadRemotes(git: Git) = withContext(Dispatchers.IO) {
val remotes = git.remoteList()
.call()
val allRemoteBranches = branchesManager.remoteBranches(git)
remotesManager.loadRemotes(git, allRemoteBranches)
val remoteInfoList = remotes.map { remoteConfig ->
val remoteBranches = allRemoteBranches.filter { branch ->
branch.name.startsWith("refs/remotes/${remoteConfig.name}")
}
RemoteInfo(remoteConfig, remoteBranches)
}
_remotes.value = remoteInfoList
}
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {
loadRemotes(git)
}
}

View File

@ -0,0 +1,124 @@
package app.viewmodels
import app.git.*
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
class StatusViewModel @Inject constructor(
private val tabState: TabState,
private val statusManager: StatusManager,
private val branchesManager: BranchesManager,
private val repositoryManager: RepositoryManager,
) {
private val _stageStatus = MutableStateFlow<StageStatus>(StageStatus.Loaded(listOf(), listOf()))
val stageStatus: StateFlow<StageStatus> = _stageStatus
private val _hasUncommitedChanges = MutableStateFlow<Boolean>(false)
val hasUncommitedChanges: StateFlow<Boolean>
get() = _hasUncommitedChanges
fun stage(diffEntry: DiffEntry) = tabState.runOperation { git ->
statusManager.stage(git, diffEntry)
loadStatus(git)
return@runOperation RefreshType.NONE
}
fun unstage(diffEntry: DiffEntry) = tabState.runOperation { git ->
statusManager.unstage(git, diffEntry)
loadStatus(git)
return@runOperation RefreshType.NONE
}
fun unstageAll() = tabState.safeProcessing { git ->
statusManager.unstageAll(git)
loadStatus(git)
return@safeProcessing RefreshType.NONE
}
fun stageAll() = tabState.safeProcessing { git ->
statusManager.stageAll(git)
loadStatus(git)
return@safeProcessing RefreshType.NONE
}
fun resetStaged(diffEntry: DiffEntry) = tabState.runOperation { git ->
statusManager.reset(git, diffEntry, staged = true)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
fun resetUnstaged(diffEntry: DiffEntry) =tabState.runOperation { git ->
statusManager.reset(git, diffEntry, staged = false)
return@runOperation RefreshType.UNCOMMITED_CHANGES
}
suspend fun loadStatus(git: Git) {
val previousStatus = _stageStatus.value
try {
_stageStatus.value = StageStatus.Loading
val repositoryState = repositoryManager.getRepositoryState(git)
val currentBranchRef = branchesManager.currentBranchRef(git)
val staged = statusManager.getStaged(git, currentBranchRef, repositoryState)
val unstaged = statusManager.getUnstaged(git, repositoryState)
_stageStatus.value = StageStatus.Loaded(staged, unstaged)
} catch (ex: Exception) {
_stageStatus.value = previousStatus
throw ex
}
}
suspend fun loadHasUncommitedChanges(git: Git) = withContext(Dispatchers.IO) {
_hasUncommitedChanges.value = statusManager.hasUncommitedChanges(git)
}
fun commit(message: String) = tabState.safeProcessing { git ->
statusManager.commit(git, message)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) = withContext(Dispatchers.IO) {
loadStatus(git)
loadHasUncommitedChanges(git)
}
/**
* Checks if there are uncommited changes and returns if the state has changed (
*/
suspend fun updateHasUncommitedChanges(git: Git): Boolean {
val hadUncommitedChanges = hasUncommitedChanges.value
loadStatus(git)
val hasNowUncommitedChanges = hasUncommitedChanges.value
// Return true to update the log only if the uncommitedChanges status has changed
return (hasNowUncommitedChanges != hadUncommitedChanges)
}
}
sealed class StageStatus {
object Loading : StageStatus()
data class Loaded(val staged: List<StatusEntry>, val unstaged: List<StatusEntry>) : StageStatus()
}

View File

@ -6,6 +6,7 @@ import app.app.newErrorNow
import app.credentials.CredentialsState import app.credentials.CredentialsState
import app.credentials.CredentialsStateManager import app.credentials.CredentialsStateManager
import app.git.diff.Hunk import app.git.diff.Hunk
import app.viewmodels.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -13,7 +14,6 @@ import kotlinx.coroutines.flow.collect
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.Ref
import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.lib.RepositoryState import org.eclipse.jgit.lib.RepositoryState
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit
@ -22,15 +22,18 @@ import java.io.File
import javax.inject.Inject import javax.inject.Inject
class GitManager @Inject constructor( class TabViewModel @Inject constructor(
private val statusManager: StatusManager, val logViewModel: LogViewModel,
private val logManager: LogManager, val branchesViewModel: BranchesViewModel,
val tagsViewModel: TagsViewModel,
val remotesViewModel: RemotesViewModel,
val statusViewModel: StatusViewModel,
val diffViewModel: DiffViewModel,
private val repositoryManager: RepositoryManager,
private val remoteOperationsManager: RemoteOperationsManager, private val remoteOperationsManager: RemoteOperationsManager,
private val branchesManager: BranchesManager,
private val stashManager: StashManager, private val stashManager: StashManager,
private val diffManager: DiffManager, private val diffManager: DiffManager,
private val tagsManager: TagsManager, private val tabState: TabState,
private val remotesManager: RemotesManager,
val errorsManager: ErrorsManager, val errorsManager: ErrorsManager,
val appStateManager: AppStateManager, val appStateManager: AppStateManager,
private val fileChangesWatcher: FileChangesWatcher, private val fileChangesWatcher: FileChangesWatcher,
@ -50,22 +53,31 @@ class GitManager @Inject constructor(
val processing: StateFlow<Boolean> val processing: StateFlow<Boolean>
get() = _processing get() = _processing
private val _lastTimeChecked = MutableStateFlow(System.currentTimeMillis())
val lastTimeChecked: StateFlow<Long>
get() = _lastTimeChecked
val stageStatus: StateFlow<StageStatus> = statusManager.stageStatus
val repositoryState: StateFlow<RepositoryState> = statusManager.repositoryState
val logStatus: StateFlow<LogStatus> = logManager.logStatus
val branches: StateFlow<List<Ref>> = branchesManager.branches
val tags: StateFlow<List<Ref>> = tagsManager.tags
val currentBranch: StateFlow<String> = branchesManager.currentBranch
val stashStatus: StateFlow<StashStatus> = stashManager.stashStatus val stashStatus: StateFlow<StashStatus> = stashManager.stashStatus
val credentialsState: StateFlow<CredentialsState> = credentialsStateManager.credentialsState val credentialsState: StateFlow<CredentialsState> = credentialsStateManager.credentialsState
val cloneStatus: StateFlow<CloneStatus> = remoteOperationsManager.cloneStatus val cloneStatus: StateFlow<CloneStatus> = remoteOperationsManager.cloneStatus
val remotes: StateFlow<List<RemoteInfo>> = remotesManager.remotes
private var git: Git? = null private val _repositoryState = MutableStateFlow(RepositoryState.SAFE)
val repositoryState: StateFlow<RepositoryState> = _repositoryState
init {
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()
}
}
}
}
private fun refreshLog() = tabState.runOperation { git ->
logViewModel.refresh(git)
return@runOperation RefreshType.NONE
}
/** /**
* Property that indicates if a git operation is running * Property that indicates if a git operation is running
@ -74,7 +86,7 @@ class GitManager @Inject constructor(
private val safeGit: Git private val safeGit: Git
get() { get() {
val git = this.git val git = this.tabState.git
if (git == null) { if (git == null) {
_repositorySelectionStatus.value = RepositorySelectionStatus.None _repositorySelectionStatus.value = RepositorySelectionStatus.None
throw CancellationException() throw CancellationException()
@ -110,13 +122,15 @@ class GitManager @Inject constructor(
try { try {
repository.workTree // test if repository is valid repository.workTree // test if repository is valid
_repositorySelectionStatus.value = RepositorySelectionStatus.Open(repository) _repositorySelectionStatus.value = RepositorySelectionStatus.Open(repository)
git = Git(repository) tabState.git = Git(repository)
onRepositoryChanged(repository.directory.parent) onRepositoryChanged(repository.directory.parent)
refreshRepositoryInfo() refreshRepositoryInfo()
launch { launch {
watchRepositoryChanges() watchRepositoryChanges()
} }
println("AppStateManagerReference $appStateManager")
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
onRepositoryChanged(null) onRepositoryChanged(null)
@ -125,6 +139,10 @@ class GitManager @Inject constructor(
} }
} }
suspend fun loadRepositoryState(git: Git) = withContext(Dispatchers.IO) {
_repositoryState.value = repositoryManager.getRepositoryState(git)
}
private suspend fun watchRepositoryChanges() { private suspend fun watchRepositoryChanges() {
val ignored = safeGit.status().call().ignoredNotInIndex.toList() val ignored = safeGit.status().call().ignoredNotInIndex.toList()
@ -135,78 +153,35 @@ class GitManager @Inject constructor(
if (!operationRunning) { // Only update if there isn't any process running if (!operationRunning) { // Only update if there isn't any process running
safeProcessing(showError = false) { safeProcessing(showError = false) {
println("Changes detected, loading status") println("Changes detected, loading status")
val hasUncommitedChanges = statusManager.hasUncommitedChanges.value // val hasUncommitedChanges = statusManager.hasUncommitedChanges.value
statusManager.loadHasUncommitedChanges(safeGit) // statusManager.loadHasUncommitedChanges(safeGit)
statusManager.loadStatus(safeGit) // statusManager.loadStatus(safeGit)
if(!hasUncommitedChanges) { statusViewModel.refresh(safeGit)
logManager.loadLog(safeGit) checkUncommitedChanges()
}
} }
} }
} }
} }
fun loadLog() = managerScope.launch { private suspend fun loadLog() {
coLoadLog() logViewModel.loadLog(safeGit)
} }
private suspend fun coLoadLog() { suspend fun checkUncommitedChanges() {
logManager.loadLog(safeGit) val uncommitedChangesStateChanged = statusViewModel.updateHasUncommitedChanges(safeGit)
}
suspend fun loadStatus() {
val hadUncommitedChanges = statusManager.hasUncommitedChanges.value
statusManager.loadStatus(safeGit)
val hasNowUncommitedChanges = statusManager.hasUncommitedChanges.value
// Update the log only if the uncommitedChanges status has changed // Update the log only if the uncommitedChanges status has changed
if (hasNowUncommitedChanges != hadUncommitedChanges) if (uncommitedChangesStateChanged)
coLoadLog() loadLog()
} }
fun stage(diffEntry: DiffEntry) = managerScope.launch {
runOperation {
statusManager.stage(safeGit, diffEntry)
}
}
fun stageHunk(diffEntry: DiffEntry, hunk: Hunk) = managerScope.launch {
runOperation {
statusManager.stageHunk(safeGit, diffEntry, hunk)
}
}
fun unstageHunk(diffEntry: DiffEntry, hunk: Hunk) = managerScope.launch {
runOperation {
statusManager.unstageHunk(safeGit, diffEntry, hunk)
}
}
fun unstage(diffEntry: DiffEntry) = managerScope.launch {
runOperation {
statusManager.unstage(safeGit, diffEntry)
}
}
fun commit(message: String) = managerScope.launch {
safeProcessing {
statusManager.commit(safeGit, message)
refreshRepositoryInfo()
}
}
val hasUncommitedChanges: StateFlow<Boolean>
get() = statusManager.hasUncommitedChanges
suspend fun diffFormat(diffEntryType: DiffEntryType): List<Hunk> { suspend fun diffFormat(diffEntryType: DiffEntryType): List<Hunk> {
try { try {
return diffManager.diffFormat(safeGit, diffEntryType) return diffManager.diffFormat(safeGit, diffEntryType)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
loadStatus() checkUncommitedChanges()
return listOf() return listOf()
} }
} }
@ -214,7 +189,7 @@ class GitManager @Inject constructor(
fun pull() = managerScope.launch { fun pull() = managerScope.launch {
safeProcessing { safeProcessing {
remoteOperationsManager.pull(safeGit) remoteOperationsManager.pull(safeGit)
coLoadLog() loadLog()
} }
} }
@ -223,26 +198,27 @@ class GitManager @Inject constructor(
try { try {
remoteOperationsManager.push(safeGit) remoteOperationsManager.push(safeGit)
} finally { } finally {
coLoadLog() loadLog()
} }
} }
} }
private suspend fun refreshRepositoryInfo() { private suspend fun refreshRepositoryInfo() {
statusManager.loadRepositoryStatus(safeGit) logViewModel.refresh(safeGit)
statusManager.loadHasUncommitedChanges(safeGit) branchesViewModel.refresh(safeGit)
statusManager.loadStatus(safeGit) remotesViewModel.refresh(safeGit)
branchesManager.loadBranches(safeGit) tagsViewModel.refresh(safeGit)
remotesManager.loadRemotes(safeGit, branchesManager.remoteBranches(safeGit)) statusViewModel.refresh(safeGit)
tagsManager.loadTags(safeGit) loadRepositoryState(safeGit)
stashManager.loadStashList(safeGit) stashManager.loadStashList(safeGit)
coLoadLog() loadLog()
} }
fun stash() = managerScope.launch { fun stash() = managerScope.launch {
safeProcessing { safeProcessing {
stashManager.stash(safeGit) stashManager.stash(safeGit)
loadStatus() checkUncommitedChanges()
loadLog() loadLog()
} }
} }
@ -250,42 +226,11 @@ class GitManager @Inject constructor(
fun popStash() = managerScope.launch { fun popStash() = managerScope.launch {
safeProcessing { safeProcessing {
stashManager.popStash(safeGit) stashManager.popStash(safeGit)
loadStatus() checkUncommitedChanges()
loadLog() loadLog()
} }
} }
fun createBranch(branchName: String) = managerScope.launch {
safeProcessing {
branchesManager.createBranch(safeGit, branchName)
coLoadLog()
}
}
fun deleteBranch(branch: Ref) = managerScope.launch {
safeProcessing {
branchesManager.deleteBranch(safeGit, branch)
refreshRepositoryInfo()
}
}
fun deleteTag(tag: Ref) = managerScope.launch {
safeProcessing {
tagsManager.deleteTag(safeGit, tag)
refreshRepositoryInfo()
}
}
fun resetStaged(diffEntry: DiffEntry) = managerScope.launch {
statusManager.reset(safeGit, diffEntry, staged = true)
loadLog()
}
fun resetUnstaged(diffEntry: DiffEntry) = managerScope.launch {
statusManager.reset(safeGit, diffEntry, staged = false)
loadLog()
}
fun credentialsDenied() { fun credentialsDenied() {
credentialsStateManager.updateState(CredentialsState.CredentialsDenied) credentialsStateManager.updateState(CredentialsState.CredentialsDenied)
} }
@ -302,68 +247,8 @@ class GitManager @Inject constructor(
return diffManager.commitDiffEntries(safeGit, commit) return diffManager.commitDiffEntries(safeGit, commit)
} }
fun unstageAll() = managerScope.launch {
safeProcessing {
statusManager.unstageAll(safeGit)
}
}
fun stageAll() = managerScope.launch {
safeProcessing {
statusManager.stageAll(safeGit)
}
}
fun checkoutCommit(revCommit: RevCommit) = managerScope.launch {
safeProcessing {
logManager.checkoutCommit(safeGit, revCommit)
refreshRepositoryInfo()
}
}
fun revertCommit(revCommit: RevCommit) = managerScope.launch {
safeProcessing {
logManager.revertCommit(safeGit, revCommit)
refreshRepositoryInfo()
}
}
fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = managerScope.launch {
safeProcessing {
logManager.resetToCommit(safeGit, revCommit, resetType = resetType)
refreshRepositoryInfo()
}
}
fun createBranchOnCommit(branch: String, revCommit: RevCommit) = managerScope.launch {
safeProcessing {
branchesManager.createBranchOnCommit(safeGit, branch, revCommit)
refreshRepositoryInfo()
}
}
fun createTagOnCommit(tag: String, revCommit: RevCommit) = managerScope.launch {
safeProcessing {
tagsManager.createTagOnCommit(safeGit, tag, revCommit)
refreshRepositoryInfo()
}
}
var onRepositoryChanged: (path: String?) -> Unit = {} var onRepositoryChanged: (path: String?) -> Unit = {}
fun checkoutRef(ref: Ref) = managerScope.launch {
safeProcessing {
logManager.checkoutRef(safeGit, ref)
refreshRepositoryInfo()
}
}
fun mergeBranch(ref: Ref, fastForward: Boolean) = managerScope.launch {
safeProcessing {
branchesManager.mergeBranch(safeGit, ref, fastForward)
refreshRepositoryInfo()
}
}
fun dispose() { fun dispose() {
managerScope.cancel() managerScope.cancel()
@ -377,7 +262,6 @@ class GitManager @Inject constructor(
return safeGit.repository.parseCommit(objectId) return safeGit.repository.parseCommit(objectId)
} }
@Synchronized
private suspend fun safeProcessing(showError: Boolean = true, callback: suspend () -> Unit) { private suspend fun safeProcessing(showError: Boolean = true, callback: suspend () -> Unit) {
_processing.value = true _processing.value = true
operationRunning = true operationRunning = true
@ -395,13 +279,10 @@ class GitManager @Inject constructor(
} }
} }
private inline fun runOperation(block: () -> Unit) { fun updatedDiffEntry(diffSelected: DiffEntryType) = tabState.runOperation { git ->
operationRunning = true diffViewModel.updateDiff(git , diffSelected)
try {
block() return@runOperation RefreshType.NONE
} finally {
operationRunning = false
}
} }
} }

View File

@ -0,0 +1,45 @@
package app.viewmodels
import app.git.BranchesManager
import app.git.RefreshType
import app.git.TabState
import app.git.TagsManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Ref
import javax.inject.Inject
class TagsViewModel @Inject constructor(
private val tabState: TabState,
private val branchesManager: BranchesManager,
private val tagsManager: TagsManager,
) {
private val _tags = MutableStateFlow<List<Ref>>(listOf())
val tags: StateFlow<List<Ref>>
get() = _tags
suspend fun loadTags(git: Git) = withContext(Dispatchers.IO) {
val tagsList = tagsManager.getTags(git)
_tags.value = tagsList
}
fun checkoutRef(ref: Ref) = tabState.safeProcessing { git ->
branchesManager.checkoutRef(git, ref)
return@safeProcessing RefreshType.ALL_DATA
}
fun deleteTag(tag: Ref) = tabState.safeProcessing { git ->
tagsManager.deleteTag(git, tag)
return@safeProcessing RefreshType.ALL_DATA
}
suspend fun refresh(git: Git) {
loadTags(git)
}
}