-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 56fe91c
Showing
69 changed files
with
2,985 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
.gradle | ||
build/ | ||
!gradle/wrapper/gradle-wrapper.jar | ||
!**/src/main/**/build/ | ||
!**/src/test/**/build/ | ||
|
||
### IntelliJ IDEA ### | ||
.idea/modules.xml | ||
.idea/jarRepositories.xml | ||
.idea/compiler.xml | ||
.idea/libraries/ | ||
*.iws | ||
*.iml | ||
*.ipr | ||
out/ | ||
!**/src/main/**/out/ | ||
!**/src/test/**/out/ | ||
|
||
### Eclipse ### | ||
.apt_generated | ||
.classpath | ||
.factorypath | ||
.project | ||
.settings | ||
.springBeans | ||
.sts4-cache | ||
bin/ | ||
!**/src/main/**/bin/ | ||
!**/src/test/**/bin/ | ||
|
||
### NetBeans ### | ||
/nbproject/private/ | ||
/nbbuild/ | ||
/dist/ | ||
/nbdist/ | ||
/.nb-gradle/ | ||
|
||
### VS Code ### | ||
.vscode/ | ||
|
||
### Mac OS ### | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
# Jetpack Compose Rich Text Editor | ||
|
||
[![](https://jitpack.io/v/pChochura/richtext-compose.svg)](https://jitpack.io/#pChochura/richtext-compose) | ||
|
||
# This is a hard fork of the fanstastic library of the same name from pChochura. This fork makes it into a multi-platform library | ||
|
||
I've been looking for a library that is able to deliver an editable component which can render rich | ||
text in real time. The main issue with libraries I found was that they were using WebView and | ||
Javascript under the hood. I wanted something compatible with Jetpack Compose. | ||
|
||
So the only solution was to create my own library. | ||
|
||
# Installation | ||
|
||
1. Add a link to the Jitpack repository | ||
|
||
```groovy | ||
repositories { | ||
maven { url 'https://jitpack.io' } | ||
} | ||
``` | ||
|
||
2. Include link do the library (change the version to the current one) | ||
|
||
```groovy | ||
implementation "com.github.pChochura:richtext-compose:$version" | ||
``` | ||
|
||
# Usage | ||
|
||
Insert function call to a Composable: | ||
|
||
```kotlin | ||
var value by remember { mutableStateOf(RichTextValue.get()) } | ||
|
||
RichTextEditor( | ||
modifier = Modifier, | ||
value = value, | ||
onValueChange = { value = it }, | ||
textFieldStyle = defaultRichTextFieldStyle().copy( | ||
placeholder = "My rich text editor in action", | ||
textColor = MaterialTheme.colors.onPrimary, | ||
placeholderColor = MaterialTheme.colors.secondaryVariant, | ||
) | ||
) | ||
|
||
// If you want to render static text use `RichText` instead | ||
RichText( | ||
modifier = Modifier, | ||
value = value, | ||
textStyle = defaultRichTextStyle().copy( | ||
textColor = MaterialTheme.colors.onPrimary, | ||
) | ||
) | ||
``` | ||
|
||
You can easily stylize the TextField that is under the hood by providing values for | ||
the `textFieldStyle`. Default ones are as follows: | ||
|
||
```kotlin | ||
@Composable | ||
fun defaultRichTextFieldStyle() = RichTextFieldStyle( | ||
keyboardOptions = KeyboardOptions( | ||
capitalization = KeyboardCapitalization.Sentences, | ||
), | ||
placeholder = EMPTY_STRING, | ||
textStyle = MaterialTheme.typography.body1, | ||
textColor = MaterialTheme.colors.onPrimary, | ||
placeholderColor = MaterialTheme.colors.secondaryVariant, | ||
cursorColor = MaterialTheme.colors.secondary, | ||
) | ||
``` | ||
|
||
To insert or clear styles you can use methods provided by the `RichTextValue` object: | ||
|
||
```kotlin | ||
abstract class RichTextValue { | ||
/** | ||
* Returns styles that are used inside the current selection (or composition) | ||
*/ | ||
abstract val currentStyles: Set<Style> | ||
abstract val isUndoAvailable: Boolean | ||
abstract val isRedoAvailable: Boolean | ||
|
||
abstract fun insertStyle(style: Style): RichTextValue | ||
abstract fun clearStyles(vararg styles: Style): RichTextValue | ||
|
||
abstract fun undo(): RichTextValue | ||
abstract fun redo(): RichTextValue | ||
} | ||
``` | ||
|
||
Every method that manipulates the text inside the TextField returns a copy of the object to be | ||
passed to a state. | ||
|
||
## Available styles | ||
|
||
1. Bold | ||
```kotlin | ||
// Inserting style | ||
value = value.insertStyle(Style.Bold) | ||
|
||
// Checking if the style is used inside the current selection | ||
val isInCurrentSelection = value.currentStyles.contains(Style.Bold) | ||
``` | ||
|
||
2. Underline | ||
```kotlin | ||
// Inserting style | ||
value = value.insertStyle(Style.Underline) | ||
|
||
// Checking if the style is used inside the current selection | ||
val isInCurrentSelection = value.currentStyles.contains(Style.Underline) | ||
``` | ||
|
||
3. Italic | ||
```kotlin | ||
// Inserting style | ||
value = value.insertStyle(Style.Italic) | ||
|
||
// Checking if the style is used inside the current selection | ||
val isInCurrentSelection = value.currentStyles.contains(Style.Italic) | ||
``` | ||
|
||
4. Strikethrough | ||
```kotlin | ||
// Inserting style | ||
value = value.insertStyle(Style.Strikethrough) | ||
|
||
// Checking if the style is used inside the current selection | ||
val isInCurrentSelection = value.currentStyles.contains(Style.Strikethrough) | ||
``` | ||
|
||
5. Align Left | ||
```kotlin | ||
// Inserting style | ||
value = value.insertStyle(Style.AlignLeft) | ||
|
||
// Checking if the style is used inside the current selection | ||
val isInCurrentSelection = value.currentStyles.contains(Style.AlignLeft) | ||
``` | ||
|
||
6. Align Center | ||
```kotlin | ||
// Inserting style | ||
value = value.insertStyle(Style.AlignCenter) | ||
|
||
// Checking if the style is used inside the current selection | ||
val isInCurrentSelection = value.currentStyles.contains(Style.AlignCenter) | ||
``` | ||
|
||
7. Align Right | ||
```kotlin | ||
// Inserting style | ||
value = value.insertStyle(Style.AlignRight) | ||
|
||
// Checking if the style is used inside the current selection | ||
val isInCurrentSelection = value.currentStyles.contains(Style.AlignRight) | ||
``` | ||
|
||
8. Text Size | ||
```kotlin | ||
// We're deleting all of the text size styles from the selection to avoid multiple multiplications of the size | ||
value = value.clearStyles(Style.TextColor()) | ||
|
||
// Inserting style | ||
// You have to pass size as a parameter. It accepts values between 0.5f and 2.0f | ||
// Which means that the text size will be multiplied by the provided value | ||
value = value.insertStyle(Style.TextSize(textSize)) | ||
|
||
// Checking if the style is used inside the current selection | ||
// Here we're using `filterIsInstance` to check if there are any of the text size styles | ||
val isInCurrentSelection = value.currentStyles.filterIsInstance<Style.TextSize>().isNotEmpty() | ||
``` | ||
|
||
9. Text Color | ||
```kotlin | ||
// We're deleting all of the text color styles from the selection to avoid having more than one color on the same portion of the text (the last one would be displayed either way) | ||
value = value.clearStyles(Style.TextColor()) | ||
|
||
// Inserting style | ||
// You have to pass color as a parameter | ||
value = value.insertStyle(Style.TextColor(color)) | ||
|
||
// Checking if the style is used inside the current selection | ||
// Here we're using `filterIsInstance` to check if there are any of the text color styles | ||
val isInCurrentSelection = value.value.currentStyles.filterIsInstance<Style.TextColor>().isNotEmpty() | ||
``` | ||
|
||
## Custom styling | ||
|
||
If you want to create your own styles you're free to do so. You would have to create a class that | ||
extends `StyleMapper` and implement the styling there for the styles that you would have created. | ||
|
||
```kotlin | ||
// If you want to create a paragraph style you have to extend `ParagraphStyle` interface! | ||
object CustomParagraphStyle : Style | ||
object CustomStyle : Style | ||
|
||
class CustomStyleMapper : StyleMapper() { | ||
|
||
override fun fromTag(tag: String): Style = | ||
runCatching { super.fromTag(tag) }.getOrNull() ?: when (tag) { | ||
// It is necessary to ensure undo/redo actions work correctly | ||
"${CustomStyle.javaClass.simpleName}/" -> CustomStyle | ||
"${CustomParagraphStyle.javaClass.simpleName}/" -> CustomParagraphStyle | ||
else -> throw IllegalArgumentException() | ||
} | ||
|
||
override fun toSpanStyle(style: Style): SpanStyle? = super.toSpanStyle(style) ?: when (style) { | ||
// Here we're customizing the behavior of the style | ||
is CustomStyle -> SpanStyle( | ||
color = Color.Red, | ||
fontWeight = FontWeight.Bold, | ||
) | ||
else -> null | ||
} | ||
|
||
override fun toParagraphStyle(style: Style): ParagraphStyle? = | ||
super.toParagraphStyle(style) ?: when (style) { | ||
is CustomParagraphStyle -> ParagraphStyle( | ||
textAlign = TextAlign.Justify, | ||
textIndent = TextIndent(firstLine = 12.sp) | ||
) | ||
else -> null | ||
} | ||
} | ||
``` | ||
|
||
And then you would have to pass an instance of the class you created as a parameter to | ||
the `RichTextValue` class: | ||
|
||
```kotlin | ||
var value by remember { | ||
mutableStateOf( | ||
RichTextValue.get( | ||
styleMapper = CustomStyleMapper() | ||
) | ||
) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
val compose_version: String by extra | ||
|
||
plugins { | ||
id("org.jetbrains.compose") | ||
id("com.android.application") | ||
kotlin("android") | ||
} | ||
|
||
group "com.darkrockstudios.example.richtexteditor" | ||
version "1.0-SNAPSHOT" | ||
|
||
repositories { | ||
|
||
} | ||
|
||
dependencies { | ||
implementation(project(":library")) | ||
implementation("androidx.core:core-ktx:1.8.0") | ||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1") | ||
implementation("androidx.activity:activity-compose:1.4.0") | ||
|
||
implementation("androidx.compose.ui:ui:$compose_version") | ||
implementation("androidx.compose.material:material:$compose_version") | ||
implementation("androidx.compose.ui:ui-tooling-preview:$compose_version") | ||
} | ||
|
||
android { | ||
compileSdk = 31 | ||
defaultConfig { | ||
applicationId = "com.pointlessapps.example" | ||
minSdk = 24 | ||
targetSdk = 31 | ||
versionCode = 1 | ||
versionName = "1.0-SNAPSHOT" | ||
} | ||
compileOptions { | ||
sourceCompatibility = JavaVersion.VERSION_1_8 | ||
targetCompatibility = JavaVersion.VERSION_1_8 | ||
} | ||
buildTypes { | ||
getByName("release") { | ||
isMinifyEnabled = false | ||
proguardFiles( | ||
getDefaultProguardFile("proguard-android-optimize.txt"), | ||
File("proguard-rules.pro") | ||
) | ||
} | ||
} | ||
packagingOptions { | ||
resources { | ||
excludes += mutableSetOf("/META-INF/{AL2.0,LGPL2.1}") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.darkrockstudios.example"> | ||
<application | ||
android:allowBackup="false" | ||
android:supportsRtl="true" | ||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"> | ||
<activity android:name="com.darkrockstudios.example.MainActivity" android:exported="true"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN"/> | ||
<category android:name="android.intent.category.LAUNCHER"/> | ||
</intent-filter> | ||
</activity> | ||
</application> | ||
</manifest> |
Oops, something went wrong.