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 package app
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background 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.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items 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.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.alpha 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.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.* import androidx.compose.ui.window.*
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import app.di.DaggerAppComponent import app.di.DaggerAppComponent
import app.git.GitManager import app.git.GitManager
import app.git.RepositorySelectionStatus
import app.theme.AppTheme import app.theme.AppTheme
import app.ui.RepositoryOpenPage import app.ui.AppTab
import app.ui.WelcomePage import app.ui.components.DialogBox
import app.ui.components.RepositoriesTabPanel import app.ui.components.RepositoriesTabPanel
import app.ui.components.TabInformation import app.ui.components.TabInformation
import javax.inject.Inject import javax.inject.Inject
@ -56,89 +58,141 @@ class Main {
) )
) { ) {
AppTheme { AppTheme {
val tabs = remember { val showDialog = remember { mutableStateOf(false) }
val dialogManager = remember { DialogManager(showDialog) }
val repositoriesSavedTabs = appStateManager.openRepositoriesPathsTabs Box {
var repoTabs = repositoriesSavedTabs.map { repositoryTab ->
newAppTab(key = repositoryTab.key, path = repositoryTab.value)
}
if (repoTabs.isEmpty()) { AppTabs(dialogManager)
repoTabs = listOf(
newAppTab() 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( it.content(it)
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)
}
}
}
} }
} }
} }
@ -146,6 +200,7 @@ class Main {
} }
private fun newAppTab( private fun newAppTab(
dialogManager: DialogManager,
key: Int = 0, key: Int = 0,
tabName: MutableState<String> = mutableStateOf("New tab"), tabName: MutableState<String> = mutableStateOf("New tab"),
path: String? = null, path: String? = null,
@ -163,63 +218,26 @@ class Main {
appStateManager.repositoryTabChanged(key, path) appStateManager.repositoryTabChanged(key, path)
} }
App(gitManager, path, tabName) AppTab(gitManager, dialogManager, path, tabName)
} }
} }
} }
@Composable class DialogManager(private val showDialog: MutableState<Boolean>) {
fun App(gitManager: GitManager, repositoryPath: String?, tabName: MutableState<String>) { private var content: @Composable () -> Unit = {}
LaunchedEffect(gitManager) {
if (repositoryPath != null) fun show(content: @Composable () -> Unit) {
gitManager.openRepository(repositoryPath) this.content = content
showDialog.value = true
} }
fun dismiss() {
val repositorySelectionStatus by gitManager.repositorySelectionStatus.collectAsState() showDialog.value = false
val isProcessing by gitManager.processing.collectAsState()
if (repositorySelectionStatus is RepositorySelectionStatus.Open) {
tabName.value = gitManager.repositoryName
} }
Column( @Composable
modifier = Modifier fun dialog() {
.background(MaterialTheme.colors.background) content()
.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
}
} }
} }

View File

@ -1,12 +1,8 @@
package app.git.dialogs package app.git.dialogs
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.*
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedTextField
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
@ -15,8 +11,6 @@ import androidx.compose.ui.focus.focusOrder
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.rememberDialogState
@Composable @Composable
fun NewBranchDialog( fun NewBranchDialog(
@ -24,37 +18,48 @@ fun NewBranchDialog(
onAccept: (branchName: String) -> Unit onAccept: (branchName: String) -> Unit
) { ) {
var branchField by remember { mutableStateOf("") } var branchField by remember { mutableStateOf("") }
val userFieldFocusRequester = remember { FocusRequester() } val branchFieldFocusRequester = remember { FocusRequester() }
val buttonFieldFocusRequester = remember { FocusRequester() } val buttonFieldFocusRequester = remember { FocusRequester() }
Dialog( Column(
state = rememberDialogState(width = 0.dp, height = 0.dp), modifier = Modifier
onCloseRequest = onReject, .background(MaterialTheme.colors.background),
title = "", horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) { ) {
Column( OutlinedTextField(
modifier = Modifier.fillMaxSize(), modifier = Modifier
horizontalAlignment = Alignment.CenterHorizontally, .focusOrder(branchFieldFocusRequester) {
verticalArrangement = Arrangement.Center,
) {
Text("Enter a branch name")
OutlinedTextField(
modifier = Modifier.focusOrder(userFieldFocusRequester) {
this.next = buttonFieldFocusRequester this.next = buttonFieldFocusRequester
}, }
value = branchField, .width(300.dp),
label = { Text("User", fontSize = 14.sp) }, value = branchField,
textStyle = TextStyle(fontSize = 14.sp), singleLine = true,
onValueChange = { label = { Text("New branch name", fontSize = 14.sp) },
branchField = it 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( Button(
modifier = Modifier.focusOrder(buttonFieldFocusRequester) { modifier = Modifier.focusOrder(buttonFieldFocusRequester) {
this.previous = userFieldFocusRequester this.previous = branchFieldFocusRequester
this.next = userFieldFocusRequester this.next = branchFieldFocusRequester
}, },
enabled = branchField.isNotEmpty(),
onClick = { onClick = {
onAccept(branchField) onAccept(branchField)
} }

View File

@ -1,12 +1,8 @@
package app.git.dialogs package app.git.dialogs
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.*
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedTextField
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
@ -29,43 +25,63 @@ fun UserPasswordDialog(
val passwordFieldFocusRequester = remember { FocusRequester() } val passwordFieldFocusRequester = remember { FocusRequester() }
val buttonFieldFocusRequester = remember { FocusRequester() } val buttonFieldFocusRequester = remember { FocusRequester() }
Dialog( Column(
onCloseRequest = onReject, modifier = Modifier
title = "", .background(MaterialTheme.colors.background),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
) { Text(
Column( text = "Introduce your remote server credentials",
modifier = Modifier.fillMaxSize(), modifier = Modifier
horizontalAlignment = Alignment.CenterHorizontally, .padding(vertical = 8.dp),
verticalArrangement = Arrangement.Center, )
) {
Text("Introduce your remote server credentials")
OutlinedTextField( OutlinedTextField(
modifier = Modifier.focusOrder(userFieldFocusRequester) { modifier = Modifier
.focusOrder(userFieldFocusRequester) {
this.next = passwordFieldFocusRequester this.next = passwordFieldFocusRequester
}, }
value = userField, .width(300.dp),
label = { Text("User", fontSize = 14.sp) }, value = userField,
textStyle = TextStyle(fontSize = 14.sp), singleLine = true,
onValueChange = { label = { Text("User", fontSize = 14.sp) },
userField = it textStyle = TextStyle(fontSize = 14.sp),
}, onValueChange = {
) userField = it
OutlinedTextField( },
modifier = Modifier.padding(bottom = 8.dp) )
.focusOrder(passwordFieldFocusRequester) { OutlinedTextField(
this.previous = userFieldFocusRequester modifier = Modifier.padding(bottom = 8.dp)
this.next = buttonFieldFocusRequester .focusOrder(passwordFieldFocusRequester) {
}, this.previous = userFieldFocusRequester
value = passwordField, this.next = buttonFieldFocusRequester
label = { Text("Password", fontSize = 14.sp) }, }
textStyle = TextStyle(fontSize = 14.sp), .width(300.dp),
onValueChange = { value = passwordField,
passwordField = it singleLine = true,
}, label = { Text("Password", fontSize = 14.sp) },
visualTransformation = PasswordVisualTransformation() 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( Button(
modifier = Modifier.focusOrder(buttonFieldFocusRequester) { modifier = Modifier.focusOrder(buttonFieldFocusRequester) {
this.previous = passwordFieldFocusRequester this.previous = passwordFieldFocusRequester
@ -78,5 +94,6 @@ fun UserPasswordDialog(
Text("Ok") 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import app.DialogManager
import app.credentials.CredentialsState import app.credentials.CredentialsState
import app.git.DiffEntryType import app.git.DiffEntryType
import app.git.GitManager import app.git.GitManager
@ -21,7 +22,7 @@ import org.eclipse.jgit.revwalk.RevCommit
@Composable @Composable
fun RepositoryOpenPage(gitManager: GitManager) { fun RepositoryOpenPage(gitManager: GitManager, dialogManager: DialogManager) {
var selectedRevCommit by remember { var selectedRevCommit by remember {
mutableStateOf<RevCommit?>(null) mutableStateOf<RevCommit?>(null)
} }
@ -33,34 +34,27 @@ fun RepositoryOpenPage(gitManager: GitManager) {
mutableStateOf(false) mutableStateOf(false)
} }
var showBranchDialog by remember { // var showBranchDialog by remember {
mutableStateOf(false) // mutableStateOf(false)
} // }
val selectedIndexCommitLog = remember { mutableStateOf(-1) } val selectedIndexCommitLog = remember { mutableStateOf(-1) }
val credentialsState by gitManager.credentialsState.collectAsState() val credentialsState by gitManager.credentialsState.collectAsState()
if (credentialsState == CredentialsState.CredentialsRequested) { if (credentialsState == CredentialsState.CredentialsRequested) {
UserPasswordDialog( dialogManager.show {
onReject = { UserPasswordDialog(
gitManager.credentialsDenied() onReject = {
}, gitManager.credentialsDenied()
onAccept = { user, password -> dialogManager.dismiss()
gitManager.credentialsAccepted(user, password) },
} onAccept = { user, password ->
) gitManager.credentialsAccepted(user, password)
} dialogManager.dismiss()
}
if (showBranchDialog) { )
NewBranchDialog( }
onReject = {
showBranchDialog = false
},
onAccept = { branchName ->
gitManager.createBranch(branchName)
}
)
} }
Column { Column {
@ -72,7 +66,19 @@ fun RepositoryOpenPage(gitManager: GitManager) {
onPush = { gitManager.push() }, onPush = { gitManager.push() },
onStash = { gitManager.stash() }, onStash = { gitManager.stash() },
onPopStash = { gitManager.popStash() }, onPopStash = { gitManager.popStash() },
onCreateBranch = { showBranchDialog = true } onCreateBranch = {
dialogManager.show {
NewBranchDialog(
onReject = {
dialogManager.dismiss()
},
onAccept = { branchName ->
gitManager.createBranch(branchName)
dialogManager.dismiss()
}
)
}
}
) )
Row { 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()
}
}