Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AND-145: add TextValue #63

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ Gears could be used together or alone.

A couple of extensions to convert long operations into `Flow<Result<T>>`.

### :speech_balloon: **[TextValue](textvalue/)**

- [![Version](https://img.shields.io/maven-central/v/com.redmadrobot.gears/kotlin?style=flat-square&label=textvalue-common)][textvalue-common] - An implementation of the TextValue - an abstraction over Android text
- [![Version](https://img.shields.io/maven-central/v/com.redmadrobot.gears/kotlin?style=flat-square&label=textvalue-compose)][textvalue-compose] - A set of handy tools for dealing with TextValue inside `@Composable` functions
osipxd marked this conversation as resolved.
Show resolved Hide resolved

## Why Gears?

The goal of this mono-repository is to simplify the creation and publication of libraries.
Expand Down Expand Up @@ -75,5 +80,8 @@ For major changes, open a [discussion][discussions] first to discuss what you wo
[viewmodelevents-flow]: viewmodelevents/viewmodelevents-flow/
[viewmodelevents-livedata]: viewmodelevents/viewmodelevents-livedata/

[textvalue-common]: textvalue/textvalue-common/
[textvalue-compose]: textvalue/textvalue-compose/

[ci]: https://github.com/RedMadRobot/gears-android/actions?query=branch%3Amain++
[discussions]: https://github.com/RedMadRobot/gears-android/discussions
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ include(
":viewmodelevents:viewmodelevents-flow",
":viewmodelevents:viewmodelevents-livedata",
":resultflow",
":textvalue:textvalue-common",
":textvalue:textvalue-compose"
)
10 changes: 10 additions & 0 deletions textvalue/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## [Unreleased]

*No changes*

## [1.0.0]

- Public release textvalue library and textvalue-compose extensions library

[unreleased]: https://github.com/RedMadRobot/TextValue/compare/1.0.0...main
[1.0.0]: https://github.com/RedMadRobot/TextValue/compare/d5d1d9...1.0.0
77 changes: 77 additions & 0 deletions textvalue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# TextValue <GitHub path="RedMadRobot/textvalue/tree/main/"/>
EmogurovAnton marked this conversation as resolved.
Show resolved Hide resolved
[![Version](https://img.shields.io/maven-central/v/com.redmadrobot.textvalue/textvalue?style=flat-square)][mavenCentral]
[![License](https://img.shields.io/github/license/RedMadRobot/textvalue?style=flat-square)][license]
osipxd marked this conversation as resolved.
Show resolved Hide resolved

TextValue is an abstraction allowing to work with a `String` and a string resource ID the same way.

---
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Installation](#installation)
- [Usage](#usage)
- [Contributing](#contributing)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Installation

Add the dependency:
```groovy
repositories {
mavenCentral()
google()
}

dependencies {
// Views version
implementation("com.redmadrobot.textvalue:textvalue-common:<version>")
osipxd marked this conversation as resolved.
Show resolved Hide resolved

// Compose extensions for textvalue
implementation("com.redmadrobot.textvalue:textvalue-compose:<version>")
}
```

## Usage

**TextValue** is a wrapper to make it possible to work with plain `String` and `StringRes` in the same way.
It may be useful for cases when you want to fallback to `StringRes` if desired string value is `null`.

You can wrap `String` and `StringRes` with `TextValue` using `TextValue(String)`, `TextValue(Int)` or `TextValue(String?, Int))`, and use method `TextValue.get(Resource)` to retrieve `String`:

```kotlin
// in some place where we can't access Context
val errorMessage = TextValue(exception.message, defaultResourceId = R.string.unknown_error)
showMessage(errorMessage)

// in Activity, Fragment or View
fun showMessage(text: TextValue) {
val messageText = text.get(resources)
//...
}
```

`TextValue` also could be used with Jetpack Compose:

```kotlin
// in Composable functions
@Composable
fun Screen(title: TextValue) {
// Remember to add com.redmadrobot.textvalue:textvalue-compose dependency
Text(text = stringResource(title))
}
```

There are extensions to work with `TextValue` like with `StringRes`:

- `Context.getString(text: TextValue): String`
- `View.getString(text: TextValue): String`
- `Resources.getString(text: TextValue): String`

## Contributing

Merge requests are welcome.
For major changes, please open an issue first to discuss what you would like to change.

[mavenCentral]: https://search.maven.org/artifact/com.redmadrobot.textvalue/textvalue
osipxd marked this conversation as resolved.
Show resolved Hide resolved
[license]: LICENSE
osipxd marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions textvalue/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// For some reason gradle.properties in this project doesn't affect its subprojects
val textValueGroup = group
subprojects { group = textValueGroup }
1 change: 1 addition & 0 deletions textvalue/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
group=com.redmadrobot.textvalue
16 changes: 16 additions & 0 deletions textvalue/textvalue-common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
convention.library.android
id("kotlin-parcelize")
}

description = "TextValue is an abstraction over Android text"

android {
namespace = "$group.common"
osipxd marked this conversation as resolved.
Show resolved Hide resolved
}

dependencies {
api(kotlin("stdlib"))
api(androidx.annotation)
compileOnly(androidx.compose.runtime)
}
84 changes: 84 additions & 0 deletions textvalue/textvalue-common/src/main/kotlin/TextValue.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.redmadrobot.textvalue

import android.content.Context
import android.content.res.Resources
import android.os.Parcelable
import android.view.View
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import kotlinx.parcelize.Parcelize

/**
* Wrapper to make it possible to work with plain [String] and [StringRes] in the same way.
*
* ```
* // in some place where we can't access Context
* val errorMessage = TextValue(exception.message, defaultResourceId= R.string.unknown_error)
* showMessage(errorMessage)
*
* // in Activity, Fragment or View
* val messageText = getString(message)
* ```
*/
@Immutable
public sealed interface TextValue : Parcelable {

/** Retrieves [String] using the given [resources]. */
public fun get(resources: Resources): String

override fun equals(other: Any?): Boolean
override fun hashCode(): Int

/** Plain string. */
@Parcelize
public data class Plain(public val string: String) : TextValue {
override fun get(resources: Resources): String = string
}

/** String resource, requires [Resources] to get [String]. */
@Parcelize
public data class Resource(@StringRes public val resourceId: Int) : TextValue {
override fun get(resources: Resources): String = resources.getString(resourceId)
}

public companion object {

/** Empty [TextValue]. */
public val EMPTY: TextValue = TextValue("")
}
}

/** Creates [TextValue] from the given [resourceId]. */
public fun TextValue(@StringRes resourceId: Int): TextValue = TextValue.Resource(resourceId)

/** Creates [TextValue] from the given [string]. */
public fun TextValue(string: String): TextValue = TextValue.Plain(string)

/** Creates [TextValue] from the given [string], or from the [defaultResourceId] if string is `null`. */
public fun TextValue(string: String?, @StringRes defaultResourceId: Int): TextValue {
return if (string != null) TextValue.Plain(string) else TextValue.Resource(defaultResourceId)
}

/**
* Unwraps and returns a string for the given [text].
* @see TextValue
*/
public fun Context.getString(text: TextValue): String = resources.getString(text)

/**
* Unwraps and returns a string for the given [text].
* @see TextValue
*/
public fun View.getString(text: TextValue): String = resources.getString(text)

/**
* Unwraps and returns a string for the given [text].
* @see TextValue
*/
public fun Resources.getString(text: TextValue): String = text.get(this)

/**
* Returns TextValue itself if it is not `null`, or the [TextValue.EMPTY] otherwise.
* @see TextValue
*/
public fun TextValue?.orEmpty(): TextValue = this ?: TextValue.EMPTY
19 changes: 19 additions & 0 deletions textvalue/textvalue-compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
convention.library.android
alias(stack.plugins.kotlin.compose)
}

description = "Compose extensions for TextValue"

dependencies {
api(project(":textvalue:textvalue-common"))
api(androidx.compose.ui)
}

android {
namespace = "$group.compose"

buildFeatures {
compose = true
}
}
29 changes: 29 additions & 0 deletions textvalue/textvalue-compose/src/main/kotlin/StringResources.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.redmadrobot.textvalue

import android.content.res.Resources
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext

/**
* Unwraps and returns a string for the given [text].
* @see TextValue
*/
@Composable
@ReadOnlyComposable
public fun stringResource(text: TextValue): String {
val resources = resources()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Просто ресурсы resources() выносить не имеет смысла,
либо все в return

return resources.getString(text)

либо

val string = resources().getString(text)
return string

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Он вынесен в отдельный метод по аналогии с реализацией оригинального stringResource. Имеет смысл ориентироваться на реализацию из Compose.

return resources.getString(text)
}

/**
* A composable function that returns the [Resources]. It will be recomposed when [Configuration]
* gets updated.
*/
@Composable
@ReadOnlyComposable
private fun resources(): Resources {
LocalConfiguration.current
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем это нужно?

return LocalContext.current.resources
}