From aafcd705316274229e0cc94425b19c222e646e61 Mon Sep 17 00:00:00 2001
From: Andrew Haisting <142518658+ahaisting-livefront@users.noreply.github.com>
Date: Wed, 4 Oct 2023 13:26:41 -0500
Subject: [PATCH] BIT-181 Implement Text interface for passing strings and
 resources to UI (#95)

---
 .../bitwarden/ui/platform/base/util/Text.kt   | 104 ++++++++++++++++++
 1 file changed, 104 insertions(+)
 create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/Text.kt

diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/Text.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/Text.kt
new file mode 100644
index 000000000..7d1a64802
--- /dev/null
+++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/base/util/Text.kt
@@ -0,0 +1,104 @@
+package com.x8bit.bitwarden.ui.platform.base.util
+
+import android.content.res.Resources
+import android.os.Parcelable
+import androidx.annotation.PluralsRes
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.platform.LocalContext
+import kotlinx.parcelize.Parcelize
+import kotlinx.parcelize.RawValue
+
+/**
+ * Interface for sending strings to the UI layer.
+ */
+@Immutable
+interface Text : Parcelable {
+    /**
+     * Returns the string representation of [Text].
+     */
+    @Composable
+    operator fun invoke(): String {
+        return toString(LocalContext.current.resources)
+    }
+
+    /**
+     * Returns the string representation of [Text].
+     */
+    operator fun invoke(res: Resources): CharSequence
+
+    /**
+     * Helper method to call [invoke] and then [toString].
+     */
+    fun toString(res: Resources): String = invoke(res).toString()
+}
+
+/**
+ * Implementation of [Text] backed by a string resource.
+ */
+@Parcelize
+private data class ResText(@StringRes private val id: Int) : Text {
+    override fun invoke(res: Resources): CharSequence = res.getText(id)
+}
+
+/**
+ * Implementation of [Text] that formats a string resource with arguments.
+ */
+@Parcelize
+private data class ResArgsText(
+    @StringRes
+    private val id: Int,
+    private val args: @RawValue List<Any>,
+) : Text {
+    override fun invoke(res: Resources): String =
+        res.getString(id, *convertArgs(res, args).toTypedArray())
+
+    override fun toString(): String = "ResArgsText(id=$id, args=${args.contentToString()})"
+}
+
+/**
+ * Implementation of [Text] that formats a plurals resource.
+ */
+@Parcelize
+@Suppress("UnusedPrivateClass")
+private data class PluralsText(
+    @PluralsRes
+    private val id: Int,
+    private val quantity: Int,
+    private val args: @RawValue List<Any>,
+) : Text {
+    override fun invoke(res: Resources): String =
+        res.getQuantityString(id, quantity, *convertArgs(res, args).toTypedArray())
+
+    override fun toString(): String =
+        "PluralsText(id=$id, quantity=$quantity, args=${args.contentToString()})"
+}
+
+private fun List<Any>.contentToString() = joinToString(separator = ",", prefix = "(", postfix = ")")
+
+private fun convertArgs(res: Resources, args: List<Any>): List<Any> =
+    args.map { if (it is Text) it.invoke(res) else it }
+
+/**
+ * Implementation of [Text] backed by a raw string. For use with server responses.
+ */
+@Parcelize
+private data class StringText(private val string: String) : Text {
+    override fun invoke(res: Resources): String = string
+}
+
+/**
+ * Convert a [String] to [Text].
+ */
+fun String.asText(): Text = StringText(this)
+
+/**
+ * Convert a resource Id to [Text].
+ */
+fun @receiver:StringRes Int.asText(): Text = ResText(this)
+
+/**
+ * Convert a resource Id to [Text] with format args.
+ */
+fun @receiver:StringRes Int.asText(vararg args: Any): Text = ResArgsText(this, args.asList())