mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-26 17:52:29 +01:00
Visualizing proposed edits from other people
This commit is contained in:
parent
81cc985e3b
commit
b694ac7259
@ -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)
|
||||
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)) },
|
||||
|
@ -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() ||
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user