From b9654d164f5a3f802a4032db5d8eeca8cd2f9a55 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 6 Jun 2023 16:47:14 -0400 Subject: [PATCH] Further splitting the translation class into more methods to simplify recompositions --- .../ui/actions/JoinUserOrChannelView.kt | 94 +++-- .../ui/screen/loggedIn/SearchScreen.kt | 4 +- .../components/TranslatableRichTextViewer.kt | 324 ++++++++++-------- 3 files changed, 242 insertions(+), 180 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index 01ab9a32b..5fa9ac7e1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.remember @@ -95,7 +96,7 @@ fun JoinUserOrChannelView(searchBarViewModel: SearchBarViewModel, onClose: () -> Dialog( onDismissRequest = { NostrSearchEventOrUserDataSource.clear() - searchBarViewModel.clean() + searchBarViewModel.clear() onClose() }, properties = DialogProperties( @@ -114,7 +115,7 @@ fun JoinUserOrChannelView(searchBarViewModel: SearchBarViewModel, onClose: () -> verticalAlignment = Alignment.CenterVertically ) { CloseButton(onCancel = { - searchBarViewModel.clean() + searchBarViewModel.clear() NostrSearchEventOrUserDataSource.clear() onClose() }) @@ -225,8 +226,10 @@ private fun SearchEditTextForJoin( val keyboardController = LocalSoftwareKeyboardController.current LaunchedEffect(Unit) { - delay(100) - focusRequester.requestFocus() + launch { + delay(100) + focusRequester.requestFocus() + } } Row( @@ -272,7 +275,7 @@ private fun SearchEditTextForJoin( if (searchBarViewModel.isSearching) { IconButton( onClick = { - searchBarViewModel.clean() + searchBarViewModel.clear() NostrSearchEventOrUserDataSource.clear() } ) { @@ -316,47 +319,57 @@ private fun RenderSearchResults( users, key = { _, item -> "u" + item.pubkeyHex } ) { _, item -> - UserComposeForChat( - item, - accountViewModel = accountViewModel, - nav = nav - ) + UserComposeForChat(item, accountViewModel) { + nav("Room/${item.pubkeyHex}") + searchBarViewModel.clear() + } } itemsIndexed( channels, key = { _, item -> "c" + item.idHex } ) { _, item -> - ChannelName( - channelIdHex = item.idHex, - channelPicture = item.profilePicture(), - channelTitle = { - Text( - "${item.info.name}", - fontWeight = FontWeight.Bold - ) - }, - channelLastTime = null, - channelLastContent = item.info.about, - false, - onClick = { nav("Channel/${item.idHex}") } - ) + RenderChannel(item) { + nav("Channel/${item.idHex}") + searchBarViewModel.clear() + } } } } } } +@Composable +private fun RenderChannel( + item: com.vitorpamplona.amethyst.model.Channel, + onClick: () -> Unit +) { + ChannelName( + channelIdHex = item.idHex, + channelPicture = item.profilePicture(), + channelTitle = { + Text( + "${item.info.name}", + fontWeight = FontWeight.Bold + ) + }, + channelLastTime = null, + channelLastContent = item.info.about, + false, + onClick = onClick + ) +} + @Composable fun UserComposeForChat( baseUser: User, accountViewModel: AccountViewModel, - nav: (String) -> Unit + onClick: () -> Unit ) { Column( modifier = Modifier.clickable( - onClick = { nav("Room/${baseUser.pubkeyHex}") } + onClick = onClick ) ) { Row( @@ -368,7 +381,7 @@ fun UserComposeForChat( ), verticalAlignment = Alignment.CenterVertically ) { - UserPicture(baseUser, nav, accountViewModel, 55.dp) + UserPicture(baseUser, 55.dp, accountViewModel) Column( modifier = Modifier @@ -379,15 +392,7 @@ fun UserComposeForChat( UsernameDisplay(baseUser) } - val baseUserState by baseUser.live().metadata.observeAsState() - val user = baseUserState?.user ?: return - - Text( - user.info?.about ?: "", - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + DisplayUserAboutInfo(baseUser) } } @@ -397,3 +402,20 @@ fun UserComposeForChat( ) } } + +@Composable +private fun DisplayUserAboutInfo(baseUser: User) { + val baseUserState by baseUser.live().metadata.observeAsState() + val about by remember(baseUserState) { + derivedStateOf { + baseUserState?.user?.info?.about ?: "" + } + } + + Text( + text = about, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index acb3af18e..92c1134e3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -187,7 +187,7 @@ class SearchBarViewModel(val account: Account) : ViewModel() { _searchResultsChannels.emit(LocalCache.findChannelsStartingWith(searchValue)) } - fun clean() { + fun clear() { searchValue = "" _searchResultsUsers.value = emptyList() _searchResultsChannels.value = emptyList() @@ -317,7 +317,7 @@ private fun SearchTextField( if (searchBarViewModel.isSearching) { IconButton( onClick = { - searchBarViewModel.clean() + searchBarViewModel.clear() NostrSearchEventOrUserDataSource.clear() } ) { diff --git a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt index e19b26102..5d458c8c9 100644 --- a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt +++ b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt @@ -49,33 +49,32 @@ fun TranslatableRichTextViewer( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - var translatedTextState by remember { - mutableStateOf(ResultOrError(content, null, null, null)) - } - - var showOriginal by remember { mutableStateOf(false) } - var langSettingsPopupExpanded by remember { mutableStateOf(false) } - - TranslateAndWatchLanguageChanges(content, accountViewModel) { result, newShowOriginal -> - if (translatedTextState.result != result.result || - translatedTextState.sourceLang != result.sourceLang || - translatedTextState.targetLang != result.targetLang - ) { - translatedTextState = result - } - - if (showOriginal != newShowOriginal) { - showOriginal = newShowOriginal - } - } - - val toBeViewed by remember(translatedTextState) { - derivedStateOf { - if (showOriginal) content else translatedTextState.result ?: content - } - } - Column { + var translatedTextState by remember { + mutableStateOf(ResultOrError(content, null, null, null)) + } + + var showOriginal by remember { mutableStateOf(false) } + + TranslateAndWatchLanguageChanges(content, accountViewModel) { result, newShowOriginal -> + if (translatedTextState.result != result.result || + translatedTextState.sourceLang != result.sourceLang || + translatedTextState.targetLang != result.targetLang + ) { + translatedTextState = result + } + + if (showOriginal != newShowOriginal) { + showOriginal = newShowOriginal + } + } + + val toBeViewed by remember(translatedTextState) { + derivedStateOf { + if (showOriginal) content else translatedTextState.result ?: content + } + } + ExpandableRichTextViewer( toBeViewed, canPreview, @@ -86,69 +85,174 @@ fun TranslatableRichTextViewer( nav ) - val target = translatedTextState.targetLang - val source = translatedTextState.sourceLang - - if (source != null && target != null && source != target) { - val scope = rememberCoroutineScope() - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 5.dp) + if (translatedTextState.sourceLang != null && + translatedTextState.targetLang != null && + translatedTextState.sourceLang != translatedTextState.targetLang + ) { + TranslationMessage( + translatedTextState.sourceLang!!, + translatedTextState.targetLang!!, + accountViewModel ) { - val clickableTextStyle = - SpanStyle(color = MaterialTheme.colors.primary.copy(alpha = 0.52f)) + showOriginal = it + } + } + } +} - val annotatedTranslationString = buildAnnotatedString { - withStyle(clickableTextStyle) { - pushStringAnnotation("langSettings", true.toString()) - append(stringResource(R.string.translations_auto)) - } +@Composable +private fun TranslationMessage( + source: String, + target: String, + accountViewModel: AccountViewModel, + onChangeWhatToShow: (Boolean) -> Unit +) { + var langSettingsPopupExpanded by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() - append("-${stringResource(R.string.translations_translated_from)} ") + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 5.dp) + ) { + val clickableTextStyle = + SpanStyle(color = MaterialTheme.colors.primary.copy(alpha = 0.52f)) - withStyle(clickableTextStyle) { - pushStringAnnotation("showOriginal", true.toString()) - append(Locale(source).displayName) - } + val annotatedTranslationString = buildAnnotatedString { + withStyle(clickableTextStyle) { + pushStringAnnotation("langSettings", true.toString()) + append(stringResource(R.string.translations_auto)) + } - append(" ${stringResource(R.string.translations_to)} ") + append("-${stringResource(R.string.translations_translated_from)} ") - withStyle(clickableTextStyle) { - pushStringAnnotation("showOriginal", false.toString()) - append(Locale(target).displayName) + 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.colors.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 } } + } - ClickableText( - text = annotatedTranslationString, - style = LocalTextStyle.current.copy(color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)), - overflow = TextOverflow.Visible, - maxLines = 3 - ) { spanOffset -> - annotatedTranslationString.getStringAnnotations(spanOffset, spanOffset) - .firstOrNull() - ?.also { span -> - if (span.tag == "showOriginal") { - showOriginal = span.item.toBoolean() - } else { - langSettingsPopupExpanded = !langSettingsPopupExpanded - } - } + DropdownMenu( + expanded = langSettingsPopupExpanded, + onDismissRequest = { langSettingsPopupExpanded = false } + ) { + DropdownMenuItem(onClick = { + scope.launch(Dispatchers.IO) { + accountViewModel.dontTranslateFrom(source) + langSettingsPopupExpanded = false + } + }) { + 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)) } - DropdownMenu( - expanded = langSettingsPopupExpanded, - onDismissRequest = { langSettingsPopupExpanded = false } - ) { + Spacer(modifier = Modifier.size(10.dp)) + + Text( + stringResource( + R.string.translations_never_translate_from_lang, + Locale(source).displayName + ) + ) + } + Divider() + DropdownMenuItem(onClick = { + scope.launch(Dispatchers.IO) { + accountViewModel.prefer(source, target, source) + langSettingsPopupExpanded = false + } + }) { + 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 + ) + ) + } + DropdownMenuItem(onClick = { + scope.launch(Dispatchers.IO) { + accountViewModel.prefer(source, target, target) + langSettingsPopupExpanded = false + } + }) { + 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 + ) + ) + } + Divider() + + val languageList = + ConfigurationCompat.getLocales(Resources.getSystem().configuration) + for (i in 0 until languageList.size()) { + languageList.get(i)?.let { lang -> DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { - accountViewModel.dontTranslateFrom(source) + accountViewModel.translateTo(lang) langSettingsPopupExpanded = false } }) { - if (source in accountViewModel.account.dontTranslateFrom) { + if (lang.language in accountViewModel.account.translateTo) { Icon( imageVector = Icons.Default.Check, contentDescription = null, @@ -160,76 +264,12 @@ fun TranslatableRichTextViewer( Spacer(modifier = Modifier.size(10.dp)) - Text(stringResource(R.string.translations_never_translate_from_lang, Locale(source).displayName)) - } - Divider() - DropdownMenuItem(onClick = { - scope.launch(Dispatchers.IO) { - accountViewModel.prefer(source, target, source) - langSettingsPopupExpanded = false - } - }) { - if (accountViewModel.account.preferenceBetween(source, target) == source) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = null, - modifier = Modifier.size(24.dp) + Text( + stringResource( + R.string.translations_always_translate_to_lang, + lang.displayName ) - } 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)) - } - DropdownMenuItem(onClick = { - scope.launch(Dispatchers.IO) { - accountViewModel.prefer(source, target, target) - langSettingsPopupExpanded = false - } - }) { - 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)) - } - Divider() - - val languageList = - ConfigurationCompat.getLocales(Resources.getSystem().configuration) - for (i in 0 until languageList.size()) { - languageList.get(i)?.let { lang -> - DropdownMenuItem(onClick = { - scope.launch(Dispatchers.IO) { - accountViewModel.translateTo(lang) - langSettingsPopupExpanded = false - } - }) { - 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)) - } - } + ) } } }