- Updating Status on the Drawer

- Reducing font size for statuses and nip05s
This commit is contained in:
Vitor Pamplona
2023-08-24 17:34:02 -04:00
parent 62db68c7b2
commit 5b596d2a56
12 changed files with 200 additions and 8 deletions
@@ -1008,6 +1008,25 @@ class Account(
} }
} }
fun updateStatus(oldStatus: AddressableNote, newStatus: String) {
if (!isWriteable()) return
val oldEvent = oldStatus.event as? StatusEvent ?: return
val event = StatusEvent.update(oldEvent, newStatus, keyPair.privKey!!)
Client.send(event)
LocalCache.consume(event, null)
}
fun createStatus(newStatus: String) {
if (!isWriteable()) return
val event = StatusEvent.create(newStatus, "general", expiration = null, keyPair.privKey!!)
Client.send(event)
LocalCache.consume(event, null)
}
fun removeEmojiPack(usersEmojiList: Note, emojiList: Note) { fun removeEmojiPack(usersEmojiList: Note, emojiList: Note) {
if (!isWriteable()) return if (!isWriteable()) return
@@ -345,7 +345,7 @@ object LocalCache {
private fun consume(event: PinListEvent) { consumeBaseReplaceable(event) } private fun consume(event: PinListEvent) { consumeBaseReplaceable(event) }
private fun consume(event: RelaySetEvent) { consumeBaseReplaceable(event) } private fun consume(event: RelaySetEvent) { consumeBaseReplaceable(event) }
private fun consume(event: AudioTrackEvent) { consumeBaseReplaceable(event) } private fun consume(event: AudioTrackEvent) { consumeBaseReplaceable(event) }
private fun consume(event: StatusEvent, relay: Relay?) { fun consume(event: StatusEvent, relay: Relay?) {
val version = getOrCreateNote(event.id) val version = getOrCreateNote(event.id)
val note = getOrCreateAddressableNote(event.address()) val note = getOrCreateAddressableNote(event.address())
val author = getOrCreateUser(event.pubKey) val author = getOrCreateUser(event.pubKey)
@@ -415,6 +415,7 @@ class UserLiveSet(u: User) {
relayInfo.hasObservers() || relayInfo.hasObservers() ||
metadata.hasObservers() || metadata.hasObservers() ||
zaps.hasObservers() || zaps.hasObservers() ||
statuses.hasObservers() ||
bookmarks.hasObservers() bookmarks.hasObservers()
} }
@@ -428,6 +429,7 @@ class UserLiveSet(u: User) {
metadata.destroy() metadata.destroy()
zaps.destroy() zaps.destroy()
bookmarks.destroy() bookmarks.destroy()
statuses.destroy()
} }
} }
@@ -52,9 +52,9 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
return TypedFilter( return TypedFilter(
types = COMMON_FEED_TYPES, types = COMMON_FEED_TYPES,
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(AdvertisedRelayListEvent.kind), kinds = listOf(AdvertisedRelayListEvent.kind, StatusEvent.kind),
authors = listOf(account.userProfile().pubkeyHex), authors = listOf(account.userProfile().pubkeyHex),
limit = 1 limit = 5
) )
) )
} }
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog import androidx.compose.material.AlertDialog
import androidx.compose.material.Divider import androidx.compose.material.Divider
@@ -27,13 +28,18 @@ import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.ScaffoldState import androidx.compose.material.ScaffoldState
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TextButton import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Send
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue 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
@@ -46,12 +52,15 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
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.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.BuildConfig
@@ -64,12 +73,16 @@ import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.note.LoadStatuses
import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size16dp import com.vitorpamplona.amethyst.ui.theme.Size16dp
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.Size26Modifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.toImmutableListOfLists import com.vitorpamplona.quartz.events.toImmutableListOfLists
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -96,6 +109,7 @@ fun DrawerContent(
.padding(horizontal = 25.dp) .padding(horizontal = 25.dp)
.padding(top = 100.dp), .padding(top = 100.dp),
scaffoldState, scaffoldState,
accountViewModel,
nav nav
) )
Divider( Divider(
@@ -122,6 +136,7 @@ fun ProfileContent(
baseAccountUser: User, baseAccountUser: User,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
scaffoldState: ScaffoldState, scaffoldState: ScaffoldState,
accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@@ -214,9 +229,14 @@ fun ProfileContent(
) )
) )
} }
Row(Modifier.padding(top = Size10dp)) {
EditStatusBox(baseAccountUser, accountViewModel)
}
Row( Row(
modifier = Modifier modifier = Modifier
.padding(top = 15.dp) .padding(top = Size10dp)
.clickable(onClick = { .clickable(onClick = {
nav(route) nav(route)
coroutineScope.launch { coroutineScope.launch {
@@ -230,6 +250,132 @@ fun ProfileContent(
} }
} }
@Composable
private fun EditStatusBox(baseAccountUser: User, accountViewModel: AccountViewModel) {
val scope = rememberCoroutineScope()
val focusManager = LocalFocusManager.current
LoadStatuses(user = baseAccountUser) { statuses ->
if (statuses.isEmpty()) {
val currentStatus = remember {
mutableStateOf("")
}
val hasChanged by remember {
derivedStateOf {
currentStatus.value != ""
}
}
OutlinedTextField(
value = currentStatus.value,
onValueChange = { currentStatus.value = it },
label = { Text(text = stringResource(R.string.status_update)) },
modifier = Modifier.fillMaxWidth(),
placeholder = {
Text(
text = stringResource(R.string.status_update),
color = MaterialTheme.colors.placeholderText
)
},
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences
),
singleLine = true,
trailingIcon = {
if (hasChanged) {
UserStatusSendButton() {
scope.launch(Dispatchers.IO) {
accountViewModel.createStatus(currentStatus.value)
focusManager.clearFocus(true)
}
}
}
}
)
} else {
statuses.forEach {
val originalStatus by it.live().metadata.map {
it.note.event?.content() ?: ""
}.observeAsState(it.event?.content() ?: "")
val thisStatus = remember {
mutableStateOf(originalStatus)
}
val hasChanged by remember {
derivedStateOf {
thisStatus.value != originalStatus
}
}
OutlinedTextField(
value = thisStatus.value,
onValueChange = { thisStatus.value = it },
label = { Text(text = stringResource(R.string.status_update)) },
modifier = Modifier.fillMaxWidth(),
placeholder = {
Text(
text = stringResource(R.string.status_update),
color = MaterialTheme.colors.placeholderText
)
},
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences
),
singleLine = true,
trailingIcon = {
if (hasChanged) {
UserStatusSendButton() {
scope.launch(Dispatchers.IO) {
accountViewModel.updateStatus(it, thisStatus.value)
focusManager.clearFocus(true)
}
}
} else {
UserStatusDeleteButton() {
scope.launch(Dispatchers.IO) {
accountViewModel.updateStatus(it, "")
accountViewModel.delete(it)
focusManager.clearFocus(true)
}
}
}
}
)
}
}
}
}
@Composable
fun UserStatusSendButton(onClick: () -> Unit) {
IconButton(
modifier = Size26Modifier,
onClick = onClick
) {
Icon(
imageVector = Icons.Default.Send,
null,
modifier = Size20Modifier,
tint = MaterialTheme.colors.placeholderText
)
}
}
@Composable
fun UserStatusDeleteButton(onClick: () -> Unit) {
IconButton(
modifier = Size26Modifier,
onClick = onClick
) {
Icon(
imageVector = Icons.Default.Delete,
null,
modifier = Size20Modifier,
tint = MaterialTheme.colors.placeholderText
)
}
}
@Composable @Composable
private fun FollowingAndFollowerCounts(baseAccountUser: User) { private fun FollowingAndFollowerCounts(baseAccountUser: User) {
var followingCount by remember { mutableStateOf("--") } var followingCount by remember { mutableStateOf("--") }
@@ -229,6 +229,7 @@ private fun DisplayNIP05(
if (user != "_") { if (user != "_") {
Text( Text(
text = remember(nip05) { AnnotatedString(user) }, text = remember(nip05) { AnnotatedString(user) },
fontSize = Font14SP,
color = MaterialTheme.colors.nip05, color = MaterialTheme.colors.nip05,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@@ -240,7 +241,7 @@ private fun DisplayNIP05(
ClickableText( ClickableText(
text = remember(nip05) { AnnotatedString(domain) }, text = remember(nip05) { AnnotatedString(domain) },
onClick = { runCatching { uri.openUri("https://$domain") } }, onClick = { runCatching { uri.openUri("https://$domain") } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.nip05), style = LocalTextStyle.current.copy(color = MaterialTheme.colors.nip05, fontSize = Font14SP),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Visible overflow = TextOverflow.Visible
) )
@@ -2515,11 +2515,11 @@ fun LoadStatuses(
mutableStateOf(persistentListOf()) mutableStateOf(persistentListOf())
} }
val userStatus = user.live().statuses.observeAsState() val userStatus by user.live().statuses.observeAsState()
LaunchedEffect(key1 = userStatus) { LaunchedEffect(key1 = userStatus) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
val myUser = userStatus.value?.user ?: return@launch val myUser = userStatus?.user ?: return@launch
val newStatuses = LocalCache.findStatusesForUser(myUser) val newStatuses = LocalCache.findStatusesForUser(myUser)
if (!equalImmutableLists(statuses, newStatuses)) { if (!equalImmutableLists(statuses, newStatuses)) {
statuses = newStatuses statuses = newStatuses
@@ -14,6 +14,7 @@ import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountState import com.vitorpamplona.amethyst.model.AccountState
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.ConnectivityType import com.vitorpamplona.amethyst.model.ConnectivityType
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@@ -378,6 +379,14 @@ class AccountViewModel(val account: Account) : ViewModel() {
} }
} }
fun createStatus(newStatus: String) {
account.createStatus(newStatus)
}
fun updateStatus(it: AddressableNote, newStatus: String) {
account.updateStatus(it, newStatus)
}
class Factory(val account: Account) : ViewModelProvider.Factory { class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel { override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
return AccountViewModel(account) as AccountViewModel return AccountViewModel(account) as AccountViewModel
@@ -126,7 +126,7 @@ val ChatHeadlineBorders = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.
val VolumeBottomIconSize = Modifier.size(70.dp).padding(10.dp) val VolumeBottomIconSize = Modifier.size(70.dp).padding(10.dp)
val PinBottomIconSize = Modifier.size(70.dp).padding(10.dp) val PinBottomIconSize = Modifier.size(70.dp).padding(10.dp)
val NIP05IconSize = Modifier.size(14.dp).padding(top = 1.dp, start = 1.dp, end = 1.dp) val NIP05IconSize = Modifier.size(13.dp).padding(top = 1.dp, start = 1.dp, end = 1.dp)
val EditFieldModifier = Modifier val EditFieldModifier = Modifier
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = 5.dp) .padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = 5.dp)
+2
View File
@@ -549,4 +549,6 @@
<string name="created_at">Created at</string> <string name="created_at">Created at</string>
<string name="rules">Rules</string> <string name="rules">Rules</string>
<string name="status_update">Update your status</string>
</resources> </resources>
@@ -37,5 +37,17 @@ class StatusEvent(
val sig = CryptoUtils.sign(id, privateKey) val sig = CryptoUtils.sign(id, privateKey)
return StatusEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey()) return StatusEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
} }
fun update(
event: StatusEvent,
newStatus: String,
privateKey: ByteArray,
createdAt: Long = TimeUtils.now()
): StatusEvent {
val pubKey = event.pubKey()
val id = generateId(pubKey, createdAt, kind, event.tags, newStatus)
val sig = CryptoUtils.sign(id, privateKey)
return StatusEvent(id.toHexKey(), pubKey, createdAt, event.tags, newStatus, sig.toHexKey())
}
} }
} }
@@ -13,6 +13,7 @@ object TimeUtils {
fun now() = System.currentTimeMillis() / 1000 fun now() = System.currentTimeMillis() / 1000
fun fiveMinutesAgo() = now() - fiveMinutes fun fiveMinutesAgo() = now() - fiveMinutes
fun oneHourAgo() = now() - oneHour fun oneHourAgo() = now() - oneHour
fun oneHourAhead() = now() + oneHour
fun oneDayAgo() = now() - oneDay fun oneDayAgo() = now() - oneDay
fun eightHoursAgo() = now() - eightHours fun eightHoursAgo() = now() - eightHours
fun oneWeekAgo() = now() - oneWeek fun oneWeekAgo() = now() - oneWeek