mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-20 07:00:30 +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)
|
Client.send(event)
|
||||||
LocalCache.consume(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)
|
accountStateViewModel.saveToEncryptedStorage(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -42,6 +42,11 @@ class Channel(val id: ByteArray) {
|
|||||||
return info.picture ?: "https://robohash.org/${idHex}.png"
|
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.
|
// Observers line up here.
|
||||||
val live: ChannelLiveData = ChannelLiveData(this)
|
val live: ChannelLiveData = ChannelLiveData(this)
|
||||||
|
|
||||||
|
@@ -260,6 +260,8 @@ object LocalCache {
|
|||||||
} else {
|
} else {
|
||||||
// older data, does nothing
|
// older data, does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshObservers()
|
||||||
}
|
}
|
||||||
fun consume(event: ChannelMetadataEvent) {
|
fun consume(event: ChannelMetadataEvent) {
|
||||||
//Log.d("MT", "New User ${users.size} ${event.contactMetaData.name}")
|
//Log.d("MT", "New User ${users.size} ${event.contactMetaData.name}")
|
||||||
@@ -279,6 +281,8 @@ object LocalCache {
|
|||||||
} else {
|
} else {
|
||||||
//Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()} ${formattedDateTime(event.createdAt)} > ${formattedDateTime(oldUser.updatedAt)}")
|
//Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()} ${formattedDateTime(event.createdAt)} > ${formattedDateTime(oldUser.updatedAt)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: ChannelMessageEvent) {
|
fun consume(event: ChannelMessageEvent) {
|
||||||
@@ -293,7 +297,7 @@ object LocalCache {
|
|||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = Collections.synchronizedList(event.mentions.map { getOrCreateUser(decodePublicKey(it)) })
|
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.channel = channel
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
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.
|
// Observers line up here.
|
||||||
val live: LocalCacheLiveData = LocalCacheLiveData(this)
|
val live: LocalCacheLiveData = LocalCacheLiveData(this)
|
||||||
|
|
||||||
|
@@ -120,6 +120,7 @@ class User(val pubkey: ByteArray) {
|
|||||||
|
|
||||||
handlerWaiting = true
|
handlerWaiting = true
|
||||||
filterHandler.postDelayed({
|
filterHandler.postDelayed({
|
||||||
|
println("User Refresh")
|
||||||
live.refresh()
|
live.refresh()
|
||||||
handlerWaiting = false
|
handlerWaiting = false
|
||||||
}, 100)
|
}, 100)
|
||||||
|
@@ -23,9 +23,9 @@ class NewChannelViewModel: ViewModel() {
|
|||||||
this.account = account
|
this.account = account
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
originalChannel = channel
|
originalChannel = channel
|
||||||
channelName.value = TextFieldValue()
|
channelName.value = TextFieldValue(channel.info.name ?: "")
|
||||||
channelPicture.value = TextFieldValue()
|
channelPicture.value = TextFieldValue(channel.info.picture ?: "")
|
||||||
channelDescription.value = TextFieldValue()
|
channelDescription.value = TextFieldValue(channel.info.about ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -95,7 +95,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChannelName(
|
fun ChannelName(
|
||||||
channelPicture: String,
|
channelPicture: String,
|
||||||
channelTitle: @Composable () -> Unit,
|
channelTitle: @Composable () -> Unit,
|
||||||
channelLastTime: Long?,
|
channelLastTime: Long?,
|
||||||
@@ -124,12 +124,15 @@ private fun ChannelName(
|
|||||||
) {
|
) {
|
||||||
channelTitle()
|
channelTitle()
|
||||||
|
|
||||||
|
channelLastTime?.let {
|
||||||
Text(
|
Text(
|
||||||
timeAgo(channelLastTime),
|
timeAgo(channelLastTime),
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (channelLastContent != null)
|
if (channelLastContent != null)
|
||||||
Text(
|
Text(
|
||||||
channelLastContent,
|
channelLastContent,
|
||||||
|
@@ -48,6 +48,7 @@ import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
|||||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -72,17 +73,23 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
ChannelHeader(
|
ChannelHeader(
|
||||||
channel, account,
|
channel, account,
|
||||||
accountStateViewModel = accountStateViewModel
|
accountStateViewModel = accountStateViewModel,
|
||||||
|
navController = navController
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
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)
|
ChatroomFeedView(feedViewModel, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
//LAST ROW
|
//LAST ROW
|
||||||
Row(modifier = Modifier.padding(10.dp).fillMaxWidth(),
|
Row(modifier = Modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -92,7 +99,9 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
keyboardOptions = KeyboardOptions.Default.copy(
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
capitalization = KeyboardCapitalization.Sentences
|
capitalization = KeyboardCapitalization.Sentences
|
||||||
),
|
),
|
||||||
modifier = Modifier.weight(1f, true).padding(end = 10.dp),
|
modifier = Modifier
|
||||||
|
.weight(1f, true)
|
||||||
|
.padding(end = 10.dp),
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
text = "reply here.. ",
|
text = "reply here.. ",
|
||||||
@@ -116,7 +125,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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 channelState by baseChannel.live.observeAsState()
|
||||||
val channel = channelState?.channel
|
val channel = channelState?.channel
|
||||||
|
|
||||||
@@ -128,11 +137,14 @@ fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel:
|
|||||||
model = channel?.profilePicture(),
|
model = channel?.profilePicture(),
|
||||||
contentDescription = "Profile Image",
|
contentDescription = "Profile Image",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(35.dp).height(35.dp)
|
.width(35.dp)
|
||||||
|
.height(35.dp)
|
||||||
.clip(shape = CircleShape)
|
.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) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(
|
Text(
|
||||||
"${channel?.info?.name}",
|
"${channel?.info?.name}",
|
||||||
@@ -153,7 +165,17 @@ fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel:
|
|||||||
|
|
||||||
channel?.let { NoteCopyButton(it) }
|
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)
|
backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Text(text = "npub", color = Color.White)
|
Text(text = "note", color = Color.White)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,3 +227,39 @@ private fun EditButton(account: Account, channel: Channel, accountStateViewModel
|
|||||||
Text(text = "Edit", color = Color.White)
|
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.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
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.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -46,11 +47,15 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||||
import com.vitorpamplona.amethyst.ui.actions.SearchButton
|
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.note.UsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@@ -66,16 +71,18 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
SearchBar(navController)
|
SearchBar(accountViewModel, navController)
|
||||||
FeedView(feedViewModel, accountViewModel, navController)
|
FeedView(feedViewModel, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SearchBar(navController: NavController) {
|
private fun SearchBar(accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val searchValue = remember { mutableStateOf(TextFieldValue("")) }
|
val searchValue = remember { mutableStateOf(TextFieldValue("")) }
|
||||||
val searchResults = remember { mutableStateOf<List<User>>(emptyList()) }
|
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 {
|
val isTrailingIconVisible by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -96,6 +103,8 @@ private fun SearchBar(navController: NavController) {
|
|||||||
onValueChange = {
|
onValueChange = {
|
||||||
searchValue.value = it
|
searchValue.value = it
|
||||||
searchResults.value = LocalCache.findUsersStartingWith(it.text)
|
searchResults.value = LocalCache.findUsersStartingWith(it.text)
|
||||||
|
searchResultsNotes.value = LocalCache.findNotesStartingWith(it.text)
|
||||||
|
searchResultsChannels.value = LocalCache.findChannelsStartingWith(it.text)
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
capitalization = KeyboardCapitalization.Sentences
|
capitalization = KeyboardCapitalization.Sentences
|
||||||
@@ -123,6 +132,8 @@ private fun SearchBar(navController: NavController) {
|
|||||||
onClick = {
|
onClick = {
|
||||||
searchValue.value = TextFieldValue("")
|
searchValue.value = TextFieldValue("")
|
||||||
searchResults.value = emptyList()
|
searchResults.value = emptyList()
|
||||||
|
searchResultsChannels.value = emptyList()
|
||||||
|
searchResultsNotes.value = emptyList()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -148,6 +159,24 @@ private fun SearchBar(navController: NavController) {
|
|||||||
navController.navigate("User/${item.pubkeyHex}")
|
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