mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-22 21:51:57 +02:00
Add Channel
This commit is contained in:
@@ -2,10 +2,14 @@ package com.vitorpamplona.amethyst.model
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.service.Constants
|
import com.vitorpamplona.amethyst.service.Constants
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import nostr.postr.Contact
|
import nostr.postr.Contact
|
||||||
import nostr.postr.Persona
|
import nostr.postr.Persona
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
@@ -15,7 +19,8 @@ import nostr.postr.events.TextNoteEvent
|
|||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
val DefaultChannels = setOf(
|
val DefaultChannels = setOf(
|
||||||
"25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb" // -> Anigma's Nostr
|
"25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb", // -> Anigma's Nostr
|
||||||
|
"42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5" // -> Amethyst's Group
|
||||||
)
|
)
|
||||||
|
|
||||||
class Account(val loggedIn: Persona, val followingChannels: MutableSet<String> = DefaultChannels.toMutableSet()) {
|
class Account(val loggedIn: Persona, val followingChannels: MutableSet<String> = DefaultChannels.toMutableSet()) {
|
||||||
@@ -149,6 +154,44 @@ class Account(val loggedIn: Persona, val followingChannels: MutableSet<String> =
|
|||||||
LocalCache.consume(signedEvent)
|
LocalCache.consume(signedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sendCreateNewChannel(name: String, about: String, picture: String, accountStateViewModel: AccountStateViewModel) {
|
||||||
|
if (!isWriteable()) return
|
||||||
|
|
||||||
|
val metadata = ChannelCreateEvent.ChannelData(
|
||||||
|
name, about, picture
|
||||||
|
)
|
||||||
|
|
||||||
|
val event = ChannelCreateEvent.create(
|
||||||
|
channelInfo = metadata,
|
||||||
|
privateKey = loggedIn.privKey!!
|
||||||
|
)
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
|
||||||
|
followingChannels.add(event.id.toHex())
|
||||||
|
accountStateViewModel.saveToEncryptedStorage(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendChangeChannel(name: String, about: String, picture: String, channel: Channel) {
|
||||||
|
if (!isWriteable()) return
|
||||||
|
|
||||||
|
val metadata = ChannelCreateEvent.ChannelData(
|
||||||
|
name, about, picture
|
||||||
|
)
|
||||||
|
|
||||||
|
val event = ChannelMetadataEvent.create(
|
||||||
|
newChannelInfo = metadata,
|
||||||
|
originalChannelIdHex = channel.idHex,
|
||||||
|
privateKey = loggedIn.privKey!!
|
||||||
|
)
|
||||||
|
|
||||||
|
Client.send(event)
|
||||||
|
LocalCache.consume(event)
|
||||||
|
|
||||||
|
followingChannels.add(event.id.toHex())
|
||||||
|
}
|
||||||
|
|
||||||
fun decryptContent(note: Note): String? {
|
fun decryptContent(note: Note): String? {
|
||||||
val event = note.event
|
val event = note.event
|
||||||
return if (event is PrivateDmEvent && loggedIn.privKey != null) {
|
return if (event is PrivateDmEvent && loggedIn.privKey != null) {
|
||||||
|
@@ -13,6 +13,7 @@ class Channel(val id: ByteArray) {
|
|||||||
val idHex = id.toHexKey()
|
val idHex = id.toHexKey()
|
||||||
val idDisplayHex = id.toShortenHex()
|
val idDisplayHex = id.toShortenHex()
|
||||||
|
|
||||||
|
var creator: User? = null
|
||||||
var info = ChannelCreateEvent.ChannelData(null, null, null)
|
var info = ChannelCreateEvent.ChannelData(null, null, null)
|
||||||
|
|
||||||
var updatedMetadataAt: Long = 0;
|
var updatedMetadataAt: Long = 0;
|
||||||
@@ -28,9 +29,10 @@ class Channel(val id: ByteArray) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateChannelInfo(channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) {
|
fun updateChannelInfo(creator: User, channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) {
|
||||||
info = channelInfo
|
this.creator = creator
|
||||||
updatedMetadataAt = updatedAt
|
this.info = channelInfo
|
||||||
|
this.updatedMetadataAt = updatedAt
|
||||||
|
|
||||||
live.refresh()
|
live.refresh()
|
||||||
}
|
}
|
||||||
|
@@ -245,10 +245,18 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: ChannelCreateEvent) {
|
fun consume(event: ChannelCreateEvent) {
|
||||||
|
Log.d("MT", "New Event ${event.content} ${event.id.toHex()}")
|
||||||
// new event
|
// new event
|
||||||
val oldChannel = getOrCreateChannel(event.id.toHex())
|
val oldChannel = getOrCreateChannel(event.id.toHex())
|
||||||
|
val author = getOrCreateUser(event.pubKey)
|
||||||
if (event.createdAt > oldChannel.updatedMetadataAt) {
|
if (event.createdAt > oldChannel.updatedMetadataAt) {
|
||||||
oldChannel.updateChannelInfo(event.channelInfo, event.createdAt)
|
if (oldChannel.creator == null || oldChannel.creator == author) {
|
||||||
|
oldChannel.updateChannelInfo(author, event.channelInfo, event.createdAt)
|
||||||
|
|
||||||
|
val note = oldChannel.getOrCreateNote(event.id.toHex())
|
||||||
|
note.channel = oldChannel
|
||||||
|
note.loadEvent(event, author, emptyList(), mutableListOf())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// older data, does nothing
|
// older data, does nothing
|
||||||
}
|
}
|
||||||
@@ -259,8 +267,15 @@ object LocalCache {
|
|||||||
|
|
||||||
// new event
|
// new event
|
||||||
val oldChannel = getOrCreateChannel(event.channel)
|
val oldChannel = getOrCreateChannel(event.channel)
|
||||||
|
val author = getOrCreateUser(event.pubKey)
|
||||||
if (event.createdAt > oldChannel.updatedMetadataAt) {
|
if (event.createdAt > oldChannel.updatedMetadataAt) {
|
||||||
oldChannel.updateChannelInfo(event.channelInfo, event.createdAt)
|
if (oldChannel.creator == null || oldChannel.creator == author) {
|
||||||
|
oldChannel.updateChannelInfo(author, event.channelInfo, event.createdAt)
|
||||||
|
|
||||||
|
val note = oldChannel.getOrCreateNote(event.id.toHex())
|
||||||
|
note.channel = oldChannel
|
||||||
|
note.loadEvent(event, author, emptyList(), mutableListOf())
|
||||||
|
}
|
||||||
} 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)}")
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.vitorpamplona.amethyst.service
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
@@ -21,6 +22,11 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
|||||||
authors = listOf(account.userProfile().pubkeyHex)
|
authors = listOf(account.userProfile().pubkeyHex)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun createChannelsCreatedbyMeFilter() = JsonFilter(
|
||||||
|
kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind),
|
||||||
|
authors = listOf(account.userProfile().pubkeyHex)
|
||||||
|
)
|
||||||
|
|
||||||
fun createMyChannelsFilter() = JsonFilter(
|
fun createMyChannelsFilter() = JsonFilter(
|
||||||
kinds = listOf(ChannelCreateEvent.kind),
|
kinds = listOf(ChannelCreateEvent.kind),
|
||||||
ids = account.followingChannels.toList()
|
ids = account.followingChannels.toList()
|
||||||
@@ -53,6 +59,8 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
|||||||
val myChannelsInfoChannel = requestNewChannel()
|
val myChannelsInfoChannel = requestNewChannel()
|
||||||
val myChannelsMessagesChannel = requestNewChannel()
|
val myChannelsMessagesChannel = requestNewChannel()
|
||||||
|
|
||||||
|
val myChannelsCreatedbyMeChannel = requestNewChannel()
|
||||||
|
|
||||||
// returns the last Note of each user.
|
// returns the last Note of each user.
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val messages = account.userProfile().messages
|
val messages = account.userProfile().messages
|
||||||
@@ -66,6 +74,12 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
|||||||
it.notes.values.sortedBy { it.event?.createdAt }.lastOrNull { it.event != null }
|
it.notes.values.sortedBy { it.event?.createdAt }.lastOrNull { it.event != null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val channelsCreatedByMe = LocalCache.channels.values.filter {
|
||||||
|
it.creator == account.userProfile()
|
||||||
|
}.map {
|
||||||
|
it.notes.values.sortedBy { it.event?.createdAt }.lastOrNull { it.event != null }
|
||||||
|
}
|
||||||
|
|
||||||
return (privateMessages + publicChannels).filterNotNull().sortedBy { it.event?.createdAt }.reversed()
|
return (privateMessages + publicChannels).filterNotNull().sortedBy { it.event?.createdAt }.reversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,5 +89,7 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
|||||||
myChannelsChannel.filter = listOf(createMyChannelsFilter()).ifEmpty { null }
|
myChannelsChannel.filter = listOf(createMyChannelsFilter()).ifEmpty { null }
|
||||||
myChannelsInfoChannel.filter = createLastChannelInfoFilter().ifEmpty { null }
|
myChannelsInfoChannel.filter = createLastChannelInfoFilter().ifEmpty { null }
|
||||||
myChannelsMessagesChannel.filter = createLastMessageOfEachChannelFilter().ifEmpty { null }
|
myChannelsMessagesChannel.filter = createLastMessageOfEachChannelFilter().ifEmpty { null }
|
||||||
|
|
||||||
|
//myChannelsCreatedbyMeChannel.filter = listOf(createChannelsCreatedbyMeFilter()).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -30,14 +30,14 @@ class ChannelMetadataEvent (
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 41
|
const val kind = 41
|
||||||
|
|
||||||
fun create(newChannelInfo: ChannelCreateEvent.ChannelData?, originalChannel: ChannelCreateEvent, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMetadataEvent {
|
fun create(newChannelInfo: ChannelCreateEvent.ChannelData?, originalChannelIdHex: String, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMetadataEvent {
|
||||||
val content = if (newChannelInfo != null)
|
val content = if (newChannelInfo != null)
|
||||||
gson.toJson(newChannelInfo)
|
gson.toJson(newChannelInfo)
|
||||||
else
|
else
|
||||||
""
|
""
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val tags = listOf( listOf("e", originalChannel.id.toHex(), "", "root") )
|
val tags = listOf( listOf("e", originalChannelIdHex, "", "root") )
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
return ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
@@ -0,0 +1,123 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedTextField
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NewChannelView(onClose: () -> Unit, account: Account, accountStateViewModel: AccountStateViewModel, channel: Channel? = null) {
|
||||||
|
val postViewModel: NewChannelViewModel = viewModel()
|
||||||
|
|
||||||
|
postViewModel.load(account, channel, accountStateViewModel)
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { onClose() },
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnClickOutside = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(10.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
CloseButton(onCancel = {
|
||||||
|
postViewModel.clear()
|
||||||
|
onClose()
|
||||||
|
})
|
||||||
|
|
||||||
|
PostButton(
|
||||||
|
onPost = {
|
||||||
|
postViewModel.create()
|
||||||
|
onClose()
|
||||||
|
},
|
||||||
|
postViewModel.channelName.value.text.isNotBlank()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(15.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = "Channel Name") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = postViewModel.channelName.value,
|
||||||
|
onValueChange = { postViewModel.channelName.value = it },
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "My Awesome Group",
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
|
capitalization = KeyboardCapitalization.Sentences
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(15.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = "Picture Url") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = postViewModel.channelPicture.value,
|
||||||
|
onValueChange = { postViewModel.channelPicture.value = it },
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "http://mygroup.com/logo.jpg",
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(15.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = "Description") },
|
||||||
|
modifier = Modifier.fillMaxWidth().height(100.dp),
|
||||||
|
value = postViewModel.channelDescription.value,
|
||||||
|
onValueChange = { postViewModel.channelDescription.value = it },
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "About us.. ",
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
|
capitalization = KeyboardCapitalization.Sentences
|
||||||
|
),
|
||||||
|
maxLines = 10
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
class NewChannelViewModel: ViewModel() {
|
||||||
|
private var account: Account? = null
|
||||||
|
private var originalChannel: Channel? = null
|
||||||
|
private var accountStateViewModel: AccountStateViewModel? = null
|
||||||
|
|
||||||
|
val channelName = mutableStateOf(TextFieldValue())
|
||||||
|
val channelPicture = mutableStateOf(TextFieldValue())
|
||||||
|
val channelDescription = mutableStateOf(TextFieldValue())
|
||||||
|
|
||||||
|
|
||||||
|
fun load(account: Account, channel: Channel?, accountStateViewModel: AccountStateViewModel) {
|
||||||
|
this.accountStateViewModel = accountStateViewModel
|
||||||
|
this.account = account
|
||||||
|
if (channel != null) {
|
||||||
|
originalChannel = channel
|
||||||
|
channelName.value = TextFieldValue()
|
||||||
|
channelPicture.value = TextFieldValue()
|
||||||
|
channelDescription.value = TextFieldValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create() {
|
||||||
|
if (originalChannel == null)
|
||||||
|
this.account?.sendCreateNewChannel(
|
||||||
|
channelName.value.text,
|
||||||
|
channelDescription.value.text,
|
||||||
|
channelPicture.value.text,
|
||||||
|
accountStateViewModel!!
|
||||||
|
)
|
||||||
|
else
|
||||||
|
this.account?.sendChangeChannel(
|
||||||
|
channelName.value.text,
|
||||||
|
channelDescription.value.text,
|
||||||
|
channelPicture.value.text,
|
||||||
|
originalChannel!!
|
||||||
|
)
|
||||||
|
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
channelName.value = TextFieldValue()
|
||||||
|
channelPicture.value = TextFieldValue()
|
||||||
|
channelDescription.value = TextFieldValue()
|
||||||
|
}
|
||||||
|
}
|
@@ -241,6 +241,25 @@ fun PostButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CreateButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier = Modifier) {
|
||||||
|
Button(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = {
|
||||||
|
if (isActive) {
|
||||||
|
onPost()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(text = "Create", color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier = Modifier) {
|
fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier = Modifier) {
|
||||||
Button(
|
Button(
|
||||||
|
@@ -0,0 +1,53 @@
|
|||||||
|
package com.vitorpamplona.amethyst.buttons
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedButton
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Add
|
||||||
|
import androidx.compose.material.icons.outlined.Visibility
|
||||||
|
import androidx.compose.material.icons.outlined.VisibilityOff
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NewChannelButton(account: Account, accountStateViewModel: AccountStateViewModel) {
|
||||||
|
var wantsToPost by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantsToPost)
|
||||||
|
NewChannelView({ wantsToPost = false }, account = account, accountStateViewModel)
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { wantsToPost = true },
|
||||||
|
modifier = Modifier.size(55.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MaterialTheme.colors.primary),
|
||||||
|
contentPadding = PaddingValues(0.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Add,
|
||||||
|
contentDescription = "New Channel",
|
||||||
|
modifier = Modifier.size(26.dp),
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -4,16 +4,18 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavigation(
|
fun AppNavigation(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel,
|
||||||
|
accountStateViewModel: AccountStateViewModel
|
||||||
) {
|
) {
|
||||||
NavHost(navController, startDestination = Route.Home.route) {
|
NavHost(navController, startDestination = Route.Home.route) {
|
||||||
Routes.forEach {
|
Routes.forEach {
|
||||||
composable(it.route, it.arguments, content = it.buildScreen(accountViewModel, navController))
|
composable(it.route, it.arguments, content = it.buildScreen(accountViewModel, accountStateViewModel, navController))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -10,6 +10,7 @@ import androidx.navigation.NavType
|
|||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ChannelScreen
|
import com.vitorpamplona.amethyst.ui.screen.ChannelScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ChatroomListScreen
|
import com.vitorpamplona.amethyst.ui.screen.ChatroomListScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ChatroomScreen
|
import com.vitorpamplona.amethyst.ui.screen.ChatroomScreen
|
||||||
@@ -25,31 +26,31 @@ sealed class Route(
|
|||||||
val route: String,
|
val route: String,
|
||||||
val icon: Int,
|
val icon: Int,
|
||||||
val arguments: List<NamedNavArgument> = emptyList(),
|
val arguments: List<NamedNavArgument> = emptyList(),
|
||||||
val buildScreen: (AccountViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit
|
val buildScreen: (AccountViewModel, AccountStateViewModel, NavController) -> @Composable (NavBackStackEntry) -> Unit
|
||||||
) {
|
) {
|
||||||
object Home : Route("Home", R.drawable.ic_home, buildScreen = { acc, nav -> { _ -> HomeScreen(acc, nav) } })
|
object Home : Route("Home", R.drawable.ic_home, buildScreen = { acc, accSt, nav -> { _ -> HomeScreen(acc, nav) } })
|
||||||
object Search : Route("Search", R.drawable.ic_search, buildScreen = { acc, nav -> { _ -> SearchScreen(acc, nav) }})
|
object Search : Route("Search", R.drawable.ic_search, buildScreen = { acc, accSt, nav -> { _ -> SearchScreen(acc, nav) }})
|
||||||
object Notification : Route("Notification", R.drawable.ic_notifications,buildScreen = { acc, nav -> { _ -> NotificationScreen(acc, nav) }})
|
object Notification : Route("Notification", R.drawable.ic_notifications,buildScreen = { acc, accSt, nav -> { _ -> NotificationScreen(acc, nav) }})
|
||||||
object Message : Route("Message", R.drawable.ic_dm, buildScreen = { acc, nav -> { _ -> ChatroomListScreen(acc, nav) }})
|
object Message : Route("Message", R.drawable.ic_dm, buildScreen = { acc, accSt, nav -> { _ -> ChatroomListScreen(acc, nav) }})
|
||||||
|
|
||||||
object Profile : Route("User/{id}", R.drawable.ic_profile,
|
object Profile : Route("User/{id}", R.drawable.ic_profile,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
||||||
buildScreen = { acc, nav -> { ProfileScreen(it.arguments?.getString("id"), acc, nav) }}
|
buildScreen = { acc, accSt, nav -> { ProfileScreen(it.arguments?.getString("id"), acc, nav) }}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Note : Route("Note/{id}", R.drawable.ic_moments,
|
object Note : Route("Note/{id}", R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
||||||
buildScreen = { acc, nav -> { ThreadScreen(it.arguments?.getString("id"), acc, nav) }}
|
buildScreen = { acc, accSt, nav -> { ThreadScreen(it.arguments?.getString("id"), acc, nav) }}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Room : Route("Room/{id}", R.drawable.ic_moments,
|
object Room : Route("Room/{id}", R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
||||||
buildScreen = { acc, nav -> { ChatroomScreen(it.arguments?.getString("id"), acc, nav) }}
|
buildScreen = { acc, accSt, nav -> { ChatroomScreen(it.arguments?.getString("id"), acc, nav) }}
|
||||||
)
|
)
|
||||||
|
|
||||||
object Channel : Route("Channel/{id}", R.drawable.ic_moments,
|
object Channel : Route("Channel/{id}", R.drawable.ic_moments,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
arguments = listOf(navArgument("id") { type = NavType.StringType } ),
|
||||||
buildScreen = { acc, nav -> { ChannelScreen(it.arguments?.getString("id"), acc, nav) }}
|
buildScreen = { acc, accSt, nav -> { ChannelScreen(it.arguments?.getString("id"), acc, accSt, nav) }}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -42,6 +44,13 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
|||||||
val channelState by note.channel!!.live.observeAsState()
|
val channelState by note.channel!!.live.observeAsState()
|
||||||
val channel = channelState?.channel
|
val channel = channelState?.channel
|
||||||
|
|
||||||
|
val description = if (note.event is ChannelCreateEvent) {
|
||||||
|
"Channel created"
|
||||||
|
} else if (note.event is ChannelMetadataEvent) {
|
||||||
|
"Channel Information changed to "
|
||||||
|
} else {
|
||||||
|
note.event?.content
|
||||||
|
}
|
||||||
channel?.let {
|
channel?.let {
|
||||||
ChannelName(
|
ChannelName(
|
||||||
channelPicture = it.profilePicture(),
|
channelPicture = it.profilePicture(),
|
||||||
@@ -52,7 +61,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
channelLastTime = note.event?.createdAt,
|
channelLastTime = note.event?.createdAt,
|
||||||
channelLastContent = "${author?.toBestDisplayName()}: " + note.event?.content,
|
channelLastContent = "${author?.toBestDisplayName()}: " + description,
|
||||||
onClick = { navController.navigate("Channel/${it.idHex}") })
|
onClick = { navController.navigate("Channel/${it.idHex}") })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,7 +35,9 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@@ -134,26 +136,41 @@ fun ChatroomMessageCompose(baseNote: Note, accountViewModel: AccountViewModel, n
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
val eventContent = accountViewModel.decrypt(note)
|
val event = note.event
|
||||||
|
if (event is ChannelCreateEvent) {
|
||||||
|
Text(text = "${note.author?.toBestDisplayName()} created " +
|
||||||
|
"${event.channelInfo.name ?: ""} with " +
|
||||||
|
"description of '${event.channelInfo.about ?: ""}', " +
|
||||||
|
"and picture '${event.channelInfo.picture ?: ""}'")
|
||||||
|
} else if (event is ChannelMetadataEvent) {
|
||||||
|
Text(text = "${note.author?.toBestDisplayName()} changed " +
|
||||||
|
"chat name to '${event.channelInfo.name ?: ""}', " +
|
||||||
|
"description to '${event.channelInfo.about ?: ""}', " +
|
||||||
|
"and picture to '${event.channelInfo.picture ?: ""}'")
|
||||||
|
} else {
|
||||||
|
val eventContent = accountViewModel.decrypt(note)
|
||||||
|
|
||||||
if (eventContent != null)
|
if (eventContent != null)
|
||||||
RichTextViewer(
|
RichTextViewer(
|
||||||
eventContent,
|
eventContent,
|
||||||
note.event?.tags,
|
note.event?.tags,
|
||||||
note,
|
note,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
navController
|
navController
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
RichTextViewer(
|
RichTextViewer(
|
||||||
"Could Not decrypt the message",
|
"Could Not decrypt the message",
|
||||||
note.event?.tags,
|
note.event?.tags,
|
||||||
note,
|
note,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
navController
|
navController
|
||||||
)
|
)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
|
@@ -9,7 +9,10 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
@@ -20,9 +23,14 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.ClipboardManager
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
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
|
||||||
@@ -32,13 +40,18 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
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.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.toNote
|
||||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
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.actions.PostButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, navController: NavController) {
|
fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, navController: NavController) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account
|
val account = accountState?.account
|
||||||
|
|
||||||
@@ -58,9 +71,8 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, navCon
|
|||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
ChannelHeader(
|
ChannelHeader(
|
||||||
channel,
|
channel, account,
|
||||||
accountViewModel = accountViewModel,
|
accountStateViewModel = accountStateViewModel
|
||||||
navController = navController
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -104,7 +116,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, navCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, navController: NavController) {
|
fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel: AccountStateViewModel) {
|
||||||
val channelState by baseChannel.live.observeAsState()
|
val channelState by baseChannel.live.observeAsState()
|
||||||
val channel = channelState?.channel
|
val channel = channelState?.channel
|
||||||
|
|
||||||
@@ -120,7 +132,7 @@ fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, navC
|
|||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
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}",
|
||||||
@@ -132,12 +144,16 @@ fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, navC
|
|||||||
Text(
|
Text(
|
||||||
"${channel?.info?.about}",
|
"${channel?.info?.about}",
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
fontSize = 12.sp
|
fontSize = 12.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel?.let { NoteCopyButton(it) }
|
||||||
|
|
||||||
|
channel?.let { EditButton(account, it, accountStateViewModel) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,4 +162,46 @@ fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, navC
|
|||||||
thickness = 0.25.dp
|
thickness = 0.25.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NoteCopyButton(
|
||||||
|
note: Channel
|
||||||
|
) {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
|
onClick = { clipboardManager.setText(AnnotatedString(note.id.toNote())) },
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(text = "npub", color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EditButton(account: Account, channel: Channel, accountStateViewModel: AccountStateViewModel) {
|
||||||
|
var wantsToPost by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantsToPost)
|
||||||
|
NewChannelView({ wantsToPost = false }, account = account, accountStateViewModel, channel)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
|
onClick = { wantsToPost = true },
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(text = "Edit", color = Color.White)
|
||||||
|
}
|
||||||
}
|
}
|
@@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.vitorpamplona.amethyst.buttons.NewChannelButton
|
||||||
import com.vitorpamplona.amethyst.buttons.NewNoteButton
|
import com.vitorpamplona.amethyst.buttons.NewNoteButton
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar
|
import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
|
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
|
||||||
@@ -49,7 +50,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
|||||||
scaffoldState = scaffoldState
|
scaffoldState = scaffoldState
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(bottom = it.calculateBottomPadding())) {
|
Column(modifier = Modifier.padding(bottom = it.calculateBottomPadding())) {
|
||||||
AppNavigation(navController, accountViewModel)
|
AppNavigation(navController, accountViewModel, accountStateViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,4 +74,20 @@ fun FloatingButton(navController: NavHostController, accountViewModel: AccountSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentRoute(navController) == Route.Message.route) {
|
||||||
|
Crossfade(targetState = accountState) { state ->
|
||||||
|
when (state) {
|
||||||
|
is AccountState.LoggedInViewOnly -> {
|
||||||
|
// Does nothing.
|
||||||
|
}
|
||||||
|
is AccountState.LoggedOff -> {
|
||||||
|
// Does nothing.
|
||||||
|
}
|
||||||
|
is AccountState.LoggedIn -> {
|
||||||
|
NewChannelButton(state.account, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -51,7 +51,9 @@ import com.google.accompanist.pager.HorizontalPager
|
|||||||
import com.google.accompanist.pager.pagerTabIndicatorOffset
|
import com.google.accompanist.pager.pagerTabIndicatorOffset
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.model.toNote
|
||||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource
|
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource
|
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource
|
||||||
@@ -73,8 +75,6 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
val accountUserState by accountViewModel.userLiveData.observeAsState()
|
val accountUserState by accountViewModel.userLiveData.observeAsState()
|
||||||
val accountUser = accountUserState?.user
|
val accountUser = accountUserState?.user
|
||||||
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
|
||||||
|
|
||||||
if (userId != null && account != null && accountUser != null) {
|
if (userId != null && account != null && accountUser != null) {
|
||||||
DisposableEffect(account) {
|
DisposableEffect(account) {
|
||||||
NostrUserProfileDataSource.loadUserProfile(userId)
|
NostrUserProfileDataSource.loadUserProfile(userId)
|
||||||
@@ -138,7 +138,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
|||||||
|
|
||||||
MessageButton(user, navController)
|
MessageButton(user, navController)
|
||||||
|
|
||||||
NPubCopyButton(clipboardManager, user)
|
NPubCopyButton(user)
|
||||||
|
|
||||||
if (accountUser == user) {
|
if (accountUser == user) {
|
||||||
EditButton()
|
EditButton()
|
||||||
@@ -262,9 +262,10 @@ fun TabFollowers(user: User, accountViewModel: AccountViewModel, navController:
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NPubCopyButton(
|
private fun NPubCopyButton(
|
||||||
clipboardManager: ClipboardManager,
|
|
||||||
user: User
|
user: User
|
||||||
) {
|
) {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp),
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
onClick = { clipboardManager.setText(AnnotatedString(user.pubkey.toNpub())) },
|
onClick = { clipboardManager.setText(AnnotatedString(user.pubkey.toNpub())) },
|
||||||
|
Reference in New Issue
Block a user