mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-08 20:08:06 +02:00
Add Channel
This commit is contained in:
parent
0e3b007730
commit
75219f0f2d
@ -2,10 +2,14 @@ package com.vitorpamplona.amethyst.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
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.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
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.Persona
|
||||
import nostr.postr.Utils
|
||||
@ -15,7 +19,8 @@ import nostr.postr.events.TextNoteEvent
|
||||
import nostr.postr.toHex
|
||||
|
||||
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()) {
|
||||
@ -149,6 +154,44 @@ class Account(val loggedIn: Persona, val followingChannels: MutableSet<String> =
|
||||
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? {
|
||||
val event = note.event
|
||||
return if (event is PrivateDmEvent && loggedIn.privKey != null) {
|
||||
|
@ -13,6 +13,7 @@ class Channel(val id: ByteArray) {
|
||||
val idHex = id.toHexKey()
|
||||
val idDisplayHex = id.toShortenHex()
|
||||
|
||||
var creator: User? = null
|
||||
var info = ChannelCreateEvent.ChannelData(null, null, null)
|
||||
|
||||
var updatedMetadataAt: Long = 0;
|
||||
@ -28,9 +29,10 @@ class Channel(val id: ByteArray) {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateChannelInfo(channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) {
|
||||
info = channelInfo
|
||||
updatedMetadataAt = updatedAt
|
||||
fun updateChannelInfo(creator: User, channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) {
|
||||
this.creator = creator
|
||||
this.info = channelInfo
|
||||
this.updatedMetadataAt = updatedAt
|
||||
|
||||
live.refresh()
|
||||
}
|
||||
|
@ -245,10 +245,18 @@ object LocalCache {
|
||||
}
|
||||
|
||||
fun consume(event: ChannelCreateEvent) {
|
||||
Log.d("MT", "New Event ${event.content} ${event.id.toHex()}")
|
||||
// new event
|
||||
val oldChannel = getOrCreateChannel(event.id.toHex())
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
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 {
|
||||
// older data, does nothing
|
||||
}
|
||||
@ -259,8 +267,15 @@ object LocalCache {
|
||||
|
||||
// new event
|
||||
val oldChannel = getOrCreateChannel(event.channel)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
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 {
|
||||
//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
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
@ -21,6 +22,11 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
||||
authors = listOf(account.userProfile().pubkeyHex)
|
||||
)
|
||||
|
||||
fun createChannelsCreatedbyMeFilter() = JsonFilter(
|
||||
kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind),
|
||||
authors = listOf(account.userProfile().pubkeyHex)
|
||||
)
|
||||
|
||||
fun createMyChannelsFilter() = JsonFilter(
|
||||
kinds = listOf(ChannelCreateEvent.kind),
|
||||
ids = account.followingChannels.toList()
|
||||
@ -53,6 +59,8 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
||||
val myChannelsInfoChannel = requestNewChannel()
|
||||
val myChannelsMessagesChannel = requestNewChannel()
|
||||
|
||||
val myChannelsCreatedbyMeChannel = requestNewChannel()
|
||||
|
||||
// returns the last Note of each user.
|
||||
override fun feed(): List<Note> {
|
||||
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 }
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@ -75,5 +89,7 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
||||
myChannelsChannel.filter = listOf(createMyChannelsFilter()).ifEmpty { null }
|
||||
myChannelsInfoChannel.filter = createLastChannelInfoFilter().ifEmpty { null }
|
||||
myChannelsMessagesChannel.filter = createLastMessageOfEachChannelFilter().ifEmpty { null }
|
||||
|
||||
//myChannelsCreatedbyMeChannel.filter = listOf(createChannelsCreatedbyMeFilter()).ifEmpty { null }
|
||||
}
|
||||
}
|
@ -30,14 +30,14 @@ class ChannelMetadataEvent (
|
||||
companion object {
|
||||
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)
|
||||
gson.toJson(newChannelInfo)
|
||||
else
|
||||
""
|
||||
|
||||
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 sig = Utils.sign(id, privateKey)
|
||||
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
|
||||
fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier = Modifier) {
|
||||
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.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
fun AppNavigation(
|
||||
navController: NavHostController,
|
||||
accountViewModel: AccountViewModel
|
||||
accountViewModel: AccountViewModel,
|
||||
accountStateViewModel: AccountStateViewModel
|
||||
) {
|
||||
NavHost(navController, startDestination = Route.Home.route) {
|
||||
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.navArgument
|
||||
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.ChatroomListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.ChatroomScreen
|
||||
@ -25,31 +26,31 @@ sealed class Route(
|
||||
val route: String,
|
||||
val icon: Int,
|
||||
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 Search : Route("Search", R.drawable.ic_search, buildScreen = { acc, nav -> { _ -> SearchScreen(acc, nav) }})
|
||||
object Notification : Route("Notification", R.drawable.ic_notifications,buildScreen = { acc, nav -> { _ -> NotificationScreen(acc, nav) }})
|
||||
object Message : Route("Message", R.drawable.ic_dm, buildScreen = { acc, nav -> { _ -> ChatroomListScreen(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, accSt, nav -> { _ -> SearchScreen(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, accSt, nav -> { _ -> ChatroomListScreen(acc, nav) }})
|
||||
|
||||
object Profile : Route("User/{id}", R.drawable.ic_profile,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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 coil.compose.AsyncImage
|
||||
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
|
||||
|
||||
@Composable
|
||||
@ -42,6 +44,13 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
||||
val channelState by note.channel!!.live.observeAsState()
|
||||
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 {
|
||||
ChannelName(
|
||||
channelPicture = it.profilePicture(),
|
||||
@ -52,7 +61,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
|
||||
)
|
||||
},
|
||||
channelLastTime = note.event?.createdAt,
|
||||
channelLastContent = "${author?.toBestDisplayName()}: " + note.event?.content,
|
||||
channelLastContent = "${author?.toBestDisplayName()}: " + description,
|
||||
onClick = { navController.navigate("Channel/${it.idHex}") })
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,9 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.AsyncImage
|
||||
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.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@ -134,26 +136,41 @@ fun ChatroomMessageCompose(baseNote: Note, accountViewModel: AccountViewModel, n
|
||||
}
|
||||
|
||||
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)
|
||||
RichTextViewer(
|
||||
eventContent,
|
||||
note.event?.tags,
|
||||
note,
|
||||
accountViewModel,
|
||||
navController
|
||||
)
|
||||
else
|
||||
RichTextViewer(
|
||||
"Could Not decrypt the message",
|
||||
note.event?.tags,
|
||||
note,
|
||||
accountViewModel,
|
||||
navController
|
||||
)
|
||||
if (eventContent != null)
|
||||
RichTextViewer(
|
||||
eventContent,
|
||||
note.event?.tags,
|
||||
note,
|
||||
accountViewModel,
|
||||
navController
|
||||
)
|
||||
else
|
||||
RichTextViewer(
|
||||
"Could Not decrypt the message",
|
||||
note.event?.tags,
|
||||
note,
|
||||
accountViewModel,
|
||||
navController
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End,
|
||||
|
@ -9,7 +9,10 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
@ -20,9 +23,14 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.input.KeyboardCapitalization
|
||||
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.navigation.NavController
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
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.ui.actions.NewChannelView
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@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 account = accountState?.account
|
||||
|
||||
@ -58,9 +71,8 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, navCon
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
ChannelHeader(
|
||||
channel,
|
||||
accountViewModel = accountViewModel,
|
||||
navController = navController
|
||||
channel, account,
|
||||
accountStateViewModel = accountStateViewModel
|
||||
)
|
||||
|
||||
Column(
|
||||
@ -104,7 +116,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, navCon
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel: AccountStateViewModel) {
|
||||
val channelState by baseChannel.live.observeAsState()
|
||||
val channel = channelState?.channel
|
||||
|
||||
@ -120,7 +132,7 @@ fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, navC
|
||||
.clip(shape = CircleShape)
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
"${channel?.info?.name}",
|
||||
@ -132,12 +144,16 @@ fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, navC
|
||||
Text(
|
||||
"${channel?.info?.about}",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
maxLines = 1,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@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.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.vitorpamplona.amethyst.buttons.NewChannelButton
|
||||
import com.vitorpamplona.amethyst.buttons.NewNoteButton
|
||||
import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
|
||||
@ -49,7 +50,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
||||
scaffoldState = scaffoldState
|
||||
) {
|
||||
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.rememberPagerState
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.model.toNote
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource
|
||||
@ -73,8 +75,6 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
||||
val accountUserState by accountViewModel.userLiveData.observeAsState()
|
||||
val accountUser = accountUserState?.user
|
||||
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
if (userId != null && account != null && accountUser != null) {
|
||||
DisposableEffect(account) {
|
||||
NostrUserProfileDataSource.loadUserProfile(userId)
|
||||
@ -138,7 +138,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
||||
|
||||
MessageButton(user, navController)
|
||||
|
||||
NPubCopyButton(clipboardManager, user)
|
||||
NPubCopyButton(user)
|
||||
|
||||
if (accountUser == user) {
|
||||
EditButton()
|
||||
@ -262,9 +262,10 @@ fun TabFollowers(user: User, accountViewModel: AccountViewModel, navController:
|
||||
|
||||
@Composable
|
||||
private fun NPubCopyButton(
|
||||
clipboardManager: ClipboardManager,
|
||||
user: User
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = { clipboardManager.setText(AnnotatedString(user.pubkey.toNpub())) },
|
||||
|
Loading…
x
Reference in New Issue
Block a user