Join/Leave Channels, Channel search.

This commit is contained in:
Vitor Pamplona
2023-01-19 08:03:01 -05:00
parent 75219f0f2d
commit 9d52180550
8 changed files with 146 additions and 21 deletions

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -120,6 +120,7 @@ class User(val pubkey: ByteArray) {
handlerWaiting = true
filterHandler.postDelayed({
println("User Refresh")
live.refresh()
handlerWaiting = false
}, 100)

View File

@@ -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 ?: "")
}
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}
}