Skip to content

Instantly share code, notes, and snippets.

@surajsau
Last active December 2, 2021 13:21
Show Gist options
  • Save surajsau/33a5730466874a7f4b6d1bb07b22e896 to your computer and use it in GitHub Desktop.
Save surajsau/33a5730466874a7f4b6d1bb07b22e896 to your computer and use it in GitHub Desktop.
Google Translate Draw with Jetpack Compose
This is a sample note since first file isn't being recognised in Medium.
sealed class DrawEvent {
// MotionEvent.ACTION_DOWN
data class Down(val x: Float, val y: Float): DrawEvent()
// MotionEvent.ACTION_MOVE
data class Move(val x: Float, val y: Float): DrawEvent()
// MotionEvent.ACTION_UP
object Up: DrawEvent()
}
@Composable
fun TranslateScreen() {
..
DrawSpace(
onDrawEvent = { event -> .. }
)
}
@Composable
fun DrawSpace(
onDrawEvent: (DrawEvent) -> Unit,
..
) {
..
Canvas(
modifier = modifier
.pointerInteropFilter { event ->
val drawEvent = when (event.action) {
MotionEvent.ACTION_DOWN -> DrawEvent.Down(event.x, event.y)
MotionEvent.ACTION_MOVE -> DrawEvent.Move(event.x, event.y)
MotionEvent.ACTION_UP -> DrawEvent.Up
else -> null
}
drawEvent?.let { onDrawEvent.invoke(it) }
return true
}
) {..}
}
private val remoteModelManager = RemoteModelManager.getInstance()
fun checkModelAvailability() {
this.remoteModelManager
.isModelDownloaded(this.recognitionModel)
.addOnSuccessListener { isDownloaded ->
if (isDownloaded)
// model already available locally
else
// model needs to be downloaded
}
private var recordStrokeJob: Job? = null
private const val DEBOUNCE_DELAY = 500L
when (event) {
/*
cancel previously running Job to prevent it
while in delay()
*/
is DrawEvent.Down -> {
recordStrokeJob?.cancel()
..
},
/*
add a delay before DigitalInkRecognizer.recognizer()
*/
is DrawEvent.Up -> {
..
this.recordStrokeJob = /* CoroutineScope */.launch {
delay(DEBOUNCE_DELAY)
recognizer.recognize(..)
}
}
}
val stroke: Ink.Stroke = this.strokeBuilder.build()
val inkBuilder = Ink.builder()
inkBuilder.addStroke(stroke)
this.recognizer.recognize(inkBuilder.build())
.addOnCompleteListener { .. }
.addOnSuccessListener { result -> .. }
.addOnFailureListener { .. }
fun downloadModel() {
val downloadConditions = DownloadConditions.Builder()
/**
add download conditions if required
.requireWifi()
.requireCharging()
*/
.build()
this.remoteModelManager
.downloadModel(this.recognitionModel, downloadConditions)
.addOnSuccessListener {
// model has been downloaded and is now available locally
}
}
private sealed class DrawEvent {
data class MoveTo(val x: Float, val y: Float): DrawEvent()
data class CurveTo(val prevX: Float, val prevY: Float, val x: Float, val y: Float): DrawEvent()
}
@Composable
fun DrawSpace(
..
) {
val path = remember { Path() }
var drawEvent by remember { mutableStateOf<DrawEvent?>(null) }
Canvas(
modifier = Modifier
.pointerInteropFilter { event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
drawPath = DrawPath.MoveTo(event.x, event.y)
}
MotionEvent.ACTION_MOVE -> {
val prevX = when (drawEvent) {
is DrawPath.MoveTo -> (drawPath as DrawPath.MoveTo).x
is DrawPath.CurveTo -> (drawPath as DrawPath.CurveTo).x
}
val prevY = when (drawEvent) { .. }
drawPath = DrawPath.CurveTo(prevX, prevY, event.x, event.y)
}
else -> { /* do nothing */ }
}
return true
}
) {
if (drawEvent == null)
return
when (drawEvent) {
is DrawEvent.MoveTo -> {
// start drawing a new subpath
val (x, y) = drawPath as DrawPath.MoveTo
path.moveTo(x, y)
}
is DrawEvent.CurveTo -> {
// continue drawing the subpath as a smooth curve
val (prevX, prevY, x, y) = drawPath as DrawPath.CurveTo
path.quadraticBezierTo(prevX, prevY, (x + prevX)/2, (y + prevY)/2)
}
}
drawPath(path = path, ..)
}
}
private val recognitionModel: DigitalInkRecognitionModel
= DigitalInkRecognitionModel
.builder(DigitalInkRecognitionModelIdentifier.JA) // for Japanese language
.build()
private val recognizer: DigitalInkRecognizer
= DigitalInkRecognition.getClient(
DigitalInkRecognizerOptions
.builder(this.recognitionModel)
.build()
)
private var strokeBuilder: Ink.Stroke.Builder = Ink.Stroke.builder()
when (event) {
/*
add coordinates obtained from MotionEvent.ACTION_MOVE,
MotionEvent.ACTION_DOWN to Ink.Stroke
*/
is DrawEvent.Down, is DrawEvent.Move -> {
val point = Ink.Point.create(event.x, event.y)
strokeBuilder.addPoint(point)
}
is DrawEvent.Up -> {
// build stroke from the drawn points
val stroke = strokeBuilder.build()
val inkBuilder = Ink.builder()
inkBuilder.addStroke(stroke)
recognizer.recognize(inkBuilder.build())
.addOnCompleteListener {
// reset strokeBuilder for further drawings
strokeBuilder = Ink.Stroke.builder()
}
.addOnSuccessListener { result ->
val predictions = result.candidates.map { it.text }
// choose first value the main prediction
// display other predictions as suggestions if required
}
}
}
}
@Composable
fun TranslateScreen() {
val lifecycleOwner = LocalLifecyleOwner.current
DisposableEffect(Unit) {
val lifecycleObserver = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_STOP) {
// close Translator & Recognizer here
}
}
lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
onDispose { lifecycleOwner.lifecycle.removeObserver(lifecycleObserver) }
}
}
fun record(event: MotionEvent) {
val point = Ink.Point.create(event.x, event.y)
this.strokeBuilder.addPoint(point)
}
private val translator: Translator
= Translation.getClient(
TranslatorOptions.Builder()
.setSourceLanguage(TranslateLanguage.JAPANESE)
.setTargetLanguage(TranslateLanguage.ENGLISH)
.build()
)
fun checkIfModelIsDownloaded() {
val downloadConditions = DownloadConditions.Builder()
..
.build()
this.translator.downloadModelIfNeeded(downloadConditions)
.addOnSuccessListener { /* model is available locally now */ }
}
fun translate(text: String) {
this.translator.translate(text)
.addOnSuccessListener { result -> /* string result is returned */ }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment