/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.amazonaws.services.chime.sdkdemo.adapter
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.style.LineBackgroundSpan
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.amazonaws.services.chime.sdk.meetings.audiovideo.TranscriptItemType
import com.amazonaws.services.chime.sdkdemo.R
import com.amazonaws.services.chime.sdkdemo.data.Caption
import com.amazonaws.services.chime.sdkdemo.utils.inflate
import kotlinx.android.synthetic.main.row_caption.view.captionText
import kotlinx.android.synthetic.main.row_caption.view.speakerName
class CaptionAdapter(
private val captions: Collection
) :
RecyclerView.Adapter() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CaptionHolder {
val inflatedView = parent.inflate(R.layout.row_caption, false)
return CaptionHolder(inflatedView)
}
override fun getItemCount(): Int {
return captions.size
}
override fun onBindViewHolder(holder: CaptionHolder, position: Int) {
holder.bindCaption(captions.elementAt(position), position)
}
}
class CaptionHolder(inflatedView: View) :
RecyclerView.ViewHolder(inflatedView) {
companion object {
private const val UPPER_THRESHOLD = 0.3
private const val LOWER_THRESHOLD = 0.0
private const val FILTERED_CAPTION_FIRST_INDEX = "["
}
private var view: View = inflatedView
fun bindCaption(caption: Caption, position: Int) {
val speakerName = caption.speakerName ?: ""
val captionTextBackgroundColor = caption.speakerName?.let { R.color.colorWhite } ?: R.color.colorMissingSpeaker
view.captionText.setBackgroundResource(captionTextBackgroundColor)
view.speakerName.text = speakerName
view.captionText.text = caption.content
val spannable = SpannableString(caption.content)
caption.entities?.let { contents ->
// Highlight PII identified and redacted words.
contents.forEach { word ->
spannable.setSpan(
ForegroundColorSpan(Color.GREEN),
caption.content.indexOf(word),
caption.content.indexOf(word) + word.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
} ?: run { view.captionText.setTextColor(Color.BLACK) }
caption.items?.let { items ->
// Underline words with low confidence score.
items.forEach { item ->
val word = item.content
val hasLowConfidence = item.confidence?.let { it > LOWER_THRESHOLD && it < UPPER_THRESHOLD } ?: run { false }
val isCorrectContentType = !word.startsWith(FILTERED_CAPTION_FIRST_INDEX) && item.type != TranscriptItemType.Punctuation
if (hasLowConfidence && isCorrectContentType && caption.content.contains(word)) {
spannable.setSpan(
CustomUnderlineSpan(Color.RED, caption.content.indexOf(word), caption.content.indexOf(word) + word.length),
caption.content.indexOf(word),
caption.content.indexOf(word) + word.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
view.captionText.text = spannable
view.captionText.contentDescription = "caption-$position"
}
}
private class CustomUnderlineSpan(underlineColor: Int, underlineStart: Int, underlineEnd: Int) : LineBackgroundSpan {
var color: Int
var start: Int
var end: Int
var underlinePaint: Paint
init {
this.color = underlineColor
this.start = underlineStart
this.end = underlineEnd
underlinePaint = Paint()
underlinePaint.setColor(color)
underlinePaint.strokeWidth = 3F
underlinePaint.style = Paint.Style.FILL_AND_STROKE
}
override fun drawBackground(
canvas: Canvas,
paint: Paint,
left: Int,
right: Int,
top: Int,
baseline: Int,
bottom: Int,
text: CharSequence,
start: Int,
end: Int,
lnum: Int
) {
if (this.end <= start) return
if (this.start >= end || this.start < 0) return
var offsetX = 0
if (this.start > start) {
offsetX = paint.measureText(text.subSequence(start, this.start).toString()).toInt()
}
val length = paint.measureText(text.subSequence(
Math.max(start, this.start),
Math.min(end, this.end)
).toString()).toInt()
canvas.drawLine(offsetX.toFloat(), baseline + 3F, (length + offsetX).toFloat(), baseline + 3F, this.underlinePaint)
}
}