From 3efa3a68fe3615c8d744273b4418703d1af5a9b7 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 20 Sep 2023 12:23:50 -0400 Subject: [PATCH] Adds communities, hashtags and geohashes to the lists on the top navigation bar. --- .../vitorpamplona/amethyst/model/Account.kt | 32 +-- .../com/vitorpamplona/amethyst/model/Note.kt | 4 + .../com/vitorpamplona/amethyst/model/User.kt | 2 +- .../amethyst/ui/actions/NewMediaView.kt | 5 +- .../amethyst/ui/actions/NewPostView.kt | 5 +- .../amethyst/ui/components/TextSpinner.kt | 63 ++++-- .../amethyst/ui/navigation/AppTopBar.kt | 211 +++++++++++++++--- .../amethyst/ui/note/NoteCompose.kt | 31 ++- .../amethyst/ui/note/ReactionsRow.kt | 7 +- .../amethyst/ui/note/UpdateZapAmountDialog.kt | 5 +- .../amethyst/ui/note/ZapCustomDialog.kt | 5 +- .../ui/screen/loggedIn/ReportNoteDialog.kt | 2 +- .../ui/screen/loggedIn/SettingsScreen.kt | 22 +- 13 files changed, 265 insertions(+), 129 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index ce14f770e..c182a34ac 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -2478,13 +2478,7 @@ class Account( val privKey = keyPair.privKey return if (listName != null) { - val aTag = ATag( - PeopleListEvent.kind, - userProfile().pubkeyHex, - listName, - null - ).toTag() - val list = LocalCache.addressables[aTag] + val list = LocalCache.addressables[listName] if (list != null) { val publicHexList = (list.event as? PeopleListEvent)?.bookmarkedPeople() ?: emptySet() val privateHexList = privKey?.let { @@ -2507,13 +2501,7 @@ class Account( val privKey = keyPair.privKey return if (listName != null) { - val aTag = ATag( - PeopleListEvent.kind, - userProfile().pubkeyHex, - listName, - null - ).toTag() - val list = LocalCache.addressables[aTag] + val list = LocalCache.addressables[listName] if (list != null) { val publicAddresses = list.event?.hashtags() ?: emptySet() val privateAddresses = privKey?.let { @@ -2536,13 +2524,7 @@ class Account( val privKey = keyPair.privKey return if (listName != null) { - val aTag = ATag( - PeopleListEvent.kind, - userProfile().pubkeyHex, - listName, - null - ).toTag() - val list = LocalCache.addressables[aTag] + val list = LocalCache.addressables[listName] if (list != null) { val publicAddresses = list.event?.geohashes() ?: emptySet() val privateAddresses = privKey?.let { @@ -2565,13 +2547,7 @@ class Account( val privKey = keyPair.privKey return if (listName != null) { - val aTag = ATag( - PeopleListEvent.kind, - userProfile().pubkeyHex, - listName, - null - ).toTag() - val list = LocalCache.addressables[aTag] + val list = LocalCache.addressables[listName] if (list != null) { val publicAddresses = list.event?.taggedAddresses()?.map { it.toTag() } ?: emptySet() val privateAddresses = privKey?.let { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 35ebae885..e1e2744ed 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -43,6 +43,10 @@ class AddressableNote(val address: ATag) : Note(address.toTag()) { return minOf(publishedAt, lastCreatedAt) } + + fun dTag(): String? { + return (event as? AddressableEvent)?.dTag() + } } @Stable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 0d15a10cc..d294a58a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -319,7 +319,7 @@ class User(val pubkeyHex: String) { return latestContactList?.verifiedFollowKeySet ?: emptySet() } - fun cachedFollowingTagSet(): Set { + fun cachedFollowingTagSet(): Set { return latestContactList?.verifiedFollowTagSet ?: emptySet() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index ab3a29fc6..2e86c9997 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -50,6 +50,7 @@ import com.vitorpamplona.amethyst.model.ServersAvailable import com.vitorpamplona.amethyst.ui.components.VideoView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner +import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers @@ -186,8 +187,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, accountViewModel: AccountViewMo Triple(ServersAvailable.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer)) ) - val fileServerOptions = remember { fileServers.map { it.second }.toImmutableList() } - val fileServerExplainers = remember { fileServers.map { it.third }.toImmutableList() } + val fileServerOptions = remember { fileServers.map { TitleExplainer(it.second, it.third) }.toImmutableList() } val resolver = LocalContext.current.contentResolver Row( @@ -252,7 +252,6 @@ fun ImageVideoPost(postViewModel: NewMediaModel, accountViewModel: AccountViewMo label = stringResource(id = R.string.file_server), placeholder = fileServers.firstOrNull { it.first == accountViewModel.account.defaultFileServer }?.second ?: fileServers[0].second, options = fileServerOptions, - explainers = fileServerExplainers, onSelect = { postViewModel.selectedServer = fileServers[it].first }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 4bbbd8449..c16dedb45 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -93,6 +93,7 @@ import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.MyTextField import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner +import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.ButtonBorder @@ -1318,8 +1319,7 @@ fun ImageVideoDescription( Triple(ServersAvailable.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer)) ) - val fileServerOptions = remember { fileServers.map { it.second }.toImmutableList() } - val fileServerExplainers = remember { fileServers.map { it.third }.toImmutableList() } + val fileServerOptions = remember { fileServers.map { TitleExplainer(it.second, it.third) }.toImmutableList() } var selectedServer by remember { mutableStateOf(defaultServer) } var message by remember { mutableStateOf("") } @@ -1433,7 +1433,6 @@ fun ImageVideoDescription( label = stringResource(id = R.string.file_server), placeholder = fileServers.filter { it.first == defaultServer }.firstOrNull()?.second ?: fileServers[0].second, options = fileServerOptions, - explainers = fileServerExplainers, onSelect = { selectedServer = fileServers[it].first }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt index afa004ddd..b08361ab4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/TextSpinner.kt @@ -20,6 +20,7 @@ import androidx.compose.material.OutlinedTextField import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -37,8 +38,7 @@ import kotlinx.collections.immutable.ImmutableList fun TextSpinner( label: String?, placeholder: String, - options: ImmutableList, - explainers: ImmutableList? = null, + options: ImmutableList, onSelect: (Int) -> Unit, modifier: Modifier = Modifier ) { @@ -74,8 +74,8 @@ fun TextSpinner( if (optionsShowing) { options.isNotEmpty().also { - SpinnerSelectionDialog(options = options, explainers = explainers, onDismiss = { optionsShowing = false }) { - currentText = options[it] + SpinnerSelectionDialog(options = options, onDismiss = { optionsShowing = false }) { + currentText = options[it].title optionsShowing = false onSelect(it) } @@ -85,10 +85,40 @@ fun TextSpinner( @Composable fun SpinnerSelectionDialog( - options: ImmutableList, - explainers: ImmutableList?, + options: ImmutableList, onDismiss: () -> Unit, onSelect: (Int) -> Unit +) { + SpinnerSelectionDialog( + options = options, + onSelect = onSelect, + onDismiss = onDismiss + ) { item -> + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = item.title, color = MaterialTheme.colors.onSurface) + } + item.explainer?.let { + Spacer(modifier = Modifier.height(5.dp)) + Row( + horizontalArrangement = Arrangement.Start, + modifier = Modifier + .fillMaxWidth() + ) { + Text(text = it, color = Color.Gray, fontSize = Font14SP) + } + } + } +} + +@Composable +fun SpinnerSelectionDialog( + options: ImmutableList, + onSelect: (Int) -> Unit, + onDismiss: () -> Unit, + onRenderItem: @Composable (T) -> Unit ) { Dialog(onDismissRequest = onDismiss) { Surface( @@ -106,23 +136,7 @@ fun SpinnerSelectionDialog( } ) { Column() { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - ) { - Text(text = item, color = MaterialTheme.colors.onSurface) - } - explainers?.getOrNull(index)?.let { - Spacer(modifier = Modifier.height(5.dp)) - Row( - horizontalArrangement = Arrangement.Start, - modifier = Modifier - .fillMaxWidth() - ) { - Text(text = it, color = Color.Gray, fontSize = Font14SP) - } - } + onRenderItem(item) } } if (index < options.lastIndex) { @@ -133,3 +147,6 @@ fun SpinnerSelectionDialog( } } } + +@Immutable +data class TitleExplainer(val title: String, val explainer: String? = null) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index 20254536a..e44dfa9ae 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -33,6 +33,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState @@ -54,11 +55,13 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import androidx.navigation.NavBackStackEntry import coil.Coil import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS import com.vitorpamplona.amethyst.model.LiveActivitiesChannel @@ -88,6 +91,7 @@ import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.LoadChannel +import com.vitorpamplona.amethyst.ui.note.LoadCityName import com.vitorpamplona.amethyst.ui.note.LoadUser import com.vitorpamplona.amethyst.ui.note.LongCommunityHeader import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures @@ -117,6 +121,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size34dp import com.vitorpamplona.amethyst.ui.theme.Size40dp import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.ChatroomKey +import com.vitorpamplona.quartz.events.ContactListEvent import com.vitorpamplona.quartz.events.PeopleListEvent import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -382,11 +387,12 @@ fun StoriesTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState val list by accountViewModel.storiesListLiveData.observeAsState(GLOBAL_FOLLOWS) FollowList( - followLists, - list, - true + followListsModel = followLists, + listName = list, + withGlobal = true, + withRoutes = false ) { listName -> - accountViewModel.account.changeDefaultStoriesFollowList(listName) + accountViewModel.account.changeDefaultStoriesFollowList(listName.code) } } } @@ -397,11 +403,16 @@ fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, a val list by accountViewModel.homeListLiveData.observeAsState(KIND3_FOLLOWS) FollowList( - followLists, - list, - true + followListsModel = followLists, + listName = list, + withGlobal = true, + withRoutes = true ) { listName -> - accountViewModel.account.changeDefaultHomeFollowList(listName) + if (listName.type == CodeNameType.ROUTE) { + nav(listName.code) + } else { + accountViewModel.account.changeDefaultHomeFollowList(listName.code) + } } } } @@ -412,11 +423,12 @@ fun NotificationTopBar(followLists: FollowListViewModel, scaffoldState: Scaffold val list by accountViewModel.notificationListLiveData.observeAsState(GLOBAL_FOLLOWS) FollowList( - followLists, - list, - true + followListsModel = followLists, + listName = list, + withGlobal = true, + withRoutes = false ) { listName -> - accountViewModel.account.changeDefaultNotificationFollowList(listName) + accountViewModel.account.changeDefaultNotificationFollowList(listName.code) } } } @@ -427,11 +439,12 @@ fun DiscoveryTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldSta val list by accountViewModel.discoveryListLiveData.observeAsState(GLOBAL_FOLLOWS) FollowList( - followLists, - list, - true + followListsModel = followLists, + listName = list, + withGlobal = true, + withRoutes = false ) { listName -> - accountViewModel.account.changeDefaultDiscoveryFollowList(listName) + accountViewModel.account.changeDefaultDiscoveryFollowList(listName.code) } } } @@ -523,37 +536,81 @@ private fun LoggedInUserPictureDrawer( } @Composable -fun FollowList(followListsModel: FollowListViewModel, listName: String, withGlobal: Boolean, onChange: (String) -> Unit) { - val kind3Follow = Pair(KIND3_FOLLOWS, stringResource(id = R.string.follow_list_kind3follows)) - val globalFollow = Pair(GLOBAL_FOLLOWS, stringResource(id = R.string.follow_list_global)) +fun FollowList( + followListsModel: FollowListViewModel, + listName: String, + withGlobal: Boolean, + withRoutes: Boolean, + onChange: (CodeName) -> Unit +) { + val context = LocalContext.current + + val kind3Follow = CodeName(KIND3_FOLLOWS, ResourceName(R.string.follow_list_kind3follows, context), CodeNameType.HARDCODED) + val globalFollow = CodeName(GLOBAL_FOLLOWS, ResourceName(R.string.follow_list_global, context), CodeNameType.HARDCODED) val defaultOptions = if (withGlobal) listOf(kind3Follow, globalFollow) else listOf(kind3Follow) - val followLists by followListsModel.followLists.collectAsState() + val followLists by followListsModel.peopleLists.collectAsState() + val routeList by followListsModel.routes.collectAsState() val allLists = remember(followLists) { - (defaultOptions + followLists) + if (withRoutes) { + (defaultOptions + followLists + routeList) + } else { + (defaultOptions + followLists) + } } val followNames by remember(followLists) { derivedStateOf { - allLists.map { it.second }.toImmutableList() + allLists.map { it.name }.toImmutableList() } } SimpleTextSpinner( - placeholder = allLists.firstOrNull { it.first == listName }?.second ?: "Select an Option", + placeholder = allLists.firstOrNull { it.code == listName }?.name?.name() ?: "Select an Option", options = followNames, onSelect = { - onChange(allLists.getOrNull(it)?.first ?: KIND3_FOLLOWS) + onChange(allLists.getOrNull(it) ?: kind3Follow) } ) } +enum class CodeNameType { + HARDCODED, PEOPLE_LIST, ROUTE +} + +abstract class Name { + abstract fun name(): String +} + +class GeoHashName(val geoHashTag: String) : Name() { + override fun name() = "/g/$geoHashTag" +} +class HashtagName(val hashTag: String) : Name() { + override fun name() = "#$hashTag" +} +class ResourceName(val resourceId: Int, val context: Context) : Name() { + override fun name() = context.getString(resourceId) +} + +class PeopleListName(val note: AddressableNote) : Name() { + override fun name() = note.dTag() ?: "" +} +class CommunityName(val note: AddressableNote) : Name() { + override fun name() = "/n/${(note.dTag() ?: "")}" +} + +@Immutable +data class CodeName(val code: String, val name: Name, val type: CodeNameType) + @Stable class FollowListViewModel(val account: Account) : ViewModel() { - private var _followLists = MutableStateFlow>>(emptyList>().toPersistentList()) - val followLists = _followLists.asStateFlow() + private var _peopleLists = MutableStateFlow>(emptyList().toPersistentList()) + val peopleLists = _peopleLists.asStateFlow() + + private var _routes = MutableStateFlow>(emptyList().toPersistentList()) + val routes = _routes.asStateFlow() fun refresh() { viewModelScope.launch(Dispatchers.Default) { @@ -571,14 +628,34 @@ class FollowListViewModel(val account: Account) : ViewModel() { event.pubKey == account.userProfile().pubkeyHex && (event.tags.size > 1 || event.content.length > 50) ) { - Pair(event.dTag(), event.dTag()) + CodeName(event.address().toTag(), PeopleListName(it.value), CodeNameType.PEOPLE_LIST) } else { null } - }.sortedBy { it.second }.toImmutableList() + }.sortedBy { it.name.name() }.toImmutableList() - if (!equalImmutableLists(_followLists.value, newFollowLists)) { - _followLists.emit(newFollowLists) + if (!equalImmutableLists(_peopleLists.value, newFollowLists)) { + _peopleLists.emit(newFollowLists) + } + + val communities = account.userProfile().cachedFollowingCommunitiesSet().mapNotNull { + LocalCache.checkGetOrCreateAddressableNote(it)?.let { communityNote -> + CodeName("Community/${communityNote.idHex}", CommunityName(communityNote), CodeNameType.ROUTE) + } + } + + val hashtags = account.userProfile().cachedFollowingTagSet().map { + CodeName("Hashtag/$it", HashtagName(it), CodeNameType.ROUTE) + } + + val geotags = account.userProfile().cachedFollowingGeohashSet().map { + CodeName("Geohash/$it", GeoHashName(it), CodeNameType.ROUTE) + } + + val routeList = (communities + hashtags + geotags).sortedBy { it.name.name() }.toImmutableList() + + if (!equalImmutableLists(_routes.value, routeList)) { + _routes.emit(routeList) } } @@ -590,7 +667,10 @@ class FollowListViewModel(val account: Account) : ViewModel() { collectorJob = viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes -> checkNotInMainThread() - if (newNotes.any { it.event is PeopleListEvent }) { + if (newNotes.any { + it.event?.pubKey() == account.userProfile().pubkeyHex && (it.event is PeopleListEvent || it.event is ContactListEvent) + } + ) { refresh() } } @@ -612,8 +692,7 @@ class FollowListViewModel(val account: Account) : ViewModel() { @Composable fun SimpleTextSpinner( placeholder: String, - options: ImmutableList, - explainers: ImmutableList? = null, + options: ImmutableList, onSelect: (Int) -> Unit, modifier: Modifier = Modifier ) { @@ -649,10 +728,68 @@ fun SimpleTextSpinner( if (optionsShowing) { options.isNotEmpty().also { - SpinnerSelectionDialog(options = options, explainers = explainers, onDismiss = { optionsShowing = false }) { - currentText = options[it] - optionsShowing = false - onSelect(it) + SpinnerSelectionDialog( + options = options, + onDismiss = { optionsShowing = false }, + onSelect = { + currentText = options[it].name() + optionsShowing = false + onSelect(it) + } + ) { + RenderOption(it) + } + } + } +} + +@Composable +fun RenderOption(it: Name) { + when (it) { + is GeoHashName -> { + LoadCityName(it.geoHashTag) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = "/g/$it", color = MaterialTheme.colors.onSurface) + } + } + } + is HashtagName -> { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = it.name(), color = MaterialTheme.colors.onSurface) + } + } + is ResourceName -> { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = it.resourceId), color = MaterialTheme.colors.onSurface) + } + } + is PeopleListName -> { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = it.name(), color = MaterialTheme.colors.onSurface) + } + } + is CommunityName -> { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + val name by it.note.live().metadata.map { + "/n/" + (it.note as? AddressableNote)?.dTag() + }.observeAsState() + + Text(text = name ?: "", color = MaterialTheme.colors.onSurface) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index dae30f5c3..5b1dc0e80 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -2579,7 +2579,7 @@ fun LoadStatuses( } @Composable -fun DisplayLocation(geohash: String, nav: (String) -> Unit) { +fun LoadCityName(geohash: String, content: @Composable (String) -> Unit) { val context = LocalContext.current var cityName by remember(geohash) { mutableStateOf(geohash) @@ -2594,18 +2594,25 @@ fun DisplayLocation(geohash: String, nav: (String) -> Unit) { } } - ClickableText( - text = AnnotatedString(cityName), - onClick = { nav("Geohash/$geohash") }, - style = LocalTextStyle.current.copy( - color = MaterialTheme.colors.primary.copy( - alpha = 0.52f + content(cityName) +} + +@Composable +fun DisplayLocation(geohash: String, nav: (String) -> Unit) { + LoadCityName(geohash) { cityName -> + ClickableText( + text = AnnotatedString(cityName), + onClick = { nav("Geohash/$geohash") }, + style = LocalTextStyle.current.copy( + color = MaterialTheme.colors.primary.copy( + alpha = 0.52f + ), + fontSize = Font14SP, + fontWeight = FontWeight.Bold ), - fontSize = Font14SP, - fontWeight = FontWeight.Bold - ), - maxLines = 1 - ) + maxLines = 1 + ) + } } @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 41f1bf22a..b8da5452c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -89,7 +89,6 @@ import com.vitorpamplona.amethyst.ui.theme.DarkerGreen import com.vitorpamplona.amethyst.ui.theme.Font14SP import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding -import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp import com.vitorpamplona.amethyst.ui.theme.NoSoTinyBorders import com.vitorpamplona.amethyst.ui.theme.ReactionRowExpandButton @@ -135,13 +134,14 @@ fun ReactionsRow( InnerReactionRow(baseNote, showReactionDetail, wantsToSeeReactions, accountViewModel, nav) + Spacer(modifier = HalfDoubleVertSpacer) + LoadAndDisplayZapraiser(baseNote, showReactionDetail, wantsToSeeReactions, accountViewModel) if (showReactionDetail && wantsToSeeReactions.value) { ReactionDetailGallery(baseNote, nav, accountViewModel) + Spacer(modifier = HalfDoubleVertSpacer) } - - Spacer(modifier = HalfDoubleVertSpacer) } @Composable @@ -251,7 +251,6 @@ private fun LoadAndDisplayZapraiser( } if (zapraiserAmount > 0) { - Spacer(modifier = Height4dpModifier) Box( modifier = remember { ReactionRowZapraiserSize diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index d267dcf33..e82dfc6af 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.ui.actions.SaveButton import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner +import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.screen.loggedIn.getFragmentActivity import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.Font14SP @@ -219,8 +220,7 @@ fun UpdateZapAmountDialog( Triple(LnZapEvent.ZapType.NONZAP, stringResource(id = R.string.zap_type_nonzap), stringResource(id = R.string.zap_type_nonzap_explainer)) ) - val zapOptions = remember { zapTypes.map { it.second }.toImmutableList() } - val zapOptionExplainers = remember { zapTypes.map { it.third }.toImmutableList() } + val zapOptions = remember { zapTypes.map { TitleExplainer(it.second, it.third) }.toImmutableList() } LaunchedEffect(accountViewModel, nip47uri) { postViewModel.load() @@ -361,7 +361,6 @@ fun UpdateZapAmountDialog( placeholder = zapTypes.filter { it.first == accountViewModel.defaultZapType() } .first().second, options = zapOptions, - explainers = zapOptionExplainers, onSelect = { postViewModel.selectedZapType = zapTypes[it].first }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 16d334433..55cd5b569 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -47,6 +47,7 @@ import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner +import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer @@ -107,8 +108,7 @@ fun ZapCustomDialog( Triple(LnZapEvent.ZapType.NONZAP, stringResource(id = R.string.zap_type_nonzap), stringResource(id = R.string.zap_type_nonzap_explainer)) ) - val zapOptions = remember { zapTypes.map { it.second }.toImmutableList() } - val zapOptionExplainers = remember { zapTypes.map { it.third }.toImmutableList() } + val zapOptions = remember { zapTypes.map { TitleExplainer(it.second, it.third) }.toImmutableList() } var selectedZapType by remember(accountViewModel) { mutableStateOf(accountViewModel.account.defaultZapType) } Dialog( @@ -181,7 +181,6 @@ fun ZapCustomDialog( label = stringResource(id = R.string.zap_type), placeholder = zapTypes.filter { it.first == accountViewModel.account.defaultZapType }.first().second, options = zapOptions, - explainers = zapOptionExplainers, onSelect = { selectedZapType = zapTypes[it].first }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt index e0b56ed9f..8ddb6fa20 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt @@ -56,7 +56,7 @@ fun ReportNoteDialog(note: Note, accountViewModel: AccountViewModel, onDismiss: Pair(ReportEvent.ReportType.ILLEGAL, stringResource(R.string.report_dialog_illegal)) ) - val reasonOptions = remember { reportTypes.map { it.second }.toImmutableList() } + val reasonOptions = remember { reportTypes.map { TitleExplainer(it.second) }.toImmutableList() } var additionalReason by remember { mutableStateOf("") } var selectedReason by remember { mutableStateOf(-1) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt index e7e18bc5a..b832846bf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SettingsScreen.kt @@ -111,15 +111,15 @@ fun SettingsScreen( ) { val scope = rememberCoroutineScope() val selectedItens = persistentListOf( - stringResource(ConnectivityType.ALWAYS.reourceId), - stringResource(ConnectivityType.WIFI_ONLY.reourceId), - stringResource(ConnectivityType.NEVER.reourceId) + TitleExplainer(stringResource(ConnectivityType.ALWAYS.reourceId)), + TitleExplainer(stringResource(ConnectivityType.WIFI_ONLY.reourceId)), + TitleExplainer(stringResource(ConnectivityType.NEVER.reourceId)) ) val themeItens = persistentListOf( - stringResource(R.string.system), - stringResource(R.string.light), - stringResource(R.string.dark) + TitleExplainer(stringResource(R.string.system)), + TitleExplainer(stringResource(R.string.light)), + TitleExplainer(stringResource(R.string.dark)) ) val settings = accountViewModel.account.settings @@ -132,7 +132,7 @@ fun SettingsScreen( val context = LocalContext.current val languageEntries = context.getLangPreferenceDropdownEntries() - val languageList = languageEntries.keys.toImmutableList() + val languageList = languageEntries.keys.map { TitleExplainer(it) }.toImmutableList() val languageIndex = getLanguageIndex(languageEntries) Column( @@ -149,12 +149,12 @@ fun SettingsScreen( ) { GlobalScope.launch(Dispatchers.Main) { val job = scope.launch(Dispatchers.IO) { - val locale = languageEntries[languageList[it]] + val locale = languageEntries[languageList[it].title] accountViewModel.account.settings.preferredLanguage = locale LocalPreferences.saveToEncryptedStorage(accountViewModel.account) } job.join() - val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(languageEntries[languageList[it]]) + val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(languageEntries[languageList[it].title]) AppCompatDelegate.setApplicationLocales(appLocale) } } @@ -227,7 +227,7 @@ fun SettingsScreen( fun SettingsRow( name: Int, description: Int, - selectedItens: ImmutableList, + selectedItens: ImmutableList, selectedIndex: Int, onSelect: (Int) -> Unit ) { @@ -255,7 +255,7 @@ fun SettingsRow( TextSpinner( label = "", - placeholder = selectedItens[selectedIndex], + placeholder = selectedItens[selectedIndex].title, options = selectedItens, onSelect = onSelect, modifier = Modifier