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

Highlighted text #731

Merged
merged 3 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,4 @@ include ':sphinx:activity:concepts:concept-signer-manager'
include ':sphinx:activity:features:signer-manager'
include ':sphinx:application:common:menus:menu-bottom-signer'
include ':sphinx:application:common:menus:menu-bottom-phone-signer-method'
include ':sphinx:application:common:highlighting-tool'
1 change: 1 addition & 0 deletions sphinx/application/common/highlighting-tool/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
35 changes: 35 additions & 0 deletions sphinx/application/common/highlighting-tool/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
id 'com.android.library'
id 'kotlin-android'
}

android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools

buildFeatures.viewBinding = true

defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments disableAnalytics: 'true'
consumerProguardFiles "consumer-rules.pro"
}

buildTypes {
release {
minifyEnabled false
}
}
namespace 'chat.sphinx.highlighting_tool'
}

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])

implementation project(path: ':sphinx:application:common:resources')

implementation deps.androidx.appCompat
}
Empty file.
21 changes: 21 additions & 0 deletions sphinx/application/common/highlighting-tool/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package chat.sphinx.highlighting_tool

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.os.Build
import android.text.Spannable
import android.text.SpannableString
import android.text.style.BackgroundColorSpan
import android.text.style.TypefaceSpan
import android.widget.TextView
import androidx.annotation.IntRange
import androidx.annotation.RequiresApi
import androidx.core.content.res.ResourcesCompat
import java.util.*


/**
* LinkifyCompat brings in `Linkify` improvements for URLs and email addresses to older API
* levels.
*/
@SuppressLint("RestrictedApi")
object SphinxHighlightingTool {
/**
* Scans the text of the provided TextView and turns all occurrences of
* the link types indicated in the mask into clickable links. If matches
* are found the movement method for the TextView is set to
* LinkMovementMethod.
*
* @param text TextView whose text is to be marked-up with links
* @param mask Mask to define which kinds of links will be searched.
*
* @return True if at least one link is found and applied.
*/
fun addHighlights(
text: TextView,
highlightedTexts: List<Pair<String, IntRange>>,
resources: Resources,
context: Context
) {

if (highlightedTexts.isNotEmpty()) {
val t = text.text
if (t is Spannable) {
for (highlightedText in highlightedTexts) {
ResourcesCompat.getFont(context, R.font.roboto_light)?.let { typeface ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
t.setSpan(
TypefaceSpan(typeface),
highlightedText.second.from.toInt(),
highlightedText.second.to.toInt(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}

t.setSpan(
BackgroundColorSpan(resources.getColor(R.color.highlightedTextBackground)),
highlightedText.second.from.toInt(),
highlightedText.second.to.toInt(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

text.setText(t, TextView.BufferType.SPANNABLE)
}
} else {
val spannable: Spannable = SpannableString(text.text)

for (highlightedText in highlightedTexts) {
ResourcesCompat.getFont(context, R.font.roboto_light)?.let { typeface ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
spannable.setSpan(
TypefaceSpan(typeface),
highlightedText.second.from.toInt(),
highlightedText.second.to.toInt(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}

spannable.setSpan(
BackgroundColorSpan(resources.getColor(R.color.highlightedTextBackground)),
highlightedText.second.from.toInt(),
highlightedText.second.to.toInt(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

text.setText(spannable, TextView.BufferType.SPANNABLE)
}
}
}
}
}

@Suppress("NOTHING_TO_INLINE")
inline fun String.highlightedTexts(): List<Pair<String, IntRange>> {
val matcher = "`([^`]*)`".toRegex()
val ranges = matcher.findAll(this).map{ it.range }.toList()

if (ranges.isEmpty()) {
return emptyList()
}

var adaptedText = this

ranges.forEachIndexed { index, range ->
val subtraction = index * 2

val adaptedRange = IntRange(
start = range.first - subtraction,
endInclusive = range.last - subtraction
)

val rangeString = adaptedText.substring(adaptedRange.first, adaptedRange.last).replace("`","")

adaptedText = adaptedText.replaceRange(adaptedRange, rangeString)
}

var matches: MutableList<Pair<String, IntRange>> = mutableListOf()

ranges.forEachIndexed { index, range ->
val subtraction = index * 2

val adaptedRange = IntRange(
from = (range.first - subtraction).toLong(),
to = (range.last - subtraction - 1).toLong()
)

val rangeString = adaptedText.substring(adaptedRange.from.toInt(), adaptedRange.to.toInt())

matches.add(
Pair(rangeString, adaptedRange)
)
}

return matches
}

@Suppress("NOTHING_TO_INLINE")
inline fun String.replacingHighlightedDelimiters(): String {
val matcher = "`([^`]*)`".toRegex()
val ranges = matcher.findAll(this).map{ it.range }.toList()

if (ranges.isEmpty()) {
return this
}

var adaptedText = this

ranges.forEachIndexed { index, range ->
val subtraction = index * 2

val adaptedRange = IntRange(
start = range.first - subtraction,
endInclusive = range.last - subtraction
)

val rangeString = adaptedText.substring(adaptedRange.first, adaptedRange.last).replace("`","")

adaptedText = adaptedText.replaceRange(adaptedRange, rangeString)
}

return adaptedText
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
<color name="storageBarYellow">#FAE676</color>
<color name="storageBarBackground">#222E3A</color>

<color name="highlightedTextBackground">#26FFFFFF</color>

<!-- Random users colors-->
<color name="randomColor1">#7077FF</color>
<color name="randomColor2">#DBD23C</color>
Expand Down
2 changes: 1 addition & 1 deletion sphinx/screens/chats/chat-common/chat-common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ dependencies {
api project(path: ':sphinx:application:common:logger')
api project(path: ':sphinx:application:common:resources')
api project(path: ':sphinx:application:common:keyboard-inset-fragment')

api project(path: ':sphinx:application:common:wrappers:wrapper-common')
implementation project(path: ':sphinx:application:common:highlighting-tool')

api project(path: ':sphinx:application:data:concepts:concept-image-loader')
api project(path: ':sphinx:application:data:concepts:concept-media-cache')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import chat.sphinx.chat_common.ui.viewstate.selected.SelectedMessageViewState
import chat.sphinx.chat_common.util.*
import chat.sphinx.concept_image_loader.*
import chat.sphinx.concept_user_colors_helper.UserColorsHelper
import chat.sphinx.highlighting_tool.SphinxHighlightingTool
import chat.sphinx.resources.getRandomHexCode
import chat.sphinx.resources.getString
import chat.sphinx.resources.setBackgroundRandomColor
Expand All @@ -40,6 +41,7 @@ import chat.sphinx.wrapper_contact.ContactAlias
import chat.sphinx.wrapper_message.Message
import chat.sphinx.wrapper_message.MessageType
import chat.sphinx.wrapper_view.Px
import com.giphy.sdk.analytics.GiphyPingbacks.context
import io.matthewnelson.android_feature_screens.util.gone
import io.matthewnelson.android_feature_screens.util.goneIfFalse
import io.matthewnelson.android_feature_screens.util.visible
Expand Down Expand Up @@ -775,9 +777,17 @@ internal class MessageListAdapter<ARGS : NavArgs>(

textViewContactMessageHeaderName.text = senderInfo?.second?.value ?: ""
textViewThreadDate.text = threadHeader.timestamp

textViewThreadMessageContent.text = threadHeader.bubbleMessage?.text ?: ""
textViewThreadMessageContent.goneIfFalse(threadHeader.bubbleMessage?.text?.isNotEmpty() == true)

SphinxHighlightingTool.addHighlights(
textViewThreadMessageContent,
threadHeader.bubbleMessage?.highlightedTexts ?: emptyList(),
textViewThreadMessageContent.resources,
textViewThreadMessageContent.context
)

textViewThreadDate.post(Runnable {
val linesCount: Int = textViewThreadDate.lineCount

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import chat.sphinx.chat_common.util.AudioPlayerController
import chat.sphinx.chat_common.util.AudioPlayerControllerImpl
import chat.sphinx.chat_common.util.AudioRecorderController
import chat.sphinx.chat_common.util.SphinxLinkify
import chat.sphinx.highlighting_tool.highlightedTexts
import chat.sphinx.highlighting_tool.replacingHighlightedDelimiters
import chat.sphinx.concept_image_loader.ImageLoaderOptions
import chat.sphinx.concept_link_preview.LinkPreviewHandler
import chat.sphinx.concept_link_preview.model.TribePreviewName
Expand Down Expand Up @@ -381,6 +383,8 @@ abstract class ChatViewModel<ARGS : NavArgs>(
val groupingMinutesLimit = 5.0
var date = groupingDate ?: message.date

val isPreviousMessageThreadHeader = (previousMessage?.uuid?.value == getThreadUUID()?.value && previousMessage?.type?.isGroupAction() == false)

val shouldAvoidGroupingWithPrevious =
(previousMessage?.shouldAvoidGrouping() ?: true) || message.shouldAvoidGrouping()
val isGroupedBySenderWithPrevious =
Expand All @@ -389,7 +393,7 @@ abstract class ChatViewModel<ARGS : NavArgs>(
message.date.getMinutesDifferenceWithDateTime(date) < groupingMinutesLimit

val groupedWithPrevious =
(!shouldAvoidGroupingWithPrevious && isGroupedBySenderWithPrevious && isGroupedByDateWithPrevious)
(!shouldAvoidGroupingWithPrevious && isGroupedBySenderWithPrevious && isGroupedByDateWithPrevious && !isPreviousMessageThreadHeader)

date = if (groupedWithPrevious) date else message.date

Expand Down Expand Up @@ -476,8 +480,6 @@ abstract class ChatViewModel<ARGS : NavArgs>(
openReceivedPaidInvoicesCount > 0
)

val isOwner: Boolean = message.sender == owner.id

val isThreadHeaderMessage = (message.uuid?.value == getThreadUUID()?.value && index == 0 && !message.type.isGroupAction())

if (isThreadHeaderMessage) {
Expand Down Expand Up @@ -567,7 +569,6 @@ abstract class ChatViewModel<ARGS : NavArgs>(
newList.add(
MessageHolderViewState.Sent(
message,

chat,
tribeAdmin,
background = when {
Expand Down Expand Up @@ -928,7 +929,8 @@ abstract class ChatViewModel<ARGS : NavArgs>(

text?.let { nnText ->
messageLayoutState = LayoutState.Bubble.ContainerThird.Message(
text = nnText,
text = nnText.replacingHighlightedDelimiters(),
highlightedTexts = nnText.highlightedTexts(),
decryptionError = false,
isThread = false
)
Expand Down Expand Up @@ -1505,6 +1507,7 @@ abstract class ChatViewModel<ARGS : NavArgs>(
viewState.messageSenderInfo(viewState.message!!),
viewState.timestamp,
viewState.bubbleMessage?.text,
viewState.bubbleMessage?.highlightedTexts,
viewState.bubbleImageAttachment,
viewState.bubbleVideoAttachment,
viewState.bubbleFileAttachment,
Expand Down
Loading
Loading