Skip to content

Commit

Permalink
Merge pull request #124 from kaleidot725/feature/rework_screenshot
Browse files Browse the repository at this point in the history
feat: rework gallery ui
  • Loading branch information
kaleidot725 authored Feb 16, 2025
2 parents bcc074c + 4f69b6b commit 8dff9d7
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 176 deletions.
8 changes: 8 additions & 0 deletions .idea/artifacts/adbpad_jvm_1_5_1.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@file:OptIn(ExperimentalComposeLibrary::class)

import org.jetbrains.compose.ExperimentalComposeLibrary
import org.jetbrains.compose.reload.ComposeHotRun
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag

Expand All @@ -23,6 +26,7 @@ kotlin {

sourceSets.jvmMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(compose.desktop.components.splitPane)
implementation(compose.material)
implementation(compose.materialIconsExtended)
implementation(libs.adam)
Expand Down
11 changes: 11 additions & 0 deletions src/jvmMain/kotlin/jp/kaleidot725/adbpad/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import jp.kaleidot725.adbpad.ui.screen.setting.SettingStateHolder
import jp.kaleidot725.adbpad.ui.screen.text.TextCommandScreen
import jp.kaleidot725.adbpad.ui.section.TopSection
import org.jetbrains.compose.reload.DevelopmentEntryPoint
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
import org.jetbrains.compose.splitpane.rememberSplitPaneState
import org.koin.core.context.GlobalContext
import org.koin.core.context.startKoin

Expand Down Expand Up @@ -74,10 +76,12 @@ fun main() {
}
}

@OptIn(ExperimentalSplitPaneApi::class)
@Composable
fun WindowScope.App(mainStateHolder: MainStateHolder) {
val state by mainStateHolder.state.collectAsState()
val decoratedWindowScope = this
val screenshotSplitPaneState = rememberSplitPaneState()

DisposableEffect(mainStateHolder) {
mainStateHolder.setup()
Expand Down Expand Up @@ -165,6 +169,7 @@ fun WindowScope.App(mainStateHolder: MainStateHolder) {

ScreenshotScreen(
screenshot = screenshotState.preview,
splitterState = screenshotSplitPaneState,
screenshots = screenshotState.previews,
canCapture = screenshotState.canExecute,
isCapturing = screenshotState.isCapturing,
Expand All @@ -186,6 +191,12 @@ fun WindowScope.App(mainStateHolder: MainStateHolder) {
onSelectScreenshot = { screenshot ->
screenshotStateHolder.selectScreenshot(screenshot)
},
onNextScreenshot = {
screenshotStateHolder.nextScreenshot()
},
onPreviousScreenshot = {
screenshotStateHolder.previousScreenshot()
},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,38 @@ import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.unit.dp
import jp.kaleidot725.adbpad.domain.model.UserColor
import jp.kaleidot725.adbpad.domain.model.command.ScreenshotCommand
import jp.kaleidot725.adbpad.domain.model.screenshot.Screenshot
import jp.kaleidot725.adbpad.ui.screen.screenshot.component.ScreenshotGallery
import jp.kaleidot725.adbpad.ui.screen.screenshot.component.ScreenshotExplorer
import jp.kaleidot725.adbpad.ui.screen.screenshot.component.ScreenshotMenu
import jp.kaleidot725.adbpad.ui.screen.screenshot.component.ScreenshotViewer
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
import org.jetbrains.compose.splitpane.HorizontalSplitPane
import org.jetbrains.compose.splitpane.SplitPaneState
import org.jetbrains.compose.splitpane.rememberSplitPaneState
import java.awt.Cursor

private fun Modifier.cursorForHorizontalResize(): Modifier = pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))

@OptIn(ExperimentalSplitPaneApi::class)
@Composable
fun ScreenshotScreen(
screenshot: Screenshot,
splitterState: SplitPaneState,
screenshots: List<Screenshot>,
canCapture: Boolean,
isCapturing: Boolean,
Expand All @@ -38,63 +45,87 @@ fun ScreenshotScreen(
onDeleteScreenshot: () -> Unit,
onTakeScreenshot: (ScreenshotCommand) -> Unit,
onSelectScreenshot: (Screenshot) -> Unit,
onNextScreenshot: () -> Unit,
onPreviousScreenshot: () -> Unit,
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxSize().padding(16.dp),
modifier = Modifier.fillMaxSize(),
) {
Row(
HorizontalSplitPane(
splitPaneState = splitterState,
modifier =
Modifier
.fillMaxWidth()
.weight(1.0f)
.border(
border = BorderStroke(1.dp, UserColor.getSplitterColor()),
shape = RoundedCornerShape(4.dp),
),
.weight(1.0f),
) {
ScreenshotViewer(
screenshot = screenshot,
isCapturing = isCapturing,
onOpenDirectory = onOpenDirectory,
onCopyScreenshot = onCopyScreenshot,
onDeleteScreenshot = onDeleteScreenshot,
modifier =
Modifier
.weight(1.0f)
.fillMaxHeight(),
)

if (screenshots.isNotEmpty()) {
Spacer(Modifier.width(1.dp).fillMaxHeight().border(BorderStroke(1.dp, UserColor.getSplitterColor())))

ScreenshotGallery(
first(minSize = 350.dp) {
ScreenshotExplorer(
selectedScreenshot = screenshot,
screenshots = screenshots,
onSelectScreenShot = onSelectScreenshot,
modifier =
Modifier
.wrapContentWidth()
.fillMaxHeight(),
onNextScreenshot = onNextScreenshot,
onPreviousScreenshot = onPreviousScreenshot,
modifier = Modifier.fillMaxSize(),
)
}
}

ScreenshotMenu(
commands = commands,
canCapture = canCapture,
isCapturing = isCapturing,
onTakeScreenshot = onTakeScreenshot,
modifier = Modifier.wrapContentSize().align(Alignment.End),
)
second {
Column {
ScreenshotViewer(
screenshot = screenshot,
isCapturing = isCapturing,
onOpenDirectory = onOpenDirectory,
onCopyScreenshot = onCopyScreenshot,
onDeleteScreenshot = onDeleteScreenshot,
modifier =
Modifier
.weight(1.0f)
.fillMaxHeight(),
)

ScreenshotMenu(
commands = commands,
canCapture = canCapture,
isCapturing = isCapturing,
onTakeScreenshot = onTakeScreenshot,
modifier = Modifier.wrapContentSize().align(Alignment.End),
)
}
}

splitter {
visiblePart {
Box(
Modifier
.width(1.dp)
.fillMaxHeight()
.border(BorderStroke(1.dp, UserColor.getSplitterColor())),
)
}

handle {
Box(
Modifier
.markAsHandle()
.cursorForHorizontalResize()
.width(10.dp)
.fillMaxHeight()
.markAsHandle(),
)
}
}
}
}
}

@OptIn(ExperimentalSplitPaneApi::class)
@Composable
@Preview
private fun ScreenshotScreen_Preview() {
ScreenshotScreen(
screenshot = Screenshot(null),
splitterState = rememberSplitPaneState(),
screenshots = emptyList(),
canCapture = true,
isCapturing = false,
Expand All @@ -104,5 +135,7 @@ private fun ScreenshotScreen_Preview() {
onDeleteScreenshot = {},
onTakeScreenshot = {},
onSelectScreenshot = {},
onNextScreenshot = {},
onPreviousScreenshot = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ class ScreenshotStateHolder(
preview.value = screenshot
}

fun nextScreenshot() {
val nextIndex = previews.value.indexOf(preview.value) + 1
val nextPreview = previews.value.getOrNull(nextIndex) ?: return
preview.value = nextPreview
}

fun previousScreenshot() {
val previousIndex = previews.value.indexOf(preview.value) - 1
val previousPreview = previews.value.getOrNull(previousIndex) ?: return
preview.value = previousPreview
}

private suspend fun initPreviews() {
previews.value = screenshotCommandRepository.getScreenshots()
preview.value = previews.value.firstOrNull() ?: Screenshot(null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package jp.kaleidot725.adbpad.ui.screen.screenshot.component

import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import jp.kaleidot725.adbpad.domain.model.screenshot.Screenshot
import jp.kaleidot725.adbpad.ui.common.resource.clickableBackground

@Composable
fun ScreenshotExplorer(
selectedScreenshot: Screenshot,
screenshots: List<Screenshot>,
onSelectScreenShot: (Screenshot) -> Unit,
onNextScreenshot: () -> Unit,
onPreviousScreenshot: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier) {
if (screenshots.isNotEmpty()) {
val lazyColumnState = rememberLazyListState()
LazyColumn(
state = lazyColumnState,
modifier =
Modifier.onKeyEvent { event ->
when {
event.key == Key.DirectionUp && event.type == KeyEventType.KeyDown -> {
onPreviousScreenshot()
true
}
event.key == Key.DirectionDown && event.type == KeyEventType.KeyDown -> {
onNextScreenshot()
true
}
else -> false
}
},
) {
items(
items = screenshots,
key = { screenshot -> screenshot.file?.absolutePath ?: "" },
) { screenshot ->
Row(
modifier =
Modifier
.fillMaxWidth()
.clickableBackground(
isSelected = selectedScreenshot == screenshot,
shape = RoundedCornerShape(4.dp),
)
.clickable { onSelectScreenShot(screenshot) }
.padding(horizontal = 12.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
AsyncImage(
model = screenshot.file,
contentDescription = null,
modifier = Modifier.size(24.dp),
)

Text(
text = screenshot.file?.name ?: "",
)
}
}
}

VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd).width(8.dp).fillMaxHeight(),
adapter = rememberScrollbarAdapter(scrollState = lazyColumnState),
)
} else {
Text(
text = "Not Found Screenshot",
modifier = Modifier.align(Alignment.Center),
)
}
}
}
Loading

0 comments on commit 8dff9d7

Please sign in to comment.