From 5b596d2a56c97b57f87ba83cb96098fc4fdc3849 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 24 Aug 2023 17:34:02 -0400 Subject: [PATCH] - Updating Status on the Drawer - Reducing font size for statuses and nip05s --- .../vitorpamplona/amethyst/model/Account.kt | 19 +++ .../amethyst/model/LocalCache.kt | 2 +- .../com/vitorpamplona/amethyst/model/User.kt | 2 + .../service/NostrAccountDataSource.kt | 4 +- .../amethyst/ui/navigation/DrawerContent.kt | 148 +++++++++++++++++- .../ui/note/NIP05VerificationDisplay.kt | 3 +- .../amethyst/ui/note/NoteCompose.kt | 4 +- .../ui/screen/loggedIn/AccountViewModel.kt | 9 ++ .../vitorpamplona/amethyst/ui/theme/Shape.kt | 2 +- app/src/main/res/values/strings.xml | 2 + .../quartz/events/StatusEvent.kt | 12 ++ .../vitorpamplona/quartz/utils/TimeUtils.kt | 1 + 12 files changed, 200 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 967a7a79d..6b74a5855 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -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) { if (!isWriteable()) return diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index c053c1962..294676ca6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -345,7 +345,7 @@ object LocalCache { private fun consume(event: PinListEvent) { consumeBaseReplaceable(event) } private fun consume(event: RelaySetEvent) { 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 note = getOrCreateAddressableNote(event.address()) val author = getOrCreateUser(event.pubKey) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 625e7ec4a..d46bdcc76 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -415,6 +415,7 @@ class UserLiveSet(u: User) { relayInfo.hasObservers() || metadata.hasObservers() || zaps.hasObservers() || + statuses.hasObservers() || bookmarks.hasObservers() } @@ -428,6 +429,7 @@ class UserLiveSet(u: User) { metadata.destroy() zaps.destroy() bookmarks.destroy() + statuses.destroy() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 2f4c8311b..9901a7de3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -52,9 +52,9 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { return TypedFilter( types = COMMON_FEED_TYPES, filter = JsonFilter( - kinds = listOf(AdvertisedRelayListEvent.kind), + kinds = listOf(AdvertisedRelayListEvent.kind, StatusEvent.kind), authors = listOf(account.userProfile().pubkeyHex), - limit = 1 + limit = 5 ) ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 973fad4f5..f180aaef9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.AlertDialog import androidx.compose.material.Divider @@ -27,13 +28,18 @@ import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.OutlinedTextField import androidx.compose.material.ScaffoldState import androidx.compose.material.Surface import androidx.compose.material.Text 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.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState 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.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource 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.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.map import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage 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.components.CreateTextWithEmoji 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.loggedIn.AccountBackupDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog 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.Size20Modifier +import com.vitorpamplona.amethyst.ui.theme.Size26Modifier import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.toImmutableListOfLists import kotlinx.coroutines.CoroutineScope @@ -96,6 +109,7 @@ fun DrawerContent( .padding(horizontal = 25.dp) .padding(top = 100.dp), scaffoldState, + accountViewModel, nav ) Divider( @@ -122,6 +136,7 @@ fun ProfileContent( baseAccountUser: User, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, + accountViewModel: AccountViewModel, nav: (String) -> Unit ) { val coroutineScope = rememberCoroutineScope() @@ -214,9 +229,14 @@ fun ProfileContent( ) ) } + + Row(Modifier.padding(top = Size10dp)) { + EditStatusBox(baseAccountUser, accountViewModel) + } + Row( modifier = Modifier - .padding(top = 15.dp) + .padding(top = Size10dp) .clickable(onClick = { nav(route) 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 private fun FollowingAndFollowerCounts(baseAccountUser: User) { var followingCount by remember { mutableStateOf("--") } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt index 598bbccff..620997127 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt @@ -229,6 +229,7 @@ private fun DisplayNIP05( if (user != "_") { Text( text = remember(nip05) { AnnotatedString(user) }, + fontSize = Font14SP, color = MaterialTheme.colors.nip05, maxLines = 1, overflow = TextOverflow.Ellipsis @@ -240,7 +241,7 @@ private fun DisplayNIP05( ClickableText( text = remember(nip05) { AnnotatedString(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, overflow = TextOverflow.Visible ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 00e83c797..dc856596c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -2515,11 +2515,11 @@ fun LoadStatuses( mutableStateOf(persistentListOf()) } - val userStatus = user.live().statuses.observeAsState() + val userStatus by user.live().statuses.observeAsState() LaunchedEffect(key1 = userStatus) { launch(Dispatchers.IO) { - val myUser = userStatus.value?.user ?: return@launch + val myUser = userStatus?.user ?: return@launch val newStatuses = LocalCache.findStatusesForUser(myUser) if (!equalImmutableLists(statuses, newStatuses)) { statuses = newStatuses diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index c6186509a..35213f3dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -14,6 +14,7 @@ import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AccountState +import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.ConnectivityType import com.vitorpamplona.amethyst.model.Note 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 { override fun create(modelClass: Class): AccountViewModel { return AccountViewModel(account) as AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index e61c0de51..bf7074b88 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -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 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 .padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = 5.dp) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9d1c8d733..992ec266c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -549,4 +549,6 @@ Created at Rules + + Update your status diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/StatusEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/StatusEvent.kt index 8151f0278..c326a6f47 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/StatusEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/StatusEvent.kt @@ -37,5 +37,17 @@ class StatusEvent( val sig = CryptoUtils.sign(id, privateKey) 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()) + } } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt index 140355dd6..e88af3ad4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt @@ -13,6 +13,7 @@ object TimeUtils { fun now() = System.currentTimeMillis() / 1000 fun fiveMinutesAgo() = now() - fiveMinutes fun oneHourAgo() = now() - oneHour + fun oneHourAhead() = now() + oneHour fun oneDayAgo() = now() - oneDay fun eightHoursAgo() = now() - eightHours fun oneWeekAgo() = now() - oneWeek