diff --git a/designsystem/detekt-baseline.xml b/designsystem/detekt-baseline.xml index 7b3fe37e..9d56895a 100644 --- a/designsystem/detekt-baseline.xml +++ b/designsystem/detekt-baseline.xml @@ -11,7 +11,6 @@ CyclomaticComplexMethod:DiffLine.kt$@Composable fun AslDiffLine( kind: AslDiffKind, text: String, modifier: Modifier = Modifier, oldNo: Int? = null, newNo: Int? = null, noWrap: Boolean = false, ) CyclomaticComplexMethod:FileIcons.kt$AslFileIcons$fun iconFor(fileName: String): String CyclomaticComplexMethod:FileIcons.kt$AslFileIcons$fun tintFor(fileName: String, colors: AslColorScheme): Color - CyclomaticComplexMethod:FileTree.kt$@Composable private fun AslFileTreeRow( node: AslFileTreeNode, depth: Int, expanded: Boolean, selected: Boolean, rowWidth: Dp, actionsEnabled: Boolean, canPaste: Boolean, selectDirectories: Boolean, onToggle: (String) -> Unit, onSelect: (AslFileTreeNode) -> Unit, onFocus: (AslFileTreeNode) -> Unit, onAction: (AslFileTreeNode, AslFileTreeAction) -> Unit, ) CyclomaticComplexMethod:ListItem.kt$@Composable fun AslListItem( title: String, modifier: Modifier = Modifier, subtitle: String? = null, icon: String? = null, iconColor: Color? = null, leading: (@Composable () -> Unit)? = null, trailing: (@Composable () -> Unit)? = null, divider: Boolean = true, selected: Boolean = false, disabled: Boolean = false, onClick: (() -> Unit)? = null, ) CyclomaticComplexMethod:TextField.kt$@OptIn(ExperimentalFoundationApi::class) @Composable fun AslTextField( value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, label: String? = null, placeholder: String? = null, helper: String? = null, error: String? = null, disabled: Boolean = false, type: AslTextFieldType = AslTextFieldType.Text, leadingIcon: String? = null, trailingIcon: String? = null, onTrailingClick: (() -> Unit)? = null, onFocusChanged: ((Boolean) -> Unit)? = null, ) CyclomaticComplexMethod:ToolCallCard.kt$private fun computeLineDiff(old: String?, new: String): List<DiffLine> @@ -82,7 +81,6 @@ LongParameterList:Button.kt$( label: String, onClick: () -> Unit, modifier: Modifier = Modifier, variant: AslButtonVariant = AslButtonVariant.Primary, size: AslButtonSize = AslButtonSize.Md, icon: String? = null, trailingIcon: String? = null, disabled: Boolean = false, loading: Boolean = false, fullWidth: Boolean = false, ) LongParameterList:Chip.kt$( label: String, modifier: Modifier = Modifier, kind: AslChipKind = AslChipKind.Filter, selected: Boolean = false, disabled: Boolean = false, icon: String? = null, status: AslChipStatus = AslChipStatus.Neutral, onRemove: (() -> Unit)? = null, onClick: (() -> Unit)? = null, ) LongParameterList:Dialog.kt$( title: String, onDismiss: () -> Unit, modifier: Modifier = Modifier, variant: AslDialogVariant = AslDialogVariant.Alert, body: String? = null, confirmLabel: String = stringResource(R.string.asl_action_ok), cancelLabel: String? = null, destructive: Boolean = false, onConfirm: () -> Unit = {}, inputContent: (@Composable () -> Unit)? = null, ) - LongParameterList:EditorToolbar.kt$( modifier: Modifier = Modifier, projectName: String = "MyApplication", running: Boolean = false, onRun: () -> Unit = {}, onMenu: () -> Unit = {}, actions: @Composable (RowScope.() -> Unit)? = null, overflowItems: List<AslOverflowMenuEntry> = emptyList(), onOverflowSelect: (AslOverflowMenuEntry.Item, Int) -> Unit = { _, _ -> }, ) LongParameterList:EmptyState.kt$( title: String, modifier: Modifier = Modifier, icon: String = "folder-open", subtitle: String? = null, actionLabel: String? = null, onAction: () -> Unit = {}, secondaryLabel: String? = null, onSecondary: () -> Unit = {}, ) LongParameterList:ErrorState.kt$( title: String, modifier: Modifier = Modifier, icon: String = "triangle-alert", explanation: String? = null, detail: String? = null, actionLabel: String = stringResource(R.string.asl_action_retry), onAction: () -> Unit = {}, secondaryLabel: String? = null, onSecondary: () -> Unit = {}, ) LongParameterList:FileTree.kt$( items: List<AslFileTreeNode>, expandedIds: Set<String>, modifier: Modifier = Modifier, selectedId: String? = null, actionsEnabled: Boolean = false, canPaste: Boolean = false, selectDirectories: Boolean = false, onToggle: (String) -> Unit = {}, onSelect: (AslFileTreeNode) -> Unit = {}, onFocus: (AslFileTreeNode) -> Unit = {}, onAction: (AslFileTreeNode, AslFileTreeAction) -> Unit = { _, _ -> }, ) diff --git a/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/component/content/FileTree.kt b/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/component/content/FileTree.kt index 2687ce93..832afaf9 100644 --- a/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/component/content/FileTree.kt +++ b/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/component/content/FileTree.kt @@ -5,9 +5,11 @@ import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -191,111 +193,176 @@ private fun AslFileTreeRow( }, ), ) { - Row( + FileTreeRowContent( + node = node, + expanded = expanded, + selected = selected, + selectDirectories = selectDirectories, + callbacks = FileTreeRowCallbacks(onToggle, onSelect, onFocus), modifier = Modifier .width(rowWidth) .height(AslMetrics.treeRow) .padding(start = (8 + depth * 16).dp, end = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - if (isDir) { - val rotation by animateFloatAsState( - targetValue = if (expanded) 90f else 0f, - animationSpec = AslMotion.standardSpec(), - label = "chevronRotation", - ) - Box( - modifier = Modifier - .size(20.dp) - .then( - if (selectDirectories) { - Modifier.pointerInput(node.id) { - detectTapGestures(onTap = { onToggle(node.id) }) - } - } else { - Modifier - }, - ), - contentAlignment = Alignment.Center, - ) { - AslIcon( - name = "chevron-right", - size = 14.dp, - tint = colors.textTertiary, - modifier = Modifier.rotate(rotation), - ) - } - } else { - Box(modifier = Modifier.width(14.dp)) - } - Spacer(Modifier.width(6.dp)) - Row( - modifier = Modifier - .weight(1f) - .fillMaxWidth() - .then( - if (selectDirectories) { - Modifier.pointerInput(node.id) { - detectTapGestures(onTap = { - onFocus(node) - onSelect(node) - }) - } - } else { - Modifier - }, - ), - verticalAlignment = Alignment.CenterVertically, - ) { - AslIcon( - name = node.icon ?: if (isDir) (if (expanded) "folder-open" else "folder") else "file", - size = 16.dp, - tint = when { - isDir -> colors.textSecondary - else -> AslFileIcons.tintFor(node.name, colors) - }, - ) - Spacer(Modifier.width(6.dp)) - Text( - text = node.name, - style = MaterialTheme.typography.bodySmall, - fontWeight = if (selected) FontWeight.Medium else FontWeight.Normal, - color = node.git?.let { gitTint(it, colors) } ?: colors.textPrimary, - maxLines = 1, - softWrap = false, - ) - if (node.git != null) { - Spacer(Modifier.width(8.dp)) - Text( - text = node.git.letter, - style = AslCode.codeTiny, - color = gitTint(node.git, colors), - ) - } + ) + if (actionsEnabled && menuOpen) { + FileTreeRowMenu(menuAnchor, isDir, canPaste, openMenuUpward, onDismiss = { menuOpen = false }) { action -> + menuOpen = false + onAction(node, action) } } - if (actionsEnabled && menuOpen) { - Box( - modifier = Modifier - .align(Alignment.TopStart) - .offset(x = menuAnchor.x, y = menuAnchor.y) - .size(1.dp), - ) { - AslFileTreeActionMenu( - isDirectory = isDir, - canPaste = canPaste, - openUpward = openMenuUpward, - onDismiss = { menuOpen = false }, - onSelect = { action -> - menuOpen = false - onAction(node, action) + } +} + +@Immutable +private data class FileTreeRowCallbacks( + val onToggle: (String) -> Unit, + val onSelect: (AslFileTreeNode) -> Unit, + val onFocus: (AslFileTreeNode) -> Unit, +) + +@Composable +private fun FileTreeRowContent( + node: AslFileTreeNode, + expanded: Boolean, + selected: Boolean, + selectDirectories: Boolean, + callbacks: FileTreeRowCallbacks, + modifier: Modifier = Modifier, +) { + val isDirectory = node.children != null + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + FileTreeRowChevron(node, isDirectory, expanded, selectDirectories, callbacks.onToggle) + Spacer(Modifier.width(6.dp)) + FileTreeRowLabel(node, isDirectory, expanded, selected, selectDirectories, callbacks.onSelect, callbacks.onFocus) + } +} + +@Composable +private fun FileTreeRowChevron( + node: AslFileTreeNode, + isDirectory: Boolean, + expanded: Boolean, + selectDirectories: Boolean, + onToggle: (String) -> Unit, +) { + if (isDirectory) { + val colors = AslTheme.colors + val rotation by animateFloatAsState( + targetValue = if (expanded) 90f else 0f, + animationSpec = AslMotion.standardSpec(), + label = "chevronRotation", + ) + Box( + modifier = Modifier + .size(20.dp) + .then( + if (selectDirectories) { + Modifier.pointerInput(node.id) { + detectTapGestures(onTap = { onToggle(node.id) }) + } + } else { + Modifier }, - ) - } + ), + contentAlignment = Alignment.Center, + ) { + AslIcon( + name = "chevron-right", + size = 14.dp, + tint = colors.textTertiary, + modifier = Modifier.rotate(rotation), + ) + } + } else { + Box(modifier = Modifier.width(14.dp)) + } +} + +@Composable +private fun RowScope.FileTreeRowLabel( + node: AslFileTreeNode, + isDirectory: Boolean, + expanded: Boolean, + selected: Boolean, + selectDirectories: Boolean, + onSelect: (AslFileTreeNode) -> Unit, + onFocus: (AslFileTreeNode) -> Unit, +) { + val colors = AslTheme.colors + Row( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .then( + if (selectDirectories) { + Modifier.pointerInput(node.id) { + detectTapGestures(onTap = { + onFocus(node) + onSelect(node) + }) + } + } else { + Modifier + }, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + AslIcon( + name = node.icon ?: if (isDirectory) (if (expanded) "folder-open" else "folder") else "file", + size = 16.dp, + tint = when { + isDirectory -> colors.textSecondary + else -> AslFileIcons.tintFor(node.name, colors) + }, + ) + Spacer(Modifier.width(6.dp)) + Text( + text = node.name, + style = MaterialTheme.typography.bodySmall, + fontWeight = if (selected) FontWeight.Medium else FontWeight.Normal, + color = node.git?.let { gitTint(it, colors) } ?: colors.textPrimary, + maxLines = 1, + softWrap = false, + ) + if (node.git != null) { + Spacer(Modifier.width(8.dp)) + Text( + text = node.git.letter, + style = AslCode.codeTiny, + color = gitTint(node.git, colors), + ) } } } +@Composable +private fun BoxScope.FileTreeRowMenu( + menuAnchor: DpOffset, + isDirectory: Boolean, + canPaste: Boolean, + openUpward: Boolean, + onDismiss: () -> Unit, + onSelect: (AslFileTreeAction) -> Unit, +) { + Box( + modifier = Modifier + .align(Alignment.TopStart) + .offset(x = menuAnchor.x, y = menuAnchor.y) + .size(1.dp), + ) { + AslFileTreeActionMenu( + isDirectory = isDirectory, + canPaste = canPaste, + openUpward = openUpward, + onDismiss = onDismiss, + onSelect = onSelect, + ) + } +} + @Composable private fun AslFileTreeActionMenu( isDirectory: Boolean, diff --git a/feature/git/detekt-baseline.xml b/feature/git/detekt-baseline.xml index 3043930a..69c0e8e1 100644 --- a/feature/git/detekt-baseline.xml +++ b/feature/git/detekt-baseline.xml @@ -6,7 +6,6 @@ CyclomaticComplexMethod:GitAuthPrompt.kt$@Composable fun GitHubAuthDialog(state: GitAuthPromptState, actions: GitAuthPromptActions) CyclomaticComplexMethod:GitErrorMessageMapper.kt$fun gitErrorMessage(error: Throwable): String CyclomaticComplexMethod:GitHistoryScreen.kt$@Composable private fun GitHistoryScreen( uiState: GitHistoryUiState, interactionListener: GitHistoryInteractionListener, onBack: () -> Unit, onOpenDiff: (String, String) -> Unit, ) - CyclomaticComplexMethod:GitPanelScreen.kt$@Composable private fun GitChangesHeader( uiState: GitPanelUiState, interactionListener: GitPanelInteractionListener, ) CyclomaticComplexMethod:GitPanelScreen.kt$@Composable private fun GitPanelScreen( uiState: GitPanelUiState, interactionListener: GitPanelInteractionListener, onClose: () -> Unit, onOpenDiff: (String, GitDiffTarget) -> Unit, onOpenHistory: () -> Unit, onOpenBranches: () -> Unit, onOpenTags: () -> Unit, onOpenStashes: () -> Unit, onOpenConflicts: () -> Unit, ) ForbiddenImport:GitDiffScreen.kt$import androidx.compose.material3.MaterialTheme ForbiddenImport:GitHistoryScreen.kt$import androidx.compose.material3.MaterialTheme @@ -14,9 +13,7 @@ LongMethod:GitAuthPrompt.kt$@Composable fun GitHubAuthDialog(state: GitAuthPromptState, actions: GitAuthPromptActions) LongMethod:GitDiffScreen.kt$@Composable private fun GitDiffScreen( uiState: GitDiffUiState, interactionListener: GitDiffInteractionListener, onBack: () -> Unit, ) LongMethod:GitHistoryScreen.kt$@Composable private fun GitHistoryScreen( uiState: GitHistoryUiState, interactionListener: GitHistoryInteractionListener, onBack: () -> Unit, onOpenDiff: (String, String) -> Unit, ) - LongMethod:GitPanelScreen.kt$@Composable private fun GitChangesHeader( uiState: GitPanelUiState, interactionListener: GitPanelInteractionListener, ) LongMethod:GitPanelScreen.kt$@Composable private fun GitPanelScreen( uiState: GitPanelUiState, interactionListener: GitPanelInteractionListener, onClose: () -> Unit, onOpenDiff: (String, GitDiffTarget) -> Unit, onOpenHistory: () -> Unit, onOpenBranches: () -> Unit, onOpenTags: () -> Unit, onOpenStashes: () -> Unit, onOpenConflicts: () -> Unit, ) - LongMethod:GitPanelScreen.kt$@Composable private fun RemotesView(uiState: GitPanelUiState, interactionListener: GitPanelInteractionListener) LongMethod:GitRefsScreen.kt$@Composable private fun BranchList( state: GitRefsUiState, interactionListener: GitRefsInteractionListener, onRename: (GitBranch) -> Unit, onDelete: (GitBranch) -> Unit, onMerge: (GitBranch) -> Unit, ) LongMethod:GitRefsScreen.kt$@Composable private fun GitRefsScreen( uiState: GitRefsUiState, interactionListener: GitRefsInteractionListener, onBack: () -> Unit, ) LongParameterList:GitPanelApi.kt$GitPanelApi$( projectId: String, onClose: () -> Unit, onOpenDiff: (String, GitDiffTarget) -> Unit = { _, _ -> }, onOpenHistory: () -> Unit = {}, onOpenBranches: () -> Unit = {}, onOpenTags: () -> Unit = {}, onOpenStashes: () -> Unit = {}, onOpenConflicts: () -> Unit = {}, ) @@ -24,7 +21,6 @@ LongParameterList:GitPanelScreen.kt$( uiState: GitPanelUiState, interactionListener: GitPanelInteractionListener, onClose: () -> Unit, onOpenDiff: (String, GitDiffTarget) -> Unit, onOpenHistory: () -> Unit, onOpenBranches: () -> Unit, onOpenTags: () -> Unit, onOpenStashes: () -> Unit, onOpenConflicts: () -> Unit, ) ModifierMissing:GitAuthPrompt.kt$GitHubAuthDialog ModifierMissing:GitBlameScreen.kt$GitBlameRoute - MultipleEmitters:GitPanelScreen.kt$GitChangesHeader TooGenericExceptionCaught:GitPanelControllerContext.kt$GitPanelControllerContext$error: Throwable TooGenericExceptionCaught:GitPanelViewModel.kt$GitPanelViewModel$pushError: Throwable diff --git a/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/GitPanelScreen.kt b/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/GitPanelScreen.kt index 42c7a526..0955f125 100644 --- a/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/GitPanelScreen.kt +++ b/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/GitPanelScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -288,113 +289,130 @@ private fun GitChangesHeader( } val statusText = uiState.operationLabel ?: uiState.statusMessage if (statusText != null || uiState.isBusy) { - Column(modifier = Modifier.fillMaxWidth().padding(start = 12.dp, end = 8.dp, top = 6.dp)) { - if (statusText != null) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = statusText, - style = AslTypography.labelSmall, - color = colors.textSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f), + GitChangesStatus( + uiState = uiState, + statusText = statusText, + interactionListener = interactionListener, + textColor = colors.textSecondary, + ) + } + if (uiState.hasChipRow) { + GitChangesChipRow(uiState, interactionListener) + } + HorizontalDivider(color = colors.borderSubtle, thickness = 1.dp) +} + +@Composable +private fun GitChangesStatus( + uiState: GitPanelUiState, + statusText: String?, + interactionListener: GitPanelInteractionListener, + textColor: Color, +) { + Column(modifier = Modifier.fillMaxWidth().padding(start = 12.dp, end = 8.dp, top = 6.dp)) { + if (statusText != null) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = statusText, + style = AslTypography.labelSmall, + color = textColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + if (uiState.operationCancellable) { + AslIconButton( + icon = "x", + contentDescription = stringResource(R.string.git_panel_cancel_operation), + onClick = interactionListener::onCancelOperation, + size = 32.dp, + iconSize = 16.dp, ) - if (uiState.operationCancellable) { - AslIconButton( - icon = "x", - contentDescription = stringResource(R.string.git_panel_cancel_operation), - onClick = interactionListener::onCancelOperation, - size = 32.dp, - iconSize = 16.dp, - ) - } } } - if (uiState.isBusy) { - AslLinearProgress(value = uiState.operationProgress, modifier = Modifier.padding(top = 4.dp, end = 4.dp)) - } + } + if (uiState.isBusy) { + AslLinearProgress(value = uiState.operationProgress, modifier = Modifier.padding(top = 4.dp, end = 4.dp)) } } - if (uiState.hasChipRow) { - Row( - modifier = Modifier - .fillMaxWidth() - .horizontalScroll(rememberScrollState()) - .padding(horizontal = 12.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - if (uiState.hasSelection) { +} + +@Composable +private fun GitChangesChipRow( + uiState: GitPanelUiState, + interactionListener: GitPanelInteractionListener, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + .padding(horizontal = 12.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (uiState.hasSelection) { + AslChip(label = pluralStringResource(R.plurals.git_panel_selected, uiState.selectionCount, uiState.selectionCount), kind = AslChipKind.Filter, selected = true) + if (uiState.canStageSelection) { + GitSelectionActionChip(stringResource(R.string.git_panel_stage), "plus", uiState.isBusy, interactionListener::onStageSelected) + } + if (uiState.canUnstageSelection) { + GitSelectionActionChip(stringResource(R.string.git_panel_unstage), "minus", uiState.isBusy, interactionListener::onUnstageSelected) + } + if (uiState.canRevertSelection) { + GitSelectionActionChip(stringResource(R.string.git_panel_revert), "rotate-ccw", uiState.isBusy, interactionListener::onRevertSelected) + } + AslChip( + label = stringResource(R.string.git_panel_clear), + icon = "x", + kind = AslChipKind.Assist, + disabled = uiState.isBusy, + onClick = interactionListener::onClearSelection, + ) + } else { + if (uiState.changeCount > 0) { AslChip( - label = pluralStringResource(R.plurals.git_panel_selected, uiState.selectionCount, uiState.selectionCount), - kind = AslChipKind.Filter, - selected = true, + label = pluralStringResource( + R.plurals.git_panel_changes, + uiState.changeCount, + uiState.changeCount, + ), + kind = AslChipKind.Status, + status = AslChipStatus.Neutral, ) - if (uiState.canStageSelection) { - AslChip( - label = stringResource(R.string.git_panel_stage), - icon = "plus", - kind = AslChipKind.Filter, - disabled = uiState.isBusy, - onClick = interactionListener::onStageSelected, - ) - } - if (uiState.canUnstageSelection) { - AslChip( - label = stringResource(R.string.git_panel_unstage), - icon = "minus", - kind = AslChipKind.Filter, - disabled = uiState.isBusy, - onClick = interactionListener::onUnstageSelected, - ) - } - if (uiState.canRevertSelection) { - AslChip( - label = stringResource(R.string.git_panel_revert), - icon = "rotate-ccw", - kind = AslChipKind.Filter, - disabled = uiState.isBusy, - onClick = interactionListener::onRevertSelected, - ) - } + } + uiState.behind?.takeIf { it > 0 }?.let { + AslChip(label = behindLabel(it), kind = AslChipKind.Status, status = AslChipStatus.Info) + } + uiState.ahead?.takeIf { it > 0 }?.let { + AslChip(label = aheadLabel(it), kind = AslChipKind.Status, status = AslChipStatus.Success) + } + if (uiState.changeCount > 0) { AslChip( - label = stringResource(R.string.git_panel_clear), - icon = "x", - kind = AslChipKind.Assist, + label = stringResource(R.string.git_panel_select), + icon = "circle-check", + kind = AslChipKind.Filter, disabled = uiState.isBusy, - onClick = interactionListener::onClearSelection, + onClick = interactionListener::onSelectAllChanges, ) - } else { - if (uiState.changeCount > 0) { - AslChip( - label = pluralStringResource( - R.plurals.git_panel_changes, - uiState.changeCount, - uiState.changeCount, - ), - kind = AslChipKind.Status, - status = AslChipStatus.Neutral, - ) - } - uiState.behind?.takeIf { it > 0 }?.let { - AslChip(label = behindLabel(it), kind = AslChipKind.Status, status = AslChipStatus.Info) - } - uiState.ahead?.takeIf { it > 0 }?.let { - AslChip(label = aheadLabel(it), kind = AslChipKind.Status, status = AslChipStatus.Success) - } - if (uiState.changeCount > 0) { - AslChip( - label = stringResource(R.string.git_panel_select), - icon = "circle-check", - kind = AslChipKind.Filter, - disabled = uiState.isBusy, - onClick = interactionListener::onSelectAllChanges, - ) - } } } } - HorizontalDivider(color = colors.borderSubtle, thickness = 1.dp) +} + +@Composable +private fun GitSelectionActionChip( + label: String, + icon: String, + disabled: Boolean, + onClick: () -> Unit, +) { + AslChip( + label = label, + icon = icon, + kind = AslChipKind.Filter, + disabled = disabled, + onClick = onClick, + ) } @Composable @@ -846,45 +864,50 @@ private fun RemotesView(uiState: GitPanelUiState, interactionListener: GitPanelI ) } HorizontalDivider(color = AslTheme.colors.borderSubtle) - when { - uiState.remotesLoading -> AslLinearProgress( - label = stringResource(R.string.git_remotes_loading), - modifier = Modifier.padding(16.dp), - ) - uiState.remotes.isEmpty() -> AslEmptyState( - icon = "globe", - title = stringResource(R.string.git_remotes_none), - subtitle = stringResource(R.string.git_remotes_none_hint), - modifier = Modifier.fillMaxSize(), - ) - else -> Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { - uiState.remotes.forEach { remote -> - AslListItem( - title = remote.name, - subtitle = remote.url.middleEllipsis(), - icon = "globe", - trailing = { - Row { - AslIconButton( - icon = "edit-2", - contentDescription = stringResource(R.string.git_remotes_edit_description, remote.name), - onClick = { interactionListener.onEditRemote(remote.name) }, - size = 28.dp, - iconSize = 14.dp, - disabled = uiState.isBusy, - ) - AslIconButton( - icon = "trash-2", - contentDescription = stringResource(R.string.git_remotes_remove_description, remote.name), - onClick = { interactionListener.onRequestRemoveRemote(remote.name) }, - size = 28.dp, - iconSize = 14.dp, - disabled = uiState.isBusy, - ) - } - }, - ) - } + RemotesContent(uiState, interactionListener) + } +} + +@Composable +private fun RemotesContent(uiState: GitPanelUiState, interactionListener: GitPanelInteractionListener) { + when { + uiState.remotesLoading -> AslLinearProgress( + label = stringResource(R.string.git_remotes_loading), + modifier = Modifier.padding(16.dp), + ) + uiState.remotes.isEmpty() -> AslEmptyState( + icon = "globe", + title = stringResource(R.string.git_remotes_none), + subtitle = stringResource(R.string.git_remotes_none_hint), + modifier = Modifier.fillMaxSize(), + ) + else -> Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + uiState.remotes.forEach { remote -> + AslListItem( + title = remote.name, + subtitle = remote.url.middleEllipsis(), + icon = "globe", + trailing = { + Row { + AslIconButton( + icon = "edit-2", + contentDescription = stringResource(R.string.git_remotes_edit_description, remote.name), + onClick = { interactionListener.onEditRemote(remote.name) }, + size = 28.dp, + iconSize = 14.dp, + disabled = uiState.isBusy, + ) + AslIconButton( + icon = "trash-2", + contentDescription = stringResource(R.string.git_remotes_remove_description, remote.name), + onClick = { interactionListener.onRequestRemoveRemote(remote.name) }, + size = 28.dp, + iconSize = 14.dp, + disabled = uiState.isBusy, + ) + } + }, + ) } } }