From e6cd822b17c1b588aa2870757179a17779d28aa3 Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Mon, 25 Oct 2021 02:26:58 +0200 Subject: [PATCH] Created custom dialog UI --- src/main/kotlin/app/App.kt | 280 ++++++++++-------- .../kotlin/app/git/dialogs/NewBranchDialog.kt | 71 ++--- .../app/git/dialogs/UserPasswordDialog.kt | 99 ++++--- src/main/kotlin/app/ui/AppTab.kt | 74 +++++ src/main/kotlin/app/ui/RepositoryOpen.kt | 54 ++-- .../kotlin/app/ui/components/DialogBox.kt | 26 ++ 6 files changed, 375 insertions(+), 229 deletions(-) create mode 100644 src/main/kotlin/app/ui/AppTab.kt create mode 100644 src/main/kotlin/app/ui/components/DialogBox.kt diff --git a/src/main/kotlin/app/App.kt b/src/main/kotlin/app/App.kt index 03ceb89..dea91a0 100644 --- a/src/main/kotlin/app/App.kt +++ b/src/main/kotlin/app/App.kt @@ -1,26 +1,28 @@ package app -import androidx.compose.animation.Crossfade import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.* +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.DefaultAlpha +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.* import androidx.compose.ui.zIndex import app.di.DaggerAppComponent import app.git.GitManager -import app.git.RepositorySelectionStatus import app.theme.AppTheme -import app.ui.RepositoryOpenPage -import app.ui.WelcomePage +import app.ui.AppTab +import app.ui.components.DialogBox import app.ui.components.RepositoriesTabPanel import app.ui.components.TabInformation import javax.inject.Inject @@ -56,89 +58,141 @@ class Main { ) ) { AppTheme { - val tabs = remember { + val showDialog = remember { mutableStateOf(false) } + val dialogManager = remember { DialogManager(showDialog) } - val repositoriesSavedTabs = appStateManager.openRepositoriesPathsTabs - var repoTabs = repositoriesSavedTabs.map { repositoryTab -> - newAppTab(key = repositoryTab.key, path = repositoryTab.value) - } + Box { - if (repoTabs.isEmpty()) { - repoTabs = listOf( - newAppTab() + AppTabs(dialogManager) + + if (showDialog.value) { + val interactionSource = remember { MutableInteractionSource() } + + Box( + modifier = Modifier + .fillMaxSize() + .alpha(0.8f) + .background(Color.Black) + .clickable( + enabled = true, + onClick = {}, + interactionSource = interactionSource, + indication = null + ) ) + DialogBox( + modifier = Modifier + .align(Alignment.Center) + .clickable( + enabled = true, + onClick = {}, + interactionSource = interactionSource, + indication = null + ) + ) { + dialogManager.dialog() + } } - - mutableStateOf(repoTabs) } - var selectedTabKey by remember { mutableStateOf(0) } - Column( - modifier = - Modifier.background(MaterialTheme.colors.surface) + } + } + } + } + + + @Composable + fun AppTabs(dialogManager: DialogManager) { + val tabs = remember { + + val repositoriesSavedTabs = appStateManager.openRepositoriesPathsTabs + var repoTabs = repositoriesSavedTabs.map { repositoryTab -> + newAppTab( + dialogManager = dialogManager, + key = repositoryTab.key, + path = repositoryTab.value + ) + } + + if (repoTabs.isEmpty()) { + repoTabs = listOf( + newAppTab( + dialogManager = dialogManager + ) + ) + } + + mutableStateOf(repoTabs) + } + + var selectedTabKey by remember { mutableStateOf(0) } + + Column( + modifier = + Modifier.background(MaterialTheme.colors.surface) + ) { + Row( + modifier = Modifier + .padding(top = 4.dp, bottom = 2.dp, start = 4.dp, end = 4.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + RepositoriesTabPanel( + modifier = Modifier + .weight(1f), + tabs = tabs.value, + selectedTabKey = selectedTabKey, + onTabSelected = { newSelectedTabKey -> + selectedTabKey = newSelectedTabKey + }, + newTabContent = { key -> + newAppTab( + dialogManager = dialogManager, + key = key + ) + }, + onTabsUpdated = { tabInformationList -> + tabs.value = tabInformationList + }, + onTabClosed = { key -> + appStateManager.repositoryTabRemoved(key) + } + ) + IconButton( + modifier = Modifier + .padding(horizontal = 8.dp) + .size(24.dp), + onClick = {} + ) { + Icon( + painter = painterResource("settings.svg"), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + tint = MaterialTheme.colors.primary, + ) + } + } + + LazyColumn( + modifier = Modifier + .fillMaxSize(), + ) { + items(items = tabs.value, key = { it.key }) { + val isItemSelected = it.key == selectedTabKey + + var tabMod: Modifier = if (!isItemSelected) + Modifier.size(0.dp) + else + Modifier + .fillParentMaxSize() + + tabMod = tabMod.background(MaterialTheme.colors.primary) + .alpha(if (isItemSelected) 1f else -1f) + .zIndex(if (isItemSelected) 1f else -1f) + Box( + modifier = tabMod, ) { - Row( - modifier = Modifier - .padding(top = 4.dp, bottom = 2.dp, start = 4.dp, end = 4.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - RepositoriesTabPanel( - modifier = Modifier - .weight(1f), - tabs = tabs.value, - selectedTabKey = selectedTabKey, - onTabSelected = { newSelectedTabKey -> - selectedTabKey = newSelectedTabKey - }, - newTabContent = { key -> - newAppTab(key) - }, - onTabsUpdated = { tabInformationList -> - tabs.value = tabInformationList - }, - onTabClosed = { key -> - appStateManager.repositoryTabRemoved(key) - } - ) - IconButton( - modifier = Modifier - .padding(horizontal = 8.dp) - .size(24.dp), - onClick = {} - ) { - Icon( - painter = painterResource("settings.svg"), - contentDescription = null, - modifier = Modifier.fillMaxSize(), - tint = MaterialTheme.colors.primary, - ) - } - } - - LazyColumn( - modifier = Modifier - .fillMaxSize(), - ) { - items(items = tabs.value, key = { it.key }) { - val isItemSelected = it.key == selectedTabKey - - var tabMod: Modifier = if (!isItemSelected) - Modifier.size(0.dp) - else - Modifier - .fillParentMaxSize() - - tabMod = tabMod.background(MaterialTheme.colors.primary) - .alpha(if (isItemSelected) 1f else -1f) - .zIndex(if (isItemSelected) 1f else -1f) - Box( - modifier = tabMod, - ) { - it.content(it) - } - } - } + it.content(it) } } } @@ -146,6 +200,7 @@ class Main { } private fun newAppTab( + dialogManager: DialogManager, key: Int = 0, tabName: MutableState = mutableStateOf("New tab"), path: String? = null, @@ -163,63 +218,26 @@ class Main { appStateManager.repositoryTabChanged(key, path) } - App(gitManager, path, tabName) + AppTab(gitManager, dialogManager, path, tabName) } } } -@Composable -fun App(gitManager: GitManager, repositoryPath: String?, tabName: MutableState) { - LaunchedEffect(gitManager) { - if (repositoryPath != null) - gitManager.openRepository(repositoryPath) +class DialogManager(private val showDialog: MutableState) { + private var content: @Composable () -> Unit = {} + + fun show(content: @Composable () -> Unit) { + this.content = content + showDialog.value = true } - - val repositorySelectionStatus by gitManager.repositorySelectionStatus.collectAsState() - val isProcessing by gitManager.processing.collectAsState() - - if (repositorySelectionStatus is RepositorySelectionStatus.Open) { - tabName.value = gitManager.repositoryName + fun dismiss() { + showDialog.value = false } - Column( - modifier = Modifier - .background(MaterialTheme.colors.background) - .fillMaxSize() - ) { - - val linearProgressAlpha = if (isProcessing) - DefaultAlpha - else - 0f - - LinearProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .alpha(linearProgressAlpha) - ) - - Box(modifier = Modifier.fillMaxSize()) { - Crossfade(targetState = repositorySelectionStatus) { - - @Suppress("UnnecessaryVariable") // Don't inline it because smart cast won't work - when (repositorySelectionStatus) { - RepositorySelectionStatus.None -> { - WelcomePage(gitManager = gitManager) - } - RepositorySelectionStatus.Loading -> { - LoadingRepository() - } - is RepositorySelectionStatus.Open -> { - RepositoryOpenPage(gitManager = gitManager) - } - } - } - - if (isProcessing) - Box(modifier = Modifier.fillMaxSize()) //TODO this should block of the mouse/keyboard events while visible - } + @Composable + fun dialog() { + content() } } diff --git a/src/main/kotlin/app/git/dialogs/NewBranchDialog.kt b/src/main/kotlin/app/git/dialogs/NewBranchDialog.kt index f0975b2..c278172 100644 --- a/src/main/kotlin/app/git/dialogs/NewBranchDialog.kt +++ b/src/main/kotlin/app/git/dialogs/NewBranchDialog.kt @@ -1,12 +1,8 @@ package app.git.dialogs -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -15,8 +11,6 @@ import androidx.compose.ui.focus.focusOrder import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.rememberDialogState @Composable fun NewBranchDialog( @@ -24,37 +18,48 @@ fun NewBranchDialog( onAccept: (branchName: String) -> Unit ) { var branchField by remember { mutableStateOf("") } - val userFieldFocusRequester = remember { FocusRequester() } + val branchFieldFocusRequester = remember { FocusRequester() } val buttonFieldFocusRequester = remember { FocusRequester() } - Dialog( - state = rememberDialogState(width = 0.dp, height = 0.dp), - onCloseRequest = onReject, - title = "", + Column( + modifier = Modifier + .background(MaterialTheme.colors.background), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text("Enter a branch name") - - OutlinedTextField( - modifier = Modifier.focusOrder(userFieldFocusRequester) { + OutlinedTextField( + modifier = Modifier + .focusOrder(branchFieldFocusRequester) { this.next = buttonFieldFocusRequester - }, - value = branchField, - label = { Text("User", fontSize = 14.sp) }, - textStyle = TextStyle(fontSize = 14.sp), - onValueChange = { - branchField = it - }, - ) + } + .width(300.dp), + value = branchField, + singleLine = true, + label = { Text("New branch name", fontSize = 14.sp) }, + textStyle = TextStyle(fontSize = 14.sp), + onValueChange = { + branchField = it + }, + ) + Row( + modifier = Modifier + .padding(top = 16.dp) + .align(Alignment.End) + ) { + TextButton( + modifier = Modifier.padding(end = 8.dp), + onClick = { + onReject() + } + ) { + Text("Cancel") + } Button( modifier = Modifier.focusOrder(buttonFieldFocusRequester) { - this.previous = userFieldFocusRequester - this.next = userFieldFocusRequester + this.previous = branchFieldFocusRequester + this.next = branchFieldFocusRequester }, + enabled = branchField.isNotEmpty(), onClick = { onAccept(branchField) } diff --git a/src/main/kotlin/app/git/dialogs/UserPasswordDialog.kt b/src/main/kotlin/app/git/dialogs/UserPasswordDialog.kt index 6b5affd..8104a39 100644 --- a/src/main/kotlin/app/git/dialogs/UserPasswordDialog.kt +++ b/src/main/kotlin/app/git/dialogs/UserPasswordDialog.kt @@ -1,12 +1,8 @@ package app.git.dialogs -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -29,43 +25,63 @@ fun UserPasswordDialog( val passwordFieldFocusRequester = remember { FocusRequester() } val buttonFieldFocusRequester = remember { FocusRequester() } - Dialog( - onCloseRequest = onReject, - title = "", + Column( + modifier = Modifier + .background(MaterialTheme.colors.background), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { - ) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text("Introduce your remote server credentials") + Text( + text = "Introduce your remote server credentials", + modifier = Modifier + .padding(vertical = 8.dp), + ) - OutlinedTextField( - modifier = Modifier.focusOrder(userFieldFocusRequester) { + OutlinedTextField( + modifier = Modifier + .focusOrder(userFieldFocusRequester) { this.next = passwordFieldFocusRequester - }, - value = userField, - label = { Text("User", fontSize = 14.sp) }, - textStyle = TextStyle(fontSize = 14.sp), - onValueChange = { - userField = it - }, - ) - OutlinedTextField( - modifier = Modifier.padding(bottom = 8.dp) - .focusOrder(passwordFieldFocusRequester) { - this.previous = userFieldFocusRequester - this.next = buttonFieldFocusRequester - }, - value = passwordField, - label = { Text("Password", fontSize = 14.sp) }, - textStyle = TextStyle(fontSize = 14.sp), - onValueChange = { - passwordField = it - }, - visualTransformation = PasswordVisualTransformation() - ) + } + .width(300.dp), + value = userField, + singleLine = true, + label = { Text("User", fontSize = 14.sp) }, + textStyle = TextStyle(fontSize = 14.sp), + onValueChange = { + userField = it + }, + ) + OutlinedTextField( + modifier = Modifier.padding(bottom = 8.dp) + .focusOrder(passwordFieldFocusRequester) { + this.previous = userFieldFocusRequester + this.next = buttonFieldFocusRequester + } + .width(300.dp), + value = passwordField, + singleLine = true, + label = { Text("Password", fontSize = 14.sp) }, + textStyle = TextStyle(fontSize = 14.sp), + onValueChange = { + passwordField = it + }, + visualTransformation = PasswordVisualTransformation() + ) + + Row( + modifier = Modifier + .padding(top = 16.dp) + .align(Alignment.End) + ) { + TextButton( + modifier = Modifier.padding(end = 8.dp), + onClick = { + onReject() + } + ) { + Text("Cancel") + } Button( modifier = Modifier.focusOrder(buttonFieldFocusRequester) { this.previous = passwordFieldFocusRequester @@ -78,5 +94,6 @@ fun UserPasswordDialog( Text("Ok") } } + } } diff --git a/src/main/kotlin/app/ui/AppTab.kt b/src/main/kotlin/app/ui/AppTab.kt new file mode 100644 index 0000000..dcdc85f --- /dev/null +++ b/src/main/kotlin/app/ui/AppTab.kt @@ -0,0 +1,74 @@ +package app.ui + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.DefaultAlpha +import app.DialogManager +import app.LoadingRepository +import app.git.GitManager +import app.git.RepositorySelectionStatus + + +@Composable +fun AppTab(gitManager: GitManager, dialogManager: DialogManager, repositoryPath: String?, tabName: MutableState) { + LaunchedEffect(gitManager) { + if (repositoryPath != null) + gitManager.openRepository(repositoryPath) + } + + + val repositorySelectionStatus by gitManager.repositorySelectionStatus.collectAsState() + val isProcessing by gitManager.processing.collectAsState() + + if (repositorySelectionStatus is RepositorySelectionStatus.Open) { + tabName.value = gitManager.repositoryName + } + + Column( + modifier = Modifier + .background(MaterialTheme.colors.background) + .fillMaxSize() + ) { + + val linearProgressAlpha = if (isProcessing) + DefaultAlpha + else + 0f + + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .alpha(linearProgressAlpha) + ) + + Box(modifier = Modifier.fillMaxSize()) { + Crossfade(targetState = repositorySelectionStatus) { + + @Suppress("UnnecessaryVariable") // Don't inline it because smart cast won't work + when (repositorySelectionStatus) { + RepositorySelectionStatus.None -> { + WelcomePage(gitManager = gitManager) + } + RepositorySelectionStatus.Loading -> { + LoadingRepository() + } + is RepositorySelectionStatus.Open -> { + RepositoryOpenPage(gitManager = gitManager, dialogManager = dialogManager) + } + } + } + + if (isProcessing) + Box(modifier = Modifier.fillMaxSize()) //TODO this should block of the mouse/keyboard events while visible + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/ui/RepositoryOpen.kt b/src/main/kotlin/app/ui/RepositoryOpen.kt index 2297164..d8119ff 100644 --- a/src/main/kotlin/app/ui/RepositoryOpen.kt +++ b/src/main/kotlin/app/ui/RepositoryOpen.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog +import app.DialogManager import app.credentials.CredentialsState import app.git.DiffEntryType import app.git.GitManager @@ -21,7 +22,7 @@ import org.eclipse.jgit.revwalk.RevCommit @Composable -fun RepositoryOpenPage(gitManager: GitManager) { +fun RepositoryOpenPage(gitManager: GitManager, dialogManager: DialogManager) { var selectedRevCommit by remember { mutableStateOf(null) } @@ -33,34 +34,27 @@ fun RepositoryOpenPage(gitManager: GitManager) { mutableStateOf(false) } - var showBranchDialog by remember { - mutableStateOf(false) - } +// var showBranchDialog by remember { +// mutableStateOf(false) +// } val selectedIndexCommitLog = remember { mutableStateOf(-1) } val credentialsState by gitManager.credentialsState.collectAsState() if (credentialsState == CredentialsState.CredentialsRequested) { - UserPasswordDialog( - onReject = { - gitManager.credentialsDenied() - }, - onAccept = { user, password -> - gitManager.credentialsAccepted(user, password) - } - ) - } - - if (showBranchDialog) { - NewBranchDialog( - onReject = { - showBranchDialog = false - }, - onAccept = { branchName -> - gitManager.createBranch(branchName) - } - ) + dialogManager.show { + UserPasswordDialog( + onReject = { + gitManager.credentialsDenied() + dialogManager.dismiss() + }, + onAccept = { user, password -> + gitManager.credentialsAccepted(user, password) + dialogManager.dismiss() + } + ) + } } Column { @@ -72,7 +66,19 @@ fun RepositoryOpenPage(gitManager: GitManager) { onPush = { gitManager.push() }, onStash = { gitManager.stash() }, onPopStash = { gitManager.popStash() }, - onCreateBranch = { showBranchDialog = true } + onCreateBranch = { + dialogManager.show { + NewBranchDialog( + onReject = { + dialogManager.dismiss() + }, + onAccept = { branchName -> + gitManager.createBranch(branchName) + dialogManager.dismiss() + } + ) + } + } ) Row { diff --git a/src/main/kotlin/app/ui/components/DialogBox.kt b/src/main/kotlin/app/ui/components/DialogBox.kt new file mode 100644 index 0000000..c199071 --- /dev/null +++ b/src/main/kotlin/app/ui/components/DialogBox.kt @@ -0,0 +1,26 @@ +package app.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp + +@Composable +fun DialogBox( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Box( + modifier = modifier + .clip(RoundedCornerShape(5)) + .background(MaterialTheme.colors.background) + .padding(16.dp) + ) { + content() + } +} \ No newline at end of file