diff --git a/.idea/artifacts/adbpad_jvm_1_5_2.xml b/.idea/artifacts/adbpad_jvm_1_5_2.xml
new file mode 100644
index 00000000..e17f3913
--- /dev/null
+++ b/.idea/artifacts/adbpad_jvm_1_5_2.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/ktlint-plugin.xml b/.idea/ktlint-plugin.xml
new file mode 100644
index 00000000..e8bd90cf
--- /dev/null
+++ b/.idea/ktlint-plugin.xml
@@ -0,0 +1,7 @@
+
+
+
+ DISTRACT_FREE
+ DEFAULT
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 1d014227..8add7c54 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ https://github.com/user-attachments/assets/b250bf2b-e61c-4a6d-9872-6d3dcb3339e6
# ⬇️ Install
-- Download from [here](https://github.com/kaleidot725/AdbPad/releases/tag/v1.5.2).
+- Download from [here](https://github.com/kaleidot725/AdbPad/releases/tag/v2.0.0).
- Setup adb path on Setting.
https://github.com/user-attachments/assets/f5542ace-118d-4165-a138-49d59c7bda8b
diff --git a/build.gradle.kts b/build.gradle.kts
index dea08e49..114d80c3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -14,7 +14,7 @@ plugins {
}
group = "jp.kaleidot725"
-version = "1.5.2"
+version = "2.0.0"
kotlin {
jvm()
@@ -39,8 +39,6 @@ kotlin {
implementation(libs.ktor.client.okhttp)
implementation(libs.jSystemThemeDetectorVer)
implementation(libs.coil)
- implementation(libs.hot.reload.core)
- implementation(libs.hot.reload.analysis)
}
sourceSets.jvmTest.dependencies {
implementation(libs.junit5)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b0d500b9..2f51b7f2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,9 +1,8 @@
[versions]
kotlin="2.1.20-Beta2"
-compose_reload_kotlin = "2.1.20-firework.34"
kotlin_coroutines="1.10.1"
kotlin_serialization="1.8.0"
-compose_hot_reload = "1.0.0-dev-39"
+compose_hot_reload = "1.0.0-alpha01"
compose="1.7.3"
ktlint_plugin="12.1.2"
adam="0.5.10"
@@ -26,14 +25,12 @@ ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
lucide = { module = "com.composables:icons-lucide", version.ref = "lucide" }
coil = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
-hot-reload-core = { module = "org.jetbrains.compose:hot-reload-core", version.ref = "compose_hot_reload" }
-hot-reload-analysis = { module = "org.jetbrains.compose:hot-reload-analysis", version.ref = "compose_hot_reload" }
[plugins]
-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "compose_reload_kotlin" }
-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "compose_reload_kotlin" }
-compose-hot-reload = { id = "org.jetbrains.compose-hot-reload", version.ref = "compose_hot_reload" }
+multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+compose-hot-reload = { id = "org.jetbrains.compose.hot-reload", version.ref = "compose_hot_reload" }
compose = { id = "org.jetbrains.compose", version.ref = "compose" }
-kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "compose_reload_kotlin" }
+kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint_plugin" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 579e5185..8af855f4 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -3,8 +3,6 @@ pluginManagement {
google()
gradlePluginPortal()
mavenCentral()
- maven(file("../..//build/repo"))
- maven("https://packages.jetbrains.team/maven/p/firework/dev")
}
}
@@ -13,8 +11,6 @@ dependencyResolutionManagement {
google()
gradlePluginPortal()
mavenCentral()
- maven(file("../..//build/repo"))
- maven("https://packages.jetbrains.team/maven/p/firework/dev")
maven("https://jitpack.io")
}
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/Main.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/Main.kt
index 4081f5f9..e648c6e6 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/Main.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/Main.kt
@@ -30,6 +30,7 @@ import jp.kaleidot725.adbpad.ui.di.stateHolderModule
import jp.kaleidot725.adbpad.ui.screen.CommandScreen
import jp.kaleidot725.adbpad.ui.screen.ScreenLayout
import jp.kaleidot725.adbpad.ui.screen.error.AdbErrorScreen
+import jp.kaleidot725.adbpad.ui.screen.screenshot.ScreenshotAction
import jp.kaleidot725.adbpad.ui.screen.screenshot.ScreenshotScreen
import jp.kaleidot725.adbpad.ui.screen.setting.SettingScreen
import jp.kaleidot725.adbpad.ui.screen.setting.SettingStateHolder
@@ -81,6 +82,7 @@ fun main() {
fun WindowScope.App(mainStateHolder: MainStateHolder) {
val state by mainStateHolder.state.collectAsState()
val decoratedWindowScope = this
+ val textSplitPaneState = rememberSplitPaneState()
val screenshotSplitPaneState = rememberSplitPaneState()
DisposableEffect(mainStateHolder) {
@@ -128,44 +130,19 @@ fun WindowScope.App(mainStateHolder: MainStateHolder) {
}
MainCategory.Text -> {
- val inputTextStateHolder = mainStateHolder.textCommandStateHolder
- val inputTextState by inputTextStateHolder.state.collectAsState()
-
+ val inputTextState by mainStateHolder.textCommandStateHolder.state.collectAsState()
+ val onAction = mainStateHolder.textCommandStateHolder::onAction
TextCommandScreen(
- // InputText
- inputText = inputTextState.userInputText,
- onTextChange = { text ->
- inputTextStateHolder.updateInputText(text)
- },
- isSendingInputText = inputTextState.isSendingUserInputText,
- onSendInputText = {
- inputTextStateHolder.sendInputText()
- },
- canSendInputText = inputTextState.canSendInputText,
- canSendTabKey = inputTextState.canSendTabKey,
- onSendTabKey = {
- inputTextStateHolder.sendTabCommand()
- },
- onSaveInputText = {
- inputTextStateHolder.saveInputText()
- },
- canSaveInputText = inputTextState.canSaveInputText,
- // Commands
- commands = inputTextState.commands,
- onSendCommand = { text ->
- inputTextStateHolder.sendTextCommand(text)
- },
- canSendCommand = inputTextState.canSendCommand,
- isSendingTab = inputTextState.isSendingTab,
- onDeleteCommand = { text ->
- inputTextStateHolder.deleteInputText(text)
- },
+ state = inputTextState,
+ onAction = onAction,
+ splitterState = textSplitPaneState,
)
}
MainCategory.Screenshot -> {
val screenshotStateHolder = mainStateHolder.screenshotStateHolder
val screenshotState by screenshotStateHolder.state.collectAsState()
+ val onAction = screenshotStateHolder::onAction
ScreenshotScreen(
screenshot = screenshotState.preview,
@@ -174,28 +151,30 @@ fun WindowScope.App(mainStateHolder: MainStateHolder) {
canCapture = screenshotState.canExecute,
isCapturing = screenshotState.isCapturing,
commands = screenshotState.commands,
+ searchText = screenshotState.searchText,
onOpenDirectory = {
- screenshotStateHolder.openDirectory()
+ onAction(ScreenshotAction.OpenDirectory)
},
onCopyScreenshot = {
- screenshotStateHolder.copyScreenShotToClipboard()
+ onAction(ScreenshotAction.CopyScreenshotToClipboard)
},
onDeleteScreenshot = {
- screenshotStateHolder.deleteScreenShotToClipboard()
+ onAction(ScreenshotAction.DeleteScreenshotToClipboard)
},
onTakeScreenshot = { screenshot ->
- screenshotStateHolder.takeScreenShot(
- screenshot,
- )
+ onAction(ScreenshotAction.TakeScreenshot(screenshot))
},
onSelectScreenshot = { screenshot ->
- screenshotStateHolder.selectScreenshot(screenshot)
+ onAction(ScreenshotAction.SelectScreenshot(screenshot))
},
onNextScreenshot = {
- screenshotStateHolder.nextScreenshot()
+ onAction(ScreenshotAction.NextScreenshot)
},
onPreviousScreenshot = {
- screenshotStateHolder.previousScreenshot()
+ onAction(ScreenshotAction.PreviousScreenshot)
+ },
+ onUpdateSearchText = {
+ onAction(ScreenshotAction.UpdateSearchText(it))
},
)
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/MainStateHolder.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/MainStateHolder.kt
index d8a238dd..387546e9 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/MainStateHolder.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/MainStateHolder.kt
@@ -60,8 +60,6 @@ class MainStateHolder(
private val children: List> =
listOf(
commandStateHolder,
- textCommandStateHolder,
- screenshotStateHolder,
topStateHolder,
)
@@ -74,6 +72,8 @@ class MainStateHolder(
override fun setup() {
children.forEach { it.setup() }
+ textCommandStateHolder.onSetup()
+ screenshotStateHolder.onSetup()
}
override fun refresh() {
@@ -82,10 +82,14 @@ class MainStateHolder(
syncLanguage()
refreshUseCase()
children.forEach { it.refresh() }
+ textCommandStateHolder.onRefresh()
+ screenshotStateHolder.onRefresh()
}
override fun dispose() {
children.forEach { it.dispose() }
+ textCommandStateHolder.onDispose()
+ screenshotStateHolder.onDispose()
}
fun saveSetting(windowSize: WindowSize) {
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVI.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVI.kt
new file mode 100644
index 00000000..2af314a8
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVI.kt
@@ -0,0 +1,24 @@
+package jp.kaleidot725.adbpad.core.mvi
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+interface MVI {
+ val coroutineScope: CoroutineScope
+ val state: StateFlow
+ val currentState: UiState
+ val sideEffect: Flow
+
+ fun onSetup()
+
+ fun onAction(uiAction: UiAction)
+
+ fun onRefresh()
+
+ fun onDispose()
+
+ fun update(block: UiState.() -> UiState)
+
+ suspend fun sideEffect(effect: SideEffect)
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIAction.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIAction.kt
new file mode 100644
index 00000000..09a184c5
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIAction.kt
@@ -0,0 +1,3 @@
+package jp.kaleidot725.adbpad.core.mvi
+
+interface MVIAction
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIDelegate.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIDelegate.kt
new file mode 100644
index 00000000..5dbc30c0
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIDelegate.kt
@@ -0,0 +1,45 @@
+package jp.kaleidot725.adbpad.core.mvi
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+class MVIDelegate internal constructor(
+ initialUiState: UiState,
+) : MVI {
+ override val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main + Dispatchers.IO)
+
+ private val uiState = MutableStateFlow(initialUiState)
+ override val state: StateFlow = uiState.asStateFlow()
+ override val currentState: UiState get() = state.value
+ private val _sideEffect by lazy { Channel() }
+ override val sideEffect: Flow by lazy { _sideEffect.receiveAsFlow() }
+
+ override fun onSetup() {}
+
+ override fun onAction(uiAction: UiAction) {}
+
+ override fun onRefresh() {}
+
+ override fun onDispose() {}
+
+ override fun update(block: UiState.() -> UiState) {
+ uiState.update { block(it) }
+ }
+
+ override suspend fun sideEffect(effect: SideEffect) {
+ coroutineScope.launch { _sideEffect.send(effect) }
+ }
+}
+
+fun mvi(
+ initialUiState: UiState,
+): MVI = MVIDelegate(initialUiState)
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVISideEffect.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVISideEffect.kt
new file mode 100644
index 00000000..551ef500
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVISideEffect.kt
@@ -0,0 +1,3 @@
+package jp.kaleidot725.adbpad.core.mvi
+
+interface MVISideEffect
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIState.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIState.kt
new file mode 100644
index 00000000..6fa10a65
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/core/mvi/MVIState.kt
@@ -0,0 +1,3 @@
+package jp.kaleidot725.adbpad.core.mvi
+
+interface MVIState
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/di/DomainModule.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/di/DomainModule.kt
index 7c7f5ba5..5ef3a059 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/di/DomainModule.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/di/DomainModule.kt
@@ -18,11 +18,8 @@ import jp.kaleidot725.adbpad.domain.usecase.screenshot.TakeScreenshotUseCase
import jp.kaleidot725.adbpad.domain.usecase.sdkpath.GetSdkPathUseCase
import jp.kaleidot725.adbpad.domain.usecase.sdkpath.SaveSdkPathUseCase
import jp.kaleidot725.adbpad.domain.usecase.text.AddTextCommandUseCase
-import jp.kaleidot725.adbpad.domain.usecase.text.DeleteTextCommandUseCase
import jp.kaleidot725.adbpad.domain.usecase.text.ExecuteTextCommandUseCase
import jp.kaleidot725.adbpad.domain.usecase.text.GetTextCommandUseCase
-import jp.kaleidot725.adbpad.domain.usecase.text.SendTabCommandUseCase
-import jp.kaleidot725.adbpad.domain.usecase.text.SendUserInputTextCommandUseCase
import jp.kaleidot725.adbpad.domain.usecase.theme.GetDarkModeFlowUseCase
import jp.kaleidot725.adbpad.domain.usecase.window.GetWindowSizeUseCase
import jp.kaleidot725.adbpad.domain.usecase.window.SaveWindowSizeUseCase
@@ -54,9 +51,6 @@ val domainModule =
factory {
AddTextCommandUseCase(get())
}
- factory {
- DeleteTextCommandUseCase(get())
- }
factory {
ExecuteTextCommandUseCase(get())
}
@@ -69,9 +63,6 @@ val domainModule =
factory {
GetScreenshotCommandUseCase(get())
}
- factory {
- SendUserInputTextCommandUseCase(get())
- }
factory {
GetWindowSizeUseCase(get())
}
@@ -99,9 +90,6 @@ val domainModule =
factory {
GetLanguageUseCase(get())
}
- factory {
- SendTabCommandUseCase(get())
- }
factory {
RefreshUseCase(get(), get(), get())
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/command/TextCommand.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/command/TextCommand.kt
index cf3c4ec6..26749ae6 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/command/TextCommand.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/command/TextCommand.kt
@@ -1,10 +1,31 @@
package jp.kaleidot725.adbpad.domain.model.command
import com.malinskiy.adam.request.shell.v1.ShellCommandRequest
+import kotlinx.serialization.Serializable
+import java.util.UUID
+@Serializable
data class TextCommand(
+ val id: String = UUID.randomUUID().toString(),
+ val title: String,
val text: String,
val isRunning: Boolean = false,
) {
- val requests: List = listOf(ShellCommandRequest("input text $text"))
+ val requests: List get() {
+ return buildList {
+ val texts = text.split('\n')
+ texts.forEach { text ->
+ if (text.isEmpty()) {
+ add(ShellCommandRequest(""))
+ } else {
+ add(ShellCommandRequest("input text $text"))
+ }
+ }
+ }
+ }
+
+ enum class Option {
+ SendWithTab,
+ SendWithNewLine,
+ }
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/Language.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/Language.kt
index 5b594164..c2856f49 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/Language.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/Language.kt
@@ -40,6 +40,10 @@ object Language : StringResources {
get() = getCurrentResources().light
override val system: String
get() = getCurrentResources().system
+ override val search: String
+ get() = getCurrentResources().search
+ override val textCommandUnTitle: String
+ get() = getCurrentResources().textCommandUnTitle
override val screenshotTakeByCurrentTheme: String
get() = getCurrentResources().screenshotTakeByCurrentTheme
override val screenshotTakeByDarkTheme: String
@@ -199,19 +203,23 @@ object Language : StringResources {
override val adbErrorOpenSetting: String
get() = getCurrentResources().adbErrorOpenSetting
+ override val textCommandOptionNewLine: String
+ get() = getCurrentResources().textCommandOptionNewLine
+ override val textCommandOptionTab: String
+ get() = getCurrentResources().textCommandOptionTab
+
private var currentType: Type = Type.ENGLISH
fun switch(type: Type) {
currentType = type
}
- private fun getCurrentResources(): StringResources {
- return when (currentType) {
+ private fun getCurrentResources(): StringResources =
+ when (currentType) {
Type.ENGLISH -> EnglishResources
Type.JAPANESE -> JapaneseResources
Type.CHINESE -> ChineseResources
}
- }
enum class Type {
ENGLISH,
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/ChineseResources.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/ChineseResources.kt
index 24bdfc68..50f31148 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/ChineseResources.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/ChineseResources.kt
@@ -20,12 +20,18 @@ object ChineseResources : StringResources {
override val dark = "深色"
override val light = "浅色"
override val system = "系统"
+ override val search: String = "搜索"
+
+ override val textCommandUnTitle: String = "取消文本标题命令"
override val screenshotTakeByCurrentTheme = "按当前主题截图"
override val screenshotTakeByDarkTheme = "按深色主题截图"
override val screenshotTakeByLightTheme = "按浅色主题截图"
override val screenshotTakeByBothTheme = "按两种主题截图"
+ override val textCommandOptionNewLine: String = "用换行键发送"
+ override val textCommandOptionTab: String = "用制表符键发送"
+
override val commandStartEventFormat = "开始发送命令 「%s」"
override val commandEndEventFormat = "结束发送命令 「%s」"
override val commandErrorEventFormat = "发送命令失败 「%s」"
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/EnglishResources.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/EnglishResources.kt
index 3cff56e5..1d66f38d 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/EnglishResources.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/EnglishResources.kt
@@ -20,12 +20,18 @@ object EnglishResources : StringResources {
override val dark = "Dark"
override val light = "Light"
override val system = "System"
+ override val search: String = "Search"
+
+ override val textCommandUnTitle: String = "untitle text command"
override val screenshotTakeByCurrentTheme = "Take by current theme"
override val screenshotTakeByDarkTheme = "Take by dark theme"
override val screenshotTakeByLightTheme = "Take by light theme"
override val screenshotTakeByBothTheme = "Take by both theme"
+ override val textCommandOptionNewLine: String = "Send with newline key"
+ override val textCommandOptionTab: String = "Send with tab key"
+
override val commandStartEventFormat = "Start sending command 「%s」"
override val commandEndEventFormat = "End sending command 「%s」"
override val commandErrorEventFormat = "Error sending command 「%s」"
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/JapaneseResources.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/JapaneseResources.kt
index 39fdb00f..f210481b 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/JapaneseResources.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/JapaneseResources.kt
@@ -20,12 +20,17 @@ object JapaneseResources : StringResources {
override val dark = "Dark"
override val light = "Light"
override val system = "System"
+ override val search: String = "Search"
+ override val textCommandUnTitle: String = "untitle text command"
override val screenshotTakeByCurrentTheme = "現在のテーマで撮影する"
override val screenshotTakeByDarkTheme = "ダークテーマで撮影する"
override val screenshotTakeByLightTheme = "ライトテーマで撮影する"
override val screenshotTakeByBothTheme = "両方のテーマで撮影する"
+ override val textCommandOptionNewLine: String = "送信する(改行キー)"
+ override val textCommandOptionTab: String = "送信する(タブキー)"
+
override val commandStartEventFormat = "「%s」のコマンド送信を開始しました"
override val commandEndEventFormat = "「%s」のコマンド送信が完了しました"
override val commandErrorEventFormat = "「%s」のコマンド送信に失敗しました"
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/StringResources.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/StringResources.kt
index 615bc0ea..26672dfc 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/StringResources.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/model/language/resources/StringResources.kt
@@ -1,6 +1,6 @@
package jp.kaleidot725.adbpad.domain.model.language.resources
-val APP_VERSION = "v1.5.2"
+val APP_VERSION = "v2.0.0"
interface StringResources {
val windowTitle: String
@@ -21,7 +21,9 @@ interface StringResources {
val dark: String
val light: String
val system: String
+ val search: String
+ val textCommandUnTitle: String
val screenshotTakeByCurrentTheme: String
val screenshotTakeByDarkTheme: String
val screenshotTakeByLightTheme: String
@@ -108,4 +110,7 @@ interface StringResources {
val adbErrorTitle: String
val adbErrorMessage: String
val adbErrorOpenSetting: String
+
+ val textCommandOptionNewLine: String
+ val textCommandOptionTab: String
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/repository/KeyCommandRepository.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/repository/KeyCommandRepository.kt
deleted file mode 100644
index 24bd63ad..00000000
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/repository/KeyCommandRepository.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package jp.kaleidot725.adbpad.domain.repository
-
-import jp.kaleidot725.adbpad.domain.model.device.Device
-
-interface KeyCommandRepository {
- suspend fun sendKeyCommand(
- device: Device,
- keycode: Int,
- onStart: suspend () -> Unit,
- onComplete: suspend () -> Unit,
- onFailed: suspend () -> Unit,
- )
-}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/repository/TextCommandRepository.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/repository/TextCommandRepository.kt
index 2cfe7bb2..044849dd 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/repository/TextCommandRepository.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/repository/TextCommandRepository.kt
@@ -8,19 +8,22 @@ interface TextCommandRepository {
suspend fun addTextCommand(command: TextCommand): Boolean
+ suspend fun updateTextCommandTitle(
+ id: String,
+ title: String,
+ ): Boolean
+
+ suspend fun updateTextCommandValue(
+ id: String,
+ value: String,
+ ): Boolean
+
suspend fun removeTextCommand(command: TextCommand): Boolean
suspend fun sendCommand(
device: Device,
command: TextCommand,
- onStart: suspend () -> Unit,
- onComplete: suspend () -> Unit,
- onFailed: suspend () -> Unit,
- )
-
- suspend fun sendUserInputText(
- device: Device,
- text: String,
+ option: TextCommand.Option,
onStart: suspend () -> Unit,
onComplete: suspend () -> Unit,
onFailed: suspend () -> Unit,
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/SettingFileCreator.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/SettingFileCreator.kt
index 98e2b57e..c34a3364 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/SettingFileCreator.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/SettingFileCreator.kt
@@ -6,15 +6,13 @@ import jp.kaleidot725.adbpad.domain.model.setting.Appearance
import jp.kaleidot725.adbpad.domain.model.setting.SdkPath
import jp.kaleidot725.adbpad.domain.model.setting.WindowSize
import kotlinx.serialization.Serializable
-import kotlinx.serialization.decodeFromString
-import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import java.io.IOException
object SettingFileCreator {
- fun save(setting: Setting): Boolean {
- return try {
+ fun save(setting: Setting): Boolean =
+ try {
createDir()
File(getFilePath()).outputStream().apply {
this.write(Json.encodeToString(setting).toByteArray())
@@ -24,16 +22,14 @@ object SettingFileCreator {
} catch (exception: IOException) {
false
}
- }
- fun load(): Setting {
- return try {
+ fun load(): Setting =
+ try {
val content = File(getFilePath()).readText()
Json.decodeFromString(string = content)
} catch (e: Exception) {
Setting()
}
- }
private fun getDirPath() = OSContext.resolveOSContext().directory
@@ -53,7 +49,6 @@ object SettingFileCreator {
val language: Language.Type = Language.Type.ENGLISH,
val appearance: Appearance = Appearance.LIGHT,
val sdkPath: SdkPath = SdkPath(),
- val inputTexts: List = emptyList(),
val windowSize: WindowSize = WindowSize.DEFAULT,
)
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/TextCommandFactory.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/TextCommandFactory.kt
index 2313a839..0e22ee87 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/TextCommandFactory.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/TextCommandFactory.kt
@@ -3,7 +3,8 @@ package jp.kaleidot725.adbpad.domain.service
import jp.kaleidot725.adbpad.domain.model.command.TextCommand
object TextCommandFactory {
- fun createNew(text: String): TextCommand {
- return TextCommand(text = text, isRunning = false)
- }
+ fun createNew(
+ title: String,
+ text: String,
+ ): TextCommand = TextCommand(title = title, text = text, isRunning = false)
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/TextCommandFileCreator.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/TextCommandFileCreator.kt
new file mode 100644
index 00000000..c046009b
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/service/TextCommandFileCreator.kt
@@ -0,0 +1,48 @@
+package jp.kaleidot725.adbpad.domain.service
+
+import jp.kaleidot725.adbpad.domain.model.command.TextCommand
+import jp.kaleidot725.adbpad.domain.model.os.OSContext
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
+import java.io.File
+import java.io.IOException
+
+object TextCommandFileCreator {
+ fun save(setting: TextCommandSetting): Boolean =
+ try {
+ createDir()
+ File(getFilePath()).outputStream().apply {
+ this.write(Json.encodeToString(setting).toByteArray())
+ this.close()
+ }
+ true
+ } catch (exception: IOException) {
+ false
+ }
+
+ fun load(): TextCommandSetting =
+ try {
+ val content = File(getFilePath()).readText()
+ Json.decodeFromString(string = content)
+ } catch (e: Exception) {
+ TextCommandSetting()
+ }
+
+ private fun getDirPath() = OSContext.resolveOSContext().directory
+
+ private fun getFilePath() = getDirPath() + "text_command.json"
+
+ private fun createDir() {
+ try {
+ val file = File(getDirPath())
+ if (!file.exists()) file.mkdir()
+ } catch (e: Exception) {
+ return
+ }
+ }
+
+ @Serializable
+ data class TextCommandSetting(
+ val values: List = emptyList(),
+ )
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/AddTextCommandUseCase.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/AddTextCommandUseCase.kt
index 07190353..4f28576b 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/AddTextCommandUseCase.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/AddTextCommandUseCase.kt
@@ -3,8 +3,11 @@ package jp.kaleidot725.adbpad.domain.usecase.text
import jp.kaleidot725.adbpad.domain.repository.TextCommandRepository
import jp.kaleidot725.adbpad.domain.service.TextCommandFactory
-class AddTextCommandUseCase(private val textCommandRepository: TextCommandRepository) {
- suspend operator fun invoke(text: String): Boolean {
- return textCommandRepository.addTextCommand(TextCommandFactory.createNew(text))
- }
+class AddTextCommandUseCase(
+ private val textCommandRepository: TextCommandRepository,
+) {
+ suspend operator fun invoke(
+ title: String,
+ text: String,
+ ): Boolean = textCommandRepository.addTextCommand(TextCommandFactory.createNew(title, text))
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/DeleteTextCommandUseCase.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/DeleteTextCommandUseCase.kt
deleted file mode 100644
index fb36e9c2..00000000
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/DeleteTextCommandUseCase.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package jp.kaleidot725.adbpad.domain.usecase.text
-
-import jp.kaleidot725.adbpad.domain.model.command.TextCommand
-import jp.kaleidot725.adbpad.domain.repository.TextCommandRepository
-
-class DeleteTextCommandUseCase(private val textCommandRepository: TextCommandRepository) {
- suspend operator fun invoke(command: TextCommand): Boolean {
- return textCommandRepository.removeTextCommand(command)
- }
-}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/ExecuteTextCommandUseCase.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/ExecuteTextCommandUseCase.kt
index 272fbf06..d32924ec 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/ExecuteTextCommandUseCase.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/ExecuteTextCommandUseCase.kt
@@ -10,6 +10,7 @@ class ExecuteTextCommandUseCase(
suspend operator fun invoke(
device: Device,
command: TextCommand,
+ option: TextCommand.Option,
onStart: suspend () -> Unit,
onFailed: suspend () -> Unit,
onComplete: suspend () -> Unit,
@@ -17,6 +18,7 @@ class ExecuteTextCommandUseCase(
textCommandRepository.sendCommand(
device = device,
command = command,
+ option = option,
onStart = {
onStart()
},
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/SendTabCommandUseCase.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/SendTabCommandUseCase.kt
deleted file mode 100644
index 560026fe..00000000
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/SendTabCommandUseCase.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package jp.kaleidot725.adbpad.domain.usecase.text
-
-import jp.kaleidot725.adbpad.domain.model.device.Device
-import jp.kaleidot725.adbpad.domain.repository.KeyCommandRepository
-
-class SendTabCommandUseCase(
- private val keyCommandRepository: KeyCommandRepository,
-) {
- suspend operator fun invoke(
- device: Device,
- onStart: suspend () -> Unit,
- onFailed: suspend () -> Unit,
- onComplete: suspend () -> Unit,
- ) {
- val tabKeyCode = 61
- keyCommandRepository.sendKeyCommand(
- device = device,
- keycode = tabKeyCode,
- onStart = {
- onStart()
- },
- onFailed = {
- onFailed()
- },
- onComplete = {
- onComplete()
- },
- )
- }
-}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/SendUserInputTextCommandUseCase.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/SendUserInputTextCommandUseCase.kt
deleted file mode 100644
index a87b3d11..00000000
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/domain/usecase/text/SendUserInputTextCommandUseCase.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package jp.kaleidot725.adbpad.domain.usecase.text
-
-import jp.kaleidot725.adbpad.domain.model.device.Device
-import jp.kaleidot725.adbpad.domain.repository.TextCommandRepository
-
-class SendUserInputTextCommandUseCase(
- private val textCommandRepository: TextCommandRepository,
-) {
- suspend operator fun invoke(
- device: Device,
- text: String,
- onStart: suspend () -> Unit,
- onFailed: suspend () -> Unit,
- onComplete: suspend () -> Unit,
- ) {
- textCommandRepository.sendUserInputText(
- device = device,
- text = text,
- onStart = {
- onStart()
- },
- onFailed = {
- onFailed()
- },
- onComplete = {
- onComplete()
- },
- )
- }
-}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/di/RepositoryModule.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/di/RepositoryModule.kt
index 37a59964..e0ce15f2 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/di/RepositoryModule.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/di/RepositoryModule.kt
@@ -2,14 +2,12 @@ package jp.kaleidot725.adbpad.repository.di
import jp.kaleidot725.adbpad.domain.repository.DeviceControlCommandRepository
import jp.kaleidot725.adbpad.domain.repository.DeviceRepository
-import jp.kaleidot725.adbpad.domain.repository.KeyCommandRepository
import jp.kaleidot725.adbpad.domain.repository.NormalCommandRepository
import jp.kaleidot725.adbpad.domain.repository.ScreenshotCommandRepository
import jp.kaleidot725.adbpad.domain.repository.SettingRepository
import jp.kaleidot725.adbpad.domain.repository.TextCommandRepository
import jp.kaleidot725.adbpad.repository.impl.DeviceControlCommandRepositoryImpl
import jp.kaleidot725.adbpad.repository.impl.DeviceRepositoryImpl
-import jp.kaleidot725.adbpad.repository.impl.KeyCommandRepositoryImpl
import jp.kaleidot725.adbpad.repository.impl.NormalCommandRepositoryImpl
import jp.kaleidot725.adbpad.repository.impl.ScreenshotCommandRepositoryImpl
import jp.kaleidot725.adbpad.repository.impl.SettingRepositoryImpl
@@ -37,9 +35,6 @@ val repositoryModule =
factory {
VersionRepository()
}
- factory {
- KeyCommandRepositoryImpl()
- }
factory {
DeviceControlCommandRepositoryImpl()
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/impl/KeyCommandRepositoryImpl.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/impl/KeyCommandRepositoryImpl.kt
deleted file mode 100644
index 39d1bfe3..00000000
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/impl/KeyCommandRepositoryImpl.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package jp.kaleidot725.adbpad.repository.impl
-
-import com.malinskiy.adam.AndroidDebugBridgeClientFactory
-import jp.kaleidot725.adbpad.domain.model.command.KeyCommand
-import jp.kaleidot725.adbpad.domain.model.device.Device
-import jp.kaleidot725.adbpad.domain.repository.KeyCommandRepository
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-
-class KeyCommandRepositoryImpl : KeyCommandRepository {
- private val adbClient = AndroidDebugBridgeClientFactory().build()
-
- override suspend fun sendKeyCommand(
- device: Device,
- keycode: Int,
- onStart: suspend () -> Unit,
- onComplete: suspend () -> Unit,
- onFailed: suspend () -> Unit,
- ) {
- withContext(Dispatchers.IO) {
- val command = KeyCommand(keycode)
- command.requests.forEach { request ->
- val result = adbClient.execute(request, device.serial)
- if (result.exitCode != 0) {
- onFailed()
- return@withContext
- }
- }
-
- onComplete()
- }
- }
-}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/impl/TextCommandRepositoryImpl.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/impl/TextCommandRepositoryImpl.kt
index 94afd0d8..94ebeb99 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/impl/TextCommandRepositoryImpl.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/repository/impl/TextCommandRepositoryImpl.kt
@@ -1,10 +1,11 @@
package jp.kaleidot725.adbpad.repository.impl
import com.malinskiy.adam.AndroidDebugBridgeClientFactory
+import jp.kaleidot725.adbpad.domain.model.command.KeyCommand
import jp.kaleidot725.adbpad.domain.model.command.TextCommand
import jp.kaleidot725.adbpad.domain.model.device.Device
import jp.kaleidot725.adbpad.domain.repository.TextCommandRepository
-import jp.kaleidot725.adbpad.domain.service.SettingFileCreator
+import jp.kaleidot725.adbpad.domain.service.TextCommandFileCreator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
@@ -12,85 +13,124 @@ import kotlinx.coroutines.withContext
class TextCommandRepositoryImpl : TextCommandRepository {
private val runningCommands: MutableSet = mutableSetOf()
private val adbClient = AndroidDebugBridgeClientFactory().build()
+ private val lock: Any = Any()
override suspend fun getAllTextCommand(): List {
return withContext(Dispatchers.IO) {
- val setting = SettingFileCreator.load()
- return@withContext setting.inputTexts.map { text ->
- TextCommand(text = text, isRunning = runningCommands.any { it.text == text })
+ synchronized(lock) {
+ val setting = TextCommandFileCreator.load()
+ return@withContext setting.values.map { text ->
+ text.copy(isRunning = runningCommands.any { it.id == text.id })
+ }
}
}
}
override suspend fun addTextCommand(command: TextCommand): Boolean {
return withContext(Dispatchers.IO) {
- val oldSetting = SettingFileCreator.load()
- if (oldSetting.inputTexts.any { it == command.text }) return@withContext true
-
- val newInputTexts = oldSetting.inputTexts.toMutableList().apply { add(command.text) }
- val newSetting = oldSetting.copy(inputTexts = newInputTexts)
- return@withContext SettingFileCreator.save(newSetting)
+ synchronized(lock) {
+ val oldSetting = TextCommandFileCreator.load()
+ val newInputTexts = oldSetting.values.toMutableList().apply { add(command) }
+ val newSetting = oldSetting.copy(values = newInputTexts)
+ return@withContext TextCommandFileCreator.save(newSetting)
+ }
}
}
override suspend fun removeTextCommand(command: TextCommand): Boolean {
return withContext(Dispatchers.IO) {
- val oldSetting = SettingFileCreator.load()
- val newInputTexts = oldSetting.inputTexts.toMutableList().apply { remove(command.text) }
- val newSetting = oldSetting.copy(inputTexts = newInputTexts)
- return@withContext SettingFileCreator.save(newSetting)
+ synchronized(lock) {
+ val oldSetting = TextCommandFileCreator.load()
+ val newInputTexts = oldSetting.values.toMutableList().apply { remove(command) }
+ val newSetting = oldSetting.copy(values = newInputTexts)
+ return@withContext TextCommandFileCreator.save(newSetting)
+ }
}
}
- override suspend fun sendCommand(
- device: Device,
- command: TextCommand,
- onStart: suspend () -> Unit,
- onComplete: suspend () -> Unit,
- onFailed: suspend () -> Unit,
- ) {
- withContext(Dispatchers.IO) {
- runningCommands.add(command)
- onStart()
-
- delay(300)
+ override suspend fun updateTextCommandTitle(
+ id: String,
+ title: String,
+ ): Boolean {
+ return withContext(Dispatchers.IO) {
+ synchronized(lock) {
+ val oldSetting = TextCommandFileCreator.load()
+ val targetIndex = oldSetting.values.indexOfFirst { it.id == id }
+ val target = oldSetting.values.getOrNull(targetIndex) ?: return@withContext false
+ val newTarget = target.copy(title = title)
+ val newCommands = oldSetting.values.toMutableList()
+ newCommands.remove(target)
+ newCommands.add(targetIndex, newTarget)
- command.requests.forEach { request ->
- val result = adbClient.execute(request, device.serial)
- if (result.exitCode != 0) {
- runningCommands.remove(command)
- onFailed()
- return@withContext
- }
+ val newSetting = oldSetting.copy(values = newCommands)
+ return@withContext TextCommandFileCreator.save(newSetting)
}
+ }
+ }
- runningCommands.remove(command)
- onComplete()
+ override suspend fun updateTextCommandValue(
+ id: String,
+ text: String,
+ ): Boolean {
+ return withContext(Dispatchers.IO) {
+ synchronized(lock) {
+ val oldSetting = TextCommandFileCreator.load()
+ val targetIndex = oldSetting.values.indexOfFirst { it.id == id }
+ val target = oldSetting.values.getOrNull(targetIndex) ?: return@withContext false
+ val newTarget = target.copy(text = text)
+ val newCommands = oldSetting.values.toMutableList()
+ newCommands.remove(target)
+ newCommands.add(targetIndex, newTarget)
+
+ val newSetting = oldSetting.copy(values = newCommands)
+ return@withContext TextCommandFileCreator.save(newSetting)
+ }
}
}
- override suspend fun sendUserInputText(
+ override suspend fun sendCommand(
device: Device,
- text: String,
+ command: TextCommand,
+ option: TextCommand.Option,
onStart: suspend () -> Unit,
onComplete: suspend () -> Unit,
onFailed: suspend () -> Unit,
) {
withContext(Dispatchers.IO) {
+ runningCommands.add(command)
onStart()
delay(300)
- val command = TextCommand(text)
- command.requests.forEach { request ->
- val result = adbClient.execute(request, device.serial)
- if (result.exitCode != 0) {
- runningCommands.remove(command)
- onFailed()
- return@withContext
+ command.requests.forEachIndexed { index, request ->
+ if (request.cmd.isNotEmpty()) {
+ val result = adbClient.execute(request, device.serial)
+ if (result.exitCode != 0) {
+ runningCommands.remove(command)
+ onFailed()
+ return@withContext
+ }
+ }
+
+ if (command.requests.lastIndex != index && TextCommand.Option.SendWithNewLine != option) {
+ val keyCode =
+ when (option) {
+ TextCommand.Option.SendWithTab -> 61
+ TextCommand.Option.SendWithNewLine -> 66
+ }
+
+ val keyCommand = KeyCommand(keyCode)
+ keyCommand.requests.forEach { keyRequest ->
+ val keyResult = adbClient.execute(keyRequest, device.serial)
+ if (keyResult.exitCode != 0) {
+ onFailed()
+ return@withContext
+ }
+ }
}
}
+ runningCommands.remove(command)
onComplete()
}
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/common/dummy/TextCommandDummy.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/common/dummy/TextCommandDummy.kt
new file mode 100644
index 00000000..c2e164e8
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/common/dummy/TextCommandDummy.kt
@@ -0,0 +1,27 @@
+package jp.kaleidot725.adbpad.ui.common.dummy
+
+import jp.kaleidot725.adbpad.domain.model.command.TextCommand
+
+object TextCommandDummy {
+ val value =
+ TextCommand(
+ title = "TITLE",
+ text = "TEXT",
+ )
+
+ val values =
+ listOf(
+ TextCommand(
+ title = "TITLE",
+ text = "TEXT",
+ ),
+ TextCommand(
+ title = "TITLE",
+ text = "TEXT",
+ ),
+ TextCommand(
+ title = "TITLE",
+ text = "TEXT",
+ ),
+ )
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/component/DefaultTextField.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/component/DefaultTextField.kt
new file mode 100644
index 00000000..e7e562a8
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/component/DefaultTextField.kt
@@ -0,0 +1,62 @@
+package jp.kaleidot725.adbpad.ui.component
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.sp
+
+@Composable
+fun DefaultTextField(
+ id: String = "",
+ initialText: String,
+ placeHolder: String,
+ onUpdateText: (String) -> Unit,
+ maxLines: Int = 1,
+ modifier: Modifier = Modifier,
+) {
+ var localText by remember(id) { mutableStateOf(initialText) }
+
+ Box(modifier) {
+ if (localText.isEmpty()) {
+ Text(
+ text = placeHolder,
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.5f),
+ fontSize = 16.sp,
+ modifier = Modifier.align(Alignment.CenterStart),
+ )
+ }
+
+ BasicTextField(
+ value = localText,
+ onValueChange = {
+ localText = it
+ onUpdateText(it)
+ },
+ maxLines = maxLines,
+ textStyle = TextStyle(color = MaterialTheme.colors.onSurface, fontSize = 16.sp),
+ cursorBrush = SolidColor(MaterialTheme.colors.onSurface),
+ modifier = Modifier.align(Alignment.CenterStart),
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ DefaultTextField(
+ initialText = "",
+ placeHolder = "Search",
+ onUpdateText = {},
+ )
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/di/StateHolderModule.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/di/StateHolderModule.kt
index 43dc34aa..6898f175 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/di/StateHolderModule.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/di/StateHolderModule.kt
@@ -20,13 +20,10 @@ val stateHolderModule =
factory {
TextCommandStateHolder(
- addTextCommandUseCase = get(),
- deleteTextCommandUseCase = get(),
+ textCommandRepository = get(),
getTextCommandUseCase = get(),
executeTextCommandUseCase = get(),
getSelectedDeviceFlowUseCase = get(),
- sendUserInputTextCommandUseCase = get(),
- sendTabCommandUseCase = get(),
)
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotAction.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotAction.kt
new file mode 100644
index 00000000..83fa8ca4
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotAction.kt
@@ -0,0 +1,29 @@
+package jp.kaleidot725.adbpad.ui.screen.screenshot
+
+import jp.kaleidot725.adbpad.core.mvi.MVIAction
+import jp.kaleidot725.adbpad.domain.model.command.ScreenshotCommand
+import jp.kaleidot725.adbpad.domain.model.screenshot.Screenshot
+
+sealed class ScreenshotAction : MVIAction {
+ data class UpdateSearchText(
+ val text: String,
+ ) : ScreenshotAction()
+
+ data class TakeScreenshot(
+ val command: ScreenshotCommand,
+ ) : ScreenshotAction()
+
+ data object OpenDirectory : ScreenshotAction()
+
+ data object CopyScreenshotToClipboard : ScreenshotAction()
+
+ data object DeleteScreenshotToClipboard : ScreenshotAction()
+
+ data class SelectScreenshot(
+ val screenshot: Screenshot,
+ ) : ScreenshotAction()
+
+ data object NextScreenshot : ScreenshotAction()
+
+ data object PreviousScreenshot : ScreenshotAction()
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotScreen.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotScreen.kt
index a6d4058a..3be0aedb 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotScreen.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotScreen.kt
@@ -9,8 +9,10 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.Divider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -20,7 +22,9 @@ 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.common.resource.defaultBorder
import jp.kaleidot725.adbpad.ui.screen.screenshot.component.ScreenshotExplorer
+import jp.kaleidot725.adbpad.ui.screen.screenshot.component.ScreenshotHeader
import jp.kaleidot725.adbpad.ui.screen.screenshot.component.ScreenshotMenu
import jp.kaleidot725.adbpad.ui.screen.screenshot.component.ScreenshotViewer
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
@@ -29,7 +33,7 @@ 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)))
+fun Modifier.cursorForHorizontalResize(): Modifier = pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
@OptIn(ExperimentalSplitPaneApi::class)
@Composable
@@ -40,6 +44,7 @@ fun ScreenshotScreen(
canCapture: Boolean,
isCapturing: Boolean,
commands: List,
+ searchText: String,
onOpenDirectory: () -> Unit,
onCopyScreenshot: () -> Unit,
onDeleteScreenshot: () -> Unit,
@@ -47,6 +52,7 @@ fun ScreenshotScreen(
onSelectScreenshot: (Screenshot) -> Unit,
onNextScreenshot: () -> Unit,
onPreviousScreenshot: () -> Unit,
+ onUpdateSearchText: (String) -> Unit,
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
@@ -60,14 +66,24 @@ fun ScreenshotScreen(
.weight(1.0f),
) {
first(minSize = 350.dp) {
- ScreenshotExplorer(
- selectedScreenshot = screenshot,
- screenshots = screenshots,
- onSelectScreenShot = onSelectScreenshot,
- onNextScreenshot = onNextScreenshot,
- onPreviousScreenshot = onPreviousScreenshot,
- modifier = Modifier.fillMaxSize(),
- )
+ Column {
+ ScreenshotHeader(
+ searchText = searchText,
+ onUpdateSearchText = onUpdateSearchText,
+ modifier = Modifier,
+ )
+
+ Divider(modifier = Modifier.height(1.dp).fillMaxWidth().defaultBorder())
+
+ ScreenshotExplorer(
+ selectedScreenshot = screenshot,
+ screenshots = screenshots,
+ onSelectScreenShot = onSelectScreenshot,
+ onNextScreenshot = onNextScreenshot,
+ onPreviousScreenshot = onPreviousScreenshot,
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
}
second {
@@ -130,6 +146,7 @@ private fun ScreenshotScreen_Preview() {
canCapture = true,
isCapturing = false,
commands = emptyList(),
+ searchText = "",
onOpenDirectory = {},
onCopyScreenshot = {},
onDeleteScreenshot = {},
@@ -137,5 +154,6 @@ private fun ScreenshotScreen_Preview() {
onSelectScreenshot = {},
onNextScreenshot = {},
onPreviousScreenshot = {},
+ onUpdateSearchText = {},
)
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotSideEffect.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotSideEffect.kt
new file mode 100644
index 00000000..e1ed5575
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotSideEffect.kt
@@ -0,0 +1,5 @@
+package jp.kaleidot725.adbpad.ui.screen.screenshot
+
+import jp.kaleidot725.adbpad.core.mvi.MVISideEffect
+
+sealed class ScreenshotSideEffect : MVISideEffect
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotState.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotState.kt
index cb6f074b..d3a39dc6 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotState.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotState.kt
@@ -1,15 +1,17 @@
package jp.kaleidot725.adbpad.ui.screen.screenshot
+import jp.kaleidot725.adbpad.core.mvi.MVIState
import jp.kaleidot725.adbpad.domain.model.command.ScreenshotCommand
import jp.kaleidot725.adbpad.domain.model.device.Device
import jp.kaleidot725.adbpad.domain.model.screenshot.Screenshot
data class ScreenshotState(
+ val searchText: String = "",
val preview: Screenshot = Screenshot(null),
val previews: List = emptyList(),
val commands: List = emptyList(),
val selectedDevice: Device? = null,
val isCapturing: Boolean = false,
-) {
+) : MVIState {
val canExecute: Boolean = selectedDevice != null
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotStateHolder.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotStateHolder.kt
index a8203bb9..4b56fd73 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotStateHolder.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/ScreenshotStateHolder.kt
@@ -1,7 +1,8 @@
package jp.kaleidot725.adbpad.ui.screen.screenshot
+import jp.kaleidot725.adbpad.core.mvi.MVI
+import jp.kaleidot725.adbpad.core.mvi.mvi
import jp.kaleidot725.adbpad.domain.model.command.ScreenshotCommand
-import jp.kaleidot725.adbpad.domain.model.device.Device
import jp.kaleidot725.adbpad.domain.model.os.OSContext
import jp.kaleidot725.adbpad.domain.model.screenshot.Screenshot
import jp.kaleidot725.adbpad.domain.repository.ScreenshotCommandRepository
@@ -9,17 +10,11 @@ import jp.kaleidot725.adbpad.domain.usecase.device.GetSelectedDeviceFlowUseCase
import jp.kaleidot725.adbpad.domain.usecase.screenshot.GetScreenshotCommandUseCase
import jp.kaleidot725.adbpad.domain.usecase.screenshot.TakeScreenshotUseCase
import jp.kaleidot725.adbpad.domain.utils.ClipBoardUtils
-import jp.kaleidot725.adbpad.ui.common.ChildStateHolder
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.awt.Desktop
import java.io.File
@@ -28,110 +23,145 @@ class ScreenshotStateHolder(
private val getScreenshotCommandUseCase: GetScreenshotCommandUseCase,
private val getSelectedDeviceFlowUseCase: GetSelectedDeviceFlowUseCase,
private val screenshotCommandRepository: ScreenshotCommandRepository,
-) : ChildStateHolder {
- private val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main + Dispatchers.IO)
- private val commands: MutableStateFlow> = MutableStateFlow(emptyList())
- private val preview: MutableStateFlow = MutableStateFlow(Screenshot(null))
- private val previews: MutableStateFlow> = MutableStateFlow(emptyList())
- private val isCapturing: MutableStateFlow = MutableStateFlow(false)
- private val selectedDevice: StateFlow =
- getSelectedDeviceFlowUseCase()
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
-
- override val state: StateFlow =
- combine(
- preview,
- previews,
- commands,
- selectedDevice,
- isCapturing,
- ) { preview, previews, commands, selectedDevice, isCapturing ->
- ScreenshotState(preview, previews, commands, selectedDevice, isCapturing)
- }.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), ScreenshotState())
-
- override fun setup() {
+) : MVI by mvi(initialUiState = ScreenshotState()) {
+ override fun onSetup() {
coroutineScope.launch {
- commands.value = getScreenshotCommandUseCase()
+ val commands = getScreenshotCommandUseCase()
+ update { copy(commands = commands) }
+
initPreviews()
}
+
+ coroutineScope.launch {
+ getSelectedDeviceFlowUseCase().collectLatest {
+ update { copy(selectedDevice = it) }
+ }
+ }
}
- override fun refresh() {
+ override fun onRefresh() {
coroutineScope.launch {
- commands.value = getScreenshotCommandUseCase()
+ val commands = getScreenshotCommandUseCase()
+ update { copy(commands = commands) }
+
initPreviews()
}
}
- override fun dispose() {
+ override fun onDispose() {
coroutineScope.cancel()
}
- fun takeScreenShot(command: ScreenshotCommand) {
- val selectedDevice = state.value.selectedDevice ?: return
+ override fun onAction(uiAction: ScreenshotAction) {
coroutineScope.launch {
- takeScreenshotUseCase(
- device = selectedDevice,
- command = command,
- onStart = {
- commands.value = getScreenshotCommandUseCase()
- preview.value = Screenshot.EMPTY
- isCapturing.value = true
- },
- onFailed = {
- commands.value = getScreenshotCommandUseCase()
- preview.value = Screenshot.EMPTY
- isCapturing.value = false
- },
- onComplete = {
- commands.value = getScreenshotCommandUseCase()
- preview.value = it
- previews.value = screenshotCommandRepository.getScreenshots()
- isCapturing.value = false
- },
- )
+ when (uiAction) {
+ is ScreenshotAction.TakeScreenshot -> takeScreenShot(uiAction.command)
+ ScreenshotAction.OpenDirectory -> openDirectory()
+ ScreenshotAction.CopyScreenshotToClipboard -> copyScreenShotToClipboard()
+ ScreenshotAction.DeleteScreenshotToClipboard -> deleteScreenShotToClipboard()
+ is ScreenshotAction.SelectScreenshot -> selectScreenshot(uiAction.screenshot)
+ ScreenshotAction.NextScreenshot -> nextScreenshot()
+ ScreenshotAction.PreviousScreenshot -> previousScreenshot()
+ is ScreenshotAction.UpdateSearchText -> updateSearchText(uiAction.text)
+ }
}
}
- fun openDirectory() {
- coroutineScope.launch {
- val file = File(OSContext.resolveOSContext().screenshotDirectory)
- Desktop.getDesktop().open(file)
+ private suspend fun updateSearchText(searchText: String) {
+ val screenshots = screenshotCommandRepository.getScreenshots()
+ update {
+ copy(
+ searchText = searchText,
+ previews = screenshots.filter { it.file?.name?.startsWith(searchText) ?: false },
+ )
}
}
- fun copyScreenShotToClipboard() {
- coroutineScope.launch {
- val file = preview.value.file ?: return@launch
- ClipBoardUtils.copyFile(file)
- }
+ private suspend fun takeScreenShot(command: ScreenshotCommand) {
+ val selectedDevice = state.value.selectedDevice ?: return
+ takeScreenshotUseCase(
+ device = selectedDevice,
+ command = command,
+ onStart = {
+ val commands = getScreenshotCommandUseCase()
+ update {
+ copy(
+ commands = commands,
+ preview = Screenshot.EMPTY,
+ isCapturing = true,
+ )
+ }
+ },
+ onFailed = {
+ val commands = getScreenshotCommandUseCase()
+ update {
+ copy(
+ commands = commands,
+ preview = Screenshot.EMPTY,
+ isCapturing = false,
+ )
+ }
+ },
+ onComplete = {
+ val commands = getScreenshotCommandUseCase()
+ val screenshots = screenshotCommandRepository.getScreenshots()
+ update {
+ copy(
+ commands = commands,
+ preview = it,
+ previews = screenshots,
+ isCapturing = false,
+ )
+ }
+ },
+ )
}
- fun deleteScreenShotToClipboard() {
- coroutineScope.launch {
- screenshotCommandRepository.delete(preview.value)
- initPreviews()
- }
+ private suspend fun openDirectory() {
+ val file = File(OSContext.resolveOSContext().screenshotDirectory)
+ withContext(Dispatchers.IO) { Desktop.getDesktop().open(file) }
+ }
+
+ private fun copyScreenShotToClipboard() {
+ val file = currentState.preview.file ?: return
+ ClipBoardUtils.copyFile(file)
+ }
+
+ private suspend fun deleteScreenShotToClipboard() {
+ screenshotCommandRepository.delete(currentState.preview)
+ initPreviews()
}
- fun selectScreenshot(screenshot: Screenshot) {
- preview.value = screenshot
+ private fun selectScreenshot(screenshot: Screenshot) {
+ update {
+ this.copy(preview = screenshot)
+ }
}
- fun nextScreenshot() {
- val nextIndex = previews.value.indexOf(preview.value) + 1
- val nextPreview = previews.value.getOrNull(nextIndex) ?: return
- preview.value = nextPreview
+ private fun nextScreenshot() {
+ val nextIndex = currentState.previews.indexOf(currentState.preview) + 1
+ val nextPreview = currentState.previews.getOrNull(nextIndex) ?: return
+ update {
+ this.copy(preview = nextPreview)
+ }
}
- fun previousScreenshot() {
- val previousIndex = previews.value.indexOf(preview.value) - 1
- val previousPreview = previews.value.getOrNull(previousIndex) ?: return
- preview.value = previousPreview
+ private fun previousScreenshot() {
+ val previousIndex = currentState.previews.indexOf(currentState.preview) - 1
+ val previousPreview = currentState.previews.getOrNull(previousIndex) ?: return
+ update {
+ this.copy(preview = previousPreview)
+ }
}
private suspend fun initPreviews() {
- previews.value = screenshotCommandRepository.getScreenshots()
- preview.value = previews.value.firstOrNull() ?: Screenshot(null)
+ val screenshots = screenshotCommandRepository.getScreenshots()
+ val screenshot = screenshots.first()
+ update {
+ this.copy(
+ previews = screenshots,
+ preview = screenshot,
+ )
+ }
}
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotActions.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotActions.kt
new file mode 100644
index 00000000..7ec6737f
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotActions.kt
@@ -0,0 +1,105 @@
+package jp.kaleidot725.adbpad.ui.screen.screenshot.component
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.FileCopy
+import androidx.compose.material.icons.filled.FileOpen
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun ScreenshotActions(
+ enabled: Boolean,
+ onOpen: () -> Unit,
+ onCopy: () -> Unit,
+ onDelete: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ modifier = modifier,
+ ) {
+ IconButton(
+ onClick = onOpen,
+ enabled = enabled,
+ modifier =
+ Modifier
+ .padding(vertical = 4.dp)
+ .size(32.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .align(Alignment.CenterVertically),
+ ) {
+ Icon(
+ imageVector = Icons.Default.FileOpen,
+ contentDescription = "copy",
+ modifier = Modifier.height(20.dp),
+ )
+ }
+
+ IconButton(
+ onClick = onCopy,
+ enabled = enabled,
+ modifier =
+ Modifier
+ .padding(vertical = 4.dp)
+ .size(32.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .align(Alignment.CenterVertically),
+ ) {
+ Icon(
+ imageVector = Icons.Default.FileCopy,
+ contentDescription = "copy",
+ modifier = Modifier.height(20.dp),
+ )
+ }
+
+ IconButton(
+ onClick = onDelete,
+ enabled = enabled,
+ modifier =
+ Modifier
+ .padding(vertical = 4.dp)
+ .size(32.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .align(Alignment.CenterVertically),
+ ) {
+ Icon(
+ imageVector = Icons.Default.Delete,
+ contentDescription = "delete",
+ modifier = Modifier.height(20.dp),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun ScreenshotHeader_Preview() {
+ ScreenshotActions(
+ enabled = true,
+ onOpen = {},
+ onCopy = {},
+ onDelete = {},
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .background(Color.Gray),
+ )
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotExplorer.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotExplorer.kt
index f72b437e..2665e9de 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotExplorer.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotExplorer.kt
@@ -8,7 +8,6 @@ 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
@@ -25,7 +24,6 @@ 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
@@ -44,19 +42,23 @@ fun ScreenshotExplorer(
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
+ Modifier
+ .padding(4.dp)
+ .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
}
- else -> false
- }
- },
+ },
) {
items(
items = screenshots,
@@ -69,17 +71,10 @@ fun ScreenshotExplorer(
.clickableBackground(
isSelected = selectedScreenshot == screenshot,
shape = RoundedCornerShape(4.dp),
- )
- .clickable { onSelectScreenShot(screenshot) }
+ ).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 ?: "",
)
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotHeader.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotHeader.kt
index 1a4cb1b6..15125f43 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotHeader.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotHeader.kt
@@ -1,105 +1,45 @@
package jp.kaleidot725.adbpad.ui.screen.screenshot.component
import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.FileCopy
-import androidx.compose.material.icons.filled.FileOpen
+import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import jp.kaleidot725.adbpad.domain.model.language.Language
+import jp.kaleidot725.adbpad.ui.component.DefaultTextField
@Composable
fun ScreenshotHeader(
- enabled: Boolean,
- onOpen: () -> Unit,
- onCopy: () -> Unit,
- onDelete: () -> Unit,
+ searchText: String,
+ onUpdateSearchText: (String) -> Unit,
modifier: Modifier = Modifier,
) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(4.dp),
- modifier = modifier,
- ) {
- IconButton(
- onClick = onOpen,
- enabled = enabled,
- modifier =
- Modifier
- .padding(vertical = 4.dp)
- .size(32.dp)
- .clip(RoundedCornerShape(8.dp))
- .align(Alignment.CenterVertically),
- ) {
- Icon(
- imageVector = Icons.Default.FileOpen,
- contentDescription = "copy",
- modifier = Modifier.height(20.dp),
- )
- }
+ Row(modifier) {
+ Icon(
+ imageVector = Icons.Default.Search,
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.CenterVertically).padding(12.dp),
+ )
- IconButton(
- onClick = onCopy,
- enabled = enabled,
- modifier =
- Modifier
- .padding(vertical = 4.dp)
- .size(32.dp)
- .clip(RoundedCornerShape(8.dp))
- .align(Alignment.CenterVertically),
- ) {
- Icon(
- imageVector = Icons.Default.FileCopy,
- contentDescription = "copy",
- modifier = Modifier.height(20.dp),
- )
- }
-
- IconButton(
- onClick = onDelete,
- enabled = enabled,
- modifier =
- Modifier
- .padding(vertical = 4.dp)
- .size(32.dp)
- .clip(RoundedCornerShape(8.dp))
- .align(Alignment.CenterVertically),
- ) {
- Icon(
- imageVector = Icons.Default.Delete,
- contentDescription = "delete",
- modifier = Modifier.height(20.dp),
- )
- }
+ DefaultTextField(
+ initialText = searchText,
+ onUpdateText = onUpdateSearchText,
+ placeHolder = Language.search,
+ modifier = Modifier.align(Alignment.CenterVertically).weight(1.0f),
+ )
}
}
@Preview
@Composable
-private fun ScreenshotHeader_Preview() {
+private fun Preview() {
ScreenshotHeader(
- enabled = true,
- onOpen = {},
- onCopy = {},
- onDelete = {},
- modifier =
- Modifier
- .fillMaxWidth()
- .wrapContentHeight()
- .background(Color.Gray),
+ searchText = "TEST",
+ onUpdateSearchText = {},
)
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotViewer.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotViewer.kt
index 7ac0bd4d..fe1ce3c0 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotViewer.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/screenshot/component/ScreenshotViewer.kt
@@ -2,15 +2,22 @@ package jp.kaleidot725.adbpad.ui.screen.screenshot.component
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import jp.kaleidot725.adbpad.domain.model.language.Language
import jp.kaleidot725.adbpad.domain.model.screenshot.Screenshot
+import jp.kaleidot725.adbpad.ui.common.resource.defaultBorder
@Composable
fun ScreenshotViewer(
@@ -21,15 +28,17 @@ fun ScreenshotViewer(
onDeleteScreenshot: () -> Unit,
modifier: Modifier = Modifier,
) {
- Box(modifier) {
- ScreenshotHeader(
+ Column(modifier) {
+ ScreenshotActions(
enabled = screenshot.file != null,
onOpen = onOpenDirectory,
onCopy = onCopyScreenshot,
onDelete = onDeleteScreenshot,
- modifier = Modifier.align(Alignment.TopEnd),
+ modifier = Modifier.height(48.dp).padding(horizontal = 12.dp).align(Alignment.End),
)
+ Divider(modifier = Modifier.height(1.dp).fillMaxWidth().defaultBorder())
+
if (isCapturing) {
Box(modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandAction.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandAction.kt
new file mode 100644
index 00000000..e6ca2815
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandAction.kt
@@ -0,0 +1,38 @@
+package jp.kaleidot725.adbpad.ui.screen.text
+
+import jp.kaleidot725.adbpad.core.mvi.MVIAction
+import jp.kaleidot725.adbpad.domain.model.command.TextCommand
+
+sealed class TextCommandAction : MVIAction {
+ data class UpdateSearchText(
+ val text: String,
+ ) : TextCommandAction()
+
+ data object AddNewText : TextCommandAction()
+
+ data class UpdateCommandTitle(
+ val id: String,
+ val value: String,
+ ) : TextCommandAction()
+
+ data class UpdateCommandText(
+ val id: String,
+ val value: String,
+ ) : TextCommandAction()
+
+ data object SendTextCommand : TextCommandAction()
+
+ data object DeleteSelectedCommandText : TextCommandAction()
+
+ data object NextCommand : TextCommandAction()
+
+ data class SelectCommand(
+ val command: TextCommand,
+ ) : TextCommandAction()
+
+ data object PreviousCommand : TextCommandAction()
+
+ data class UpdateTextCommandOption(
+ val value: TextCommand.Option,
+ ) : TextCommandAction()
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandScreen.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandScreen.kt
index f99c234e..0271a217 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandScreen.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandScreen.kt
@@ -1,80 +1,123 @@
package jp.kaleidot725.adbpad.ui.screen.text
import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+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.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.Divider
+import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import jp.kaleidot725.adbpad.domain.model.command.TextCommand
-import jp.kaleidot725.adbpad.ui.screen.text.component.InputTextActionMenu
+import jp.kaleidot725.adbpad.domain.model.UserColor
+import jp.kaleidot725.adbpad.ui.common.resource.defaultBorder
+import jp.kaleidot725.adbpad.ui.screen.screenshot.cursorForHorizontalResize
+import jp.kaleidot725.adbpad.ui.screen.text.component.TextCommandActions
+import jp.kaleidot725.adbpad.ui.screen.text.component.TextCommandEditor
+import jp.kaleidot725.adbpad.ui.screen.text.component.TextCommandHeader
import jp.kaleidot725.adbpad.ui.screen.text.component.TextCommandList
+import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
+import org.jetbrains.compose.splitpane.HorizontalSplitPane
+import org.jetbrains.compose.splitpane.SplitPaneState
+import org.jetbrains.compose.splitpane.rememberSplitPaneState
+@OptIn(ExperimentalSplitPaneApi::class)
@Composable
fun TextCommandScreen(
- // InputText
- inputText: String,
- onTextChange: (String) -> Unit,
- isSendingInputText: Boolean,
- onSendInputText: () -> Unit,
- canSendInputText: Boolean,
- onSaveInputText: () -> Unit,
- canSaveInputText: Boolean,
- canSendTabKey: Boolean,
- onSendTabKey: () -> Unit,
- isSendingTab: Boolean,
- // Commands
- commands: List,
- onSendCommand: (TextCommand) -> Unit,
- canSendCommand: Boolean,
- onDeleteCommand: (TextCommand) -> Unit,
+ state: TextCommandState,
+ onAction: (TextCommandAction) -> Unit,
+ splitterState: SplitPaneState,
) {
- Box(modifier = Modifier.fillMaxSize().padding(16.dp)) {
- TextCommandList(
- commands = commands,
- onSend = onSendCommand,
- canSend = canSendCommand,
- onDelete = onDeleteCommand,
- modifier = Modifier.fillMaxSize().padding(bottom = 60.dp),
- )
+ HorizontalSplitPane(
+ splitPaneState = splitterState,
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ first(minSize = 350.dp) {
+ Column {
+ TextCommandHeader(
+ searchText = state.searchText,
+ onUpdateSearchText = { onAction(TextCommandAction.UpdateSearchText(it)) },
+ onAddNewTextCommand = { onAction(TextCommandAction.AddNewText) },
+ )
- InputTextActionMenu(
- inputText = inputText,
- onTextChange = onTextChange,
- isSending = isSendingInputText,
- onSend = onSendInputText,
- canSend = canSendInputText,
- onSendTab = onSendTabKey,
- isSendingTag = isSendingTab,
- canSendTab = canSendTabKey,
- onSave = onSaveInputText,
- canSave = canSaveInputText,
- modifier = Modifier.height(50.dp).fillMaxWidth().align(Alignment.BottomEnd),
- )
+ Divider(modifier = Modifier.fillMaxWidth().defaultBorder())
+
+ TextCommandList(
+ selectedCommand = state.selectedCommand,
+ commands = state.commands,
+ onSelectCommand = { onAction(TextCommandAction.SelectCommand(it)) },
+ onNextCommand = { onAction(TextCommandAction.NextCommand) },
+ onPreviousCommand = { onAction(TextCommandAction.PreviousCommand) },
+ modifier = Modifier.fillMaxSize().padding(top = 2.dp),
+ )
+ }
+ }
+
+ second {
+ Column(modifier = Modifier.background(MaterialTheme.colors.surface)) {
+ if (state.selectedCommand != null) {
+ TextCommandEditor(
+ command = state.selectedCommand,
+ onUpdateTitle = { id, title -> onAction(TextCommandAction.UpdateCommandTitle(id, title)) },
+ onUpdateText = { id, text -> onAction(TextCommandAction.UpdateCommandText(id, text)) },
+ onDelete = { onAction(TextCommandAction.DeleteSelectedCommandText) },
+ )
+
+ Spacer(
+ modifier = Modifier.weight(1.0f),
+ )
+
+ TextCommandActions(
+ command = state.selectedCommand,
+ canSend = state.canSend,
+ onSendText = { onAction(TextCommandAction.SendTextCommand) },
+ selectedOption = state.selectedTextCommandOption,
+ onUpdateTextCommandOption = { onAction(TextCommandAction.UpdateTextCommandOption(it)) },
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ }
+ }
+
+ 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)
@Preview
@Composable
private fun InputTextScreen_Preview() {
TextCommandScreen(
- inputText = "SAMPLE INPUT TEXT",
- onTextChange = {},
- isSendingInputText = false,
- onSendInputText = {},
- onSaveInputText = {},
- canSaveInputText = true,
- commands = listOf(TextCommand("TEST1"), TextCommand("TEST2")),
- onSendCommand = {},
- isSendingTab = false,
- canSendCommand = true,
- canSendInputText = true,
- onDeleteCommand = {},
- canSendTabKey = false,
- onSendTabKey = {},
+ state = TextCommandState(),
+ onAction = {},
+ splitterState = rememberSplitPaneState(),
)
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandSideEffect.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandSideEffect.kt
new file mode 100644
index 00000000..1cf1818d
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandSideEffect.kt
@@ -0,0 +1,5 @@
+package jp.kaleidot725.adbpad.ui.screen.text
+
+import jp.kaleidot725.adbpad.core.mvi.MVISideEffect
+
+sealed class TextCommandSideEffect : MVISideEffect
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandState.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandState.kt
index e0f15025..c28547de 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandState.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandState.kt
@@ -1,17 +1,19 @@
package jp.kaleidot725.adbpad.ui.screen.text
+import jp.kaleidot725.adbpad.core.mvi.MVIState
import jp.kaleidot725.adbpad.domain.model.command.TextCommand
import jp.kaleidot725.adbpad.domain.model.device.Device
data class TextCommandState(
+ val selectedCommandIndex: Int? = null,
val commands: List = emptyList(),
val userInputText: String = "",
val isSendingUserInputText: Boolean = false,
val selectedDevice: Device? = null,
val isSendingTab: Boolean = false,
-) {
- val canSendCommand: Boolean get() = selectedDevice != null
- val canSendInputText: Boolean get() = selectedDevice != null && userInputText.isNotEmpty()
- val canSaveInputText: Boolean get() = userInputText.isNotEmpty()
- val canSendTabKey: Boolean get() = selectedDevice != null
+ val searchText: String = "",
+ val selectedTextCommandOption: TextCommand.Option = TextCommand.Option.SendWithTab,
+) : MVIState {
+ val selectedCommand: TextCommand? = commands.getOrNull(selectedCommandIndex ?: 0)
+ val canSend: Boolean = selectedDevice != null
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandStateHolder.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandStateHolder.kt
index 07751535..91f4d5b4 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandStateHolder.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/TextCommandStateHolder.kt
@@ -1,127 +1,202 @@
package jp.kaleidot725.adbpad.ui.screen.text
+import jp.kaleidot725.adbpad.core.mvi.MVI
+import jp.kaleidot725.adbpad.core.mvi.mvi
import jp.kaleidot725.adbpad.domain.model.command.TextCommand
-import jp.kaleidot725.adbpad.domain.model.device.Device
+import jp.kaleidot725.adbpad.domain.repository.TextCommandRepository
import jp.kaleidot725.adbpad.domain.usecase.device.GetSelectedDeviceFlowUseCase
-import jp.kaleidot725.adbpad.domain.usecase.text.AddTextCommandUseCase
-import jp.kaleidot725.adbpad.domain.usecase.text.DeleteTextCommandUseCase
import jp.kaleidot725.adbpad.domain.usecase.text.ExecuteTextCommandUseCase
import jp.kaleidot725.adbpad.domain.usecase.text.GetTextCommandUseCase
-import jp.kaleidot725.adbpad.domain.usecase.text.SendTabCommandUseCase
-import jp.kaleidot725.adbpad.domain.usecase.text.SendUserInputTextCommandUseCase
-import jp.kaleidot725.adbpad.ui.common.ChildStateHolder
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class TextCommandStateHolder(
- private val addTextCommandUseCase: AddTextCommandUseCase,
- private val deleteTextCommandUseCase: DeleteTextCommandUseCase,
+ private val textCommandRepository: TextCommandRepository,
private val getTextCommandUseCase: GetTextCommandUseCase,
private val executeTextCommandUseCase: ExecuteTextCommandUseCase,
- private val sendUserInputTextCommandUseCase: SendUserInputTextCommandUseCase,
- private val sendTabCommandUseCase: SendTabCommandUseCase,
private val getSelectedDeviceFlowUseCase: GetSelectedDeviceFlowUseCase,
-) : ChildStateHolder {
- private val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main + Dispatchers.IO)
- private val commands: MutableStateFlow> = MutableStateFlow(emptyList())
- private val userInputText: MutableStateFlow = MutableStateFlow("")
- private val isSending: MutableStateFlow = MutableStateFlow(false)
- private val isSendingTag: MutableStateFlow = MutableStateFlow(false)
- private val selectedDevice: StateFlow =
- getSelectedDeviceFlowUseCase()
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
-
- override val state: StateFlow =
- combine(
- commands,
- userInputText,
- isSending,
- isSendingTag,
- selectedDevice,
- ) { inputTexts, userInputText, isSending, isSendingTag, selectedDevice ->
- TextCommandState(inputTexts, userInputText, isSending, selectedDevice, isSendingTag)
- }.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), TextCommandState())
-
- override fun setup() {
+) : MVI by mvi(initialUiState = TextCommandState()) {
+ override fun onSetup() {
coroutineScope.launch {
- commands.value = getTextCommandUseCase()
+ getSelectedDeviceFlowUseCase().collect {
+ update { this.copy(selectedDevice = it) }
+ }
+ }
+ coroutineScope.launch {
+ val commands = getTextCommandUseCase()
+ update { this.copy(commands = commands) }
}
}
- override fun refresh() {
+ override fun onRefresh() {
coroutineScope.launch {
- commands.value = getTextCommandUseCase()
+ val commands = getTextCommandUseCase()
+ update {
+ this.copy(commands = commands)
+ }
}
}
- override fun dispose() {
+ override fun onDispose() {
coroutineScope.cancel()
}
- private val ascii = (0..255).map { it.toChar() }
+ override fun onAction(uiAction: TextCommandAction) {
+ coroutineScope.launch {
+ when (uiAction) {
+ is TextCommandAction.DeleteSelectedCommandText -> {
+ deleteInputText()
+ }
+
+ is TextCommandAction.SendTextCommand -> {
+ sendTextCommand()
+ }
+
+ is TextCommandAction.NextCommand -> {
+ nextCommand()
+ }
+
+ is TextCommandAction.PreviousCommand -> {
+ previousCommand()
+ }
+
+ is TextCommandAction.SelectCommand -> {
+ selectCommand(uiAction.command)
+ }
+
+ TextCommandAction.AddNewText -> {
+ addNewTextCommand()
+ }
- fun updateInputText(text: String) {
- val isAscii = text.none { it !in ascii }
- if (isAscii) this.userInputText.value = text
+ is TextCommandAction.UpdateSearchText -> {
+ updateSearchText(uiAction.text)
+ }
+
+ is TextCommandAction.UpdateCommandText -> {
+ updateTextCommandValue(uiAction.id, uiAction.value)
+ }
+
+ is TextCommandAction.UpdateCommandTitle -> {
+ updateTextCommandTitle(uiAction.id, uiAction.value)
+ }
+
+ is TextCommandAction.UpdateTextCommandOption -> {
+ updateTextCommandOption(uiAction.value)
+ }
+ }
+ }
}
- fun sendTextCommand(command: TextCommand) {
- val selectedDevice = state.value.selectedDevice ?: return
- coroutineScope.launch {
- executeTextCommandUseCase(
- device = selectedDevice,
- command = command,
- onStart = { commands.value = getTextCommandUseCase() },
- onFailed = { commands.value = getTextCommandUseCase() },
- onComplete = { commands.value = getTextCommandUseCase() },
+ private suspend fun updateSearchText(searchText: String) {
+ val commands = getTextCommandUseCase()
+ update {
+ copy(
+ searchText = searchText,
+ commands = commands.filter { it.title.startsWith(searchText) },
)
}
}
- fun sendInputText() {
- val selectedDevice = state.value.selectedDevice ?: return
- coroutineScope.launch {
- sendUserInputTextCommandUseCase(
- device = selectedDevice,
- text = state.value.userInputText,
- onStart = { isSending.value = true },
- onFailed = { isSending.value = false },
- onComplete = { isSending.value = false },
+ private val ascii = (0..255).map { it.toChar() }
+
+ private suspend fun updateTextCommandValue(
+ id: String,
+ value: String,
+ ) {
+ val isAscii = value.none { it !in ascii }
+ if (isAscii) {
+ textCommandRepository.updateTextCommandValue(id, value)
+ val commands = getTextCommandUseCase()
+ update { copy(commands = commands) }
+ }
+ }
+
+ private suspend fun updateTextCommandTitle(
+ id: String,
+ value: String,
+ ) {
+ textCommandRepository.updateTextCommandTitle(id, value)
+ val commands = getTextCommandUseCase()
+ update { copy(commands = commands) }
+ }
+
+ private suspend fun sendTextCommand() {
+ val selectedDevice = currentState.selectedDevice ?: return
+ val selectedCommand = currentState.selectedCommand ?: return
+ val selectedOption = currentState.selectedTextCommandOption
+
+ executeTextCommandUseCase(
+ device = selectedDevice,
+ command = selectedCommand,
+ option = selectedOption,
+ onStart = {
+ val commands = getTextCommandUseCase()
+ update { copy(commands = commands) }
+ },
+ onFailed = {
+ val commands = getTextCommandUseCase()
+ update { copy(commands = commands) }
+ },
+ onComplete = {
+ val commands = getTextCommandUseCase()
+ update { copy(commands = commands) }
+ },
+ )
+ }
+
+ private suspend fun addNewTextCommand() {
+ val command =
+ TextCommand(
+ title = "",
+ text = "",
+ )
+ textCommandRepository.addTextCommand(command)
+ val commands = getTextCommandUseCase()
+ val commandIndex = commands.indexOf(command)
+ update {
+ copy(
+ commands = commands,
+ selectedCommandIndex = commandIndex,
)
}
}
- fun sendTabCommand() {
- val selectedDevice = state.value.selectedDevice ?: return
- coroutineScope.launch {
- sendTabCommandUseCase(
- device = selectedDevice,
- onStart = { isSendingTag.value = true },
- onFailed = { isSendingTag.value = false },
- onComplete = { isSendingTag.value = false },
+ private suspend fun deleteInputText() {
+ val selectedCommand = currentState.selectedCommand ?: return
+ val selectedCommandIndex = currentState.commands.indexOf(selectedCommand)
+ textCommandRepository.removeTextCommand(selectedCommand)
+
+ val commands = getTextCommandUseCase()
+ val newSelectedCommand = commands.getOrNull(selectedCommandIndex)
+ val newSelectedCommandIndex = if (newSelectedCommand == null) commands.lastIndex else selectedCommandIndex
+ update {
+ copy(
+ commands = commands,
+ selectedCommandIndex = newSelectedCommandIndex,
)
}
}
- fun saveInputText() {
- coroutineScope.launch {
- addTextCommandUseCase(state.value.userInputText)
- commands.value = getTextCommandUseCase()
+ private fun nextCommand() {
+ val nextIndex = currentState.commands.indexOf(currentState.selectedCommand) + 1
+ if (0 <= nextIndex && nextIndex <= currentState.commands.lastIndex) {
+ update { copy(selectedCommandIndex = nextIndex) }
}
}
- fun deleteInputText(command: TextCommand) {
- coroutineScope.launch {
- deleteTextCommandUseCase(command)
- commands.value = getTextCommandUseCase()
+ private fun previousCommand() {
+ val previousIndex = currentState.commands.indexOf(currentState.selectedCommand) - 1
+ if (0 <= previousIndex && previousIndex <= currentState.commands.lastIndex) {
+ update { copy(selectedCommandIndex = previousIndex) }
}
}
+
+ private fun selectCommand(command: TextCommand) {
+ val index = currentState.commands.indexOf(command)
+ update { copy(selectedCommandIndex = index) }
+ }
+
+ private fun updateTextCommandOption(value: TextCommand.Option) {
+ update { copy(selectedTextCommandOption = value) }
+ }
}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/InputTextActionMenu.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/InputTextActionMenu.kt
deleted file mode 100644
index ccd8ff91..00000000
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/InputTextActionMenu.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-package jp.kaleidot725.adbpad.ui.screen.text.component
-
-import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.KeyboardTab
-import androidx.compose.material.icons.automirrored.filled.Send
-import androidx.compose.material.icons.filled.Save
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.dp
-import jp.kaleidot725.adbpad.ui.common.resource.defaultBorder
-import jp.kaleidot725.adbpad.ui.component.RunningIndicator
-
-@Composable
-fun InputTextActionMenu(
- inputText: String,
- onTextChange: (String) -> Unit,
- onSend: () -> Unit,
- isSending: Boolean,
- canSendTab: Boolean,
- onSendTab: () -> Unit,
- isSendingTag: Boolean,
- canSend: Boolean,
- onSave: () -> Unit,
- canSave: Boolean,
- modifier: Modifier = Modifier,
-) {
- var text by remember { mutableStateOf(inputText) }
- Row(
- modifier =
- modifier
- .clip(RoundedCornerShape(4.dp))
- .defaultBorder()
- .padding(horizontal = 12.dp),
- horizontalArrangement = Arrangement.spacedBy(2.dp),
- ) {
- BasicTextField(
- value = text,
- onValueChange = {
- text = it
- onTextChange(it)
- },
- cursorBrush = SolidColor(MaterialTheme.colors.onBackground),
- textStyle = TextStyle(color = MaterialTheme.colors.onBackground),
- modifier =
- Modifier
- .weight(0.9f, true)
- .align(Alignment.CenterVertically),
- )
-
- IconButton(
- enabled = canSave,
- onClick = { onSave() },
- modifier = Modifier.fillMaxHeight(),
- ) {
- Icon(
- imageVector = Icons.Default.Save,
- contentDescription = "Save",
- )
- }
-
- IconButton(
- enabled = canSendTab,
- onClick = { onSendTab() },
- modifier = Modifier.fillMaxHeight(),
- ) {
- when (isSendingTag) {
- true -> RunningIndicator(color = MaterialTheme.colors.primary)
- else -> {
- Icon(
- imageVector = Icons.AutoMirrored.Default.KeyboardTab,
- contentDescription = "Save",
- )
- }
- }
- }
-
- IconButton(
- enabled = canSend,
- onClick = { onSend() },
- modifier = Modifier.fillMaxHeight(),
- ) {
- when (isSending) {
- true -> RunningIndicator(color = MaterialTheme.colors.primary)
- else -> {
- Icon(
- imageVector = Icons.AutoMirrored.Default.Send,
- contentDescription = "Save",
- )
- }
- }
- }
- }
-}
-
-@Preview
-@Composable
-private fun InputTextActionMenu_Preview() {
- InputTextActionMenu(
- inputText = "INPUT TEXT SAMPLE",
- onSend = {},
- isSending = false,
- canSend = true,
- canSendTab = false,
- onSendTab = {},
- onSave = {},
- canSave = true,
- onTextChange = {},
- isSendingTag = false,
- modifier = Modifier.height(50.dp),
- )
-}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandActions.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandActions.kt
new file mode 100644
index 00000000..9f78171b
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandActions.kt
@@ -0,0 +1,108 @@
+package jp.kaleidot725.adbpad.ui.screen.text.component
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import jp.kaleidot725.adbpad.domain.model.command.TextCommand
+import jp.kaleidot725.adbpad.domain.model.language.Language
+import jp.kaleidot725.adbpad.ui.common.dummy.TextCommandDummy
+
+@Composable
+fun TextCommandActions(
+ command: TextCommand,
+ canSend: Boolean,
+ onSendText: () -> Unit,
+ selectedOption: TextCommand.Option,
+ onUpdateTextCommandOption: (TextCommand.Option) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ var expanded by remember { mutableStateOf(false) }
+
+ Row(
+ modifier = modifier,
+ ) {
+ Spacer(
+ modifier = Modifier.weight(1.0f),
+ )
+
+ Box {
+ TextCommandButton(
+ selectedOption = selectedOption,
+ canSend = canSend,
+ isSending = command.isRunning,
+ onSend = onSendText,
+ onChangeOption = { expanded = true },
+ modifier = Modifier,
+ )
+
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ modifier = Modifier.width(250.dp),
+ ) {
+ TextCommand.Option.entries.forEach { option ->
+ DropdownMenuItem(
+ onClick = {
+ onUpdateTextCommandOption(option)
+ expanded = false
+ },
+ ) {
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ Box(modifier = Modifier.size(20.dp).align(Alignment.CenterVertically)) {
+ if (selectedOption == option) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "",
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+ }
+
+ Text(
+ text =
+ when (option) {
+ TextCommand.Option.SendWithTab -> Language.textCommandOptionTab
+ TextCommand.Option.SendWithNewLine -> Language.textCommandOptionNewLine
+ },
+ style = MaterialTheme.typography.subtitle2,
+ modifier = Modifier.align(Alignment.CenterVertically),
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ TextCommandActions(
+ command = TextCommandDummy.value,
+ canSend = true,
+ onSendText = {},
+ selectedOption = TextCommand.Option.SendWithTab,
+ onUpdateTextCommandOption = {},
+ )
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandButton.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandButton.kt
new file mode 100644
index 00000000..6be8c521
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandButton.kt
@@ -0,0 +1,111 @@
+package jp.kaleidot725.adbpad.ui.screen.text.component
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.ContentAlpha
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import jp.kaleidot725.adbpad.domain.model.command.TextCommand
+import jp.kaleidot725.adbpad.domain.model.language.Language
+import jp.kaleidot725.adbpad.ui.component.RunningIndicator
+
+@Composable
+fun TextCommandButton(
+ canSend: Boolean,
+ isSending: Boolean,
+ onSend: () -> Unit,
+ selectedOption: TextCommand.Option,
+ onChangeOption: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier = modifier) {
+ Row(
+ modifier =
+ Modifier
+ .padding(8.dp)
+ .width(250.dp)
+ .height(35.dp)
+ .alpha(if (canSend) 1f else ContentAlpha.disabled)
+ .background(MaterialTheme.colors.primary, RoundedCornerShape(4.dp)),
+ ) {
+ Box(
+ modifier =
+ Modifier
+ .fillMaxHeight()
+ .weight(0.8f)
+ .clickable(enabled = canSend) { if (!isSending) onSend() },
+ ) {
+ if (isSending) {
+ Box(Modifier.align(Alignment.Center)) { RunningIndicator() }
+ } else {
+ Text(
+ text =
+ when (selectedOption) {
+ TextCommand.Option.SendWithTab -> Language.textCommandOptionTab
+ TextCommand.Option.SendWithNewLine -> Language.textCommandOptionNewLine
+ },
+ color = MaterialTheme.colors.onPrimary,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+
+ Spacer(
+ modifier =
+ Modifier
+ .fillMaxHeight()
+ .width(1.dp)
+ .background(Color.Black.copy(alpha = 0.6f)),
+ )
+
+ Box(
+ modifier =
+ Modifier
+ .fillMaxHeight()
+ .width(50.dp)
+ .background(Color.Black.copy(alpha = 0.3f))
+ .clickable(enabled = canSend) { onChangeOption() },
+ ) {
+ Icon(
+ imageVector = Icons.Default.ArrowDropDown,
+ contentDescription = "",
+ tint = MaterialTheme.colors.onPrimary,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun ScreenshotButton_Preview() {
+ MaterialTheme {
+ TextCommandButton(
+ selectedOption = TextCommand.Option.SendWithTab,
+ canSend = true,
+ isSending = false,
+ onSend = {},
+ onChangeOption = {},
+ modifier = Modifier,
+ )
+ }
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandEditor.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandEditor.kt
new file mode 100644
index 00000000..4bee55bf
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandEditor.kt
@@ -0,0 +1,75 @@
+package jp.kaleidot725.adbpad.ui.screen.text.component
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Divider
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import jp.kaleidot725.adbpad.domain.model.command.TextCommand
+import jp.kaleidot725.adbpad.domain.model.language.Language
+import jp.kaleidot725.adbpad.ui.common.dummy.TextCommandDummy
+import jp.kaleidot725.adbpad.ui.common.resource.defaultBorder
+import jp.kaleidot725.adbpad.ui.component.DefaultTextField
+
+@Composable
+fun TextCommandEditor(
+ command: TextCommand,
+ onUpdateTitle: (id: String, value: String) -> Unit,
+ onUpdateText: (id: String, value: String) -> Unit,
+ onDelete: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Column(modifier) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ DefaultTextField(
+ id = command.id,
+ initialText = command.title,
+ placeHolder = Language.textCommandUnTitle,
+ onUpdateText = { onUpdateTitle(command.id, it) },
+ modifier = Modifier.weight(1.0f).height(48.dp).padding(horizontal = 12.dp),
+ )
+
+ IconButton(
+ onClick = onDelete,
+ ) {
+ Icon(
+ imageVector = Icons.Default.Delete,
+ contentDescription = "",
+ )
+ }
+ }
+
+ Divider(modifier = Modifier.height(1.dp).fillMaxWidth().defaultBorder())
+
+ DefaultTextField(
+ id = command.id,
+ initialText = command.text,
+ placeHolder = "",
+ maxLines = Int.MAX_VALUE,
+ onUpdateText = { onUpdateText(command.id, it) },
+ modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 12.dp),
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ TextCommandEditor(
+ command = TextCommandDummy.value,
+ onUpdateTitle = { _, _ -> },
+ onUpdateText = { _, _ -> },
+ onDelete = {},
+ )
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandHeader.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandHeader.kt
new file mode 100644
index 00000000..a2d22d10
--- /dev/null
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandHeader.kt
@@ -0,0 +1,55 @@
+package jp.kaleidot725.adbpad.ui.screen.text.component
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import jp.kaleidot725.adbpad.domain.model.language.Language
+import jp.kaleidot725.adbpad.ui.component.DefaultTextField
+
+@Composable
+fun TextCommandHeader(
+ searchText: String,
+ onUpdateSearchText: (String) -> Unit,
+ onAddNewTextCommand: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Row(modifier) {
+ Icon(
+ imageVector = Icons.Default.Search,
+ contentDescription = null,
+ modifier = Modifier.align(Alignment.CenterVertically).padding(12.dp),
+ )
+
+ DefaultTextField(
+ initialText = searchText,
+ onUpdateText = onUpdateSearchText,
+ placeHolder = Language.search,
+ modifier = Modifier.align(Alignment.CenterVertically).weight(1.0f),
+ )
+
+ IconButton(
+ onClick = onAddNewTextCommand,
+ ) {
+ Icon(imageVector = Icons.Default.Add, contentDescription = null)
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ TextCommandHeader(
+ searchText = "TEST",
+ onUpdateSearchText = {},
+ onAddNewTextCommand = {},
+ )
+}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandItem.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandItem.kt
deleted file mode 100644
index cf241f79..00000000
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandItem.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package jp.kaleidot725.adbpad.ui.screen.text.component
-
-import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.Button
-import androidx.compose.material.Card
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import jp.kaleidot725.adbpad.domain.model.language.Language
-import jp.kaleidot725.adbpad.ui.component.RunningIndicator
-
-@Composable
-fun TextCommandItem(
- text: String,
- isRunning: Boolean,
- onSend: () -> Unit,
- canSend: Boolean,
- onDelete: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- Card(modifier, elevation = 1.dp) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- modifier = Modifier.padding(8.dp),
- ) {
- Text(
- text = text,
- fontWeight = FontWeight.Bold,
- maxLines = 2,
- modifier =
- Modifier
- .fillMaxWidth()
- .weight(weight = 0.8f, fill = true)
- .align(Alignment.CenterVertically),
- )
- Button(
- onClick = { onDelete() },
- modifier = Modifier.align(Alignment.CenterVertically).width(85.dp),
- ) {
- Text(Language.delete)
- }
- Button(
- onClick = { onSend() },
- enabled = canSend,
- modifier = Modifier.align(Alignment.CenterVertically).width(85.dp),
- ) {
- when {
- isRunning -> RunningIndicator()
- else -> Text(text = Language.send)
- }
- }
- }
- }
-}
-
-@Preview
-@Composable
-private fun TextCommandItem_Preview() {
- TextCommandItem(
- text = "あいうえお",
- isRunning = false,
- onSend = {},
- canSend = true,
- onDelete = {},
- modifier = Modifier.fillMaxWidth().height(50.dp),
- )
-}
diff --git a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandList.kt b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandList.kt
index eaeae6c7..cb7157b1 100644
--- a/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandList.kt
+++ b/src/jvmMain/kotlin/jp/kaleidot725/adbpad/ui/screen/text/component/TextCommandList.kt
@@ -2,46 +2,79 @@ package jp.kaleidot725.adbpad.ui.screen.text.component
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
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.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+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.graphics.Color
+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 jp.kaleidot725.adbpad.domain.model.command.TextCommand
import jp.kaleidot725.adbpad.domain.model.language.Language
+import jp.kaleidot725.adbpad.ui.common.resource.clickableBackground
@Composable
fun TextCommandList(
+ selectedCommand: TextCommand?,
commands: List,
- onSend: (TextCommand) -> Unit,
- canSend: Boolean,
- onDelete: (TextCommand) -> Unit,
+ onSelectCommand: (TextCommand) -> Unit,
+ onNextCommand: () -> Unit,
+ onPreviousCommand: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
if (commands.isNotEmpty()) {
- Column(
- verticalArrangement = Arrangement.spacedBy(8.dp),
- modifier = Modifier.verticalScroll(rememberScrollState()),
+ val lazyColumnState = rememberLazyListState()
+ LazyColumn(
+ state = lazyColumnState,
+ modifier =
+ Modifier
+ .padding(4.dp)
+ .onKeyEvent { event ->
+ when {
+ event.key == Key.DirectionUp && event.type == KeyEventType.KeyDown -> {
+ onPreviousCommand()
+ true
+ }
+ event.key == Key.DirectionDown && event.type == KeyEventType.KeyDown -> {
+ onNextCommand()
+ true
+ }
+ else -> false
+ }
+ },
) {
- commands.forEach { command ->
- TextCommandItem(
- text = command.text,
- isRunning = command.isRunning,
- onSend = { onSend(command) },
- canSend = canSend,
- onDelete = { onDelete(command) },
- modifier = Modifier.height(60.dp).fillMaxWidth().padding(2.dp),
- )
+ items(
+ items = commands,
+ ) { command ->
+ Row(
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .clickableBackground(
+ isSelected = selectedCommand?.id == command.id,
+ shape = RoundedCornerShape(4.dp),
+ ).clickable { onSelectCommand(command) }
+ .padding(horizontal = 12.dp, vertical = 4.dp),
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ Text(text = command.title.ifEmpty { Language.textCommandUnTitle })
+ }
}
}
} else {
@@ -58,18 +91,20 @@ fun TextCommandList(
private fun TextCommandList_Preview() {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
TextCommandList(
- commands = listOf(TextCommand("TEST1"), TextCommand("TEST2")),
- onSend = {},
- canSend = true,
- onDelete = {},
+ selectedCommand = TextCommand(title = "Title", text = "Text"),
+ commands = listOf(TextCommand(title = "Title", text = "Text"), TextCommand(title = "Title", text = "Text")),
+ onSelectCommand = {},
+ onNextCommand = {},
+ onPreviousCommand = {},
modifier = Modifier.fillMaxWidth().weight(0.5f, true),
)
TextCommandList(
+ selectedCommand = TextCommand(title = "Title", text = "Text"),
commands = emptyList(),
- onSend = {},
- canSend = true,
- onDelete = {},
+ onSelectCommand = {},
+ onNextCommand = {},
+ onPreviousCommand = {},
modifier = Modifier.fillMaxWidth().weight(0.5f, true).background(Color.LightGray),
)
}