Created custom dialog UI

This commit is contained in:
Abdelilah El Aissaoui 2021-10-25 02:26:58 +02:00
parent e718f10b60
commit e6cd822b17
6 changed files with 375 additions and 229 deletions

View file

@ -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<String> = 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<String>) {
LaunchedEffect(gitManager) {
if (repositoryPath != null)
gitManager.openRepository(repositoryPath)
class DialogManager(private val showDialog: MutableState<Boolean>) {
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()
}
}

View file

@ -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)
}

View file

@ -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")
}
}
}
}

View file

@ -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<String>) {
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
}
}
}

View file

@ -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<RevCommit?>(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 {

View file

@ -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()
}
}