Reverts to the non-Google kotlin style.

This commit is contained in:
Vitor Pamplona
2024-01-06 11:32:41 -05:00
parent 0be1f89368
commit f8e7dd78d9
415 changed files with 64333 additions and 64218 deletions
@@ -38,176 +38,178 @@ import java.util.regex.Pattern
@Immutable
data class ResultOrError(
val result: String?,
val sourceLang: String?,
val targetLang: String?,
val result: String?,
val sourceLang: String?,
val targetLang: String?,
)
object LanguageTranslatorService {
var executorService = Executors.newScheduledThreadPool(5)
var executorService = Executors.newScheduledThreadPool(5)
private val options =
LanguageIdentificationOptions.Builder()
.setExecutor(executorService)
.setConfidenceThreshold(0.6f)
.build()
private val languageIdentification = LanguageIdentification.getClient(options)
val lnRegex = Pattern.compile("\\blnbc[a-z0-9]+\\b", Pattern.CASE_INSENSITIVE)
val tagRegex =
Pattern.compile(
"(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)",
Pattern.CASE_INSENSITIVE,
)
private val options =
LanguageIdentificationOptions.Builder()
.setExecutor(executorService)
.setConfidenceThreshold(0.6f)
.build()
private val languageIdentification = LanguageIdentification.getClient(options)
val lnRegex = Pattern.compile("\\blnbc[a-z0-9]+\\b", Pattern.CASE_INSENSITIVE)
val tagRegex =
Pattern.compile(
"(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)",
Pattern.CASE_INSENSITIVE,
)
private val translators =
object : LruCache<TranslatorOptions, Translator>(3) {
override fun create(options: TranslatorOptions): Translator {
return Translation.getClient(options)
}
private val translators =
object : LruCache<TranslatorOptions, Translator>(3) {
override fun create(options: TranslatorOptions): Translator {
return Translation.getClient(options)
}
override fun entryRemoved(
evicted: Boolean,
key: TranslatorOptions,
oldValue: Translator,
newValue: Translator?,
) {
oldValue.close()
}
}
fun clear() {
translators.evictAll()
}
fun identifyLanguage(text: String): Task<String> {
return languageIdentification.identifyLanguage(text)
}
fun translate(
text: String,
source: String,
target: String,
): Task<ResultOrError> {
checkNotInMainThread()
val sourceLangCode = TranslateLanguage.fromLanguageTag(source)
val targetLangCode = TranslateLanguage.fromLanguageTag(target)
if (sourceLangCode == null || targetLangCode == null) {
return Tasks.forCanceled()
}
val options =
TranslatorOptions.Builder()
.setExecutor(executorService)
.setSourceLanguage(sourceLangCode)
.setTargetLanguage(targetLangCode)
.build()
val translator = translators[options]
return translator.downloadModelIfNeeded().onSuccessTask(executorService) {
checkNotInMainThread()
val tasks = mutableListOf<Task<String>>()
val dict = lnDictionary(text) + urlDictionary(text) + tagDictionary(text)
for (paragraph in encodeDictionary(text, dict).split("\n")) {
tasks.add(translator.translate(paragraph))
}
Tasks.whenAll(tasks).continueWith(executorService) {
checkNotInMainThread()
val results: MutableList<String> = ArrayList()
for (task in tasks) {
val fixedText =
task.result.replace("# [", "#[") // fixes tags that always return with a space
results.add(decodeDictionary(fixedText, dict))
override fun entryRemoved(
evicted: Boolean,
key: TranslatorOptions,
oldValue: Translator,
newValue: Translator?,
) {
oldValue.close()
}
}
ResultOrError(results.joinToString("\n"), source, target)
}
fun clear() {
translators.evictAll()
}
}
private fun encodeDictionary(
text: String,
dict: Map<String, String>,
): String {
var newText = text
for (pair in dict) {
newText = newText.replace(pair.value, pair.key, true)
fun identifyLanguage(text: String): Task<String> {
return languageIdentification.identifyLanguage(text)
}
return newText
}
private fun decodeDictionary(
text: String,
dict: Map<String, String>,
): String {
var newText = text
for (pair in dict) {
newText = newText.replace(pair.key, pair.value, true)
fun translate(
text: String,
source: String,
target: String,
): Task<ResultOrError> {
checkNotInMainThread()
val sourceLangCode = TranslateLanguage.fromLanguageTag(source)
val targetLangCode = TranslateLanguage.fromLanguageTag(target)
if (sourceLangCode == null || targetLangCode == null) {
return Tasks.forCanceled()
}
val options =
TranslatorOptions.Builder()
.setExecutor(executorService)
.setSourceLanguage(sourceLangCode)
.setTargetLanguage(targetLangCode)
.build()
val translator = translators[options]
return translator.downloadModelIfNeeded().onSuccessTask(executorService) {
checkNotInMainThread()
val tasks = mutableListOf<Task<String>>()
val dict = lnDictionary(text) + urlDictionary(text) + tagDictionary(text)
for (paragraph in encodeDictionary(text, dict).split("\n")) {
tasks.add(translator.translate(paragraph))
}
Tasks.whenAll(tasks).continueWith(executorService) {
checkNotInMainThread()
val results: MutableList<String> = ArrayList()
for (task in tasks) {
val fixedText =
task.result.replace("# [", "#[") // fixes tags that always return with a space
results.add(decodeDictionary(fixedText, dict))
}
ResultOrError(results.joinToString("\n"), source, target)
}
}
}
return newText
}
private fun tagDictionary(text: String): Map<String, String> {
val matcher = tagRegex.matcher(text)
val returningList = mutableMapOf<String, String>()
var counter = 0
while (matcher.find()) {
try {
val tag = matcher.group()
val short = "A$counter"
counter++
returningList.put(short, tag)
} catch (_: Exception) {}
private fun encodeDictionary(
text: String,
dict: Map<String, String>,
): String {
var newText = text
for (pair in dict) {
newText = newText.replace(pair.value, pair.key, true)
}
return newText
}
return returningList
}
private fun lnDictionary(text: String): Map<String, String> {
val matcher = lnRegex.matcher(text)
val returningList = mutableMapOf<String, String>()
var counter = 0
while (matcher.find()) {
try {
val lnInvoice = matcher.group()
val short = "A$counter"
counter++
returningList.put(short, lnInvoice)
} catch (_: Exception) {}
private fun decodeDictionary(
text: String,
dict: Map<String, String>,
): String {
var newText = text
for (pair in dict) {
newText = newText.replace(pair.key, pair.value, true)
}
return newText
}
return returningList
}
private fun urlDictionary(text: String): Map<String, String> {
val parser = UrlDetector(text, UrlDetectorOptions.Default)
val urlsInText = parser.detect()
var counter = 0
return urlsInText
.filter { !it.originalUrl.contains("") && !it.originalUrl.contains("") }
.associate {
counter++
"A$counter" to it.originalUrl
}
}
fun autoTranslate(
text: String,
dontTranslateFrom: Set<String>,
translateTo: String,
): Task<ResultOrError> {
return identifyLanguage(text).onSuccessTask(executorService) {
if (it.equals(translateTo, true)) {
Tasks.forCanceled()
} else if (it != "und" && !dontTranslateFrom.contains(it)) {
translate(text, it, translateTo)
} else {
Tasks.forCanceled()
}
private fun tagDictionary(text: String): Map<String, String> {
val matcher = tagRegex.matcher(text)
val returningList = mutableMapOf<String, String>()
var counter = 0
while (matcher.find()) {
try {
val tag = matcher.group()
val short = "A$counter"
counter++
returningList.put(short, tag)
} catch (_: Exception) {
}
}
return returningList
}
private fun lnDictionary(text: String): Map<String, String> {
val matcher = lnRegex.matcher(text)
val returningList = mutableMapOf<String, String>()
var counter = 0
while (matcher.find()) {
try {
val lnInvoice = matcher.group()
val short = "A$counter"
counter++
returningList.put(short, lnInvoice)
} catch (_: Exception) {
}
}
return returningList
}
private fun urlDictionary(text: String): Map<String, String> {
val parser = UrlDetector(text, UrlDetectorOptions.Default)
val urlsInText = parser.detect()
var counter = 0
return urlsInText
.filter { !it.originalUrl.contains("") && !it.originalUrl.contains("") }
.associate {
counter++
"A$counter" to it.originalUrl
}
}
fun autoTranslate(
text: String,
dontTranslateFrom: Set<String>,
translateTo: String,
): Task<ResultOrError> {
return identifyLanguage(text).onSuccessTask(executorService) {
if (it.equals(translateTo, true)) {
Tasks.forCanceled()
} else if (it != "und" && !dontTranslateFrom.contains(it)) {
translate(text, it, translateTo)
} else {
Tasks.forCanceled()
}
}
}
}
}
@@ -31,65 +31,65 @@ import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrC
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.GiftWrapEvent
import kotlin.time.measureTimedValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlin.time.measureTimedValue
class PushNotificationReceiverService : FirebaseMessagingService() {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val eventCache = LruCache<String, String>(100)
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val eventCache = LruCache<String, String>(100)
// this is called when a message is received
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Log.d("Time", "Notification received $remoteMessage")
scope.launch(Dispatchers.IO) {
val (value, elapsed) =
measureTimedValue { parseMessage(remoteMessage.data)?.let { receiveIfNew(it) } }
Log.d("Time", "Notification processed in $elapsed")
// this is called when a message is received
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Log.d("Time", "Notification received $remoteMessage")
scope.launch(Dispatchers.IO) {
val (value, elapsed) =
measureTimedValue { parseMessage(remoteMessage.data)?.let { receiveIfNew(it) } }
Log.d("Time", "Notification processed in $elapsed")
}
}
}
private suspend fun parseMessage(params: Map<String, String>): GiftWrapEvent? {
params["encryptedEvent"]?.let { eventStr ->
(Event.fromJson(eventStr) as? GiftWrapEvent)?.let {
return it
}
private suspend fun parseMessage(params: Map<String, String>): GiftWrapEvent? {
params["encryptedEvent"]?.let { eventStr ->
(Event.fromJson(eventStr) as? GiftWrapEvent)?.let {
return it
}
}
return null
}
return null
}
private suspend fun receiveIfNew(event: GiftWrapEvent) {
if (eventCache.get(event.id) == null) {
eventCache.put(event.id, event.id)
EventNotificationConsumer(applicationContext).consume(event)
private suspend fun receiveIfNew(event: GiftWrapEvent) {
if (eventCache.get(event.id) == null) {
eventCache.put(event.id, event.id)
EventNotificationConsumer(applicationContext).consume(event)
}
}
}
override fun onCreate() {
super.onCreate()
Log.d("Lifetime Event", "PushNotificationReceiverService.onCreate")
}
override fun onDestroy() {
Log.d("Lifetime Event", "PushNotificationReceiverService.onDestroy")
scope.cancel()
super.onDestroy()
}
override fun onNewToken(token: String) {
scope.launch(Dispatchers.IO) {
RegisterAccounts(LocalPreferences.allSavedAccounts()).go(token)
notificationManager().getOrCreateZapChannel(applicationContext)
notificationManager().getOrCreateDMChannel(applicationContext)
override fun onCreate() {
super.onCreate()
Log.d("Lifetime Event", "PushNotificationReceiverService.onCreate")
}
}
fun notificationManager(): NotificationManager {
return ContextCompat.getSystemService(applicationContext, NotificationManager::class.java)
as NotificationManager
}
override fun onDestroy() {
Log.d("Lifetime Event", "PushNotificationReceiverService.onDestroy")
scope.cancel()
super.onDestroy()
}
override fun onNewToken(token: String) {
scope.launch(Dispatchers.IO) {
RegisterAccounts(LocalPreferences.allSavedAccounts()).go(token)
notificationManager().getOrCreateZapChannel(applicationContext)
notificationManager().getOrCreateDMChannel(applicationContext)
}
}
fun notificationManager(): NotificationManager {
return ContextCompat.getSystemService(applicationContext, NotificationManager::class.java)
as NotificationManager
}
}
@@ -27,18 +27,18 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.tasks.await
object PushNotificationUtils {
var hasInit: Boolean = false
var hasInit: Boolean = false
suspend fun init(accounts: List<AccountInfo>) =
with(Dispatchers.IO) {
if (hasInit) {
return@with
}
// get user notification token provided by firebase
try {
RegisterAccounts(accounts).go(FirebaseMessaging.getInstance().token.await())
} catch (e: Exception) {
Log.e("Firebase token", "failed to get firebase token", e)
}
}
suspend fun init(accounts: List<AccountInfo>) =
with(Dispatchers.IO) {
if (hasInit) {
return@with
}
// get user notification token provided by firebase
try {
RegisterAccounts(accounts).go(FirebaseMessaging.getInstance().token.await())
} catch (e: Exception) {
Log.e("Firebase token", "failed to get firebase token", e)
}
}
}
@@ -28,7 +28,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.CheckifItNeedsToRequestNoti
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesViewModel) {
CheckifItNeedsToRequestNotificationPermission(sharedPreferencesViewModel)
CheckifItNeedsToRequestNotificationPermission(sharedPreferencesViewModel)
}
@Composable
@@ -62,316 +62,317 @@ import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Locale
@Composable
fun TranslatableRichTextViewer(
content: String,
canPreview: Boolean,
modifier: Modifier = Modifier,
tags: ImmutableListOfLists<String>,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
content: String,
canPreview: Boolean,
modifier: Modifier = Modifier,
tags: ImmutableListOfLists<String>,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
var translatedTextState by
remember(content) { mutableStateOf(TranslationConfig(content, null, null, false)) }
var translatedTextState by
remember(content) { mutableStateOf(TranslationConfig(content, null, null, false)) }
TranslateAndWatchLanguageChanges(content, accountViewModel) { result ->
if (
!translatedTextState.result.equals(result.result, true) ||
translatedTextState.sourceLang != result.sourceLang ||
translatedTextState.targetLang != result.targetLang
) {
translatedTextState = result
TranslateAndWatchLanguageChanges(content, accountViewModel) { result ->
if (
!translatedTextState.result.equals(result.result, true) ||
translatedTextState.sourceLang != result.sourceLang ||
translatedTextState.targetLang != result.targetLang
) {
translatedTextState = result
}
}
}
Crossfade(targetState = translatedTextState) {
RenderText(
it,
content,
canPreview,
modifier,
tags,
backgroundColor,
accountViewModel,
nav,
)
}
Crossfade(targetState = translatedTextState) {
RenderText(
it,
content,
canPreview,
modifier,
tags,
backgroundColor,
accountViewModel,
nav,
)
}
}
@Composable
private fun RenderText(
translatedTextState: TranslationConfig,
content: String,
canPreview: Boolean,
modifier: Modifier,
tags: ImmutableListOfLists<String>,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
translatedTextState: TranslationConfig,
content: String,
canPreview: Boolean,
modifier: Modifier,
tags: ImmutableListOfLists<String>,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
var showOriginal by
remember(translatedTextState) { mutableStateOf(translatedTextState.showOriginal) }
var showOriginal by
remember(translatedTextState) { mutableStateOf(translatedTextState.showOriginal) }
val toBeViewed by
remember(translatedTextState) {
derivedStateOf { if (showOriginal) content else translatedTextState.result ?: content }
val toBeViewed by
remember(translatedTextState) {
derivedStateOf { if (showOriginal) content else translatedTextState.result ?: content }
}
Column {
ExpandableRichTextViewer(
toBeViewed,
canPreview,
modifier,
tags,
backgroundColor,
accountViewModel,
nav,
)
if (
translatedTextState.sourceLang != null &&
translatedTextState.targetLang != null &&
translatedTextState.sourceLang != translatedTextState.targetLang
) {
TranslationMessage(
translatedTextState.sourceLang,
translatedTextState.targetLang,
accountViewModel,
) {
showOriginal = it
}
}
}
Column {
ExpandableRichTextViewer(
toBeViewed,
canPreview,
modifier,
tags,
backgroundColor,
accountViewModel,
nav,
)
if (
translatedTextState.sourceLang != null &&
translatedTextState.targetLang != null &&
translatedTextState.sourceLang != translatedTextState.targetLang
) {
TranslationMessage(
translatedTextState.sourceLang,
translatedTextState.targetLang,
accountViewModel,
) {
showOriginal = it
}
}
}
}
@Composable
private fun TranslationMessage(
source: String,
target: String,
accountViewModel: AccountViewModel,
onChangeWhatToShow: (Boolean) -> Unit,
source: String,
target: String,
accountViewModel: AccountViewModel,
onChangeWhatToShow: (Boolean) -> Unit,
) {
var langSettingsPopupExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
var langSettingsPopupExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Row(
modifier = Modifier.fillMaxWidth().padding(top = 5.dp),
) {
val clickableTextStyle = SpanStyle(color = MaterialTheme.colorScheme.lessImportantLink)
val annotatedTranslationString = buildAnnotatedString {
withStyle(clickableTextStyle) {
pushStringAnnotation("langSettings", true.toString())
append(stringResource(R.string.translations_auto))
}
append("-${stringResource(R.string.translations_translated_from)} ")
withStyle(clickableTextStyle) {
pushStringAnnotation("showOriginal", true.toString())
append(Locale(source).displayName)
}
append(" ${stringResource(R.string.translations_to)} ")
withStyle(clickableTextStyle) {
pushStringAnnotation("showOriginal", false.toString())
append(Locale(target).displayName)
}
}
ClickableText(
text = annotatedTranslationString,
style =
LocalTextStyle.current.copy(
color =
MaterialTheme.colorScheme.onSurface.copy(
alpha = 0.32f,
),
),
overflow = TextOverflow.Visible,
maxLines = 3,
) { spanOffset ->
annotatedTranslationString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also {
span ->
if (span.tag == "showOriginal") {
onChangeWhatToShow(span.item.toBoolean())
} else {
langSettingsPopupExpanded = !langSettingsPopupExpanded
}
}
}
DropdownMenu(
expanded = langSettingsPopupExpanded,
onDismissRequest = { langSettingsPopupExpanded = false },
Row(
modifier = Modifier.fillMaxWidth().padding(top = 5.dp),
) {
DropdownMenuItem(
text = {
if (source in accountViewModel.account.dontTranslateFrom) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
val clickableTextStyle = SpanStyle(color = MaterialTheme.colorScheme.lessImportantLink)
Spacer(modifier = Modifier.size(10.dp))
val annotatedTranslationString =
buildAnnotatedString {
withStyle(clickableTextStyle) {
pushStringAnnotation("langSettings", true.toString())
append(stringResource(R.string.translations_auto))
}
Text(
stringResource(
R.string.translations_never_translate_from_lang,
Locale(source).displayName,
),
)
},
onClick = {
scope.launch(Dispatchers.IO) {
accountViewModel.dontTranslateFrom(source)
langSettingsPopupExpanded = false
}
},
)
Divider()
DropdownMenuItem(
text = {
if (accountViewModel.account.preferenceBetween(source, target) == source) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
append("-${stringResource(R.string.translations_translated_from)} ")
Spacer(modifier = Modifier.size(10.dp))
withStyle(clickableTextStyle) {
pushStringAnnotation("showOriginal", true.toString())
append(Locale(source).displayName)
}
Text(
stringResource(
R.string.translations_show_in_lang_first,
Locale(source).displayName,
),
)
},
onClick = {
scope.launch(Dispatchers.IO) {
accountViewModel.prefer(source, target, source)
langSettingsPopupExpanded = false
}
},
)
DropdownMenuItem(
text = {
if (accountViewModel.account.preferenceBetween(source, target) == target) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
append(" ${stringResource(R.string.translations_to)} ")
Spacer(modifier = Modifier.size(10.dp))
withStyle(clickableTextStyle) {
pushStringAnnotation("showOriginal", false.toString())
append(Locale(target).displayName)
}
}
Text(
stringResource(
R.string.translations_show_in_lang_first,
Locale(target).displayName,
),
)
},
onClick = {
scope.launch(Dispatchers.IO) {
accountViewModel.prefer(source, target, target)
langSettingsPopupExpanded = false
}
},
)
Divider()
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().configuration)
for (i in 0 until languageList.size()) {
languageList.get(i)?.let { lang ->
DropdownMenuItem(
text = {
if (lang.language in accountViewModel.account.translateTo) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
Spacer(modifier = Modifier.size(10.dp))
Text(
stringResource(
R.string.translations_always_translate_to_lang,
lang.displayName,
ClickableText(
text = annotatedTranslationString,
style =
LocalTextStyle.current.copy(
color =
MaterialTheme.colorScheme.onSurface.copy(
alpha = 0.32f,
),
),
)
},
onClick = {
scope.launch(Dispatchers.IO) {
accountViewModel.translateTo(lang)
langSettingsPopupExpanded = false
}
},
)
overflow = TextOverflow.Visible,
maxLines = 3,
) { spanOffset ->
annotatedTranslationString.getStringAnnotations(spanOffset, spanOffset).firstOrNull()?.also {
span ->
if (span.tag == "showOriginal") {
onChangeWhatToShow(span.item.toBoolean())
} else {
langSettingsPopupExpanded = !langSettingsPopupExpanded
}
}
}
DropdownMenu(
expanded = langSettingsPopupExpanded,
onDismissRequest = { langSettingsPopupExpanded = false },
) {
DropdownMenuItem(
text = {
if (source in accountViewModel.account.dontTranslateFrom) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
Spacer(modifier = Modifier.size(10.dp))
Text(
stringResource(
R.string.translations_never_translate_from_lang,
Locale(source).displayName,
),
)
},
onClick = {
scope.launch(Dispatchers.IO) {
accountViewModel.dontTranslateFrom(source)
langSettingsPopupExpanded = false
}
},
)
Divider()
DropdownMenuItem(
text = {
if (accountViewModel.account.preferenceBetween(source, target) == source) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
Spacer(modifier = Modifier.size(10.dp))
Text(
stringResource(
R.string.translations_show_in_lang_first,
Locale(source).displayName,
),
)
},
onClick = {
scope.launch(Dispatchers.IO) {
accountViewModel.prefer(source, target, source)
langSettingsPopupExpanded = false
}
},
)
DropdownMenuItem(
text = {
if (accountViewModel.account.preferenceBetween(source, target) == target) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
Spacer(modifier = Modifier.size(10.dp))
Text(
stringResource(
R.string.translations_show_in_lang_first,
Locale(target).displayName,
),
)
},
onClick = {
scope.launch(Dispatchers.IO) {
accountViewModel.prefer(source, target, target)
langSettingsPopupExpanded = false
}
},
)
Divider()
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().configuration)
for (i in 0 until languageList.size()) {
languageList.get(i)?.let { lang ->
DropdownMenuItem(
text = {
if (lang.language in accountViewModel.account.translateTo) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
Spacer(modifier = Modifier.size(10.dp))
Text(
stringResource(
R.string.translations_always_translate_to_lang,
lang.displayName,
),
)
},
onClick = {
scope.launch(Dispatchers.IO) {
accountViewModel.translateTo(lang)
langSettingsPopupExpanded = false
}
},
)
}
}
}
}
}
}
}
@Composable
fun TranslateAndWatchLanguageChanges(
content: String,
accountViewModel: AccountViewModel,
onTranslated: (TranslationConfig) -> Unit,
content: String,
accountViewModel: AccountViewModel,
onTranslated: (TranslationConfig) -> Unit,
) {
val accountState by accountViewModel.accountLanguagesLiveData.observeAsState()
val accountState by accountViewModel.accountLanguagesLiveData.observeAsState()
LaunchedEffect(accountState) {
// This takes some time. Launches as a Composition scope to make sure this gets cancel if this
// item gets out of view.
launch(Dispatchers.IO) {
LanguageTranslatorService.autoTranslate(
content,
accountViewModel.account.dontTranslateFrom,
accountViewModel.account.translateTo,
)
.addOnCompleteListener { task ->
if (task.isSuccessful && !content.equals(task.result.result, true)) {
if (task.result.sourceLang != null && task.result.targetLang != null) {
val preference =
accountViewModel.account.preferenceBetween(
task.result.sourceLang!!,
task.result.targetLang!!,
)
val newConfig =
TranslationConfig(
result = task.result.result,
sourceLang = task.result.sourceLang,
targetLang = task.result.targetLang,
showOriginal = preference == task.result.sourceLang,
)
LaunchedEffect(accountState) {
// This takes some time. Launches as a Composition scope to make sure this gets cancel if this
// item gets out of view.
launch(Dispatchers.IO) {
LanguageTranslatorService.autoTranslate(
content,
accountViewModel.account.dontTranslateFrom,
accountViewModel.account.translateTo,
)
.addOnCompleteListener { task ->
if (task.isSuccessful && !content.equals(task.result.result, true)) {
if (task.result.sourceLang != null && task.result.targetLang != null) {
val preference =
accountViewModel.account.preferenceBetween(
task.result.sourceLang!!,
task.result.targetLang!!,
)
val newConfig =
TranslationConfig(
result = task.result.result,
sourceLang = task.result.sourceLang,
targetLang = task.result.targetLang,
showOriginal = preference == task.result.sourceLang,
)
onTranslated(newConfig)
}
}
onTranslated(newConfig)
}
}
}
}
}
}
}