Merge branch 'main' into adding_translations

This commit is contained in:
Vitor Pamplona
2024-03-01 12:17:12 -05:00
committed by GitHub
25 changed files with 454 additions and 200 deletions
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.model
import android.util.Log
import android.util.LruCache
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.service.checkNotInMainThread
@@ -1402,6 +1403,13 @@ object LocalCache {
note.loadEvent(event, author, emptyList())
event.editedNote()?.let {
getNoteIfExists(it)?.let { editedNote ->
modificationCache.remove(editedNote.idHex)
editedNote.liveSet?.innerModifications?.invalidateData()
}
}
refreshObservers(note)
}
@@ -1716,15 +1724,31 @@ object LocalCache {
return minTime
}
val modificationCache = LruCache<HexKey, List<Note>>(20)
fun cachedModificationEventsForNote(note: Note): List<Note>? {
return modificationCache[note.idHex]
}
suspend fun findLatestModificationForNote(note: Note): List<Note> {
checkNotInMainThread()
modificationCache[note.idHex]?.let {
return it
}
val time = TimeUtils.now()
return noteListCache.filter { item ->
val noteEvent = item.event
val newNotes =
noteListCache.filter { item ->
val noteEvent = item.event
noteEvent is TextNoteModificationEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
}
noteEvent is TextNoteModificationEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
}.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
modificationCache.put(note.idHex, newNotes)
return newNotes
}
fun cleanObservers() {
@@ -37,26 +37,26 @@ class JsonFilter(
val filter =
factory.objectNode().apply {
ids?.run {
put(
replace(
"ids",
factory.arrayNode(ids.size).apply { ids.forEach { add(it) } },
)
}
authors?.run {
put(
replace(
"authors",
factory.arrayNode(authors.size).apply { authors.forEach { add(it) } },
)
}
kinds?.run {
put(
replace(
"kinds",
factory.arrayNode(kinds.size).apply { kinds.forEach { add(it) } },
)
}
tags?.run {
entries.forEach { kv ->
put(
replace(
"#${kv.key}",
factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } },
)
@@ -47,7 +47,7 @@ data class Subscription(
return factory.objectNode().apply {
put("id", id)
typedFilters?.also { filters ->
put(
replace(
"typedFilters",
factory.arrayNode(filters.size).apply {
filters.forEach { filter -> add(filter.toJsonObject()) }
@@ -36,8 +36,8 @@ class TypedFilter(
val factory = Event.mapper.nodeFactory
return factory.objectNode().apply {
put("types", typesToJson(types))
put("filter", filterToJson(filter))
replace("types", typesToJson(types))
replace("filter", filterToJson(filter))
}
}
@@ -50,26 +50,26 @@ class TypedFilter(
val factory = Event.mapper.nodeFactory
return factory.objectNode().apply {
filter.ids?.run {
put(
replace(
"ids",
factory.arrayNode(filter.ids.size).apply { filter.ids.forEach { add(it) } },
)
}
filter.authors?.run {
put(
replace(
"authors",
factory.arrayNode(filter.authors.size).apply { filter.authors.forEach { add(it) } },
)
}
filter.kinds?.run {
put(
replace(
"kinds",
factory.arrayNode(filter.kinds.size).apply { filter.kinds.forEach { add(it) } },
)
}
filter.tags?.run {
entries.forEach { kv ->
put(
replace(
"#${kv.key}",
factory.arrayNode(kv.value.size).apply { kv.value.forEach { add(it) } },
)
@@ -61,6 +61,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -1088,10 +1089,58 @@ fun InnerNoteWithReactions(
}
@Stable
class EditState(
val showOriginal: MutableState<Boolean> = mutableStateOf(false),
val modificationsInOrder: MutableState<List<Note>> = mutableStateOf(emptyList()),
)
class EditState() {
private var modificationsList: List<Note> = persistentListOf()
private var modificationToShowIndex: Int = -1
val modificationToShow: MutableState<Note?> = mutableStateOf(null)
val showingVersion: MutableState<Int> = mutableStateOf(0)
fun hasModificationsToShow(): Boolean = modificationsList.isNotEmpty()
fun isOriginal(): Boolean = modificationToShowIndex < 0
fun isLatest(): Boolean = modificationToShowIndex == modificationsList.lastIndex
fun originalVersionId() = 0
fun lastVersionId() = modificationsList.size
fun versionId() = modificationToShowIndex + 1
fun nextModification() {
if (modificationToShowIndex < 0) {
modificationToShowIndex = 0
modificationToShow.value = modificationsList.getOrNull(0)
} else {
modificationToShowIndex++
if (modificationToShowIndex >= modificationsList.size) {
modificationToShowIndex = -1
modificationToShow.value = null
} else {
modificationToShow.value = modificationsList.getOrNull(modificationToShowIndex)
}
}
showingVersion.value = versionId()
}
fun updateModifications(newModifications: List<Note>) {
if (modificationsList != newModifications) {
modificationsList = newModifications
if (newModifications.isEmpty()) {
modificationToShow.value = null
modificationToShowIndex = -1
} else {
modificationToShowIndex = newModifications.lastIndex
modificationToShow.value = newModifications.last()
}
}
showingVersion.value = versionId()
}
}
@Composable
private fun NoteBody(
@@ -1105,14 +1154,7 @@ private fun NoteBody(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val editState by
produceState(initialValue = EditState(), key1 = baseNote) {
accountViewModel.findModificationEventsForNote(baseNote) { newModifications ->
if (value.modificationsInOrder.value != newModifications) {
value.modificationsInOrder.value = newModifications
}
}
}
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
FirstUserInfoRow(
baseNote = baseNote,
@@ -1168,7 +1210,7 @@ private fun RenderNoteRow(
backgroundColor: MutableState<Color>,
makeItShort: Boolean,
canPreview: Boolean,
editState: EditState,
editState: State<GenericLoadable<EditState>>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
@@ -1358,7 +1400,7 @@ fun RenderTextEvent(
makeItShort: Boolean,
canPreview: Boolean,
backgroundColor: MutableState<Color>,
editState: EditState,
editState: State<GenericLoadable<EditState>>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
@@ -1371,10 +1413,10 @@ fun RenderTextEvent(
derivedStateOf {
val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null }
val newBody =
if (editState.showOriginal.value || editState.modificationsInOrder.value.isEmpty()) {
body
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow?.value?.event?.content() ?: body
} else {
editState.modificationsInOrder.value.firstOrNull()?.event?.content() ?: body
body
}
if (!subject.isNullOrBlank() && !newBody.split("\n")[0].contains(subject)) {
@@ -2822,7 +2864,7 @@ fun DisplayLocation(
fun FirstUserInfoRow(
baseNote: Note,
showAuthorPicture: Boolean,
editState: EditState,
editState: State<GenericLoadable<EditState>>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
@@ -2857,8 +2899,10 @@ fun FirstUserInfoRow(
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
}
if (!editState.modificationsInOrder.value.isEmpty()) {
DisplayEditStatus(editState.showOriginal)
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.let {
DisplayEditStatus(it)
}
}
TimeAgo(baseNote)
@@ -2868,15 +2912,69 @@ fun FirstUserInfoRow(
}
@Composable
fun DisplayEditStatus(showOriginal: MutableState<Boolean>) {
fun observeEdits(
baseNote: Note,
accountViewModel: AccountViewModel,
): State<GenericLoadable<EditState>> {
val editState =
remember(baseNote.idHex) {
val cached = accountViewModel.cachedModificationEventsForNote(baseNote)
mutableStateOf(
if (cached != null) {
if (cached.isEmpty()) {
GenericLoadable.Empty<EditState>()
} else {
val state = EditState()
state.updateModifications(cached)
GenericLoadable.Loaded<EditState>(state)
}
} else {
GenericLoadable.Loading<EditState>()
},
)
}
val updatedNote = baseNote.live().innerModifications.observeAsState()
LaunchedEffect(key1 = updatedNote) {
updatedNote.value?.note?.let {
accountViewModel.findModificationEventsForNote(it) { newModifications ->
if (newModifications.isEmpty()) {
if (editState.value !is GenericLoadable.Empty) {
editState.value = GenericLoadable.Empty<EditState>()
}
} else {
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.updateModifications(newModifications)
} else {
val state = EditState()
state.updateModifications(newModifications)
editState.value = GenericLoadable.Loaded(state)
}
}
}
}
}
return editState
}
@Composable
fun DisplayEditStatus(editState: EditState) {
ClickableText(
text =
if (showOriginal.value) {
buildAnnotatedString { append(stringResource(id = R.string.original)) }
} else {
buildAnnotatedString { append(stringResource(id = R.string.edited)) }
buildAnnotatedString {
if (editState.showingVersion.value == editState.originalVersionId()) {
append(stringResource(id = R.string.original))
} else if (editState.showingVersion.value == editState.lastVersionId()) {
append(stringResource(id = R.string.edited))
} else {
append(stringResource(id = R.string.edited_number, editState.versionId()))
}
},
onClick = { showOriginal.value = !showOriginal.value },
onClick = {
editState.nextModification()
},
style =
LocalTextStyle.current.copy(
color = MaterialTheme.colorScheme.placeholderText,
@@ -111,14 +111,17 @@ class PollNoteViewModel : ViewModel() {
}
tallies.forEach {
it.zappedValue.value = zappedPollOptionAmount(it.option)
it.tally.value =
if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
it.zappedValue.value.divide(totalZapped, 2, RoundingMode.HALF_UP)
val zappedValue = zappedPollOptionAmount(it.option)
val tallyValue =
if (totalZapped > BigDecimal.ZERO) {
zappedValue.divide(totalZapped, 2, RoundingMode.HALF_UP)
} else {
BigDecimal.ZERO
}
it.consensusThreadhold.value = consensusThreshold != null && it.tally.value >= consensusThreshold!!
it.zappedValue.value = zappedValue
it.tally.value = tallyValue
it.consensusThreadhold.value = consensusThreshold != null && tallyValue >= consensusThreshold!!
it.zappedByLoggedIn.value = account?.userProfile()?.let { it1 -> cachedIsPollOptionZappedBy(it.option, it1) } ?: false
}
}
@@ -483,7 +483,10 @@ fun NoteDropDownMenu(
if (wantsToEditPost.value) {
EditPostView(
onClose = { wantsToEditPost.value = false },
onClose = {
popupExpanded.value = false
wantsToEditPost.value = false
},
edit = note,
accountViewModel = accountViewModel,
nav = nav,
@@ -58,7 +58,6 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@@ -86,6 +85,7 @@ import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.ui.components.InlineCarrousel
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
@@ -132,6 +132,7 @@ 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.VideoDisplay
import com.vitorpamplona.amethyst.ui.note.observeEdits
import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.note.timeAgo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -377,20 +378,13 @@ fun NoteMaster(
onClick = { showHiddenNote = true },
)
} else {
val editState by
produceState(initialValue = EditState(), key1 = baseNote) {
accountViewModel.findModificationEventsForNote(baseNote) { newModifications ->
if (value.modificationsInOrder.value != newModifications) {
value.modificationsInOrder.value = newModifications
}
}
}
Column(
modifier
.fillMaxWidth()
.padding(top = 10.dp),
) {
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
Row(
modifier =
Modifier
@@ -421,8 +415,10 @@ fun NoteMaster(
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
}
if (!editState.modificationsInOrder.value.isEmpty()) {
DisplayEditStatus(editState.showOriginal)
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.let {
DisplayEditStatus(it)
}
}
Text(
@@ -850,9 +846,9 @@ private fun RenderWikiHeaderForThreadPreview() {
val accountViewModel = mockAccountViewModel()
val nav: (String) -> Unit = {}
val editState by
val editState =
remember {
mutableStateOf(EditState())
mutableStateOf(GenericLoadable.Empty<EditState>())
}
runBlocking {
@@ -932,6 +932,8 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
}
}
fun cachedModificationEventsForNote(note: Note) = LocalCache.cachedModificationEventsForNote(note)
suspend fun findModificationEventsForNote(
note: Note,
onResult: (List<Note>) -> Unit,
+1
View File
@@ -51,6 +51,7 @@
<string name="boost">Boost</string>
<string name="boosted">boosted</string>
<string name="edited">edited</string>
<string name="edited_number">edit #%1$s</string>
<string name="original">original</string>
<string name="quote">Quote</string>
<string name="fork">Fork</string>