Added basic positive feedback
This commit is contained in:
parent
8ca2c0be66
commit
2375f43a44
71
src/main/kotlin/com/jetpackduba/gitnuro/ProcessingScreen.kt
Normal file
71
src/main/kotlin/com/jetpackduba/gitnuro/ProcessingScreen.kt
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package com.jetpackduba.gitnuro
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.LinearProgressIndicator
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.jetpackduba.gitnuro.git.ProcessingState
|
||||||
|
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProcessingScreen(
|
||||||
|
processingState: ProcessingState.Processing,
|
||||||
|
onCancelOnGoingTask: () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colors.surface)
|
||||||
|
.onPreviewKeyEvent { true } // Disable all keyboard events
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
if (processingState.title.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
processingState.title,
|
||||||
|
style = MaterialTheme.typography.h3,
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processingState.subtitle.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
processingState.subtitle,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
modifier = Modifier.padding(bottom = 32.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier.width(280.dp)
|
||||||
|
.padding(bottom = 32.dp),
|
||||||
|
color = MaterialTheme.colors.secondary,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (processingState.isCancellable) {
|
||||||
|
PrimaryButton(
|
||||||
|
text = "Cancel",
|
||||||
|
onClick = onCancelOnGoingTask,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -77,6 +77,7 @@ class TabState @Inject constructor(
|
|||||||
title: String = "",
|
title: String = "",
|
||||||
subtitle: String = "",
|
subtitle: String = "",
|
||||||
taskType: TaskType,
|
taskType: TaskType,
|
||||||
|
positiveFeedbackText: String? = null,
|
||||||
// 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,
|
||||||
@ -104,6 +105,12 @@ class TabState @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
callback(git)
|
callback(git)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (positiveFeedbackText != null) {
|
||||||
|
launch {
|
||||||
|
errorsManager.emitPositiveNotification(positiveFeedbackText)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
hasProcessFailed = true
|
hasProcessFailed = true
|
||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
|
@ -4,6 +4,7 @@ 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 kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
@ -20,6 +21,15 @@ 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<String?>(null)
|
||||||
|
val notification: StateFlow<String?> = _notification
|
||||||
|
|
||||||
|
suspend fun emitPositiveNotification(text: String) {
|
||||||
|
_notification.emit(text)
|
||||||
|
delay(2000)
|
||||||
|
_notification.emit(null)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addError(error: Error) = withContext(Dispatchers.IO) {
|
suspend fun addError(error: Error) = withContext(Dispatchers.IO) {
|
||||||
_errorsList.value = _errorsList.value.toMutableList().apply {
|
_errorsList.value = _errorsList.value.toMutableList().apply {
|
||||||
add(error)
|
add(error)
|
||||||
|
@ -1,25 +1,29 @@
|
|||||||
package com.jetpackduba.gitnuro.ui
|
package com.jetpackduba.gitnuro.ui
|
||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material.LinearProgressIndicator
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
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.input.key.onPreviewKeyEvent
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.jetpackduba.gitnuro.LoadingRepository
|
import com.jetpackduba.gitnuro.LoadingRepository
|
||||||
|
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.ui.components.PrimaryButton
|
import com.jetpackduba.gitnuro.ui.dialogs.CloneDialog
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.*
|
import com.jetpackduba.gitnuro.ui.dialogs.GpgPasswordDialog
|
||||||
|
import com.jetpackduba.gitnuro.ui.dialogs.SshPasswordDialog
|
||||||
|
import com.jetpackduba.gitnuro.ui.dialogs.UserPasswordDialog
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.errors.ErrorDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.errors.ErrorDialog
|
||||||
import com.jetpackduba.gitnuro.ui.dialogs.settings.SettingsDialog
|
import com.jetpackduba.gitnuro.ui.dialogs.settings.SettingsDialog
|
||||||
import com.jetpackduba.gitnuro.viewmodels.RepositorySelectionStatus
|
import com.jetpackduba.gitnuro.viewmodels.RepositorySelectionStatus
|
||||||
@ -32,11 +36,20 @@ fun AppTab(
|
|||||||
val errorManager = tabViewModel.errorsManager
|
val errorManager = tabViewModel.errorsManager
|
||||||
val lastError by errorManager.error.collectAsState(null)
|
val lastError by errorManager.error.collectAsState(null)
|
||||||
val showError by tabViewModel.showError.collectAsState()
|
val showError by tabViewModel.showError.collectAsState()
|
||||||
|
val notification = errorManager.notification.collectAsState().value
|
||||||
|
var visibleNotification by remember { mutableStateOf("") }
|
||||||
|
// val (tabPosition, setTabPosition) = remember { mutableStateOf<LayoutCoordinates?>(null) }
|
||||||
|
|
||||||
val repositorySelectionStatus = tabViewModel.repositorySelectionStatus.collectAsState()
|
val repositorySelectionStatus = tabViewModel.repositorySelectionStatus.collectAsState()
|
||||||
val repositorySelectionStatusValue = repositorySelectionStatus.value
|
val repositorySelectionStatusValue = repositorySelectionStatus.value
|
||||||
val processingState = tabViewModel.processing.collectAsState().value
|
val processingState = tabViewModel.processing.collectAsState().value
|
||||||
|
|
||||||
|
LaunchedEffect(notification) {
|
||||||
|
if (notification != null) {
|
||||||
|
visibleNotification = notification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(tabViewModel) {
|
LaunchedEffect(tabViewModel) {
|
||||||
// Init the tab content when the tab is selected and also remove the "initialPath" to avoid opening the
|
// Init the tab content when the tab is selected and also remove the "initialPath" to avoid opening the
|
||||||
// repository everytime the user changes between tabs
|
// repository everytime the user changes between tabs
|
||||||
@ -107,53 +120,10 @@ fun AppTab(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (processingState is ProcessingState.Processing) {
|
if (processingState is ProcessingState.Processing) {
|
||||||
Box(
|
ProcessingScreen(
|
||||||
modifier = Modifier
|
processingState,
|
||||||
.fillMaxSize()
|
onCancelOnGoingTask = { tabViewModel.cancelOngoingTask() }
|
||||||
.background(MaterialTheme.colors.surface)
|
)
|
||||||
.onPreviewKeyEvent { true } // Disable all keyboard events
|
|
||||||
.clickable(
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
indication = null,
|
|
||||||
onClick = {},
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
if (processingState.title.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
processingState.title,
|
|
||||||
style = MaterialTheme.typography.h3,
|
|
||||||
color = MaterialTheme.colors.onBackground,
|
|
||||||
modifier = Modifier.padding(bottom = 8.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (processingState.subtitle.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
processingState.subtitle,
|
|
||||||
style = MaterialTheme.typography.body1,
|
|
||||||
color = MaterialTheme.colors.onBackground,
|
|
||||||
modifier = Modifier.padding(bottom = 32.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier.width(280.dp)
|
|
||||||
.padding(bottom = 32.dp),
|
|
||||||
color = MaterialTheme.colors.secondary,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (processingState.isCancellable) {
|
|
||||||
PrimaryButton(
|
|
||||||
text = "Cancel",
|
|
||||||
onClick = { tabViewModel.cancelOngoingTask() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -164,6 +134,24 @@ fun AppTab(
|
|||||||
onAccept = { tabViewModel.showError.value = false }
|
onAccept = { tabViewModel.showError.value = false }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = notification != null,
|
||||||
|
modifier = Modifier.align(Alignment.BottomCenter),
|
||||||
|
enter = fadeIn() + slideInVertically { it * 2 },
|
||||||
|
exit = fadeOut() + slideOutVertically { it * 2 },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = visibleNotification,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 48.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(MaterialTheme.colors.primary)
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colors.onPrimary,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ 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 successfully",
|
||||||
refreshEvenIfCrashes = true,
|
refreshEvenIfCrashes = true,
|
||||||
taskType = TaskType.PULL,
|
taskType = TaskType.PULL,
|
||||||
) { git ->
|
) { git ->
|
||||||
@ -65,6 +66,7 @@ class MenuViewModel @Inject constructor(
|
|||||||
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 have been stashed",
|
||||||
) { git ->
|
) { git ->
|
||||||
stashChangesUseCase(git, null)
|
stashChangesUseCase(git, null)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user