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 = "",
|
||||
subtitle: String = "",
|
||||
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
|
||||
isCancellable: Boolean = false,
|
||||
refreshEvenIfCrashes: Boolean = false,
|
||||
@ -104,6 +105,12 @@ class TabState @Inject constructor(
|
||||
) {
|
||||
callback(git)
|
||||
}
|
||||
|
||||
if (positiveFeedbackText != null) {
|
||||
launch {
|
||||
errorsManager.emitPositiveNotification(positiveFeedbackText)
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
hasProcessFailed = true
|
||||
ex.printStackTrace()
|
||||
|
@ -4,6 +4,7 @@ import com.jetpackduba.gitnuro.TaskType
|
||||
import com.jetpackduba.gitnuro.di.TabScope
|
||||
import com.jetpackduba.gitnuro.exceptions.GitnuroException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
@ -20,6 +21,15 @@ class ErrorsManager @Inject constructor() {
|
||||
private val _error = MutableSharedFlow<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) {
|
||||
_errorsList.value = _errorsList.value.toMutableList().apply {
|
||||
add(error)
|
||||
|
@ -1,25 +1,29 @@
|
||||
package com.jetpackduba.gitnuro.ui
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.*
|
||||
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.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
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 com.jetpackduba.gitnuro.LoadingRepository
|
||||
import com.jetpackduba.gitnuro.ProcessingScreen
|
||||
import com.jetpackduba.gitnuro.credentials.CredentialsAccepted
|
||||
import com.jetpackduba.gitnuro.credentials.CredentialsRequested
|
||||
import com.jetpackduba.gitnuro.credentials.CredentialsState
|
||||
import com.jetpackduba.gitnuro.git.ProcessingState
|
||||
import com.jetpackduba.gitnuro.ui.components.PrimaryButton
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.*
|
||||
import com.jetpackduba.gitnuro.ui.dialogs.CloneDialog
|
||||
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.settings.SettingsDialog
|
||||
import com.jetpackduba.gitnuro.viewmodels.RepositorySelectionStatus
|
||||
@ -32,11 +36,20 @@ fun AppTab(
|
||||
val errorManager = tabViewModel.errorsManager
|
||||
val lastError by errorManager.error.collectAsState(null)
|
||||
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 repositorySelectionStatusValue = repositorySelectionStatus.value
|
||||
val processingState = tabViewModel.processing.collectAsState().value
|
||||
|
||||
LaunchedEffect(notification) {
|
||||
if (notification != null) {
|
||||
visibleNotification = notification
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(tabViewModel) {
|
||||
// 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
|
||||
@ -107,53 +120,10 @@ fun AppTab(
|
||||
}
|
||||
|
||||
if (processingState is ProcessingState.Processing) {
|
||||
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 = { tabViewModel.cancelOngoingTask() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
ProcessingScreen(
|
||||
processingState,
|
||||
onCancelOnGoingTask = { tabViewModel.cancelOngoingTask() }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -164,6 +134,24 @@ fun AppTab(
|
||||
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,
|
||||
title = "Pulling",
|
||||
subtitle = "Pulling changes from the remote branch to the current branch",
|
||||
positiveFeedbackText = "Pull completed successfully",
|
||||
refreshEvenIfCrashes = true,
|
||||
taskType = TaskType.PULL,
|
||||
) { git ->
|
||||
@ -65,6 +66,7 @@ class MenuViewModel @Inject constructor(
|
||||
fun stash() = tabState.safeProcessing(
|
||||
refreshType = RefreshType.UNCOMMITTED_CHANGES_AND_LOG,
|
||||
taskType = TaskType.STASH,
|
||||
positiveFeedbackText = "Changes have been stashed",
|
||||
) { git ->
|
||||
stashChangesUseCase(git, null)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user