Added dynamic size tabs with a label to show the full repository path
Fixes #56
This commit is contained in:
parent
025f93320a
commit
0bfa91d2a7
@ -184,8 +184,7 @@ class App {
|
|||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
.height(40.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
RepositoriesTabPanel(
|
RepositoriesTabPanel(
|
||||||
@ -195,7 +194,8 @@ class App {
|
|||||||
tabsManager.selectTab(selectedTab)
|
tabsManager.selectTab(selectedTab)
|
||||||
},
|
},
|
||||||
onTabClosed = onCloseTab,
|
onTabClosed = onCloseTab,
|
||||||
onAddNewTab = onAddedTab
|
onAddNewTab = onAddedTab,
|
||||||
|
tabsHeight = 40.dp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,9 @@ val Colors.diffLineAdded: Color
|
|||||||
val Colors.diffLineRemoved: Color
|
val Colors.diffLineRemoved: Color
|
||||||
get() = appTheme.diffLineRemoved
|
get() = appTheme.diffLineRemoved
|
||||||
|
|
||||||
|
val Colors.isDark: Boolean
|
||||||
|
get() = !this.isLight
|
||||||
|
|
||||||
|
|
||||||
enum class Theme(val displayName: String) : DropDownOption {
|
enum class Theme(val displayName: String) : DropDownOption {
|
||||||
LIGHT("Light"),
|
LIGHT("Light"),
|
||||||
|
@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.jetpackduba.gitnuro.AppConstants
|
import com.jetpackduba.gitnuro.AppConstants
|
||||||
import com.jetpackduba.gitnuro.AppIcons
|
import com.jetpackduba.gitnuro.AppIcons
|
||||||
@ -221,14 +222,20 @@ fun RecentRepositories(appStateManager: AppStateManager, tabViewModel: TabViewMo
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
color = MaterialTheme.colors.primaryVariant,
|
color = MaterialTheme.colors.primaryVariant,
|
||||||
modifier = Modifier.padding(8.dp)
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.widthIn(max = 600.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = repoDirPath,
|
text = repoDirPath,
|
||||||
style = MaterialTheme.typography.body1,
|
style = MaterialTheme.typography.body1,
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 4.dp)
|
||||||
|
.widthIn(max = 600.dp),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
color = MaterialTheme.colors.onBackgroundSecondary
|
color = MaterialTheme.colors.onBackgroundSecondary
|
||||||
)
|
)
|
||||||
|
@ -10,18 +10,22 @@ import androidx.compose.foundation.lazy.LazyRow
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollbarAdapter
|
import androidx.compose.foundation.rememberScrollbarAdapter
|
||||||
|
import androidx.compose.foundation.v2.maxScrollOffset
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
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.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.jetpackduba.gitnuro.AppIcons
|
||||||
import com.jetpackduba.gitnuro.AppStateManager
|
import com.jetpackduba.gitnuro.AppStateManager
|
||||||
import com.jetpackduba.gitnuro.LocalTabScope
|
import com.jetpackduba.gitnuro.LocalTabScope
|
||||||
import com.jetpackduba.gitnuro.di.AppComponent
|
import com.jetpackduba.gitnuro.di.AppComponent
|
||||||
@ -31,6 +35,8 @@ import com.jetpackduba.gitnuro.extensions.handMouseClickable
|
|||||||
import com.jetpackduba.gitnuro.extensions.handOnHover
|
import com.jetpackduba.gitnuro.extensions.handOnHover
|
||||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
import com.jetpackduba.gitnuro.viewmodels.TabViewModel
|
||||||
import com.jetpackduba.gitnuro.viewmodels.TabViewModelsHolder
|
import com.jetpackduba.gitnuro.viewmodels.TabViewModelsHolder
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
@ -39,19 +45,15 @@ import kotlin.io.path.name
|
|||||||
fun RepositoriesTabPanel(
|
fun RepositoriesTabPanel(
|
||||||
tabs: List<TabInformation>,
|
tabs: List<TabInformation>,
|
||||||
currentTab: TabInformation?,
|
currentTab: TabInformation?,
|
||||||
|
tabsHeight: Dp,
|
||||||
onTabSelected: (TabInformation) -> Unit,
|
onTabSelected: (TabInformation) -> Unit,
|
||||||
onTabClosed: (TabInformation) -> Unit,
|
onTabClosed: (TabInformation) -> Unit,
|
||||||
onAddNewTab: () -> Unit,
|
onAddNewTab: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val stateHorizontal = rememberLazyListState()
|
val stateHorizontal = rememberLazyListState()
|
||||||
|
val scrollAdapter = rememberScrollbarAdapter(stateHorizontal)
|
||||||
// LaunchedEffect(selectedTabKey) {
|
val scope = rememberCoroutineScope()
|
||||||
// val index = tabs.indexOfFirst { it.key == selectedTabKey }
|
var latestTabCount by remember { mutableStateOf(tabs.count()) }
|
||||||
// // todo sometimes it scrolls to (index - 1) for some weird reason
|
|
||||||
// if (index > -1) {
|
|
||||||
// stateHorizontal.scrollToItem(index)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
val canBeScrolled by remember {
|
val canBeScrolled by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@ -73,126 +75,137 @@ fun RepositoriesTabPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
if (canBeScrolled) {
|
||||||
|
Tooltip(
|
||||||
|
"\"Shift + Mouse wheel\" to scroll"
|
||||||
|
) {
|
||||||
|
HorizontalScrollbar(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
adapter = scrollAdapter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f, false)
|
|
||||||
) {
|
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight(),
|
.height(tabsHeight)
|
||||||
|
.weight(1f, false),
|
||||||
state = stateHorizontal,
|
state = stateHorizontal,
|
||||||
) {
|
) {
|
||||||
items(items = tabs) { tab ->
|
items(items = tabs) { tab ->
|
||||||
Tab(
|
Tooltip(tab.path) {
|
||||||
title = tab.tabName,
|
Tab(
|
||||||
isSelected = currentTab == tab,
|
title = tab.tabName,
|
||||||
onClick = {
|
isSelected = currentTab == tab,
|
||||||
onTabSelected(tab)
|
onClick = {
|
||||||
},
|
onTabSelected(tab)
|
||||||
onCloseTab = {
|
},
|
||||||
onTabClosed(tab)
|
onCloseTab = {
|
||||||
}
|
onTabClosed(tab)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canBeScrolled) {
|
|
||||||
Tooltip(
|
|
||||||
"\"Shift + Mouse wheel\" to scroll"
|
|
||||||
) {
|
|
||||||
HorizontalScrollbar(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.TopStart)
|
|
||||||
.width((tabs.count() * 180).dp),
|
|
||||||
adapter = rememberScrollbarAdapter(stateHorizontal)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
onAddNewTab()
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.size(36.dp)
|
|
||||||
.handOnHover()
|
|
||||||
.align(Alignment.CenterVertically),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Add,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = MaterialTheme.colors.primaryVariant,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Tab(title: MutableState<String>, isSelected: Boolean, onClick: () -> Unit, onCloseTab: () -> Unit) {
|
|
||||||
Box {
|
|
||||||
val backgroundColor = if (isSelected)
|
|
||||||
MaterialTheme.colors.surface
|
|
||||||
else
|
|
||||||
MaterialTheme.colors.background
|
|
||||||
|
|
||||||
val hoverInteraction = remember { MutableInteractionSource() }
|
|
||||||
val isHovered by hoverInteraction.collectIsHoveredAsState()
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(180.dp)
|
|
||||||
.fillMaxHeight()
|
|
||||||
.hoverable(hoverInteraction)
|
|
||||||
.handMouseClickable { onClick() }
|
|
||||||
.background(backgroundColor),
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.align(Alignment.CenterStart),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = title.value,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 16.dp, end = 8.dp)
|
|
||||||
.weight(1f),
|
|
||||||
overflow = TextOverflow.Visible,
|
|
||||||
style = MaterialTheme.typography.body2,
|
|
||||||
color = MaterialTheme.colors.onBackground,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isHovered || isSelected) {
|
|
||||||
IconButton(
|
|
||||||
onClick = onCloseTab,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.size(14.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Close,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = MaterialTheme.colors.onBackground
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelected) {
|
IconButton(
|
||||||
Box(
|
onClick = {
|
||||||
modifier = Modifier
|
onAddNewTab()
|
||||||
.align(Alignment.BottomCenter)
|
},
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.height(3.dp)
|
.size(36.dp)
|
||||||
.background(MaterialTheme.colors.primaryVariant)
|
.handOnHover()
|
||||||
|
.align(Alignment.CenterVertically),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colors.primaryVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(tabs.count()) {
|
||||||
|
// Scroll to the end if a new tab has been added & it's empty (so it's not a new submodule tab)
|
||||||
|
if (latestTabCount < tabs.count() && currentTab?.path == null) {
|
||||||
|
scope.launch {
|
||||||
|
delay(50) // add small delay to wait until [scrollAdapter.maxScrollOffset] is recalculated. Seems more like a hack of some kind...
|
||||||
|
scrollAdapter.scrollTo(scrollAdapter.maxScrollOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
latestTabCount = tabs.count()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Tab(title: MutableState<String>, isSelected: Boolean, onClick: () -> Unit, onCloseTab: () -> Unit) {
|
||||||
|
val backgroundColor = if (isSelected)
|
||||||
|
MaterialTheme.colors.surface
|
||||||
|
else
|
||||||
|
MaterialTheme.colors.background
|
||||||
|
|
||||||
|
val hoverInteraction = remember { MutableInteractionSource() }
|
||||||
|
val isHovered by hoverInteraction.collectIsHoveredAsState()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.widthIn(min = 200.dp)
|
||||||
|
.width(IntrinsicSize.Min)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.hoverable(hoverInteraction)
|
||||||
|
.handMouseClickable { onClick() }
|
||||||
|
.background(backgroundColor),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterStart),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title.value.replace(" ", "-"), // TODO This replace is a workaround for https://issuetracker.google.com/issues/278044455
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp)
|
||||||
|
.weight(1f)
|
||||||
|
.widthIn(max = 720.dp),
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onBackground,
|
||||||
|
maxLines = 1,
|
||||||
|
softWrap = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onCloseTab,
|
||||||
|
enabled = isHovered || isSelected,
|
||||||
|
modifier = Modifier
|
||||||
|
.alpha(if (isHovered || isSelected) 1f else 0f)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.size(14.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(AppIcons.CLOSE),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colors.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(3.dp)
|
||||||
|
.background(MaterialTheme.colors.primaryVariant)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TabInformation(
|
class TabInformation(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.jetpackduba.gitnuro.ui.components
|
package com.jetpackduba.gitnuro.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.TooltipArea
|
import androidx.compose.foundation.TooltipArea
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -9,20 +10,30 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.jetpackduba.gitnuro.theme.isDark
|
||||||
|
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Tooltip(text: String, content: @Composable () -> Unit) {
|
fun Tooltip(text: String?, content: @Composable () -> Unit) {
|
||||||
TooltipArea(
|
TooltipArea(
|
||||||
tooltip = {
|
tooltip = {
|
||||||
Card(
|
if (text != null) {
|
||||||
backgroundColor = MaterialTheme.colors.background,
|
val border = if (MaterialTheme.colors.isDark) {
|
||||||
elevation = 10.dp,
|
BorderStroke(2.dp, MaterialTheme.colors.onBackgroundSecondary.copy(alpha = 0.4f))
|
||||||
) {
|
} else
|
||||||
Text(
|
null
|
||||||
text = text,
|
|
||||||
modifier = Modifier.padding(8.dp)
|
Card(
|
||||||
)
|
backgroundColor = MaterialTheme.colors.background,
|
||||||
|
border = border,
|
||||||
|
elevation = 10.dp,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
modifier = Modifier.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
Loading…
Reference in New Issue
Block a user