Refactored user feedback.

Added warning/error messages
Change UI design
This commit is contained in:
Abdelilah El Aissaoui 2024-08-02 16:24:55 +02:00
parent 20830be926
commit e59fe7df69
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
25 changed files with 268 additions and 83 deletions

View File

@ -0,0 +1,3 @@
package com.jetpackduba.gitnuro.exceptions
class ConflictsException(message: String) : GitnuroException(message)

View File

@ -7,7 +7,7 @@ import kotlinx.coroutines.*
* Use case: Sometimes is not worth updating the UI with a state to "loading" if the load code executed afterwards is really * Use case: Sometimes is not worth updating the UI with a state to "loading" if the load code executed afterwards is really
* fast. * fast.
*/ */
suspend fun delayedStateChange(delayMs: Long, onDelayTriggered: suspend () -> Unit, block: suspend () -> Unit) { suspend fun <T> delayedStateChange(delayMs: Long, onDelayTriggered: suspend () -> Unit, block: suspend () -> T): T {
val scope = CoroutineScope(Dispatchers.IO) val scope = CoroutineScope(Dispatchers.IO)
var completed = false var completed = false
@ -17,9 +17,10 @@ suspend fun delayedStateChange(delayMs: Long, onDelayTriggered: suspend () -> Un
onDelayTriggered() onDelayTriggered()
} }
} }
try { return try {
block() val result = block()
scope.cancel() scope.cancel()
result
} finally { } finally {
completed = true completed = true
} }

View File

@ -8,6 +8,7 @@ import com.jetpackduba.gitnuro.git.log.FindCommitUseCase
import com.jetpackduba.gitnuro.logging.printError import com.jetpackduba.gitnuro.logging.printError
import com.jetpackduba.gitnuro.managers.ErrorsManager import com.jetpackduba.gitnuro.managers.ErrorsManager
import com.jetpackduba.gitnuro.managers.newErrorNow import com.jetpackduba.gitnuro.managers.newErrorNow
import com.jetpackduba.gitnuro.models.Notification
import com.jetpackduba.gitnuro.ui.SelectedItem import com.jetpackduba.gitnuro.ui.SelectedItem
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -77,12 +78,11 @@ class TabState @Inject constructor(
title: String = "", title: String = "",
subtitle: String = "", subtitle: String = "",
taskType: TaskType, taskType: TaskType,
positiveFeedbackText: String?,
// TODO For now have it always as false because the data refresh is cancelled even when the git process couldn't be cancelled // TODO For now have it always as false because the data refresh is cancelled even when the git process couldn't be cancelled
isCancellable: Boolean = false, isCancellable: Boolean = false,
refreshEvenIfCrashes: Boolean = false, refreshEvenIfCrashes: Boolean = false,
refreshEvenIfCrashesInteractive: ((Exception) -> Boolean)? = null, refreshEvenIfCrashesInteractive: ((Exception) -> Boolean)? = null,
callback: suspend (git: Git) -> Unit callback: suspend (git: Git) -> Notification?,
): Job { ): Job {
val job = scope.launch(Dispatchers.IO) { val job = scope.launch(Dispatchers.IO) {
var hasProcessFailed = false var hasProcessFailed = false
@ -91,7 +91,7 @@ class TabState @Inject constructor(
try { try {
delayedStateChange( val notification = delayedStateChange(
delayMs = 300, delayMs = 300,
onDelayTriggered = { onDelayTriggered = {
_processing.update { processingState -> _processing.update { processingState ->
@ -106,8 +106,8 @@ class TabState @Inject constructor(
callback(git) callback(git)
} }
if (positiveFeedbackText != null) { if (notification != null) {
errorsManager.emitPositiveNotification(positiveFeedbackText) errorsManager.emitNotification(notification)
} }
} catch (ex: Exception) { } catch (ex: Exception) {
hasProcessFailed = true hasProcessFailed = true

View File

@ -1,5 +1,6 @@
package com.jetpackduba.gitnuro.git.branches package com.jetpackduba.gitnuro.git.branches
import com.jetpackduba.gitnuro.exceptions.ConflictsException
import com.jetpackduba.gitnuro.exceptions.UncommittedChangesDetectedException import com.jetpackduba.gitnuro.exceptions.UncommittedChangesDetectedException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -10,6 +11,9 @@ import org.eclipse.jgit.lib.Ref
import javax.inject.Inject import javax.inject.Inject
class MergeBranchUseCase @Inject constructor() { class MergeBranchUseCase @Inject constructor() {
/**
* @return true if success has conflicts, false if success without conflicts
*/
suspend operator fun invoke(git: Git, branch: Ref, fastForward: Boolean) = withContext(Dispatchers.IO) { suspend operator fun invoke(git: Git, branch: Ref, fastForward: Boolean) = withContext(Dispatchers.IO) {
val fastForwardMode = if (fastForward) val fastForwardMode = if (fastForward)
MergeCommand.FastForwardMode.FF MergeCommand.FastForwardMode.FF
@ -25,5 +29,7 @@ class MergeBranchUseCase @Inject constructor() {
if (mergeResult.mergeStatus == MergeResult.MergeStatus.FAILED) { if (mergeResult.mergeStatus == MergeResult.MergeStatus.FAILED) {
throw UncommittedChangesDetectedException("Merge failed, makes sure you repository doesn't contain uncommitted changes.") throw UncommittedChangesDetectedException("Merge failed, makes sure you repository doesn't contain uncommitted changes.")
} }
mergeResult.mergeStatus == MergeResult.MergeStatus.CONFLICTING
} }
} }

View File

@ -1,9 +1,11 @@
package com.jetpackduba.gitnuro.git.rebase package com.jetpackduba.gitnuro.git.rebase
import com.jetpackduba.gitnuro.exceptions.ConflictsException
import com.jetpackduba.gitnuro.exceptions.UncommittedChangesDetectedException import com.jetpackduba.gitnuro.exceptions.UncommittedChangesDetectedException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.MergeResult
import org.eclipse.jgit.api.RebaseCommand import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.RebaseResult import org.eclipse.jgit.api.RebaseResult
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
@ -19,5 +21,11 @@ class RebaseBranchUseCase @Inject constructor() {
if (rebaseResult.status == RebaseResult.Status.UNCOMMITTED_CHANGES) { if (rebaseResult.status == RebaseResult.Status.UNCOMMITTED_CHANGES) {
throw UncommittedChangesDetectedException("Rebase failed, the repository contains uncommitted changes.") throw UncommittedChangesDetectedException("Rebase failed, the repository contains uncommitted changes.")
} }
when (rebaseResult.status) {
RebaseResult.Status.UNCOMMITTED_CHANGES -> throw UncommittedChangesDetectedException("Merge failed, makes sure you repository doesn't contain uncommitted changes.")
RebaseResult.Status.CONFLICTS -> throw ConflictsException("Rebase produced conflicts, please fix them to continue.")
else -> {}
}
} }
} }

View File

@ -1,12 +1,13 @@
package com.jetpackduba.gitnuro.git.stash package com.jetpackduba.gitnuro.git.stash
import com.jetpackduba.gitnuro.models.Success
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import javax.inject.Inject import javax.inject.Inject
class StashChangesUseCase @Inject constructor() { class StashChangesUseCase @Inject constructor() {
suspend operator fun invoke(git: Git, message: String?): Unit = withContext(Dispatchers.IO) { suspend operator fun invoke(git: Git, message: String?): Success = withContext(Dispatchers.IO) {
val commit = git val commit = git
.stashCreate() .stashCreate()
.setIncludeUntracked(true) .setIncludeUntracked(true)
@ -16,8 +17,6 @@ class StashChangesUseCase @Inject constructor() {
} }
.call() .call()
if (commit == null) { commit != null
throw Exception("No changes to stash")
}
} }
} }

View File

@ -4,11 +4,15 @@ import com.jetpackduba.gitnuro.TaskType
import com.jetpackduba.gitnuro.di.TabScope import com.jetpackduba.gitnuro.di.TabScope
import com.jetpackduba.gitnuro.exceptions.GitnuroException import com.jetpackduba.gitnuro.exceptions.GitnuroException
import com.jetpackduba.gitnuro.extensions.lockUse import com.jetpackduba.gitnuro.extensions.lockUse
import com.jetpackduba.gitnuro.models.Notification
import com.jetpackduba.gitnuro.models.NotificationType
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
const val NOTIFICATION_DURATION = 2_500L
@TabScope @TabScope
class ErrorsManager @Inject constructor( class ErrorsManager @Inject constructor(
private val coroutineScope: CoroutineScope private val coroutineScope: CoroutineScope
@ -20,23 +24,23 @@ class ErrorsManager @Inject constructor(
private val _error = MutableSharedFlow<Error?>() private val _error = MutableSharedFlow<Error?>()
val error: SharedFlow<Error?> = _error val error: SharedFlow<Error?> = _error
private val _notification = MutableStateFlow<Map<Long, String>>(hashMapOf()) private val _notification = MutableStateFlow<Map<Long, Notification>>(hashMapOf())
val notification: StateFlow<Map<Long, String>> = _notification val notification: StateFlow<Map<Long, Notification>> = _notification
private val notificationsMutex = Mutex() private val notificationsMutex = Mutex()
suspend fun emitPositiveNotification(text: String) = coroutineScope.launch { suspend fun emitNotification(notification: Notification) = coroutineScope.launch {
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
notificationsMutex.lockUse { notificationsMutex.lockUse {
_notification.update { notifications -> _notification.update { notifications ->
notifications notifications
.toMutableMap() .toMutableMap()
.apply { put(time, text) } .apply { put(time, notification) }
} }
} }
launch { launch {
delay(2000) delay(NOTIFICATION_DURATION)
notificationsMutex.lockUse { notificationsMutex.lockUse {
_notification.update { notifications -> _notification.update { notifications ->
@ -142,4 +146,4 @@ fun newErrorNow(
exception = exception, exception = exception,
isUnhandled = exception !is GitnuroException isUnhandled = exception !is GitnuroException
) )
} }

View File

@ -0,0 +1,14 @@
package com.jetpackduba.gitnuro.models
data class Notification(val type: NotificationType, val text: String)
enum class NotificationType {
Warning,
Positive,
Error,
}
fun positiveNotification(text: String) = Notification(NotificationType.Positive, text)
fun errorNotification(text: String) = Notification(NotificationType.Error, text)
fun warningNotification(text: String) = Notification(NotificationType.Warning, text)

View File

@ -0,0 +1,3 @@
package com.jetpackduba.gitnuro.models
typealias Success = Boolean

View File

@ -7,6 +7,7 @@ val lightTheme = ColorsScheme(
primaryVariant = Color(0xFF0070D8), primaryVariant = Color(0xFF0070D8),
onPrimary = Color(0xFFFFFFFFF), onPrimary = Color(0xFFFFFFFFF),
secondary = Color(0xFF9c27b0), secondary = Color(0xFF9c27b0),
onSecondary = Color(0xFFFFFFFF),
onBackground = Color(0xFF212934), onBackground = Color(0xFF212934),
onBackgroundSecondary = Color(0xFF595858), onBackgroundSecondary = Color(0xFF595858),
error = Color(0xFFc93838), error = Color(0xFFc93838),
@ -37,6 +38,7 @@ val darkBlueTheme = ColorsScheme(
primaryVariant = Color(0xFF9FD1FF), primaryVariant = Color(0xFF9FD1FF),
onPrimary = Color(0xFFFFFFFFF), onPrimary = Color(0xFFFFFFFFF),
secondary = Color(0xFFe9c754), secondary = Color(0xFFe9c754),
onSecondary = Color(0xFF0E1621),
onBackground = Color(0xFFFFFFFF), onBackground = Color(0xFFFFFFFF),
onBackgroundSecondary = Color(0xFFCCCBCB), onBackgroundSecondary = Color(0xFFCCCBCB),
error = Color(0xFFc93838), error = Color(0xFFc93838),
@ -66,6 +68,7 @@ val darkGrayTheme = ColorsScheme(
primaryVariant = Color(0xFFCDEAFF), primaryVariant = Color(0xFFCDEAFF),
onPrimary = Color(0xFFFFFFFFF), onPrimary = Color(0xFFFFFFFFF),
secondary = Color(0xFFe9c754), secondary = Color(0xFFe9c754),
onSecondary = Color(0xFF0E1621),
onBackground = Color(0xFFFFFFFF), onBackground = Color(0xFFFFFFFF),
onBackgroundSecondary = Color(0xFFCCCBCB), onBackgroundSecondary = Color(0xFFCCCBCB),
error = Color(0xFFc93838), error = Color(0xFFc93838),

View File

@ -20,6 +20,7 @@ data class ColorsScheme(
val primaryVariant: Color, val primaryVariant: Color,
val onPrimary: Color, val onPrimary: Color,
val secondary: Color, val secondary: Color,
val onSecondary: Color,
val onBackground: Color, val onBackground: Color,
val onBackgroundSecondary: Color, val onBackgroundSecondary: Color,
val error: Color, val error: Color,
@ -48,12 +49,12 @@ data class ColorsScheme(
primary = this.primary, primary = this.primary,
primaryVariant = this.primaryVariant, primaryVariant = this.primaryVariant,
secondary = this.secondary, secondary = this.secondary,
onSecondary = this.onSecondary,
secondaryVariant = this.secondary, secondaryVariant = this.secondary,
background = this.background, background = this.background,
surface = this.surface, surface = this.surface,
error = this.error, error = this.error,
onPrimary = this.onPrimary, onPrimary = this.onPrimary,
onSecondary = this.onPrimary,
onBackground = this.onBackground, onBackground = this.onBackground,
onSurface = this.onBackground, onSurface = this.onBackground,
onError = this.onError, onError = this.onError,

View File

@ -1,22 +1,30 @@
package com.jetpackduba.gitnuro.ui package com.jetpackduba.gitnuro.ui
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text 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.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.AppIcons
import com.jetpackduba.gitnuro.LoadingRepository import com.jetpackduba.gitnuro.LoadingRepository
import com.jetpackduba.gitnuro.ProcessingScreen import com.jetpackduba.gitnuro.ProcessingScreen
import com.jetpackduba.gitnuro.credentials.CredentialsAccepted import com.jetpackduba.gitnuro.credentials.CredentialsAccepted
import com.jetpackduba.gitnuro.credentials.CredentialsRequested import com.jetpackduba.gitnuro.credentials.CredentialsRequested
import com.jetpackduba.gitnuro.credentials.CredentialsState import com.jetpackduba.gitnuro.credentials.CredentialsState
import com.jetpackduba.gitnuro.git.ProcessingState import com.jetpackduba.gitnuro.git.ProcessingState
import com.jetpackduba.gitnuro.models.Notification
import com.jetpackduba.gitnuro.models.NotificationType
import com.jetpackduba.gitnuro.theme.AppTheme
import com.jetpackduba.gitnuro.ui.dialogs.CloneDialog import com.jetpackduba.gitnuro.ui.dialogs.CloneDialog
import com.jetpackduba.gitnuro.ui.dialogs.GpgPasswordDialog import com.jetpackduba.gitnuro.ui.dialogs.GpgPasswordDialog
import com.jetpackduba.gitnuro.ui.dialogs.SshPasswordDialog import com.jetpackduba.gitnuro.ui.dialogs.SshPasswordDialog
@ -134,17 +142,82 @@ fun AppTab(
.padding(bottom = 48.dp), .padding(bottom = 48.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
for (notification in notifications) for (notification in notifications) {
Text( Notification(notification)
text = notification, }
modifier = Modifier }
.padding(bottom = 12.dp) }
.clip(RoundedCornerShape(8.dp)) }
.background(MaterialTheme.colors.primary)
.padding(8.dp), @Preview
color = MaterialTheme.colors.onPrimary, @Composable
style = MaterialTheme.typography.body1, fun NotificationSuccessPreview() {
) AppTheme(customTheme = null) {
Notification(NotificationType.Positive, "Hello world!")
}
}
@Composable
fun Notification(notification: Notification) {
val notificationShape = RoundedCornerShape(8.dp)
Row(
modifier = Modifier
.padding(8.dp)
.border(2.dp, MaterialTheme.colors.onBackground.copy(0.2f), notificationShape)
.clip(notificationShape)
.background(MaterialTheme.colors.background)
.height(IntrinsicSize.Max)
.padding(2.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
val backgroundColor = when (notification.type) {
NotificationType.Positive -> MaterialTheme.colors.primary
NotificationType.Warning -> MaterialTheme.colors.secondary
NotificationType.Error -> MaterialTheme.colors.error
}
val contentColor = when (notification.type) {
NotificationType.Positive -> MaterialTheme.colors.onPrimary
NotificationType.Warning -> MaterialTheme.colors.onSecondary
NotificationType.Error -> MaterialTheme.colors.onError
}
val icon = when (notification.type) {
NotificationType.Positive -> AppIcons.INFO
NotificationType.Warning -> AppIcons.WARNING
NotificationType.Error -> AppIcons.ERROR
}
Box(
modifier = Modifier
.clip(RoundedCornerShape(topStart = 6.dp, bottomStart = 6.dp))
.background(backgroundColor)
.fillMaxHeight()
// .padding(start = 2.dp, top = 2.dp, bottom = 2.dp)
) {
Icon(
painterResource(icon),
contentDescription = null,
tint = contentColor,
modifier = Modifier
.padding(4.dp)
)
}
Box(
modifier = Modifier
.padding(end = 8.dp)
.fillMaxHeight(),
contentAlignment = Alignment.CenterStart,
) {
Text(
text = notification.text,
modifier = Modifier,
color = MaterialTheme.colors.onBackground,
style = MaterialTheme.typography.body1,
)
} }
} }
} }

View File

@ -80,7 +80,6 @@ class HistoryViewModel @Inject constructor(
title = "History", title = "History",
subtitle = "Loading file history", subtitle = "Loading file history",
taskType = TaskType.HISTORY_FILE, taskType = TaskType.HISTORY_FILE,
positiveFeedbackText = null,
) { git -> ) { git ->
this@HistoryViewModel.filePath = filePath this@HistoryViewModel.filePath = filePath
_historyState.value = HistoryState.Loading(filePath) _historyState.value = HistoryState.Loading(filePath)
@ -91,6 +90,8 @@ class HistoryViewModel @Inject constructor(
.toList() .toList()
_historyState.value = HistoryState.Loaded(filePath, log) _historyState.value = HistoryState.Loaded(filePath, log)
null
} }
fun selectCommit(commit: RevCommit) = tabState.runOperation( fun selectCommit(commit: RevCommit) = tabState.runOperation(

View File

@ -18,6 +18,7 @@ import com.jetpackduba.gitnuro.git.tags.CreateTagOnCommitUseCase
import com.jetpackduba.gitnuro.git.workspace.CheckHasUncommittedChangesUseCase import com.jetpackduba.gitnuro.git.workspace.CheckHasUncommittedChangesUseCase
import com.jetpackduba.gitnuro.git.workspace.GetStatusSummaryUseCase import com.jetpackduba.gitnuro.git.workspace.GetStatusSummaryUseCase
import com.jetpackduba.gitnuro.git.workspace.StatusSummary import com.jetpackduba.gitnuro.git.workspace.StatusSummary
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
import com.jetpackduba.gitnuro.ui.SelectedItem import com.jetpackduba.gitnuro.ui.SelectedItem
import com.jetpackduba.gitnuro.ui.log.LogDialog import com.jetpackduba.gitnuro.ui.log.LogDialog
@ -163,9 +164,10 @@ class LogViewModel @Inject constructor(
title = "Commit checkout", title = "Commit checkout",
subtitle = "Checking out commit ${revCommit.name}", subtitle = "Checking out commit ${revCommit.name}",
taskType = TaskType.CHECKOUT_COMMIT, taskType = TaskType.CHECKOUT_COMMIT,
positiveFeedbackText = "Commit checked out",
) { git -> ) { git ->
checkoutCommitUseCase(git, revCommit) checkoutCommitUseCase(git, revCommit)
positiveNotification("Commit checked out")
} }
fun revertCommit(revCommit: RevCommit) = tabState.safeProcessing( fun revertCommit(revCommit: RevCommit) = tabState.safeProcessing(
@ -174,9 +176,10 @@ class LogViewModel @Inject constructor(
subtitle = "Reverting commit ${revCommit.name}", subtitle = "Reverting commit ${revCommit.name}",
refreshEvenIfCrashes = true, refreshEvenIfCrashes = true,
taskType = TaskType.REVERT_COMMIT, taskType = TaskType.REVERT_COMMIT,
positiveFeedbackText = "Commit reverted",
) { git -> ) { git ->
revertCommitUseCase(git, revCommit) revertCommitUseCase(git, revCommit)
positiveNotification("Commit reverted")
} }
fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = tabState.safeProcessing( fun resetToCommit(revCommit: RevCommit, resetType: ResetType) = tabState.safeProcessing(
@ -184,9 +187,10 @@ class LogViewModel @Inject constructor(
title = "Branch reset", title = "Branch reset",
subtitle = "Resetting branch to commit ${revCommit.shortName}", subtitle = "Resetting branch to commit ${revCommit.shortName}",
taskType = TaskType.RESET_TO_COMMIT, taskType = TaskType.RESET_TO_COMMIT,
positiveFeedbackText = "Reset completed",
) { git -> ) { git ->
resetToCommitUseCase(git, revCommit, resetType = resetType) resetToCommitUseCase(git, revCommit, resetType = resetType)
positiveNotification("Reset completed")
} }
fun cherryPickCommit(revCommit: RevCommit) = tabState.safeProcessing( fun cherryPickCommit(revCommit: RevCommit) = tabState.safeProcessing(
@ -195,9 +199,10 @@ class LogViewModel @Inject constructor(
subtitle = "Cherry-picking commit ${revCommit.shortName}", subtitle = "Cherry-picking commit ${revCommit.shortName}",
taskType = TaskType.CHERRY_PICK_COMMIT, taskType = TaskType.CHERRY_PICK_COMMIT,
refreshEvenIfCrashes = true, refreshEvenIfCrashes = true,
positiveFeedbackText = "Commit cherry-picked"
) { git -> ) { git ->
cherryPickCommitUseCase(git, revCommit) cherryPickCommitUseCase(git, revCommit)
positiveNotification("Commit cherry-picked")
} }
fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing( fun createBranchOnCommit(branch: String, revCommit: RevCommit) = tabState.safeProcessing(
@ -206,9 +211,10 @@ class LogViewModel @Inject constructor(
subtitle = "Creating new branch \"$branch\" on commit ${revCommit.shortName}", subtitle = "Creating new branch \"$branch\" on commit ${revCommit.shortName}",
refreshEvenIfCrashesInteractive = { it is CheckoutConflictException }, refreshEvenIfCrashesInteractive = { it is CheckoutConflictException },
taskType = TaskType.CREATE_BRANCH, taskType = TaskType.CREATE_BRANCH,
positiveFeedbackText = "Branch \"$branch\" created",
) { git -> ) { git ->
createBranchOnCommitUseCase(git, branch, revCommit) createBranchOnCommitUseCase(git, branch, revCommit)
positiveNotification("Branch \"$branch\" created")
} }
fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing( fun createTagOnCommit(tag: String, revCommit: RevCommit) = tabState.safeProcessing(
@ -216,9 +222,10 @@ class LogViewModel @Inject constructor(
title = "New tag", title = "New tag",
subtitle = "Creating new tag \"$tag\" on commit ${revCommit.shortName}", subtitle = "Creating new tag \"$tag\" on commit ${revCommit.shortName}",
taskType = TaskType.CREATE_TAG, taskType = TaskType.CREATE_TAG,
positiveFeedbackText = "Tag created",
) { git -> ) { git ->
createTagOnCommitUseCase(git, tag, revCommit) createTagOnCommitUseCase(git, tag, revCommit)
positiveNotification("Tag created")
} }
private suspend fun uncommittedChangesLoadLog(git: Git) { private suspend fun uncommittedChangesLoadLog(git: Git) {
@ -375,9 +382,10 @@ class LogViewModel @Inject constructor(
fun rebaseInteractive(revCommit: RevCommit) = tabState.safeProcessing( fun rebaseInteractive(revCommit: RevCommit) = tabState.safeProcessing(
refreshType = RefreshType.REBASE_INTERACTIVE_STATE, refreshType = RefreshType.REBASE_INTERACTIVE_STATE,
taskType = TaskType.REBASE_INTERACTIVE, taskType = TaskType.REBASE_INTERACTIVE,
positiveFeedbackText = null,
) { git -> ) { git ->
startRebaseInteractiveUseCase(git, revCommit) startRebaseInteractiveUseCase(git, revCommit)
null
} }
} }

View File

@ -11,6 +11,9 @@ import com.jetpackduba.gitnuro.git.stash.PopLastStashUseCase
import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase import com.jetpackduba.gitnuro.git.stash.StashChangesUseCase
import com.jetpackduba.gitnuro.git.workspace.StageUntrackedFileUseCase import com.jetpackduba.gitnuro.git.workspace.StageUntrackedFileUseCase
import com.jetpackduba.gitnuro.managers.AppStateManager import com.jetpackduba.gitnuro.managers.AppStateManager
import com.jetpackduba.gitnuro.models.errorNotification
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.models.warningNotification
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
import com.jetpackduba.gitnuro.terminal.OpenRepositoryInTerminalUseCase import com.jetpackduba.gitnuro.terminal.OpenRepositoryInTerminalUseCase
import javax.inject.Inject import javax.inject.Inject
@ -22,10 +25,9 @@ class MenuViewModel @Inject constructor(
private val fetchAllRemotesUseCase: FetchAllRemotesUseCase, private val fetchAllRemotesUseCase: FetchAllRemotesUseCase,
private val popLastStashUseCase: PopLastStashUseCase, private val popLastStashUseCase: PopLastStashUseCase,
private val stashChangesUseCase: StashChangesUseCase, private val stashChangesUseCase: StashChangesUseCase,
private val stageUntrackedFileUseCase: StageUntrackedFileUseCase,
private val openRepositoryInTerminalUseCase: OpenRepositoryInTerminalUseCase, private val openRepositoryInTerminalUseCase: OpenRepositoryInTerminalUseCase,
private val settings: AppSettingsRepository, settings: AppSettingsRepository,
private val appStateManager: AppStateManager, appStateManager: AppStateManager,
) { ) {
val isPullWithRebaseDefault = settings.pullRebaseFlow val isPullWithRebaseDefault = settings.pullRebaseFlow
val lastLoadedTabs = appStateManager.latestOpenedRepositoriesPaths val lastLoadedTabs = appStateManager.latestOpenedRepositoriesPaths
@ -34,11 +36,12 @@ class MenuViewModel @Inject constructor(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
title = "Pulling", title = "Pulling",
subtitle = "Pulling changes from the remote branch to the current branch", subtitle = "Pulling changes from the remote branch to the current branch",
positiveFeedbackText = "Pull completed",
refreshEvenIfCrashes = true, refreshEvenIfCrashes = true,
taskType = TaskType.PULL, taskType = TaskType.PULL,
) { git -> ) { git ->
pullBranchUseCase(git, pullType) pullBranchUseCase(git, pullType)
positiveNotification("Pull completed")
} }
fun fetchAll() = tabState.safeProcessing( fun fetchAll() = tabState.safeProcessing(
@ -48,9 +51,10 @@ class MenuViewModel @Inject constructor(
isCancellable = false, isCancellable = false,
refreshEvenIfCrashes = true, refreshEvenIfCrashes = true,
taskType = TaskType.FETCH, taskType = TaskType.FETCH,
positiveFeedbackText = "Fetch all completed",
) { git -> ) { git ->
fetchAllRemotesUseCase(git) fetchAllRemotesUseCase(git)
positiveNotification("Fetch all completed")
} }
fun push(force: Boolean = false, pushTags: Boolean = false) = tabState.safeProcessing( fun push(force: Boolean = false, pushTags: Boolean = false) = tabState.safeProcessing(
@ -60,26 +64,31 @@ class MenuViewModel @Inject constructor(
isCancellable = false, isCancellable = false,
refreshEvenIfCrashes = true, refreshEvenIfCrashes = true,
taskType = TaskType.PUSH, taskType = TaskType.PUSH,
positiveFeedbackText = "Push completed",
) { git -> ) { git ->
pushBranchUseCase(git, force, pushTags) pushBranchUseCase(git, force, pushTags)
positiveNotification("Push completed")
} }
fun stash() = tabState.safeProcessing( fun stash() = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG,
taskType = TaskType.STASH, taskType = TaskType.STASH,
positiveFeedbackText = "Changes stashed",
) { git -> ) { git ->
stashChangesUseCase(git, null) if (stashChangesUseCase(git, null)) {
positiveNotification("Changes stashed")
} else {
errorNotification("There are no changes to stash")
}
} }
fun popStash() = tabState.safeProcessing( fun popStash() = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG,
refreshEvenIfCrashes = true, refreshEvenIfCrashes = true,
taskType = TaskType.POP_STASH, taskType = TaskType.POP_STASH,
positiveFeedbackText = "Stash popped",
) { git -> ) { git ->
popLastStashUseCase(git) popLastStashUseCase(git)
positiveNotification("Stash popped")
} }
fun openTerminal() = tabState.runOperation( fun openTerminal() = tabState.runOperation(

View File

@ -67,13 +67,12 @@ class RebaseInteractiveViewModel @Inject constructor(
fun loadRebaseInteractiveData() = tabState.safeProcessing( fun loadRebaseInteractiveData() = tabState.safeProcessing(
refreshType = RefreshType.NONE, refreshType = RefreshType.NONE,
taskType = TaskType.REBASE_INTERACTIVE,// TODO Perhaps this should be more specific such as TaskType.LOAD_ABORT_REBASE taskType = TaskType.REBASE_INTERACTIVE,// TODO Perhaps this should be more specific such as TaskType.LOAD_ABORT_REBASE
positiveFeedbackText = null,
) { git -> ) { git ->
val state = getRepositoryStateUseCase(git) val state = getRepositoryStateUseCase(git)
if (!state.isRebasing) { if (!state.isRebasing) {
_rebaseState.value = RebaseInteractiveViewState.Loading _rebaseState.value = RebaseInteractiveViewState.Loading
return@safeProcessing return@safeProcessing null
} }
try { try {
@ -107,6 +106,8 @@ class RebaseInteractiveViewModel @Inject constructor(
throw ex throw ex
} }
} }
null
} }
private fun isSameRebase(rebaseLines: List<RebaseLine>, state: RebaseInteractiveViewState): Boolean { private fun isSameRebase(rebaseLines: List<RebaseLine>, state: RebaseInteractiveViewState): Boolean {
@ -126,10 +127,11 @@ class RebaseInteractiveViewModel @Inject constructor(
fun continueRebaseInteractive() = tabState.safeProcessing( fun continueRebaseInteractive() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.REBASE_INTERACTIVE, // TODO Perhaps be more precise with the task type taskType = TaskType.REBASE_INTERACTIVE, // TODO Perhaps be more precise with the task type
positiveFeedbackText = null,
) { git -> ) { git ->
resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue) resumeRebaseInteractiveUseCase(git, interactiveHandlerContinue)
_rebaseState.value = RebaseInteractiveViewState.Loading _rebaseState.value = RebaseInteractiveViewState.Loading
null
} }
fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) { fun onCommitMessageChanged(commit: AbbreviatedObjectId, message: String) {
@ -181,10 +183,11 @@ class RebaseInteractiveViewModel @Inject constructor(
fun selectLine(line: RebaseLine) = tabState.safeProcessing( fun selectLine(line: RebaseLine) = tabState.safeProcessing(
refreshType = RefreshType.NONE, refreshType = RefreshType.NONE,
taskType = TaskType.ABORT_REBASE, // TODO Perhaps be more precise with the task type taskType = TaskType.ABORT_REBASE, // TODO Perhaps be more precise with the task type
positiveFeedbackText = null,
) { git -> ) { git ->
val fullCommit = getCommitFromRebaseLineUseCase(git, line.commit, line.shortMessage) val fullCommit = getCommitFromRebaseLineUseCase(git, line.commit, line.shortMessage)
tabState.newSelectedCommit(fullCommit) tabState.newSelectedCommit(fullCommit)
null
} }
fun moveCommit(from: Int, to: Int) { fun moveCommit(from: Int, to: Int) {

View File

@ -8,6 +8,8 @@ import com.jetpackduba.gitnuro.git.branches.CheckoutRefUseCase
import com.jetpackduba.gitnuro.git.branches.DeleteBranchUseCase import com.jetpackduba.gitnuro.git.branches.DeleteBranchUseCase
import com.jetpackduba.gitnuro.git.branches.MergeBranchUseCase import com.jetpackduba.gitnuro.git.branches.MergeBranchUseCase
import com.jetpackduba.gitnuro.git.rebase.RebaseBranchUseCase import com.jetpackduba.gitnuro.git.rebase.RebaseBranchUseCase
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.models.warningNotification
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
@ -34,9 +36,13 @@ class SharedBranchesViewModel @Inject constructor(
title = "Branch merge", title = "Branch merge",
subtitle = "Merging branch ${ref.simpleName}", subtitle = "Merging branch ${ref.simpleName}",
taskType = TaskType.MERGE_BRANCH, taskType = TaskType.MERGE_BRANCH,
positiveFeedbackText = "Merged from \"${ref.simpleName}\"", refreshEvenIfCrashes = true,
) { git -> ) { git ->
mergeBranchUseCase(git, ref, appSettingsRepository.ffMerge) if (mergeBranchUseCase(git, ref, appSettingsRepository.ffMerge)) {
warningNotification("Merge produced conflicts, please fix them to continue.")
} else {
positiveNotification("Merged from \"${ref.simpleName}\"")
}
} }
override fun deleteBranch(branch: Ref) = tabState.safeProcessing( override fun deleteBranch(branch: Ref) = tabState.safeProcessing(
@ -44,9 +50,10 @@ class SharedBranchesViewModel @Inject constructor(
title = "Branch delete", title = "Branch delete",
subtitle = "Deleting branch ${branch.simpleName}", subtitle = "Deleting branch ${branch.simpleName}",
taskType = TaskType.DELETE_BRANCH, taskType = TaskType.DELETE_BRANCH,
positiveFeedbackText = "\"${branch.simpleName}\" deleted",
) { git -> ) { git ->
deleteBranchUseCase(git, branch) deleteBranchUseCase(git, branch)
positiveNotification("\"${branch.simpleName}\" deleted")
} }
override fun checkoutRef(ref: Ref) = tabState.safeProcessing( override fun checkoutRef(ref: Ref) = tabState.safeProcessing(
@ -54,9 +61,10 @@ class SharedBranchesViewModel @Inject constructor(
title = "Branch checkout", title = "Branch checkout",
subtitle = "Checking out branch ${ref.simpleName}", subtitle = "Checking out branch ${ref.simpleName}",
taskType = TaskType.CHECKOUT_BRANCH, taskType = TaskType.CHECKOUT_BRANCH,
positiveFeedbackText = "\"${ref.simpleName}\" checked out",
) { git -> ) { git ->
checkoutRefUseCase(git, ref) checkoutRefUseCase(git, ref)
positiveNotification("\"${ref.simpleName}\" checked out")
} }
override fun rebaseBranch(ref: Ref) = tabState.safeProcessing( override fun rebaseBranch(ref: Ref) = tabState.safeProcessing(
@ -64,8 +72,10 @@ class SharedBranchesViewModel @Inject constructor(
title = "Branch rebase", title = "Branch rebase",
subtitle = "Rebasing branch ${ref.simpleName}", subtitle = "Rebasing branch ${ref.simpleName}",
taskType = TaskType.REBASE_BRANCH, taskType = TaskType.REBASE_BRANCH,
positiveFeedbackText = "\"${ref.simpleName}\" rebased", refreshEvenIfCrashes = true,
) { git -> ) { git ->
rebaseBranchUseCase(git, ref) rebaseBranchUseCase(git, ref)
positiveNotification("\"${ref.simpleName}\" rebased")
} }
} }

View File

@ -8,6 +8,7 @@ import com.jetpackduba.gitnuro.git.branches.CheckoutRefUseCase
import com.jetpackduba.gitnuro.git.remote_operations.DeleteRemoteBranchUseCase import com.jetpackduba.gitnuro.git.remote_operations.DeleteRemoteBranchUseCase
import com.jetpackduba.gitnuro.git.remote_operations.PullFromSpecificBranchUseCase import com.jetpackduba.gitnuro.git.remote_operations.PullFromSpecificBranchUseCase
import com.jetpackduba.gitnuro.git.remote_operations.PushToSpecificBranchUseCase import com.jetpackduba.gitnuro.git.remote_operations.PushToSpecificBranchUseCase
import com.jetpackduba.gitnuro.models.positiveNotification
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
import javax.inject.Inject import javax.inject.Inject
@ -31,17 +32,19 @@ class SharedRemotesViewModel @Inject constructor(
title = "Deleting remote branch", title = "Deleting remote branch",
subtitle = "Remote branch ${ref.simpleName} will be deleted from the remote", subtitle = "Remote branch ${ref.simpleName} will be deleted from the remote",
taskType = TaskType.DELETE_REMOTE_BRANCH, taskType = TaskType.DELETE_REMOTE_BRANCH,
positiveFeedbackText = "Remote branch \"${ref.simpleName}\" deleted",
) { git -> ) { git ->
deleteRemoteBranchUseCase(git, ref) deleteRemoteBranchUseCase(git, ref)
positiveNotification("Remote branch \"${ref.simpleName}\" deleted",)
} }
override fun checkoutRemoteBranch(remoteBranch: Ref) = tabState.safeProcessing( override fun checkoutRemoteBranch(remoteBranch: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.CHECKOUT_REMOTE_BRANCH, taskType = TaskType.CHECKOUT_REMOTE_BRANCH,
positiveFeedbackText = "\"${remoteBranch.simpleName}\" checked out",
) { git -> ) { git ->
checkoutRefUseCase(git, remoteBranch) checkoutRefUseCase(git, remoteBranch)
positiveNotification("\"${remoteBranch.simpleName}\" checked out")
} }
override fun pushToRemoteBranch(branch: Ref) = tabState.safeProcessing( override fun pushToRemoteBranch(branch: Ref) = tabState.safeProcessing(
@ -49,7 +52,6 @@ class SharedRemotesViewModel @Inject constructor(
title = "Push", title = "Push",
subtitle = "Pushing current branch to ${branch.simpleName}", subtitle = "Pushing current branch to ${branch.simpleName}",
taskType = TaskType.PUSH_TO_BRANCH, taskType = TaskType.PUSH_TO_BRANCH,
positiveFeedbackText = "Pushed to \"${branch.simpleName}\"",
) { git -> ) { git ->
pushToSpecificBranchUseCase( pushToSpecificBranchUseCase(
git = git, git = git,
@ -57,6 +59,8 @@ class SharedRemotesViewModel @Inject constructor(
pushTags = false, pushTags = false,
remoteBranch = branch, remoteBranch = branch,
) )
positiveNotification("Pushed to \"${branch.simpleName}\"")
} }
override fun pullFromRemoteBranch(branch: Ref) = tabState.safeProcessing( override fun pullFromRemoteBranch(branch: Ref) = tabState.safeProcessing(
@ -64,12 +68,13 @@ class SharedRemotesViewModel @Inject constructor(
title = "Pull", title = "Pull",
subtitle = "Pulling changes from ${branch.simpleName} to the current branch", subtitle = "Pulling changes from ${branch.simpleName} to the current branch",
taskType = TaskType.PULL_FROM_BRANCH, taskType = TaskType.PULL_FROM_BRANCH,
positiveFeedbackText = "Pulled from \"${branch.simpleName}\"",
) { git -> ) { git ->
pullFromSpecificBranchUseCase( pullFromSpecificBranchUseCase(
git = git, git = git,
rebase = false, rebase = false,
remoteBranch = branch, remoteBranch = branch,
) )
positiveNotification("Pulled from \"${branch.simpleName}\"")
} }
} }

View File

@ -6,6 +6,7 @@ import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.stash.ApplyStashUseCase import com.jetpackduba.gitnuro.git.stash.ApplyStashUseCase
import com.jetpackduba.gitnuro.git.stash.DeleteStashUseCase import com.jetpackduba.gitnuro.git.stash.DeleteStashUseCase
import com.jetpackduba.gitnuro.git.stash.PopStashUseCase import com.jetpackduba.gitnuro.git.stash.PopStashUseCase
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.ui.SelectedItem import com.jetpackduba.gitnuro.ui.SelectedItem
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit
@ -29,30 +30,33 @@ class SharedStashViewModel @Inject constructor(
refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG,
refreshEvenIfCrashes = true, refreshEvenIfCrashes = true,
taskType = TaskType.APPLY_STASH, taskType = TaskType.APPLY_STASH,
positiveFeedbackText = "Stash applied",
) { git -> ) { git ->
applyStashUseCase(git, stashInfo) applyStashUseCase(git, stashInfo)
positiveNotification("Stash applied")
} }
override fun popStash(stash: RevCommit) = tabState.safeProcessing( override fun popStash(stash: RevCommit) = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG,
refreshEvenIfCrashes = true, refreshEvenIfCrashes = true,
taskType = TaskType.POP_STASH, taskType = TaskType.POP_STASH,
positiveFeedbackText = "Stash popped",
) { git -> ) { git ->
popStashUseCase(git, stash) popStashUseCase(git, stash)
stashDropped(stash) stashDropped(stash)
positiveNotification("Stash popped")
} }
override fun deleteStash(stash: RevCommit) = tabState.safeProcessing( override fun deleteStash(stash: RevCommit) = tabState.safeProcessing(
refreshType = RefreshType.STASHES, refreshType = RefreshType.STASHES,
taskType = TaskType.DELETE_STASH, taskType = TaskType.DELETE_STASH,
positiveFeedbackText = "Stash deleted",
) { git -> ) { git ->
deleteStashUseCase(git, stash) deleteStashUseCase(git, stash)
stashDropped(stash) stashDropped(stash)
positiveNotification("Stash deleted")
} }
override fun selectStash(stash: RevCommit) = tabState.runOperation( override fun selectStash(stash: RevCommit) = tabState.runOperation(

View File

@ -5,6 +5,7 @@ import com.jetpackduba.gitnuro.extensions.simpleName
import com.jetpackduba.gitnuro.git.RefreshType import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.branches.CheckoutRefUseCase import com.jetpackduba.gitnuro.git.branches.CheckoutRefUseCase
import com.jetpackduba.gitnuro.models.positiveNotification
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.eclipse.jgit.lib.Ref import org.eclipse.jgit.lib.Ref
import javax.inject.Inject import javax.inject.Inject
@ -22,8 +23,9 @@ class SharedTagsViewModel @Inject constructor(
title = "Tag delete", title = "Tag delete",
subtitle = "Deleting tag ${tag.simpleName}", subtitle = "Deleting tag ${tag.simpleName}",
taskType = TaskType.DELETE_TAG, taskType = TaskType.DELETE_TAG,
positiveFeedbackText = "Tag \"${tag.simpleName}\" deleted",
) { git -> ) { git ->
deleteTagUseCase(git, tag) deleteTagUseCase(git, tag)
positiveNotification("Tag \"${tag.simpleName}\" deleted")
} }
} }

View File

@ -18,6 +18,7 @@ import com.jetpackduba.gitnuro.git.rebase.SkipRebaseUseCase
import com.jetpackduba.gitnuro.git.repository.ResetRepositoryStateUseCase import com.jetpackduba.gitnuro.git.repository.ResetRepositoryStateUseCase
import com.jetpackduba.gitnuro.git.workspace.* import com.jetpackduba.gitnuro.git.workspace.*
import com.jetpackduba.gitnuro.models.AuthorInfo import com.jetpackduba.gitnuro.models.AuthorInfo
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.repositories.AppSettingsRepository import com.jetpackduba.gitnuro.repositories.AppSettingsRepository
import com.jetpackduba.gitnuro.ui.tree_files.TreeItem import com.jetpackduba.gitnuro.ui.tree_files.TreeItem
import com.jetpackduba.gitnuro.ui.tree_files.entriesToTreeEntry import com.jetpackduba.gitnuro.ui.tree_files.entriesToTreeEntry
@ -216,17 +217,19 @@ class StatusViewModel @Inject constructor(
fun unstageAll() = tabState.safeProcessing( fun unstageAll() = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITTED_CHANGES, refreshType = RefreshType.UNCOMMITTED_CHANGES,
taskType = TaskType.UNSTAGE_ALL_FILES, taskType = TaskType.UNSTAGE_ALL_FILES,
positiveFeedbackText = null,
) { git -> ) { git ->
unstageAllUseCase(git) unstageAllUseCase(git)
null
} }
fun stageAll() = tabState.safeProcessing( fun stageAll() = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITTED_CHANGES, refreshType = RefreshType.UNCOMMITTED_CHANGES,
taskType = TaskType.STAGE_ALL_FILES, taskType = TaskType.STAGE_ALL_FILES,
positiveFeedbackText = null,
) { git -> ) { git ->
stageAllUseCase(git) stageAllUseCase(git)
null
} }
fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation( fun resetStaged(statusEntry: StatusEntry) = tabState.runOperation(
@ -349,7 +352,6 @@ class StatusViewModel @Inject constructor(
fun commit(message: String) = tabState.safeProcessing( fun commit(message: String) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.DO_COMMIT, taskType = TaskType.DO_COMMIT,
positiveFeedbackText = if (isAmend.value) "Commit amended" else "New commit created",
) { git -> ) { git ->
val amend = isAmend.value val amend = isAmend.value
@ -364,6 +366,8 @@ class StatusViewModel @Inject constructor(
doCommitUseCase(git, commitMessage, amend, personIdent) doCommitUseCase(git, commitMessage, amend, personIdent)
updateCommitMessage("") updateCommitMessage("")
_isAmend.value = false _isAmend.value = false
positiveNotification(if (isAmend.value) "Commit amended" else "New commit created")
} }
private suspend fun getPersonIdent(git: Git): PersonIdent? { private suspend fun getPersonIdent(git: Git): PersonIdent? {
@ -404,7 +408,6 @@ class StatusViewModel @Inject constructor(
fun continueRebase(message: String) = tabState.safeProcessing( fun continueRebase(message: String) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.CONTINUE_REBASE, taskType = TaskType.CONTINUE_REBASE,
positiveFeedbackText = null,
) { git -> ) { git ->
val repositoryState = sharedRepositoryStateManager.repositoryState.value val repositoryState = sharedRepositoryStateManager.repositoryState.value
val rebaseInteractiveState = sharedRepositoryStateManager.rebaseInteractiveState.value val rebaseInteractiveState = sharedRepositoryStateManager.rebaseInteractiveState.value
@ -423,30 +426,35 @@ class StatusViewModel @Inject constructor(
} }
continueRebaseUseCase(git) continueRebaseUseCase(git)
null
} }
fun abortRebase() = tabState.safeProcessing( fun abortRebase() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.ABORT_REBASE, taskType = TaskType.ABORT_REBASE,
positiveFeedbackText = "Rebase aborted",
) { git -> ) { git ->
abortRebaseUseCase(git) abortRebaseUseCase(git)
positiveNotification("Rebase aborted")
} }
fun skipRebase() = tabState.safeProcessing( fun skipRebase() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.SKIP_REBASE, taskType = TaskType.SKIP_REBASE,
positiveFeedbackText = null,
) { git -> ) { git ->
skipRebaseUseCase(git) skipRebaseUseCase(git)
null
} }
fun resetRepoState() = tabState.safeProcessing( fun resetRepoState() = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.RESET_REPO_STATE, taskType = TaskType.RESET_REPO_STATE,
positiveFeedbackText = "Repository state has been reset",
) { git -> ) { git ->
resetRepositoryStateUseCase(git) resetRepositoryStateUseCase(git)
positiveNotification("Repository state has been reset")
} }
fun deleteFile(statusEntry: StatusEntry) = tabState.runOperation( fun deleteFile(statusEntry: StatusEntry) = tabState.runOperation(

View File

@ -20,6 +20,9 @@ import com.jetpackduba.gitnuro.managers.AppStateManager
import com.jetpackduba.gitnuro.managers.ErrorsManager import com.jetpackduba.gitnuro.managers.ErrorsManager
import com.jetpackduba.gitnuro.managers.newErrorNow import com.jetpackduba.gitnuro.managers.newErrorNow
import com.jetpackduba.gitnuro.models.AuthorInfoSimple import com.jetpackduba.gitnuro.models.AuthorInfoSimple
import com.jetpackduba.gitnuro.models.errorNotification
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.models.warningNotification
import com.jetpackduba.gitnuro.system.OpenFilePickerUseCase import com.jetpackduba.gitnuro.system.OpenFilePickerUseCase
import com.jetpackduba.gitnuro.system.OpenUrlInBrowserUseCase import com.jetpackduba.gitnuro.system.OpenUrlInBrowserUseCase
import com.jetpackduba.gitnuro.system.PickerType import com.jetpackduba.gitnuro.system.PickerType
@ -353,7 +356,6 @@ class TabViewModel @Inject constructor(
fun blameFile(filePath: String) = tabState.safeProcessing( fun blameFile(filePath: String) = tabState.safeProcessing(
refreshType = RefreshType.NONE, refreshType = RefreshType.NONE,
taskType = TaskType.BLAME_FILE, taskType = TaskType.BLAME_FILE,
positiveFeedbackText = null,
) { git -> ) { git ->
_blameState.value = BlameState.Loading(filePath) _blameState.value = BlameState.Loading(filePath)
try { try {
@ -368,6 +370,8 @@ class TabViewModel @Inject constructor(
throw ex throw ex
} }
null
} }
fun resetBlameState() { fun resetBlameState() {
@ -418,18 +422,23 @@ class TabViewModel @Inject constructor(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
refreshEvenIfCrashesInteractive = { it is CheckoutConflictException }, refreshEvenIfCrashesInteractive = { it is CheckoutConflictException },
taskType = TaskType.CREATE_BRANCH, taskType = TaskType.CREATE_BRANCH,
positiveFeedbackText = "Branch \"${branchName}\" created",
) { git -> ) { git ->
createBranchUseCase(git, branchName) createBranchUseCase(git, branchName)
positiveNotification("Branch \"${branchName}\" created")
} }
fun stashWithMessage(message: String) = tabState.safeProcessing( fun stashWithMessage(message: String) = tabState.safeProcessing(
refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG, refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG,
taskType = TaskType.STASH, taskType = TaskType.STASH,
positiveFeedbackText = "Changes stashed",
) { git -> ) { git ->
stageUntrackedFileUseCase(git) stageUntrackedFileUseCase(git)
stashChangesUseCase(git, message)
if (stashChangesUseCase(git, message)) {
positiveNotification("Changes stashed")
} else {
errorNotification("There are no changes to stash")
}
} }
fun openFolderInFileExplorer() = tabState.runOperation( fun openFolderInFileExplorer() = tabState.runOperation(

View File

@ -11,6 +11,7 @@ import com.jetpackduba.gitnuro.git.branches.GetCurrentBranchUseCase
import com.jetpackduba.gitnuro.git.branches.GetRemoteBranchesUseCase import com.jetpackduba.gitnuro.git.branches.GetRemoteBranchesUseCase
import com.jetpackduba.gitnuro.git.remotes.* import com.jetpackduba.gitnuro.git.remotes.*
import com.jetpackduba.gitnuro.models.RemoteWrapper import com.jetpackduba.gitnuro.models.RemoteWrapper
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.viewmodels.ISharedRemotesViewModel import com.jetpackduba.gitnuro.viewmodels.ISharedRemotesViewModel
import com.jetpackduba.gitnuro.viewmodels.SharedRemotesViewModel import com.jetpackduba.gitnuro.viewmodels.SharedRemotesViewModel
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -117,7 +118,6 @@ class RemotesViewModel @AssistedInject constructor(
fun deleteRemote(remoteName: String) = tabState.safeProcessing( fun deleteRemote(remoteName: String) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.DELETE_REMOTE, taskType = TaskType.DELETE_REMOTE,
positiveFeedbackText = "Remote $remoteName deleted",
) { git -> ) { git ->
deleteRemoteUseCase(git, remoteName) deleteRemoteUseCase(git, remoteName)
@ -129,6 +129,8 @@ class RemotesViewModel @AssistedInject constructor(
} }
deleteLocallyRemoteBranchesUseCase(git, remoteToDeleteBranchesNames) deleteLocallyRemoteBranchesUseCase(git, remoteToDeleteBranchesNames)
positiveNotification("Remote $remoteName deleted")
} }

View File

@ -5,6 +5,7 @@ import com.jetpackduba.gitnuro.extensions.lowercaseContains
import com.jetpackduba.gitnuro.git.RefreshType import com.jetpackduba.gitnuro.git.RefreshType
import com.jetpackduba.gitnuro.git.TabState import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.submodules.* import com.jetpackduba.gitnuro.git.submodules.*
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.ui.TabsManager import com.jetpackduba.gitnuro.ui.TabsManager
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -61,10 +62,11 @@ class SubmodulesViewModel @AssistedInject constructor(
fun initializeSubmodule(path: String) = tabState.safeProcessing( fun initializeSubmodule(path: String) = tabState.safeProcessing(
refreshType = RefreshType.SUBMODULES, refreshType = RefreshType.SUBMODULES,
taskType = TaskType.INIT_SUBMODULE, taskType = TaskType.INIT_SUBMODULE,
positiveFeedbackText = null,
) { git -> ) { git ->
initializeSubmoduleUseCase(git, path) initializeSubmoduleUseCase(git, path)
updateSubmoduleUseCase(git, path) updateSubmoduleUseCase(git, path)
null
} }
suspend fun refresh(git: Git) { suspend fun refresh(git: Git) {
@ -79,9 +81,10 @@ class SubmodulesViewModel @AssistedInject constructor(
refreshType = RefreshType.SUBMODULES, refreshType = RefreshType.SUBMODULES,
title = "Deinitializing submodule $path", title = "Deinitializing submodule $path",
taskType = TaskType.DEINIT_SUBMODULE, taskType = TaskType.DEINIT_SUBMODULE,
positiveFeedbackText = null,
) { git -> ) { git ->
deInitializeSubmoduleUseCase(git, path) deInitializeSubmoduleUseCase(git, path)
null
} }
fun onSyncSubmodule(path: String) = tabState.safeProcessing( fun onSyncSubmodule(path: String) = tabState.safeProcessing(
@ -89,9 +92,10 @@ class SubmodulesViewModel @AssistedInject constructor(
title = "Syncing submodule $path", title = "Syncing submodule $path",
subtitle = "Please wait until synchronization has finished", subtitle = "Please wait until synchronization has finished",
taskType = TaskType.SYNC_SUBMODULE, taskType = TaskType.SYNC_SUBMODULE,
positiveFeedbackText = "Submodule synced",
) { git -> ) { git ->
syncSubmoduleUseCase(git, path) syncSubmoduleUseCase(git, path)
positiveNotification("Submodule synced")
} }
fun onUpdateSubmodule(path: String) = tabState.safeProcessing( fun onUpdateSubmodule(path: String) = tabState.safeProcessing(
@ -99,15 +103,15 @@ class SubmodulesViewModel @AssistedInject constructor(
title = "Updating submodule $path", title = "Updating submodule $path",
subtitle = "Please wait until update has finished", subtitle = "Please wait until update has finished",
taskType = TaskType.UPDATE_SUBMODULE, taskType = TaskType.UPDATE_SUBMODULE,
positiveFeedbackText = "Submodule updated",
) { git -> ) { git ->
updateSubmoduleUseCase(git, path) updateSubmoduleUseCase(git, path)
positiveNotification("Submodule updated")
} }
fun onCreateSubmodule(repository: String, directory: String) = tabState.safeProcessing( fun onCreateSubmodule(repository: String, directory: String) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.ADD_SUBMODULE, taskType = TaskType.ADD_SUBMODULE,
positiveFeedbackText = "Submodule created",
) { git -> ) { git ->
addSubmoduleUseCase( addSubmoduleUseCase(
git = git, git = git,
@ -115,14 +119,17 @@ class SubmodulesViewModel @AssistedInject constructor(
path = directory, path = directory,
uri = repository, uri = repository,
) )
positiveNotification("Submodule created")
} }
fun onDeleteSubmodule(path: String) = tabState.safeProcessing( fun onDeleteSubmodule(path: String) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.DELETE_SUBMODULE, taskType = TaskType.DELETE_SUBMODULE,
positiveFeedbackText = "Submodule deleted",
) { git -> ) { git ->
deleteSubmoduleUseCase(git, path) deleteSubmoduleUseCase(git, path)
positiveNotification("Submodule deleted")
} }
} }

View File

@ -8,6 +8,7 @@ import com.jetpackduba.gitnuro.git.TabState
import com.jetpackduba.gitnuro.git.log.CheckoutCommitUseCase import com.jetpackduba.gitnuro.git.log.CheckoutCommitUseCase
import com.jetpackduba.gitnuro.git.tags.DeleteTagUseCase import com.jetpackduba.gitnuro.git.tags.DeleteTagUseCase
import com.jetpackduba.gitnuro.git.tags.GetTagsUseCase import com.jetpackduba.gitnuro.git.tags.GetTagsUseCase
import com.jetpackduba.gitnuro.models.positiveNotification
import com.jetpackduba.gitnuro.viewmodels.ISharedTagsViewModel import com.jetpackduba.gitnuro.viewmodels.ISharedTagsViewModel
import com.jetpackduba.gitnuro.viewmodels.SharedTagsViewModel import com.jetpackduba.gitnuro.viewmodels.SharedTagsViewModel
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -60,9 +61,10 @@ class TagsViewModel @AssistedInject constructor(
fun checkoutTagCommit(ref: Ref) = tabState.safeProcessing( fun checkoutTagCommit(ref: Ref) = tabState.safeProcessing(
refreshType = RefreshType.ALL_DATA, refreshType = RefreshType.ALL_DATA,
taskType = TaskType.INIT_SUBMODULE, taskType = TaskType.INIT_SUBMODULE,
positiveFeedbackText = "Commit checked out",
) { git -> ) { git ->
checkoutCommitUseCase(git, ref.objectId.name) checkoutCommitUseCase(git, ref.objectId.name)
positiveNotification("Commit checked out")
} }
fun selectTag(tag: Ref) { fun selectTag(tag: Ref) {