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

View File

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

View File

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

View File

@ -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,18 +75,28 @@ 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 ->
Tooltip(tab.path) {
Tab( Tab(
title = tab.tabName, title = tab.tabName,
isSelected = currentTab == tab, isSelected = currentTab == tab,
@ -97,19 +109,6 @@ fun RepositoriesTabPanel(
) )
} }
} }
if (canBeScrolled) {
Tooltip(
"\"Shift + Mouse wheel\" to scroll"
) {
HorizontalScrollbar(
modifier = Modifier
.align(Alignment.TopStart)
.width((tabs.count() * 180).dp),
adapter = rememberScrollbarAdapter(stateHorizontal)
)
}
}
} }
IconButton( IconButton(
@ -128,11 +127,23 @@ fun RepositoriesTabPanel(
) )
} }
} }
}
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 @Composable
fun Tab(title: MutableState<String>, isSelected: Boolean, onClick: () -> Unit, onCloseTab: () -> Unit) { fun Tab(title: MutableState<String>, isSelected: Boolean, onClick: () -> Unit, onCloseTab: () -> Unit) {
Box {
val backgroundColor = if (isSelected) val backgroundColor = if (isSelected)
MaterialTheme.colors.surface MaterialTheme.colors.surface
else else
@ -143,7 +154,8 @@ fun Tab(title: MutableState<String>, isSelected: Boolean, onClick: () -> Unit, o
Box( Box(
modifier = Modifier modifier = Modifier
.width(180.dp) .widthIn(min = 200.dp)
.width(IntrinsicSize.Min)
.fillMaxHeight() .fillMaxHeight()
.hoverable(hoverInteraction) .hoverable(hoverInteraction)
.handMouseClickable { onClick() } .handMouseClickable { onClick() }
@ -151,36 +163,38 @@ fun Tab(title: MutableState<String>, isSelected: Boolean, onClick: () -> Unit, o
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterStart), .align(Alignment.CenterStart),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
Text( Text(
text = title.value, text = title.value.replace(" ", "-"), // TODO This replace is a workaround for https://issuetracker.google.com/issues/278044455
modifier = Modifier modifier = Modifier
.padding(start = 16.dp, end = 8.dp) .padding(start = 16.dp)
.weight(1f), .weight(1f)
overflow = TextOverflow.Visible, .widthIn(max = 720.dp),
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.body2, style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onBackground, color = MaterialTheme.colors.onBackground,
maxLines = 1, maxLines = 1,
softWrap = false,
) )
if (isHovered || isSelected) {
IconButton( IconButton(
onClick = onCloseTab, onClick = onCloseTab,
enabled = isHovered || isSelected,
modifier = Modifier modifier = Modifier
.alpha(if (isHovered || isSelected) 1f else 0f)
.padding(horizontal = 8.dp) .padding(horizontal = 8.dp)
.size(14.dp) .size(14.dp)
) { ) {
Icon( Icon(
Icons.Default.Close, painterResource(AppIcons.CLOSE),
contentDescription = null, contentDescription = null,
tint = MaterialTheme.colors.onBackground tint = MaterialTheme.colors.onBackground
) )
} }
} }
}
if (isSelected) { if (isSelected) {
Box( Box(
@ -192,7 +206,6 @@ fun Tab(title: MutableState<String>, isSelected: Boolean, onClick: () -> Unit, o
) )
} }
} }
}
} }
class TabInformation( class TabInformation(

View File

@ -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,14 +10,23 @@ 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 = {
if (text != null) {
val border = if (MaterialTheme.colors.isDark) {
BorderStroke(2.dp, MaterialTheme.colors.onBackgroundSecondary.copy(alpha = 0.4f))
} else
null
Card( Card(
backgroundColor = MaterialTheme.colors.background, backgroundColor = MaterialTheme.colors.background,
border = border,
elevation = 10.dp, elevation = 10.dp,
) { ) {
Text( Text(
@ -24,6 +34,7 @@ fun Tooltip(text: String, content: @Composable () -> Unit) {
modifier = Modifier.padding(8.dp) modifier = Modifier.padding(8.dp)
) )
} }
}
}, },
) { ) {
content() content()