diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..d536a1e --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,26 @@ +name: Android CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 6b03294..8951dd1 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,8 +4,10 @@ diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index fdf8d99..e805548 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7232108..db20ece 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,13 +31,13 @@ android { targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '11' + jvmTarget = '17' } buildFeatures { compose true } composeOptions { - kotlinCompilerExtensionVersion = "1.5.1" + kotlinCompilerExtensionVersion = "1.5.4" } packagingOptions { resources { diff --git a/app/src/main/java/jp/kaleidot725/sample/ui/composable/Demo.kt b/app/src/main/java/jp/kaleidot725/sample/ui/composable/Demo.kt index c398a88..d4f601d 100644 --- a/app/src/main/java/jp/kaleidot725/sample/ui/composable/Demo.kt +++ b/app/src/main/java/jp/kaleidot725/sample/ui/composable/Demo.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -49,7 +50,15 @@ fun Demo(text: String) { val clipboardManager = LocalClipboardManager.current val keyboardController = LocalSoftwareKeyboardController.current - var textEditorState by remember { mutableStateOf(TextEditorState.create(text)) } + var textEditorState by remember { + mutableStateOf( + TextEditorState.create( + text, + TextStyle(color = Color.Black), + TextStyle(color = Color.Green) + ) + ) + } val bottomPadding = if (textEditorState.isMultipleSelectionMode) 100.dp else 0.dp val contentBottomPaddingValue = with(LocalDensity.current) { WindowInsets.ime.getBottom(this).toDp() } diff --git a/build.gradle b/build.gradle index 0639d3a..4d5f57c 100644 --- a/build.gradle +++ b/build.gradle @@ -3,15 +3,15 @@ buildscript { core_ktx_version = '1.10.1' lifecycle_ktx_version = '2.6.1' activity_compose_version = '1.7.2' - compose_ui_version = '1.5.0' + compose_ui_version = '1.5.4' kotest_version = '5.6.2' } } plugins { - id 'com.android.application' version '7.4.2' apply false - id 'com.android.library' version '7.4.2' apply false - id 'org.jetbrains.kotlin.android' version '1.9.0' apply false + id 'com.android.application' version '8.1.2' apply false + id 'com.android.library' version '8.1.2' apply false + id 'org.jetbrains.kotlin.android' version '1.9.20' apply false id 'org.jlleitschuh.gradle.ktlint' version '10.3.0' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8049c68..da1db5f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/jitpack.yml b/jitpack.yml index 46c8529..1e41e00 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,2 +1,2 @@ jdk: - - openjdk11 \ No newline at end of file + - openjdk17 \ No newline at end of file diff --git a/texteditor/build.gradle b/texteditor/build.gradle index 51c5e22..a263936 100644 --- a/texteditor/build.gradle +++ b/texteditor/build.gradle @@ -31,7 +31,7 @@ android { } kotlinOptions { - jvmTarget = '11' + jvmTarget = '17' } android.testOptions { @@ -45,7 +45,13 @@ android { } composeOptions { - kotlinCompilerExtensionVersion '1.5.1' + kotlinCompilerExtensionVersion '1.5.4' + } + publishing{ + singleVariant('release') { + withSourcesJar() + withJavadocJar() + } } } @@ -64,7 +70,7 @@ afterEvaluate { from components.release groupId = 'com.github.kaleidot725' artifactId = 'text-editor-compose' - version = '0.6.0' + version = '0.6.1' } } } diff --git a/texteditor/src/main/java/jp/kaleidot725/texteditor/controller/EditorController.kt b/texteditor/src/main/java/jp/kaleidot725/texteditor/controller/EditorController.kt index 3a05f3c..1d272b5 100644 --- a/texteditor/src/main/java/jp/kaleidot725/texteditor/controller/EditorController.kt +++ b/texteditor/src/main/java/jp/kaleidot725/texteditor/controller/EditorController.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import jp.kaleidot725.texteditor.state.TextEditorState import jp.kaleidot725.texteditor.state.TextFieldState @@ -12,7 +13,7 @@ import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock internal class EditorController( - textEditorState: TextEditorState + val textEditorState: TextEditorState ) { private var onChanged: (TextEditorState) -> Unit = {} @@ -26,7 +27,7 @@ internal class EditorController( val selectedIndices get() = _selectedIndices.toList() private val state: TextEditorState - get() = TextEditorState(fields, selectedIndices, isMultipleSelectionMode) + get() = TextEditorState(fields, selectedIndices, isMultipleSelectionMode, textEditorState.textStyle, textEditorState.textSelectedStyle) private val lock = ReentrantLock() @@ -62,7 +63,7 @@ internal class EditorController( val newSplitFieldValues = splitFieldValues.subList(1, splitFieldValues.count()) val newSplitFieldStates = - newSplitFieldValues.map { TextFieldState(value = it, isSelected = false) } + newSplitFieldValues.map { TextFieldState(value = it, isSelected = false, textStyle = textEditorState.textStyle, textSelectedStyle = textEditorState.textSelectedStyle) } _fields.addAll(targetIndex + 1, newSplitFieldStates) val lastNewSplitFieldIndex = targetIndex + newSplitFieldValues.count() @@ -95,7 +96,7 @@ internal class EditorController( _fields[targetIndex] = firstState val secondValue = TextFieldValue(second, TextRange.Zero) - val secondState = TextFieldState(value = secondValue, isSelected = false) + val secondState = TextFieldState(value = secondValue, isSelected = false, textStyle = textEditorState.textStyle, textSelectedStyle = textEditorState.textSelectedStyle) _fields.add(targetIndex + 1, secondState) selectFieldInternal(targetIndex + 1) @@ -214,7 +215,7 @@ internal class EditorController( fun deleteAllLine() { lock.withLock { _fields.clear() - _fields.addAll(emptyList().createInitTextFieldStates()) + _fields.addAll(emptyList().createInitTextFieldStates(textEditorState.textStyle, textEditorState.textSelectedStyle)) _selectedIndices.clear() selectFieldInternal(0) onChanged(state) @@ -302,12 +303,14 @@ internal class EditorController( } companion object { - fun List.createInitTextFieldStates(): List { - if (this.isEmpty()) return listOf(TextFieldState(isSelected = false)) + fun List.createInitTextFieldStates(textStyle: TextStyle, textSelectedStyle: TextStyle): List { + if (this.isEmpty()) return listOf(TextFieldState(isSelected = false, textStyle = textStyle, textSelectedStyle = textSelectedStyle)) return this.mapIndexed { _, s -> TextFieldState( value = TextFieldValue(s, TextRange.Zero), - isSelected = false + isSelected = false, + textStyle = textStyle, + textSelectedStyle = textSelectedStyle ) } } diff --git a/texteditor/src/main/java/jp/kaleidot725/texteditor/state/TextEditorState.kt b/texteditor/src/main/java/jp/kaleidot725/texteditor/state/TextEditorState.kt index f1bb35d..640dcbd 100644 --- a/texteditor/src/main/java/jp/kaleidot725/texteditor/state/TextEditorState.kt +++ b/texteditor/src/main/java/jp/kaleidot725/texteditor/state/TextEditorState.kt @@ -1,6 +1,7 @@ package jp.kaleidot725.texteditor.state import androidx.compose.runtime.Immutable +import androidx.compose.ui.text.TextStyle import jp.kaleidot725.texteditor.controller.EditorController.Companion.createInitTextFieldStates @Immutable @@ -8,6 +9,8 @@ data class TextEditorState( val fields: List, val selectedIndices: List, val isMultipleSelectionMode: Boolean, + val textStyle: TextStyle, + val textSelectedStyle: TextStyle ) { fun getAllText(): String { return fields.map { it.value.text }.foldIndexed("") { index, acc, s -> @@ -24,19 +27,23 @@ data class TextEditorState( } companion object { - fun create(text: String): TextEditorState { + fun create(text: String, textStyle: TextStyle = TextStyle(), textSelectedStyle: TextStyle = TextStyle()): TextEditorState { return TextEditorState( - fields = text.lines().createInitTextFieldStates(), + fields = text.lines().createInitTextFieldStates(textStyle, textSelectedStyle), selectedIndices = listOf(-1), - isMultipleSelectionMode = false + isMultipleSelectionMode = false, + textStyle = textStyle, + textSelectedStyle = textSelectedStyle ) } - fun create(lines: List): TextEditorState { + fun create(lines: List, textStyle: TextStyle = TextStyle(), textSelectedStyle: TextStyle = TextStyle()): TextEditorState { return TextEditorState( - fields = lines.createInitTextFieldStates(), + fields = lines.createInitTextFieldStates(textStyle, textSelectedStyle), selectedIndices = listOf(-1), - isMultipleSelectionMode = false + isMultipleSelectionMode = false, + textStyle = textStyle, + textSelectedStyle = textSelectedStyle ) } } diff --git a/texteditor/src/main/java/jp/kaleidot725/texteditor/state/TextFieldState.kt b/texteditor/src/main/java/jp/kaleidot725/texteditor/state/TextFieldState.kt index b1e1c21..5d5a1b8 100644 --- a/texteditor/src/main/java/jp/kaleidot725/texteditor/state/TextFieldState.kt +++ b/texteditor/src/main/java/jp/kaleidot725/texteditor/state/TextFieldState.kt @@ -1,6 +1,7 @@ package jp.kaleidot725.texteditor.state import androidx.compose.runtime.Stable +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import java.util.UUID @@ -8,5 +9,7 @@ import java.util.UUID data class TextFieldState( val id: String = UUID.randomUUID().toString(), val value: TextFieldValue = TextFieldValue(), - val isSelected: Boolean + val isSelected: Boolean, + val textStyle: TextStyle, + val textSelectedStyle: TextStyle ) diff --git a/texteditor/src/main/java/jp/kaleidot725/texteditor/view/TextEditor.kt b/texteditor/src/main/java/jp/kaleidot725/texteditor/view/TextEditor.kt index e35a920..1f9417c 100644 --- a/texteditor/src/main/java/jp/kaleidot725/texteditor/view/TextEditor.kt +++ b/texteditor/src/main/java/jp/kaleidot725/texteditor/view/TextEditor.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState @@ -47,15 +48,16 @@ fun TextEditor( contentPaddingValues: PaddingValues = PaddingValues(), decorationBox: DecorationBoxComposable = { _, _, innerTextField -> innerTextField(Modifier) }, ) { - val textEditorState by rememberUpdatedState(newValue = textEditorState) + val textEditorStateM by rememberUpdatedState(newValue = textEditorState) val editableController by rememberTextEditorController( - textEditorState, - onChanged = { onChanged(it) }) + textEditorStateM, + onChanged = { onChanged(it) } + ) var lastScrollEvent by remember { mutableStateOf(null as ScrollEvent?) } val lazyColumnState = rememberLazyListState() - val focusRequesters by remember { mutableStateOf(mutableMapOf()) } + val focusRequesters = remember { mutableStateMapOf() } - editableController.syncState(textEditorState) + editableController.syncState(textEditorStateM) LaunchedEffect(lastScrollEvent) { lastScrollEvent?.consume() @@ -77,7 +79,7 @@ fun TextEditor( contentPadding = contentPaddingValues ) { itemsIndexed( - items = textEditorState.fields, + items = textEditorStateM.fields, key = { _, item -> item.id } ) { index, textFieldState -> val focusRequester by remember { mutableStateOf(FocusRequester()) } @@ -99,13 +101,13 @@ fun TextEditor( interactionSource = remember { MutableInteractionSource() }, indication = null ) { - if (!textEditorState.isMultipleSelectionMode) return@clickable + if (!textEditorStateM.isMultipleSelectionMode) return@clickable editableController.selectField(targetIndex = index) } ) { TextField( textFieldState = textFieldState, - enabled = !textEditorState.isMultipleSelectionMode, + enabled = !textEditorStateM.isMultipleSelectionMode, focusRequester = focusRequester, onUpdateText = { newText -> editableController.updateField( @@ -145,9 +147,9 @@ fun TextEditor( onDownFocus = { if (lastScrollEvent != null && lastScrollEvent?.isConsumed != true) return@TextField editableController.selectNextField() - if (index != textEditorState.fields.lastIndex) lastScrollEvent = + if (index != textEditorStateM.fields.lastIndex) lastScrollEvent = ScrollEvent(index + 1) - }, + } ) } } diff --git a/texteditor/src/main/java/jp/kaleidot725/texteditor/view/TextField.kt b/texteditor/src/main/java/jp/kaleidot725/texteditor/view/TextField.kt index a1fccc5..e2b9d73 100644 --- a/texteditor/src/main/java/jp/kaleidot725/texteditor/view/TextField.kt +++ b/texteditor/src/main/java/jp/kaleidot725/texteditor/view/TextField.kt @@ -8,21 +8,17 @@ import android.view.KeyEvent.KEYCODE_TAB import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.selection.LocalTextSelectionColors -import androidx.compose.foundation.text.selection.TextSelectionColors -import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusTarget import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Color.Companion.Transparent import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.onPreviewKeyEvent @@ -46,6 +42,12 @@ internal fun TextField( modifier: Modifier = Modifier, ) { val currentTextField by rememberUpdatedState(newValue = textFieldState.value) + val textStyle by remember(textFieldState.isSelected) { + derivedStateOf { + if (textFieldState.isSelected) textFieldState.textSelectedStyle + else textFieldState.textStyle + } + } LaunchedEffect(textFieldState.isSelected) { if (textFieldState.isSelected) { @@ -86,7 +88,8 @@ internal fun TextField( if (b5) return@onPreviewKeyEvent true false - } + }, + textStyle = textStyle ) }