Visualizing proposed edits from other people

pull/792/head
Vitor Pamplona 2024-03-01 21:15:43 -05:00
rodzic 81cc985e3b
commit b694ac7259
8 zmienionych plików z 276 dodań i 11 usunięć

Wyświetl plik

@ -1467,6 +1467,8 @@ class Account(
fun sendEdit(
message: String,
originalNote: Note,
notify: HexKey?,
summary: String? = null,
relayList: List<Relay>? = null,
) {
if (!isWriteable()) return
@ -1476,6 +1478,8 @@ class Account(
TextNoteModificationEvent.create(
content = message,
eventId = idHex,
notify = notify,
summary = summary,
signer = signer,
) {
LocalCache.justConsume(it, null)

Wyświetl plik

@ -94,6 +94,7 @@ import com.vitorpamplona.amethyst.ui.components.BechLink
import com.vitorpamplona.amethyst.ui.components.InvoiceRequest
import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview
import com.vitorpamplona.amethyst.ui.components.VideoView
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
@ -101,7 +102,9 @@ import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
@ -119,6 +122,7 @@ fun EditPostView(
nav: (String) -> Unit,
) {
val postViewModel: EditPostViewModel = viewModel()
postViewModel.prepare(edit, versionLookingAt, accountViewModel)
val context = LocalContext.current
@ -257,6 +261,21 @@ fun EditPostView(
.fillMaxWidth()
.verticalScroll(scrollState),
) {
postViewModel.editedFromNote?.let {
Row(Modifier.heightIn(max = 200.dp)) {
NoteCompose(
baseNote = it,
makeItShort = true,
unPackReply = false,
isQuotedNote = true,
modifier = MaterialTheme.colorScheme.replyModifier,
accountViewModel = accountViewModel,
nav = nav,
)
Spacer(modifier = StdVertSpacer)
}
}
MessageField(postViewModel)
val myUrlPreview = postViewModel.urlPreview
@ -353,6 +372,43 @@ fun EditPostView(
}
}
}
/*
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(vertical = Size5dp, horizontal = Size10dp),
) {
Column {
Text(
text = stringResource(R.string.message_to_author),
fontSize = 18.sp,
fontWeight = FontWeight.W500,
)
Divider()
MyTextField(
value = postViewModel.subject,
onValueChange = { postViewModel.updateSubject(it) },
modifier = Modifier.fillMaxWidth(),
placeholder = {
Text(
text = stringResource(R.string.message_to_author_placeholder),
color = MaterialTheme.colorScheme.placeholderText,
)
},
visualTransformation =
UrlUserTagTransformation(
MaterialTheme.colorScheme.primary,
),
colors =
OutlinedTextFieldDefaults.colors(
unfocusedBorderColor = Color.Transparent,
focusedBorderColor = Color.Transparent,
),
)
}
}*/
}
}

Wyświetl plik

@ -59,6 +59,8 @@ open class EditPostViewModel() : ViewModel() {
var editedFromNote: Note? = null
var subject by mutableStateOf(TextFieldValue(""))
var nip94attachments by mutableStateOf<List<FileHeaderEvent>>(emptyList())
var nip95attachments by
mutableStateOf<List<Pair<FileStorageEvent, FileStorageHeaderEvent>>>(emptyList())
@ -80,6 +82,16 @@ open class EditPostViewModel() : ViewModel() {
var canAddInvoice by mutableStateOf(false)
var wantsInvoice by mutableStateOf(false)
open fun prepare(
edit: Note,
versionLookingAt: Note?,
accountViewModel: AccountViewModel,
) {
this.accountViewModel = accountViewModel
this.account = accountViewModel.account
this.editedFromNote = edit
}
open fun load(
edit: Note,
versionLookingAt: Note?,
@ -111,15 +123,29 @@ open class EditPostViewModel() : ViewModel() {
account?.sendNip95(it.first, it.second, relayList)
}
val notify =
if (editedFromNote?.author?.pubkeyHex == account?.userProfile()?.pubkeyHex) {
null
} else {
// notifies if it is not the logged in user
editedFromNote?.author?.pubkeyHex
}
account?.sendEdit(
message = message.text,
originalNote = editedFromNote!!,
notify = notify,
summary = subject.text.ifBlank { null },
relayList = relayList,
)
cancel()
}
open fun updateSubject(it: TextFieldValue) {
subject = it
}
fun upload(
galleryUri: Uri,
alt: String?,
@ -192,6 +218,7 @@ open class EditPostViewModel() : ViewModel() {
open fun cancel() {
message = TextFieldValue("")
subject = TextFieldValue("")
editedFromNote = null

Wyświetl plik

@ -48,6 +48,8 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Divider
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.IconButton
@ -85,6 +87,7 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@ -114,6 +117,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.CachedGeoLocations
import com.vitorpamplona.amethyst.ui.actions.EditPostView
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
@ -183,6 +187,7 @@ import com.vitorpamplona.amethyst.ui.theme.boostedNoteModifier
import com.vitorpamplona.amethyst.ui.theme.channelNotePictureModifier
import com.vitorpamplona.amethyst.ui.theme.grayText
import com.vitorpamplona.amethyst.ui.theme.imageModifier
import com.vitorpamplona.amethyst.ui.theme.innerPostModifier
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
@ -235,6 +240,7 @@ import com.vitorpamplona.quartz.events.RelaySetEvent
import com.vitorpamplona.quartz.events.ReportEvent
import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.TextNoteModificationEvent
import com.vitorpamplona.quartz.events.UserMetadata
import com.vitorpamplona.quartz.events.VideoEvent
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
@ -291,8 +297,7 @@ fun NoteCompose(
nav = nav,
)
} else {
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup,
->
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
BlankNote(
remember {
modifier.combinedClickable(
@ -1111,6 +1116,8 @@ class EditState() {
fun versionId() = modificationToShowIndex + 1
fun latest() = modificationsList.lastOrNull()
fun nextModification() {
if (modificationToShowIndex < 0) {
modificationToShowIndex = 0
@ -1339,6 +1346,17 @@ private fun RenderNoteRow(
nav,
)
}
is TextNoteModificationEvent -> {
RenderTextModificationEvent(
baseNote,
makeItShort,
canPreview,
backgroundColor,
editState,
accountViewModel,
nav,
)
}
else -> {
RenderTextEvent(
baseNote,
@ -1467,6 +1485,124 @@ fun RenderTextEvent(
}
}
@Composable
fun RenderTextModificationEvent(
note: Note,
makeItShort: Boolean,
canPreview: Boolean,
backgroundColor: MutableState<Color>,
editStateByAuthor: State<GenericLoadable<EditState>>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteEvent = note.event as? TextNoteModificationEvent ?: return
val noteAuthor = note.author ?: return
val isAuthorTheLoggedUser = remember(note.event) { accountViewModel.isLoggedUser(note.author) }
val editState =
remember {
derivedStateOf {
val loadable = editStateByAuthor.value as? GenericLoadable.Loaded<EditState>
val state = EditState()
val latestChangeByAuthor =
if (loadable != null && loadable.loaded.hasModificationsToShow()) {
loadable.loaded.latest()
} else {
null
}
state.updateModifications(listOfNotNull(latestChangeByAuthor, note))
GenericLoadable.Loaded(state)
}
}
val wantsToEditPost =
remember {
mutableStateOf(false)
}
Card(
modifier = MaterialTheme.colorScheme.imageModifier,
) {
Column(Modifier.fillMaxWidth().padding(Size10dp)) {
Text(
text = stringResource(id = R.string.proposal_to_edit),
style =
TextStyle(
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
),
)
Spacer(modifier = StdVertSpacer)
noteEvent.summary()?.let {
TranslatableRichTextViewer(
content = it,
canPreview = canPreview && !makeItShort,
modifier = Modifier.fillMaxWidth(),
tags = EmptyTagList,
backgroundColor = backgroundColor,
accountViewModel = accountViewModel,
nav = nav,
)
Spacer(modifier = StdVertSpacer)
}
noteEvent.editedNote()?.let {
LoadNote(baseNoteHex = it, accountViewModel = accountViewModel) { baseNote ->
baseNote?.let {
Column(
modifier =
MaterialTheme.colorScheme.innerPostModifier.padding(Size10dp).clickable {
routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) }
},
) {
NoteBody(
baseNote = baseNote,
showAuthorPicture = true,
unPackReply = false,
makeItShort = false,
canPreview = true,
showSecondRow = false,
backgroundColor = backgroundColor,
editState = editState,
accountViewModel = accountViewModel,
nav = nav,
)
if (wantsToEditPost.value) {
EditPostView(
onClose = {
wantsToEditPost.value = false
},
edit = baseNote,
versionLookingAt = note,
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
}
}
Spacer(modifier = StdVertSpacer)
Button(
onClick = { wantsToEditPost.value = true },
modifier = Modifier.fillMaxWidth(),
) {
Text(text = stringResource(id = R.string.accept_the_suggestion))
}
}
}
}
@Composable
fun RenderPoll(
note: Note,

Wyświetl plik

@ -581,13 +581,22 @@ fun NoteDropDownMenu(
},
)
Divider()
if (state.isLoggedUser && note.event is TextNoteEvent) {
DropdownMenuItem(
text = { Text(stringResource(R.string.edit_post)) },
onClick = {
wantsToEditPost.value = true
},
)
if (note.event is TextNoteEvent) {
if (state.isLoggedUser) {
DropdownMenuItem(
text = { Text(stringResource(R.string.edit_post)) },
onClick = {
wantsToEditPost.value = true
},
)
} else {
DropdownMenuItem(
text = { Text(stringResource(R.string.propose_an_edit)) },
onClick = {
wantsToEditPost.value = true
},
)
}
}
DropdownMenuItem(
text = { Text(stringResource(R.string.broadcast)) },

Wyświetl plik

@ -131,6 +131,7 @@ import com.vitorpamplona.amethyst.ui.note.RenderPoll
import com.vitorpamplona.amethyst.ui.note.RenderPostApproval
import com.vitorpamplona.amethyst.ui.note.RenderRepost
import com.vitorpamplona.amethyst.ui.note.RenderTextEvent
import com.vitorpamplona.amethyst.ui.note.RenderTextModificationEvent
import com.vitorpamplona.amethyst.ui.note.VideoDisplay
import com.vitorpamplona.amethyst.ui.note.observeEdits
import com.vitorpamplona.amethyst.ui.note.showAmount
@ -174,6 +175,7 @@ import com.vitorpamplona.quartz.events.PinListEvent
import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.RelaySetEvent
import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteModificationEvent
import com.vitorpamplona.quartz.events.VideoEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
import kotlinx.collections.immutable.toImmutableList
@ -568,6 +570,16 @@ fun NoteMaster(
)
} else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
RenderRepost(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is TextNoteModificationEvent) {
RenderTextModificationEvent(
note = baseNote,
makeItShort = false,
canPreview = true,
backgroundColor,
editState,
accountViewModel,
nav,
)
} else if (noteEvent is PollNoteEvent) {
val canPreview =
note.author == account.userProfile() ||

Wyświetl plik

@ -55,6 +55,7 @@
<string name="original">original</string>
<string name="quote">Quote</string>
<string name="fork">Fork</string>
<string name="propose_an_edit">Propose an Edit</string>
<string name="new_amount_in_sats">New Amount in Sats</string>
<string name="add">Add</string>
<string name="replying_to">"replying to "</string>
@ -795,4 +796,9 @@
<string name="ots_info_description">There\'s proof this post was signed sometime before %1$s. The proof was stamped in the Bitcoin blockchain at that date and time.</string>
<string name="edit_post">Edit Post</string>
<string name="proposal_to_edit">Proposal to improve your post</string>
<string name="message_to_author">Summary of changes</string>
<string name="message_to_author_placeholder">Quick fixes...</string>
<string name="accept_the_suggestion">Accept the Suggestion</string>
</resources>

Wyświetl plik

@ -36,6 +36,8 @@ class TextNoteModificationEvent(
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
fun editedNote() = firstTaggedEvent()
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == "summary" }?.get(1)
companion object {
const val KIND = 1010
const val ALT = "Content Change Event"
@ -43,12 +45,25 @@ class TextNoteModificationEvent(
fun create(
content: String,
eventId: HexKey,
notify: HexKey?,
summary: String?,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (TextNoteModificationEvent) -> Unit,
) {
val tags = arrayOf(arrayOf("e", eventId), arrayOf("alt", ALT))
signer.sign(createdAt, KIND, tags, content, onReady)
val tags = mutableListOf(arrayOf("e", eventId))
notify?.let {
tags.add(arrayOf("p", it))
}
summary?.let {
tags.add(arrayOf("summary", it))
}
tags.add(arrayOf("alt", ALT))
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
}
}
}