mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-19 05:40:49 +02:00
Join/Leave Channels, Channel search.
This commit is contained in:
@@ -169,7 +169,16 @@ class Account(val loggedIn: Persona, val followingChannels: MutableSet<String> =
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
|
||||
followingChannels.add(event.id.toHex())
|
||||
joinChannel(event.id.toHex(), accountStateViewModel)
|
||||
}
|
||||
|
||||
fun joinChannel(idHex: String, accountStateViewModel: AccountStateViewModel) {
|
||||
followingChannels.add(idHex)
|
||||
accountStateViewModel.saveToEncryptedStorage(this)
|
||||
}
|
||||
|
||||
fun leaveChannel(idHex: String, accountStateViewModel: AccountStateViewModel) {
|
||||
followingChannels.remove(idHex)
|
||||
accountStateViewModel.saveToEncryptedStorage(this)
|
||||
}
|
||||
|
||||
|
@@ -42,6 +42,11 @@ class Channel(val id: ByteArray) {
|
||||
return info.picture ?: "https://robohash.org/${idHex}.png"
|
||||
}
|
||||
|
||||
fun anyNameStartsWith(prefix: String): Boolean {
|
||||
return listOfNotNull(info.name, info.about)
|
||||
.filter { it.startsWith(prefix, true) }.isNotEmpty()
|
||||
}
|
||||
|
||||
// Observers line up here.
|
||||
val live: ChannelLiveData = ChannelLiveData(this)
|
||||
|
||||
|
@@ -260,6 +260,8 @@ object LocalCache {
|
||||
} else {
|
||||
// older data, does nothing
|
||||
}
|
||||
|
||||
refreshObservers()
|
||||
}
|
||||
fun consume(event: ChannelMetadataEvent) {
|
||||
//Log.d("MT", "New User ${users.size} ${event.contactMetaData.name}")
|
||||
@@ -279,6 +281,8 @@ object LocalCache {
|
||||
} else {
|
||||
//Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()} ${formattedDateTime(event.createdAt)} > ${formattedDateTime(oldUser.updatedAt)}")
|
||||
}
|
||||
|
||||
refreshObservers()
|
||||
}
|
||||
|
||||
fun consume(event: ChannelMessageEvent) {
|
||||
@@ -293,7 +297,7 @@ object LocalCache {
|
||||
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
val mentions = Collections.synchronizedList(event.mentions.map { getOrCreateUser(decodePublicKey(it)) })
|
||||
val replyTo = Collections.synchronizedList(event.replyTos.map { getOrCreateNote(it) }.toMutableList())
|
||||
val replyTo = Collections.synchronizedList(event.replyTos.map { channel.getOrCreateNote(it) }.toMutableList())
|
||||
|
||||
note.channel = channel
|
||||
note.loadEvent(event, author, mentions, replyTo)
|
||||
@@ -332,6 +336,22 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun findNotesStartingWith(text: String): List<Note> {
|
||||
return notes.values.filter {
|
||||
(it.event is TextNoteEvent && it.event?.content?.contains(text) ?: false)
|
||||
|| it.idHex.startsWith(text, true)
|
||||
|| it.id.toNote().startsWith(text, true)
|
||||
}
|
||||
}
|
||||
|
||||
fun findChannelsStartingWith(text: String): List<Channel> {
|
||||
return channels.values.filter {
|
||||
it.anyNameStartsWith(text)
|
||||
|| it.idHex.startsWith(text, true)
|
||||
|| it.id.toNote().startsWith(text, true)
|
||||
}
|
||||
}
|
||||
|
||||
// Observers line up here.
|
||||
val live: LocalCacheLiveData = LocalCacheLiveData(this)
|
||||
|
||||
|
@@ -120,6 +120,7 @@ class User(val pubkey: ByteArray) {
|
||||
|
||||
handlerWaiting = true
|
||||
filterHandler.postDelayed({
|
||||
println("User Refresh")
|
||||
live.refresh()
|
||||
handlerWaiting = false
|
||||
}, 100)
|
||||
|
@@ -23,9 +23,9 @@ class NewChannelViewModel: ViewModel() {
|
||||
this.account = account
|
||||
if (channel != null) {
|
||||
originalChannel = channel
|
||||
channelName.value = TextFieldValue()
|
||||
channelPicture.value = TextFieldValue()
|
||||
channelDescription.value = TextFieldValue()
|
||||
channelName.value = TextFieldValue(channel.info.name ?: "")
|
||||
channelPicture.value = TextFieldValue(channel.info.picture ?: "")
|
||||
channelDescription.value = TextFieldValue(channel.info.about ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -95,7 +95,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChannelName(
|
||||
fun ChannelName(
|
||||
channelPicture: String,
|
||||
channelTitle: @Composable () -> Unit,
|
||||
channelLastTime: Long?,
|
||||
@@ -124,10 +124,13 @@ private fun ChannelName(
|
||||
) {
|
||||
channelTitle()
|
||||
|
||||
Text(
|
||||
timeAgo(channelLastTime),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f)
|
||||
)
|
||||
channelLastTime?.let {
|
||||
Text(
|
||||
timeAgo(channelLastTime),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (channelLastContent != null)
|
||||
|
@@ -48,6 +48,7 @@ import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
@@ -72,17 +73,23 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
ChannelHeader(
|
||||
channel, account,
|
||||
accountStateViewModel = accountStateViewModel
|
||||
accountStateViewModel = accountStateViewModel,
|
||||
navController = navController
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight().padding(vertical = 0.dp).weight(1f, true)
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(vertical = 0.dp)
|
||||
.weight(1f, true)
|
||||
) {
|
||||
ChatroomFeedView(feedViewModel, accountViewModel, navController)
|
||||
}
|
||||
|
||||
//LAST ROW
|
||||
Row(modifier = Modifier.padding(10.dp).fillMaxWidth(),
|
||||
Row(modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
@@ -92,7 +99,9 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
),
|
||||
modifier = Modifier.weight(1f, true).padding(end = 10.dp),
|
||||
modifier = Modifier
|
||||
.weight(1f, true)
|
||||
.padding(end = 10.dp),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "reply here.. ",
|
||||
@@ -116,7 +125,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel: AccountStateViewModel) {
|
||||
fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel: AccountStateViewModel, navController: NavController) {
|
||||
val channelState by baseChannel.live.observeAsState()
|
||||
val channel = channelState?.channel
|
||||
|
||||
@@ -128,11 +137,14 @@ fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel:
|
||||
model = channel?.profilePicture(),
|
||||
contentDescription = "Profile Image",
|
||||
modifier = Modifier
|
||||
.width(35.dp).height(35.dp)
|
||||
.width(35.dp)
|
||||
.height(35.dp)
|
||||
.clip(shape = CircleShape)
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
|
||||
Column(modifier = Modifier
|
||||
.padding(start = 10.dp)
|
||||
.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
"${channel?.info?.name}",
|
||||
@@ -153,7 +165,17 @@ fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel:
|
||||
|
||||
channel?.let { NoteCopyButton(it) }
|
||||
|
||||
channel?.let { EditButton(account, it, accountStateViewModel) }
|
||||
channel?.let {
|
||||
if (channel.creator == account.userProfile()) {
|
||||
EditButton(account, it, accountStateViewModel)
|
||||
} else {
|
||||
if (account.followingChannels.contains(channel.idHex)) {
|
||||
LeaveButton(account,channel,accountStateViewModel, navController)
|
||||
} else {
|
||||
JoinButton(account,channel,accountStateViewModel, navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +202,7 @@ private fun NoteCopyButton(
|
||||
backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
),
|
||||
) {
|
||||
Text(text = "npub", color = Color.White)
|
||||
Text(text = "note", color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,4 +226,40 @@ private fun EditButton(account: Account, channel: Channel, accountStateViewModel
|
||||
) {
|
||||
Text(text = "Edit", color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun JoinButton(account: Account, channel: Channel, accountStateViewModel: AccountStateViewModel, navController: NavController) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = {
|
||||
account.joinChannel(channel.idHex, accountStateViewModel)
|
||||
navController.navigate(Route.Message.route)
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
) {
|
||||
Text(text = "Join", color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LeaveButton(account: Account, channel: Channel, accountStateViewModel: AccountStateViewModel, navController: NavController) {
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = {
|
||||
account.leaveChannel(channel.idHex, accountStateViewModel)
|
||||
navController.navigate(Route.Message.route)
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
colors = ButtonDefaults
|
||||
.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
) {
|
||||
Text(text = "Leave", color = Color.White)
|
||||
}
|
||||
}
|
@@ -39,6 +39,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -46,11 +47,15 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.SearchButton
|
||||
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@@ -66,16 +71,18 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 0.dp)
|
||||
) {
|
||||
SearchBar(navController)
|
||||
SearchBar(accountViewModel, navController)
|
||||
FeedView(feedViewModel, accountViewModel, navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchBar(navController: NavController) {
|
||||
private fun SearchBar(accountViewModel: AccountViewModel, navController: NavController) {
|
||||
val searchValue = remember { mutableStateOf(TextFieldValue("")) }
|
||||
val searchResults = remember { mutableStateOf<List<User>>(emptyList()) }
|
||||
val searchResultsNotes = remember { mutableStateOf<List<Note>>(emptyList()) }
|
||||
val searchResultsChannels = remember { mutableStateOf<List<Channel>>(emptyList()) }
|
||||
|
||||
val isTrailingIconVisible by remember {
|
||||
derivedStateOf {
|
||||
@@ -96,6 +103,8 @@ private fun SearchBar(navController: NavController) {
|
||||
onValueChange = {
|
||||
searchValue.value = it
|
||||
searchResults.value = LocalCache.findUsersStartingWith(it.text)
|
||||
searchResultsNotes.value = LocalCache.findNotesStartingWith(it.text)
|
||||
searchResultsChannels.value = LocalCache.findChannelsStartingWith(it.text)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
@@ -123,6 +132,8 @@ private fun SearchBar(navController: NavController) {
|
||||
onClick = {
|
||||
searchValue.value = TextFieldValue("")
|
||||
searchResults.value = emptyList()
|
||||
searchResultsChannels.value = emptyList()
|
||||
searchResultsNotes.value = emptyList()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
@@ -148,6 +159,24 @@ private fun SearchBar(navController: NavController) {
|
||||
navController.navigate("User/${item.pubkeyHex}")
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(searchResultsChannels.value, key = { _, item -> item.idHex }) { index, item ->
|
||||
ChannelName(
|
||||
channelPicture = item.profilePicture(),
|
||||
channelTitle = {
|
||||
Text(
|
||||
"${item.info.name}",
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
},
|
||||
channelLastTime = null,
|
||||
channelLastContent = item.info.about,
|
||||
onClick = { navController.navigate("Channel/${item.idHex}") })
|
||||
}
|
||||
|
||||
itemsIndexed(searchResultsNotes.value, key = { _, item -> item.idHex }) { index, item ->
|
||||
NoteCompose(item, accountViewModel = accountViewModel, navController = navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user