diff --git a/src/main/kotlin/git/GitManager.kt b/src/main/kotlin/git/GitManager.kt index f76b92f..a7cf4c7 100644 --- a/src/main/kotlin/git/GitManager.kt +++ b/src/main/kotlin/git/GitManager.kt @@ -1,7 +1,8 @@ package git import credentials.CredentialsState -import credentials.CredentialsStateManager import kotlinx.coroutines.* +import credentials.CredentialsStateManager +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.eclipse.jgit.api.Git @@ -15,6 +16,8 @@ import java.io.File class GitManager { + val repositoryName: String + get() = safeGit.repository.directory.parentFile.name private val preferences = GPreferences() private val statusManager = StatusManager() private val logManager = LogManager() @@ -71,7 +74,8 @@ class GitManager { return git } - init { + + fun loadLatestOpenedRepository() { val latestOpenedRepositoryPath = preferences.latestOpenedRepositoryPath if (latestOpenedRepositoryPath.isNotEmpty()) { openRepository(File(latestOpenedRepositoryPath)) diff --git a/src/main/kotlin/main.kt b/src/main/kotlin/main.kt index d88ddae..fa7c84f 100644 --- a/src/main/kotlin/main.kt +++ b/src/main/kotlin/main.kt @@ -1,12 +1,21 @@ import androidx.compose.animation.Crossfade import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.painter.Painter @@ -15,18 +24,19 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.* +import androidx.compose.ui.zIndex import git.GitManager import git.RepositorySelectionStatus import theme.* import ui.RepositoryOpenPage import ui.WelcomePage +import ui.components.RepositoriesTabPanel +import ui.components.TabInformation import javax.swing.JFileChooser - @OptIn(ExperimentalComposeUiApi::class) fun main() = application { var isOpen by remember { mutableStateOf(true) } - val gitManager = GitManager() if (isOpen) { Window( title = "Gitnuro", @@ -36,18 +46,87 @@ fun main() = application { state = rememberWindowState(placement = WindowPlacement.Maximized, size = WindowSize(1280.dp, 720.dp)) ) { GitnuroTheme { - Gitnuro(gitManager) + val tabs = remember { + val tabName = mutableStateOf("New tab") + mutableStateOf( + listOf( + TabInformation(tabName, key = 0) { + Gitnuro(false, tabName) + }, + ) + ) + } + + var selectedTabKey by remember { mutableStateOf(0) } + + Column { + RepositoriesTabPanel( + modifier = Modifier + .padding(top = 4.dp, bottom = 2.dp, start = 4.dp, end = 4.dp) + .fillMaxWidth(), + tabs = tabs.value, + selectedTabKey = selectedTabKey, + onTabSelected = { newSelectedTabKey -> + selectedTabKey = newSelectedTabKey + }, + newTabContent = { tabName -> + Gitnuro(true, tabName) + }, + onTabsUpdated = { tabInformationList -> + tabs.value = tabInformationList + } + ) + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .background(Color.Cyan) + ) { + 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() + } + } + } + } } } } } + @Composable -fun Gitnuro(gitManager: GitManager) { +fun Gitnuro(isNewTab: Boolean, tabName: MutableState) { + val gitManager = remember { + GitManager().apply { + if (!isNewTab) + loadLatestOpenedRepository() + } + } + val repositorySelectionStatus by gitManager.repositorySelectionStatus.collectAsState() + if (repositorySelectionStatus is RepositorySelectionStatus.Open) { + tabName.value = gitManager.repositoryName + } + Column( - modifier = Modifier.background(MaterialTheme.colors.background) + modifier = Modifier + .background(MaterialTheme.colors.background) + .fillMaxSize() ) { GMenu( onRepositoryOpen = { @@ -213,4 +292,4 @@ fun MenuButton( } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/ui/RepositoryNone.kt b/src/main/kotlin/ui/WelcomePage.kt similarity index 59% rename from src/main/kotlin/ui/RepositoryNone.kt rename to src/main/kotlin/ui/WelcomePage.kt index 2aadab9..cd15e13 100644 --- a/src/main/kotlin/ui/RepositoryNone.kt +++ b/src/main/kotlin/ui/WelcomePage.kt @@ -32,6 +32,26 @@ fun WelcomePage() { Text("Open repository") } + + Button(onClick = {}) { + Image( + painter = painterResource("open.svg"), + contentDescription = null, + colorFilter = ColorFilter.tint(contentColorFor(MaterialTheme.colors.primary)) + + ) + Text("Clone repository") + } + + Button(onClick = {}) { + Image( + painter = painterResource("open.svg"), + contentDescription = null, + colorFilter = ColorFilter.tint(contentColorFor(MaterialTheme.colors.primary)) + + ) + Text("Init a new repository") + } } } } diff --git a/src/main/kotlin/ui/components/RepositoriesTabPanel.kt b/src/main/kotlin/ui/components/RepositoriesTabPanel.kt new file mode 100644 index 0000000..5e34ca5 --- /dev/null +++ b/src/main/kotlin/ui/components/RepositoriesTabPanel.kt @@ -0,0 +1,158 @@ +package ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import theme.primaryLight + + +@Composable +fun RepositoriesTabPanel( + modifier: Modifier = Modifier, + tabs: List, + selectedTabKey: Int, + onTabSelected: (Int) -> Unit, + onTabsUpdated: (List) -> Unit, + newTabContent: @Composable (tabTitle: MutableState) -> Unit, +) { + var tabsIdentifier by remember { mutableStateOf(0) } + + TabPanel( + modifier = modifier, + onNewTabClicked = { + val tabsCopy = tabs.toMutableList() + + tabsIdentifier++ + + val tabName = mutableStateOf("New tab") + + tabsCopy.add( + TabInformation(tabName, key = tabsIdentifier) { + newTabContent(tabName) + } + ) + + onTabSelected(tabsIdentifier) + onTabsUpdated(tabsCopy) + } + ) { + tabs.forEach { tab -> + Tab( + title = tab.title, + selected = tab.key == selectedTabKey, + onClick = { + onTabSelected(tab.key) + }, + onCloseTab = { + val isTabSelected = selectedTabKey == tab.key + val index = tabs.indexOf(tab) + val nextIndex = if (index == 0 && tabs.count() >= 2) { + 0 + } else if (tabs.count() >= 2) + index - 1 + else + -1 + + val tabsCopy = tabs.toMutableList() + tabsCopy.remove(tab) + + val nextKey = if (nextIndex >= 0) + tabsCopy[nextIndex].key + else + -1 + + if (isTabSelected) { + if (nextKey >= 0) { + onTabSelected(nextKey) + } else { + tabsIdentifier++ + + val tabName = mutableStateOf("New tab") + tabsCopy.add( + TabInformation(tabName, key = tabsIdentifier) { + newTabContent(tabName) + } + ) + + onTabSelected(tabsIdentifier) + } + } + + onTabsUpdated(tabsCopy) + } + ) + } + } +} + + +@Composable +fun TabPanel( + modifier: Modifier = Modifier, + onNewTabClicked: () -> Unit, + tabs: @Composable RowScope.() -> Unit +) { + Row(modifier = modifier) { + this.tabs() + + IconButton( + onClick = onNewTabClicked, + modifier = Modifier + .size(36.dp), + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + } + } +} + +@Composable +fun Tab(title: MutableState, selected: Boolean, onClick: () -> Unit, onCloseTab: () -> Unit) { + Card { + Box( + modifier = Modifier + .padding(horizontal = 1.dp) + .height(36.dp) + .clip(RoundedCornerShape(5.dp)) + .background(if (selected) MaterialTheme.colors.primary else primaryLight) + .clickable { onClick() }, + ) { + Text( + text = title.value, + modifier = Modifier + .padding(vertical = 8.dp, horizontal = 32.dp), + color = contentColorFor(MaterialTheme.colors.primary), + ) + IconButton( + onClick = onCloseTab, + modifier = Modifier + .align(Alignment.CenterEnd) + .padding(horizontal = 8.dp) + .size(16.dp) + ) { + Icon(Icons.Default.Close, contentDescription = null, tint = Color.White) + } + + } + } +} + +class TabInformation( + val title: MutableState, + val key: Int, + val content: @Composable () -> Unit +) \ No newline at end of file