Added dynamic size tabs with a label to show the full repository path

Fixes #56
This commit is contained in:
Abdelilah El Aissaoui 2023-04-14 01:31:57 +02:00
parent 025f93320a
commit 0bfa91d2a7
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
5 changed files with 162 additions and 128 deletions

View File

@ -184,8 +184,7 @@ class App {
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(40.dp),
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
RepositoriesTabPanel(
@ -195,7 +194,8 @@ class App {
tabsManager.selectTab(selectedTab)
},
onTabClosed = onCloseTab,
onAddNewTab = onAddedTab
onAddNewTab = onAddedTab,
tabsHeight = 40.dp,
)
}
}

View File

@ -76,6 +76,9 @@ val Colors.diffLineAdded: Color
val Colors.diffLineRemoved: Color
get() = appTheme.diffLineRemoved
val Colors.isDark: Boolean
get() = !this.isLight
enum class Theme(val displayName: String) : DropDownOption {
LIGHT("Light"),

View File

@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.AppConstants
import com.jetpackduba.gitnuro.AppIcons
@ -221,14 +222,20 @@ fun RecentRepositories(appStateManager: AppStateManager, tabViewModel: TabViewMo
maxLines = 1,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colors.primaryVariant,
modifier = Modifier.padding(8.dp)
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.padding(8.dp)
.widthIn(max = 600.dp),
)
}
Text(
text = repoDirPath,
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,
color = MaterialTheme.colors.onBackgroundSecondary
)

View File

@ -10,18 +10,22 @@ import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.v2.maxScrollOffset
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
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.alpha
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
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.LocalTabScope
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.viewmodels.TabViewModel
import com.jetpackduba.gitnuro.viewmodels.TabViewModelsHolder
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.io.path.Path
import kotlin.io.path.name
@ -39,19 +45,15 @@ import kotlin.io.path.name
fun RepositoriesTabPanel(
tabs: List<TabInformation>,
currentTab: TabInformation?,
tabsHeight: Dp,
onTabSelected: (TabInformation) -> Unit,
onTabClosed: (TabInformation) -> Unit,
onAddNewTab: () -> Unit,
) {
val stateHorizontal = rememberLazyListState()
// LaunchedEffect(selectedTabKey) {
// val index = tabs.indexOfFirst { it.key == selectedTabKey }
// // todo sometimes it scrolls to (index - 1) for some weird reason
// if (index > -1) {
// stateHorizontal.scrollToItem(index)
// }
// }
val scrollAdapter = rememberScrollbarAdapter(stateHorizontal)
val scope = rememberCoroutineScope()
var latestTabCount by remember { mutableStateOf(tabs.count()) }
val canBeScrolled by remember {
derivedStateOf {
@ -73,126 +75,137 @@ fun RepositoriesTabPanel(
}
}
Column {
if (canBeScrolled) {
Tooltip(
"\"Shift + Mouse wheel\" to scroll"
) {
HorizontalScrollbar(
modifier = Modifier
.fillMaxWidth(),
adapter = scrollAdapter
)
}
}
Row {
Box(
modifier = Modifier
.weight(1f, false)
) {
Row {
LazyRow(
modifier = Modifier
.fillMaxHeight(),
.height(tabsHeight)
.weight(1f, false),
state = stateHorizontal,
) {
items(items = tabs) { tab ->
Tab(
title = tab.tabName,
isSelected = currentTab == tab,
onClick = {
onTabSelected(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
Tooltip(tab.path) {
Tab(
title = tab.tabName,
isSelected = currentTab == tab,
onClick = {
onTabSelected(tab)
},
onCloseTab = {
onTabClosed(tab)
}
)
}
}
}
if (isSelected) {
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.height(3.dp)
.background(MaterialTheme.colors.primaryVariant)
IconButton(
onClick = {
onAddNewTab()
},
modifier = Modifier
.size(36.dp)
.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(

View File

@ -1,5 +1,6 @@
package com.jetpackduba.gitnuro.ui.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.layout.padding
@ -9,20 +10,30 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.jetpackduba.gitnuro.theme.isDark
import com.jetpackduba.gitnuro.theme.onBackgroundSecondary
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Tooltip(text: String, content: @Composable () -> Unit) {
fun Tooltip(text: String?, content: @Composable () -> Unit) {
TooltipArea(
tooltip = {
Card(
backgroundColor = MaterialTheme.colors.background,
elevation = 10.dp,
) {
Text(
text = text,
modifier = Modifier.padding(8.dp)
)
if (text != null) {
val border = if (MaterialTheme.colors.isDark) {
BorderStroke(2.dp, MaterialTheme.colors.onBackgroundSecondary.copy(alpha = 0.4f))
} else
null
Card(
backgroundColor = MaterialTheme.colors.background,
border = border,
elevation = 10.dp,
) {
Text(
text = text,
modifier = Modifier.padding(8.dp)
)
}
}
},
) {