From 10cf182213625fe23cf0f7c644c86cd77c7fe1d8 Mon Sep 17 00:00:00 2001 From: DatLag Date: Tue, 16 Apr 2024 16:30:07 +0200 Subject: [PATCH] added description translation --- composeApp/build.gradle.kts | 2 + .../component/TranslateButton.android.kt | 105 ++++++++++++++++++ .../screen/medium/MediumComponent.kt | 2 + .../navigation/screen/medium/MediumScreen.kt | 23 +++- .../screen/medium/MediumScreenComponent.kt | 8 ++ .../medium/component/TranslateButton.kt | 6 + .../moko-resources/base/strings.xml | 2 + .../medium/component/TranslateButton.ios.kt | 7 ++ gradle/libs.versions.toml | 2 + 9 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.android.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.kt create mode 100644 composeApp/src/iosMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.ios.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 6599ff8..449cd39 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -138,6 +138,8 @@ kotlin { implementation(libs.coroutines.android) implementation(libs.android.credentials.play.services) + + implementation(libs.translate) } } } diff --git a/composeApp/src/androidMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.android.kt b/composeApp/src/androidMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.android.kt new file mode 100644 index 0000000..1fe0285 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.android.kt @@ -0,0 +1,105 @@ +package dev.datlag.aniflow.ui.navigation.screen.medium.component + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Translate +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import com.google.mlkit.common.model.DownloadConditions +import com.google.mlkit.nl.translate.TranslateLanguage +import com.google.mlkit.nl.translate.Translation +import com.google.mlkit.nl.translate.TranslatorOptions +import dev.datlag.aniflow.SharedRes +import dev.icerock.moko.resources.compose.stringResource +import io.github.aakira.napier.Napier +import java.util.Locale + +@Composable +actual fun TranslateButton(text: String, onTranslation: (String?) -> Unit) { + val locale = remember { Locale.getDefault() } + if (locale.language.equals(Locale.forLanguageTag("en").language, ignoreCase = true)) { + Napier.e("Language is english") + return + } + if (locale.toLanguageTag().equals("en", ignoreCase = true)) { + Napier.e("LanguageTag is english") + return + } + if (locale.isO3Language.equals("ENG", ignoreCase = true)) { + Napier.e("Language ISO is english") + return + } + + val targetLanguage = remember(locale) { + TranslateLanguage.fromLanguageTag(locale.toLanguageTag()) + ?: TranslateLanguage.fromLanguageTag(locale.language) + ?: TranslateLanguage.fromLanguageTag(locale.isO3Language) + } + + if (targetLanguage == null || targetLanguage == TranslateLanguage.ENGLISH) { + Napier.e("TargetLanguage is: $targetLanguage, ${locale.toLanguageTag()}") + return + } + + val options = remember(targetLanguage) { + TranslatorOptions.Builder() + .setSourceLanguage(TranslateLanguage.ENGLISH) + .setTargetLanguage(targetLanguage) + .build() + } + val englishLocaleTranslator = remember(options) { Translation.getClient(options) } + val downloadConditions = remember { + DownloadConditions.Builder() + .requireWifi() + .build() + } + var enabled by remember { mutableStateOf(true) } + var translated by remember { mutableStateOf(false) } + + TextButton( + onClick = { + if (translated) { + translated = false + onTranslation(null) + } else { + englishLocaleTranslator + .downloadModelIfNeeded(downloadConditions) + .addOnFailureListener { + enabled = false + }.addOnSuccessListener { + enabled = true + + englishLocaleTranslator + .translate(text) + .addOnFailureListener { + translated = false + onTranslation(null) + }.addOnSuccessListener { + translated = true + onTranslation(it) + } + } + } + }, + enabled = enabled + ) { + Icon( + modifier = Modifier.size(ButtonDefaults.IconSize), + imageVector = Icons.Default.Translate, + contentDescription = null + ) + Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) + Text(stringResource(SharedRes.strings.translate)) + } + + DisposableEffect(englishLocaleTranslator) { + onDispose { + englishLocaleTranslator.close() + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt index 96da36b..f053fdb 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumComponent.kt @@ -14,6 +14,7 @@ interface MediumComponent : ContentHolderComponent { val coverImage: StateFlow val title: StateFlow val description: StateFlow + val translatedDescription: StateFlow val genres: StateFlow> val format: StateFlow @@ -36,4 +37,5 @@ interface MediumComponent : ContentHolderComponent { } fun rate(onLoggedIn: () -> Unit) fun rate(value: Int) + fun descriptionTranslation(text: String?) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt index ff92dff..46a6222 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt @@ -56,6 +56,7 @@ import dev.datlag.aniflow.other.StateSaver import dev.datlag.aniflow.ui.custom.EditFAB import dev.datlag.aniflow.ui.navigation.screen.initial.home.component.GenreChip import dev.datlag.aniflow.ui.navigation.screen.medium.component.CharacterCard +import dev.datlag.aniflow.ui.navigation.screen.medium.component.TranslateButton import dev.datlag.tooling.compose.onClick import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle import dev.icerock.moko.resources.compose.painterResource @@ -405,13 +406,23 @@ fun MediumScreen(component: MediumComponent) { } if (!description.isNullOrBlank()) { item { - Text( - modifier = Modifier.padding(top = 16.dp).padding(horizontal = 16.dp), - text = "Description", - style = MaterialTheme.typography.headlineSmall - ) + Row( + modifier = Modifier.fillParentMaxWidth().padding(top = 16.dp).padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + modifier = Modifier.weight(1F), + text = stringResource(SharedRes.strings.description), + style = MaterialTheme.typography.headlineSmall + ) + TranslateButton(description!!) { text -> + component.descriptionTranslation(text) + } + } } item { + val translatedDescription by component.translatedDescription.collectAsStateWithLifecycle() val animatedLines by animateIntAsState( targetValue = if (descriptionExpanded) { Int.MAX_VALUE @@ -425,7 +436,7 @@ fun MediumScreen(component: MediumComponent) { modifier = Modifier.padding(horizontal = 16.dp).onClick { descriptionExpanded = !descriptionExpanded }, - text = description!!.htmlToAnnotatedString(), + text = (translatedDescription ?: description)!!.htmlToAnnotatedString(), maxLines = max(animatedLines, 1), softWrap = true, overflow = TextOverflow.Ellipsis, diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt index 875bfab..043eafb 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreenComponent.kt @@ -108,6 +108,8 @@ class MediumScreenComponent( initialValue = null ) + override val translatedDescription: MutableStateFlow = MutableStateFlow(null) + override val genres: StateFlow> = mediumSuccessState.mapNotNull { it?.data?.genres?.ifEmpty { null } }.flowOn( @@ -365,4 +367,10 @@ class MediumScreenComponent( } } } + + override fun descriptionTranslation(text: String?) { + launchIO { + translatedDescription.emit(text) + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.kt new file mode 100644 index 0000000..1c32442 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.kt @@ -0,0 +1,6 @@ +package dev.datlag.aniflow.ui.navigation.screen.medium.component + +import androidx.compose.runtime.Composable + +@Composable +expect fun TranslateButton(text: String, onTranslation: (String?) -> Unit) \ No newline at end of file diff --git a/composeApp/src/commonMain/moko-resources/base/strings.xml b/composeApp/src/commonMain/moko-resources/base/strings.xml index 32eaf16..12bc249 100644 --- a/composeApp/src/commonMain/moko-resources/base/strings.xml +++ b/composeApp/src/commonMain/moko-resources/base/strings.xml @@ -21,4 +21,6 @@ Rated Popular Score + Translate + Description diff --git a/composeApp/src/iosMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.ios.kt b/composeApp/src/iosMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.ios.kt new file mode 100644 index 0000000..c67881f --- /dev/null +++ b/composeApp/src/iosMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/TranslateButton.ios.kt @@ -0,0 +1,7 @@ +package dev.datlag.aniflow.ui.navigation.screen.medium.component + +import androidx.compose.runtime.Composable + +@Composable +actual fun TranslateButton(text: String, onTranslation: (String?) -> Unit) { +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9b3e0e0..69e7783 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,6 +43,7 @@ sekret = "2.0.0-alpha-04" serialization = "1.6.3" splashscreen = "1.0.1" tooling = "1.3.2" +translate = "17.0.2" versions = "0.51.0" windowsize = "0.5.0" @@ -105,6 +106,7 @@ splashscreen = { group = "androidx.core", name = "core-splashscreen", version.re tooling = { group = "dev.datlag.tooling", name = "tooling-async", version.ref = "tooling" } tooling-country = { group = "dev.datlag.tooling", name = "tooling-country", version.ref = "tooling" } tooling-decompose = { group = "dev.datlag.tooling", name = "tooling-decompose", version.ref = "tooling" } +translate = { group = "com.google.mlkit", name = "translate", version.ref = "translate" } windowsize = { group = "dev.chrisbanes.material3", name = "material3-window-size-class-multiplatform", version.ref = "windowsize" } [plugins]