feat: restore deleted post and comments (#998)

This commit is contained in:
Diego Beraldin 2024-06-18 00:39:56 +02:00 committed by GitHub
parent 72f8dd210f
commit f4a0352942
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 1307 additions and 634 deletions

View File

@ -21,6 +21,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize import androidx.compose.ui.unit.toSize
@ -95,12 +96,14 @@ fun CommentCard(
) { ) {
Box( Box(
modifier = modifier =
Modifier.onClick( Modifier
.onClick(
onClick = onClick ?: {}, onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {}, onDoubleClick = onDoubleClick ?: {},
).padding( ).padding(
start = start =
indentAmount.takeIf { indentAmount
.takeIf {
it > 0 && comment.depth > 0 it > 0 && comment.depth > 0
}?.let { }?.let {
(it * comment.depth).dp + Spacing.xxxs (it * comment.depth).dp + Spacing.xxxs
@ -147,6 +150,14 @@ fun CommentCard(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha), color = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha),
) )
} else if (comment.deleted) {
Text(
modifier = Modifier.fillMaxWidth(),
text = LocalStrings.current.messageContentDeleted,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha),
)
} else { } else {
CustomizedContent(ContentFontClass.Body) { CustomizedContent(ContentFontClass.Body) {
CompositionLocalProvider( CompositionLocalProvider(

View File

@ -5,7 +5,9 @@ data class Option(
val text: String, val text: String,
) )
sealed class OptionId(val value: Int) { sealed class OptionId(
val value: Int,
) {
data object Share : OptionId(0) data object Share : OptionId(0)
data object Hide : OptionId(1) data object Hide : OptionId(1)
@ -65,4 +67,6 @@ sealed class OptionId(val value: Int) {
data object Unsubscribe : OptionId(28) data object Unsubscribe : OptionId(28)
data object PurgeCreator : OptionId(29) data object PurgeCreator : OptionId(29)
data object Restore : OptionId(30)
} }

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@ -28,6 +29,7 @@ import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
@ -90,19 +92,18 @@ fun PostCard(
val markRead = post.read && fadeRead val markRead = post.read && fadeRead
Box( Box(
modifier = modifier =
modifier.then( modifier
.then(
if (postLayout == PostLayout.Card) { if (postLayout == PostLayout.Card) {
Modifier Modifier
.padding(horizontal = Spacing.xs) .padding(horizontal = Spacing.xs)
.shadow( .shadow(
elevation = 5.dp, elevation = 5.dp,
shape = RoundedCornerShape(CornerSize.l), shape = RoundedCornerShape(CornerSize.l),
) ).clip(RoundedCornerShape(CornerSize.l))
.clip(RoundedCornerShape(CornerSize.l))
.background( .background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp), color = MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp),
) ).padding(vertical = Spacing.s)
.padding(vertical = Spacing.s)
} else { } else {
Modifier Modifier
}, },
@ -219,7 +220,10 @@ private fun CompactPost(
val customTabsHelper = remember { getCustomTabsHelper() } val customTabsHelper = remember { getCustomTabsHelper() }
val navigationCoordinator = remember { getNavigationCoordinator() } val navigationCoordinator = remember { getNavigationCoordinator() }
val postLinkUrl = val postLinkUrl =
post.url.orEmpty().takeIf { !it.looksLikeAnImage && !it.looksLikeAVideo }.orEmpty() post.url
.orEmpty()
.takeIf { !it.looksLikeAnImage && !it.looksLikeAVideo }
.orEmpty()
Column( Column(
modifier = modifier =
@ -268,6 +272,15 @@ private fun CompactPost(
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.spacedBy(Spacing.xs), horizontalArrangement = Arrangement.spacedBy(Spacing.xs),
) { ) {
if (post.deleted) {
Text(
modifier = Modifier.fillMaxWidth(),
text = LocalStrings.current.messageContentDeleted,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha),
)
} else {
CustomizedContent(ContentFontClass.Title) { CustomizedContent(ContentFontClass.Title) {
PostCardTitle( PostCardTitle(
modifier = Modifier.weight(0.75f), modifier = Modifier.weight(0.75f),
@ -325,8 +338,7 @@ private fun CompactPost(
} else { } else {
Modifier.aspectRatio(1f) Modifier.aspectRatio(1f)
}, },
) ).padding(vertical = Spacing.xs)
.padding(vertical = Spacing.xs)
.clip(RoundedCornerShape(CornerSize.s)), .clip(RoundedCornerShape(CornerSize.s)),
minHeight = Dp.Unspecified, minHeight = Dp.Unspecified,
maxHeight = Dp.Unspecified, maxHeight = Dp.Unspecified,
@ -361,6 +373,7 @@ private fun CompactPost(
) )
} }
} }
}
PostCardFooter( PostCardFooter(
modifier = modifier =
Modifier.padding( Modifier.padding(
@ -439,7 +452,9 @@ private fun ExtendedPost(
val navigationCoordinator = remember { getNavigationCoordinator() } val navigationCoordinator = remember { getNavigationCoordinator() }
val optionsMenuOpen = remember { mutableStateOf(false) } val optionsMenuOpen = remember { mutableStateOf(false) }
val postLinkUrl = val postLinkUrl =
post.url.orEmpty().takeIf { post.url
.orEmpty()
.takeIf {
it != post.imageUrl && it != post.imageUrl &&
it != post.videoUrl && it != post.videoUrl &&
!it.looksLikeAnImage && !it.looksLikeAnImage &&
@ -487,6 +502,18 @@ private fun ExtendedPost(
optionsMenuOpen.value = true optionsMenuOpen.value = true
}, },
) )
if (post.deleted) {
Text(
modifier =
Modifier.fillMaxWidth().padding(
all = Spacing.s,
),
text = LocalStrings.current.messageContentDeleted,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha),
)
} else {
CustomizedContent(ContentFontClass.Title) { CustomizedContent(ContentFontClass.Title) {
PostCardTitle( PostCardTitle(
modifier = modifier =
@ -548,8 +575,7 @@ private fun ExtendedPost(
.padding( .padding(
vertical = Spacing.xs, vertical = Spacing.xs,
horizontal = if (fullWidthImage) 0.dp else Spacing.s, horizontal = if (fullWidthImage) 0.dp else Spacing.s,
) ).then(
.then(
if (roundedCornerImage && !fullWidthImage) { if (roundedCornerImage && !fullWidthImage) {
Modifier.clip(RoundedCornerShape(CornerSize.xl)) Modifier.clip(RoundedCornerShape(CornerSize.xl))
} else { } else {
@ -638,8 +664,7 @@ private fun ExtendedPost(
bottom = Spacing.xxs, bottom = Spacing.xxs,
start = Spacing.s, start = Spacing.s,
end = Spacing.s, end = Spacing.s,
) ).onClick(
.onClick(
onClick = { onClick = {
navigationCoordinator.handleUrl( navigationCoordinator.handleUrl(
url = postLinkUrl, url = postLinkUrl,
@ -657,6 +682,7 @@ private fun ExtendedPost(
url = postLinkUrl, url = postLinkUrl,
) )
} }
}
PostCardFooter( PostCardFooter(
modifier = modifier =
Modifier.padding( Modifier.padding(

View File

@ -417,4 +417,6 @@ internal val ArStrings =
override val settingsHiddenPosts = "المشاركات المخفية" override val settingsHiddenPosts = "المشاركات المخفية"
override val settingsMediaList = "تحميلات الوسائط" override val settingsMediaList = "تحميلات الوسائط"
override val settingsEnableToggleFavoriteInNavDrawer = "إضافة/إزالة المفضلة في درج التنقل" override val settingsEnableToggleFavoriteInNavDrawer = "إضافة/إزالة المفضلة في درج التنقل"
override val messageContentDeleted = "لقد قمت بحذف هذا المحتوى"
override val actionRestore = "يعيد"
} }

View File

@ -426,4 +426,6 @@ internal val BgStrings =
override val settingsMediaList = "Качване на мултимедия" override val settingsMediaList = "Качване на мултимедия"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Добавяне/премахване на любими в чекмеджето за навигация" "Добавяне/премахване на любими в чекмеджето за навигация"
override val messageContentDeleted = "Вие изтрихте това съдържание"
override val actionRestore = "Възстанови"
} }

View File

@ -422,4 +422,6 @@ internal val CsStrings =
override val settingsMediaList = "Nahrávání médií" override val settingsMediaList = "Nahrávání médií"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Přidat/odebrat oblíbené položky v navigační zásuvce" "Přidat/odebrat oblíbené položky v navigační zásuvce"
override val messageContentDeleted = "Tento obsah jste smazali"
override val actionRestore = "Obnovit"
} }

View File

@ -422,4 +422,6 @@ internal val DaStrings =
override val settingsMediaList = "Medieuploads" override val settingsMediaList = "Medieuploads"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Tilføj/fjern favoritter i navigationsskuffen" "Tilføj/fjern favoritter i navigationsskuffen"
override val messageContentDeleted = "Du har slettet dette indhold"
override val actionRestore = "Gendan"
} }

View File

@ -425,4 +425,6 @@ internal val DeStrings =
override val settingsMediaList = "Medien-Uploads" override val settingsMediaList = "Medien-Uploads"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Favoriten in der Navigationsleiste hinzufügen/entfernen" "Favoriten in der Navigationsleiste hinzufügen/entfernen"
override val messageContentDeleted = "Sie haben diesen Inhalt gelöscht"
override val actionRestore = "Wiederherstellen"
} }

View File

@ -429,4 +429,6 @@ internal val ElStrings =
override val settingsMediaList = "Μεταφορτώσεις πολυμέσων" override val settingsMediaList = "Μεταφορτώσεις πολυμέσων"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Προσθήκη/αφαίρεση αγαπημένων στο συρτάρι πλοήγησης" "Προσθήκη/αφαίρεση αγαπημένων στο συρτάρι πλοήγησης"
override val messageContentDeleted = "Έχετε διαγράψει αυτό το περιεχόμενο"
override val actionRestore = "Αποκαταστήστε το"
} }

View File

@ -417,5 +417,8 @@ internal val EnStrings =
override val noticeBannedUser = "The current user has been banned from this community" override val noticeBannedUser = "The current user has been banned from this community"
override val settingsHiddenPosts = "Hidden posts" override val settingsHiddenPosts = "Hidden posts"
override val settingsMediaList = "Media uploads" override val settingsMediaList = "Media uploads"
override val settingsEnableToggleFavoriteInNavDrawer = "Add/remove favorites in navigation drawer" override val settingsEnableToggleFavoriteInNavDrawer =
"Add/remove favorites in navigation drawer"
override val messageContentDeleted = "You have deleted this content"
override val actionRestore = "Restore"
} }

View File

@ -416,5 +416,8 @@ internal val EoStrings =
override val noticeBannedUser = "La nuna uzanto estas malpermesita de ĉi tiu komunumo" override val noticeBannedUser = "La nuna uzanto estas malpermesita de ĉi tiu komunumo"
override val settingsHiddenPosts = "Kaŝitaj afiŝoj" override val settingsHiddenPosts = "Kaŝitaj afiŝoj"
override val settingsMediaList = "Amaskomunikiloj alŝutoj" override val settingsMediaList = "Amaskomunikiloj alŝutoj"
override val settingsEnableToggleFavoriteInNavDrawer = "Aldoni/forigi plej ŝatatajn en navigada tirkesto" override val settingsEnableToggleFavoriteInNavDrawer =
"Aldoni/forigi plej ŝatatajn en navigada tirkesto"
override val messageContentDeleted = "Vi forigis ĉi tiun enhavon"
override val actionRestore = "Restaŭri"
} }

View File

@ -423,4 +423,6 @@ internal val EsStrings =
override val settingsMediaList = "Cargas de medios" override val settingsMediaList = "Cargas de medios"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Agregar/eliminar favoritos en el cajón de navegación" "Agregar/eliminar favoritos en el cajón de navegación"
override val messageContentDeleted = "Has eliminado este contenido"
override val actionRestore = "Restaurar"
} }

View File

@ -424,4 +424,6 @@ internal val EtStrings =
override val settingsMediaList = "Meedia üleslaadimine" override val settingsMediaList = "Meedia üleslaadimine"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Lemmikute lisamine/eemaldamine navigeerimissahtlis" "Lemmikute lisamine/eemaldamine navigeerimissahtlis"
override val messageContentDeleted = "Olete selle sisu kustutanud"
override val actionRestore = "Taastama"
} }

View File

@ -419,4 +419,6 @@ internal val FiStrings =
override val settingsMediaList = "Median lataukset" override val settingsMediaList = "Median lataukset"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Lisää/poista suosikkeja navigointipaneelissa" "Lisää/poista suosikkeja navigointipaneelissa"
override val messageContentDeleted = "Olet poistanut tämän sisällön"
override val actionRestore = "Palauttaa"
} }

View File

@ -428,4 +428,6 @@ internal val FrStrings =
override val settingsMediaList = "Téléchargements de médias" override val settingsMediaList = "Téléchargements de médias"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Ajouter/supprimer des favoris dans le tiroir de navigation" "Ajouter/supprimer des favoris dans le tiroir de navigation"
override val messageContentDeleted = "Vous avez supprimé ce contenu"
override val actionRestore = "Restaurer"
} }

View File

@ -423,4 +423,6 @@ internal val GaStrings =
override val settingsMediaList = "Uaslódálacha meáin" override val settingsMediaList = "Uaslódálacha meáin"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Cuir leis/bain na ceanáin sa tarraiceán nascleanúna" "Cuir leis/bain na ceanáin sa tarraiceán nascleanúna"
override val messageContentDeleted = "Tá an t-ábhar seo scriosta agat"
override val actionRestore = "Athchóirigh"
} }

View File

@ -423,4 +423,6 @@ internal val HrStrings =
override val settingsMediaList = "Prijenos medija" override val settingsMediaList = "Prijenos medija"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Dodajte/uklonite favorite u ladici za navigaciju" "Dodajte/uklonite favorite u ladici za navigaciju"
override val messageContentDeleted = "Izbrisali ste ovaj sadržaj"
override val actionRestore = "Vratiti"
} }

View File

@ -427,4 +427,6 @@ internal val HuStrings =
override val settingsMediaList = "Médiafeltöltések" override val settingsMediaList = "Médiafeltöltések"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Kedvencek hozzáadása/eltávolítása a navigációs fiókban" "Kedvencek hozzáadása/eltávolítása a navigációs fiókban"
override val messageContentDeleted = "Ezt a tartalmat törölted"
override val actionRestore = "visszaállítás"
} }

View File

@ -422,5 +422,8 @@ internal val ItStrings =
override val noticeBannedUser = "L\'utente corrente è stato bannato da questa comunità" override val noticeBannedUser = "L\'utente corrente è stato bannato da questa comunità"
override val settingsHiddenPosts = "Post nascosti" override val settingsHiddenPosts = "Post nascosti"
override val settingsMediaList = "Caricamenti multimediali" override val settingsMediaList = "Caricamenti multimediali"
override val settingsEnableToggleFavoriteInNavDrawer = "Aggiungi/rimuovi preferiti in menu laterale" override val settingsEnableToggleFavoriteInNavDrawer =
"Aggiungi/rimuovi preferiti in menu laterale"
override val messageContentDeleted = "Hai eliminato questo contenuto"
override val actionRestore = "Ripristina"
} }

View File

@ -422,4 +422,6 @@ internal val LtStrings =
override val settingsMediaList = "Žiniasklaidos įkėlimai" override val settingsMediaList = "Žiniasklaidos įkėlimai"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Pridėti / pašalinti mėgstamiausius naršymo skydelyje" "Pridėti / pašalinti mėgstamiausius naršymo skydelyje"
override val messageContentDeleted = "Ištrynėte šį turinį"
override val actionRestore = "Atkurti"
} }

View File

@ -421,4 +421,6 @@ internal val LvStrings =
override val settingsMediaList = "Multivides augšupielādes" override val settingsMediaList = "Multivides augšupielādes"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Pievienojiet/noņemiet izlasi navigācijas atvilktnē" "Pievienojiet/noņemiet izlasi navigācijas atvilktnē"
override val messageContentDeleted = "Jūs esat izdzēsis šo saturu"
override val actionRestore = "Atjaunot"
} }

View File

@ -423,4 +423,6 @@ internal val MtStrings =
override val settingsMediaList = "Uploads tal-midja" override val settingsMediaList = "Uploads tal-midja"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Żid/neħħi l-favoriti fil-kexxun tan-navigazzjoni" "Żid/neħħi l-favoriti fil-kexxun tan-navigazzjoni"
override val messageContentDeleted = "Ħassejt dan il-kontenut"
override val actionRestore = "Irrestawra"
} }

View File

@ -424,4 +424,6 @@ internal val NlStrings =
override val settingsMediaList = "Media-uploads" override val settingsMediaList = "Media-uploads"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Favorieten toevoegen/verwijderen in de navigatielade" "Favorieten toevoegen/verwijderen in de navigatielade"
override val messageContentDeleted = "Je hebt deze inhoud verwijderd"
override val actionRestore = "Herstellen"
} }

View File

@ -421,4 +421,6 @@ internal val NoStrings =
override val settingsMediaList = "Medieopplastinger" override val settingsMediaList = "Medieopplastinger"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Legg til/fjern favoritter i navigasjonsskuffen" "Legg til/fjern favoritter i navigasjonsskuffen"
override val messageContentDeleted = "Du har slettet dette innholdet"
override val actionRestore = "Restaurere"
} }

View File

@ -421,5 +421,8 @@ internal val PlStrings =
override val noticeBannedUser = "Bieżący użytkownik został zablokowany w tej społeczności" override val noticeBannedUser = "Bieżący użytkownik został zablokowany w tej społeczności"
override val settingsHiddenPosts = "Ukryte posty" override val settingsHiddenPosts = "Ukryte posty"
override val settingsMediaList = "Przesyłanie multimediów" override val settingsMediaList = "Przesyłanie multimediów"
override val settingsEnableToggleFavoriteInNavDrawer = "Dodaj/usuń ulubione w szufladzie nawigacji" override val settingsEnableToggleFavoriteInNavDrawer =
"Dodaj/usuń ulubione w szufladzie nawigacji"
override val messageContentDeleted = "Usunąłeś tę treść"
override val actionRestore = "Przywrócić"
} }

View File

@ -421,4 +421,6 @@ internal val PtBrStrings =
override val settingsMediaList = "Carregamentos de mídia" override val settingsMediaList = "Carregamentos de mídia"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Adicionar/remover favoritos na gaveta de navegação" "Adicionar/remover favoritos na gaveta de navegação"
override val messageContentDeleted = "Você excluiu este conteúdo"
override val actionRestore = "Restaura"
} }

View File

@ -422,4 +422,6 @@ internal val PtStrings =
override val settingsMediaList = "Carregamentos de mídia" override val settingsMediaList = "Carregamentos de mídia"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Adicionar/remover favoritos na gaveta de navegação" "Adicionar/remover favoritos na gaveta de navegação"
override val messageContentDeleted = "Você excluiu este conteúdo"
override val actionRestore = "Restaura"
} }

View File

@ -422,4 +422,6 @@ internal val RoStrings =
override val settingsMediaList = "Încărcări media" override val settingsMediaList = "Încărcări media"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Adăugă/elimină favoritele din sertarul de navigare" "Adăugă/elimină favoritele din sertarul de navigare"
override val messageContentDeleted = "Ați șters acest conținut"
override val actionRestore = "Restaurați-l"
} }

View File

@ -424,4 +424,6 @@ internal val RuStrings =
override val settingsMediaList = "Загрузка мультимедиа" override val settingsMediaList = "Загрузка мультимедиа"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Добавить/удалить избранное в панели навигации" "Добавить/удалить избранное в панели навигации"
override val messageContentDeleted = "Вы удалили этот контент"
override val actionRestore = "Восстановить"
} }

View File

@ -422,4 +422,6 @@ internal val SeStrings =
override val settingsMediaList = "Mediauppladdningar" override val settingsMediaList = "Mediauppladdningar"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Lägg till/ta bort favoriter i navigeringslådan" "Lägg till/ta bort favoriter i navigeringslådan"
override val messageContentDeleted = "Du har tagit bort detta innehåll"
override val actionRestore = "Återställ"
} }

View File

@ -424,4 +424,6 @@ internal val SkStrings =
override val settingsMediaList = "Nahrávanie médií" override val settingsMediaList = "Nahrávanie médií"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Pridať/odstrániť obľúbené položky v navigačnej zásuvke" "Pridať/odstrániť obľúbené položky v navigačnej zásuvke"
override val messageContentDeleted = "Tento obsah ste odstránili"
override val actionRestore = "Obnoviť"
} }

View File

@ -422,4 +422,6 @@ internal val SlStrings =
override val settingsMediaList = "Nalaganje medijev" override val settingsMediaList = "Nalaganje medijev"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Dodajte/odstranite priljubljene v predalu za krmarjenje" "Dodajte/odstranite priljubljene v predalu za krmarjenje"
override val messageContentDeleted = "To vsebino ste izbrisali"
override val actionRestore = "Obnovi"
} }

View File

@ -424,4 +424,6 @@ internal val SqStrings =
override val settingsMediaList = "Ngarkimet e mediave" override val settingsMediaList = "Ngarkimet e mediave"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Shto/hiq të preferuarat në sirtarin e navigimit" "Shto/hiq të preferuarat në sirtarin e navigimit"
override val messageContentDeleted = "Ju e keni fshirë këtë përmbajtje"
override val actionRestore = "Rivendos"
} }

View File

@ -424,4 +424,6 @@ internal val SrStrings =
override val settingsMediaList = "Учитавање медија" override val settingsMediaList = "Учитавање медија"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Додајте/уклоните фаворите у фиоци за навигацију" "Додајте/уклоните фаворите у фиоци за навигацију"
override val messageContentDeleted = "Избрисали сте овај садржај"
override val actionRestore = "Ресторе"
} }

View File

@ -416,6 +416,8 @@ interface Strings {
val settingsHiddenPosts: String val settingsHiddenPosts: String
val settingsMediaList: String val settingsMediaList: String
val settingsEnableToggleFavoriteInNavDrawer: String val settingsEnableToggleFavoriteInNavDrawer: String
val messageContentDeleted: String
val actionRestore: String
} }
object Locales { object Locales {

View File

@ -418,4 +418,6 @@ internal val TokStrings =
override val settingsMediaList = "sitelen linluwi" override val settingsMediaList = "sitelen linluwi"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"o sin/o weka e ijo pona lon leko luka" "o sin/o weka e ijo pona lon leko luka"
override val messageContentDeleted = "tenpo pini la, sina weka e ijo ni"
override val actionRestore = "o awen e ona"
} }

View File

@ -423,4 +423,6 @@ internal val TrStrings =
override val settingsMediaList = "Medya yüklemeleri" override val settingsMediaList = "Medya yüklemeleri"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Gezinme çekmecesinde favorileri ekle/kaldır" "Gezinme çekmecesinde favorileri ekle/kaldır"
override val messageContentDeleted = "Bu içeriği sildiniz"
override val actionRestore = "Eski haline getirmek"
} }

View File

@ -424,4 +424,6 @@ internal val UkStrings =
override val settingsMediaList = "Завантаження медіа" override val settingsMediaList = "Завантаження медіа"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer =
"Додати/видалити вибране в панелі навігації" "Додати/видалити вибране в панелі навігації"
override val messageContentDeleted = "Ви видалили цей вміст"
override val actionRestore = "Відновлення"
} }

View File

@ -411,6 +411,7 @@ internal val ZhHkStrings =
override val noticeBannedUser = "呢個用戶已被呢個社區禁止" override val noticeBannedUser = "呢個用戶已被呢個社區禁止"
override val settingsHiddenPosts = "隱藏帖子" override val settingsHiddenPosts = "隱藏帖子"
override val settingsMediaList = "媒體上傳" override val settingsMediaList = "媒體上傳"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer = "加入/攞走導航櫃桶嘅最愛"
"Add/remove favorites in navigation drawer" override val messageContentDeleted = "您刪除咗呢個內容"
override val actionRestore = "恢復"
} }

View File

@ -411,6 +411,7 @@ internal val ZhTwStrings =
override val settingsAboutLicences = "授權" override val settingsAboutLicences = "授權"
override val never = "從不" override val never = "從不"
override val appIconDefault = "默認" override val appIconDefault = "默認"
override val settingsEnableToggleFavoriteInNavDrawer = override val settingsEnableToggleFavoriteInNavDrawer = "加入/移除導航抽屜中的收藏"
"Add/remove favorites in navigation drawer" override val messageContentDeleted = "您已刪除此內容"
override val actionRestore = "恢復"
} }

View File

@ -27,37 +27,62 @@ sealed interface NotificationCenterEvent {
val value: SortType, val value: SortType,
val defaultForCommunity: Boolean, val defaultForCommunity: Boolean,
val screenKey: String?, val screenKey: String?,
) : ) : NotificationCenterEvent
NotificationCenterEvent
data class ChangeCommentSortType(val value: SortType, val screenKey: String?) : data class ChangeCommentSortType(
NotificationCenterEvent val value: SortType,
val screenKey: String?,
) : NotificationCenterEvent
data class ChangeFeedType(val value: ListingType, val screenKey: String?) : data class ChangeFeedType(
NotificationCenterEvent val value: ListingType,
val screenKey: String?,
) : NotificationCenterEvent
data class ChangeInboxType(val unreadOnly: Boolean) : NotificationCenterEvent data class ChangeInboxType(
val unreadOnly: Boolean,
) : NotificationCenterEvent
data class ChangeTheme(val value: UiTheme?) : NotificationCenterEvent data class ChangeTheme(
val value: UiTheme?,
) : NotificationCenterEvent
data class ChangeContentFontSize(val value: Float, val contentClass: ContentFontClass) : data class ChangeContentFontSize(
NotificationCenterEvent val value: Float,
val contentClass: ContentFontClass,
) : NotificationCenterEvent
data class ChangeUiFontSize(val value: Float) : NotificationCenterEvent data class ChangeUiFontSize(
val value: Float,
) : NotificationCenterEvent
data class ChangeFontFamily(val value: UiFontFamily) : NotificationCenterEvent data class ChangeFontFamily(
val value: UiFontFamily,
) : NotificationCenterEvent
data class ChangeContentFontFamily(val value: UiFontFamily) : NotificationCenterEvent data class ChangeContentFontFamily(
val value: UiFontFamily,
) : NotificationCenterEvent
data class ChangeZombieInterval(val value: Duration) : NotificationCenterEvent data class ChangeZombieInterval(
val value: Duration,
) : NotificationCenterEvent
data class ChangeInboxBackgroundCheckPeriod(val value: Duration) : NotificationCenterEvent data class ChangeInboxBackgroundCheckPeriod(
val value: Duration,
) : NotificationCenterEvent
data class ChangeLanguage(val value: String) : NotificationCenterEvent data class ChangeLanguage(
val value: String,
) : NotificationCenterEvent
data class ChangePostLayout(val value: PostLayout) : NotificationCenterEvent data class ChangePostLayout(
val value: PostLayout,
) : NotificationCenterEvent
data class ChangeVoteFormat(val value: VoteFormat) : NotificationCenterEvent data class ChangeVoteFormat(
val value: VoteFormat,
) : NotificationCenterEvent
data object Logout : NotificationCenterEvent data object Logout : NotificationCenterEvent
@ -65,35 +90,58 @@ sealed interface NotificationCenterEvent {
data object CommentCreated : NotificationCenterEvent data object CommentCreated : NotificationCenterEvent
data class PostUpdated(val model: PostModel) : NotificationCenterEvent data class PostUpdated(
val model: PostModel,
) : NotificationCenterEvent
data class CommentUpdated(val model: CommentModel) : NotificationCenterEvent data class CommentUpdated(
val model: CommentModel,
) : NotificationCenterEvent
data class PostDeleted(val model: PostModel) : NotificationCenterEvent data class ChangeColor(
val color: Color?,
) : NotificationCenterEvent
data class ChangeColor(val color: Color?) : NotificationCenterEvent data class ChangeActionColor(
val color: Color?,
val actionType: Int,
) : NotificationCenterEvent
data class ChangeActionColor(val color: Color?, val actionType: Int) : NotificationCenterEvent data class ChangeZombieScrollAmount(
val value: Float,
) : NotificationCenterEvent
data class ChangeZombieScrollAmount(val value: Float) : NotificationCenterEvent data class MultiCommunityCreated(
val model: MultiCommunityModel,
data class MultiCommunityCreated(val model: MultiCommunityModel) : NotificationCenterEvent ) : NotificationCenterEvent
data object CloseDialog : NotificationCenterEvent data object CloseDialog : NotificationCenterEvent
data class SelectCommunity(val model: CommunityModel) : NotificationCenterEvent data class SelectCommunity(
val model: CommunityModel,
) : NotificationCenterEvent
data class PostRemoved(val model: PostModel) : NotificationCenterEvent data class PostRemoved(
val model: PostModel,
) : NotificationCenterEvent
data class CommentRemoved(val model: CommentModel) : NotificationCenterEvent data class ChangeReportListType(
val unresolvedOnly: Boolean,
) : NotificationCenterEvent
data class ChangeReportListType(val unresolvedOnly: Boolean) : NotificationCenterEvent data class UserBannedPost(
val postId: Long,
val user: UserModel,
) : NotificationCenterEvent
data class UserBannedPost(val postId: Long, val user: UserModel) : NotificationCenterEvent data class UserBannedComment(
val commentId: Long,
val user: UserModel,
) : NotificationCenterEvent
data class UserBannedComment(val commentId: Long, val user: UserModel) : NotificationCenterEvent data class ChangeCommentBarTheme(
val value: CommentBarTheme,
data class ChangeCommentBarTheme(val value: CommentBarTheme) : NotificationCenterEvent ) : NotificationCenterEvent
data class BlockActionSelected( data class BlockActionSelected(
val userId: Long? = null, val userId: Long? = null,
@ -101,11 +149,17 @@ sealed interface NotificationCenterEvent {
val instanceId: Long? = null, val instanceId: Long? = null,
) : NotificationCenterEvent ) : NotificationCenterEvent
data class Share(val url: String) : NotificationCenterEvent data class Share(
val url: String,
) : NotificationCenterEvent
data class ChangePostBodyMaxLines(val value: Int?) : NotificationCenterEvent data class ChangePostBodyMaxLines(
val value: Int?,
) : NotificationCenterEvent
data class InstanceSelected(val value: String) : NotificationCenterEvent data class InstanceSelected(
val value: String,
) : NotificationCenterEvent
data class ActionsOnSwipeSelected( data class ActionsOnSwipeSelected(
val value: ActionOnSwipe, val value: ActionOnSwipe,
@ -113,11 +167,15 @@ sealed interface NotificationCenterEvent {
val target: ActionOnSwipeTarget, val target: ActionOnSwipeTarget,
) : NotificationCenterEvent ) : NotificationCenterEvent
data class ChangeSystemBarTheme(val value: UiBarTheme) : NotificationCenterEvent data class ChangeSystemBarTheme(
val value: UiBarTheme,
) : NotificationCenterEvent
data object DraftDeleted : NotificationCenterEvent data object DraftDeleted : NotificationCenterEvent
data class ModeratorZoneActionSelected(val value: Int) : NotificationCenterEvent data class ModeratorZoneActionSelected(
val value: Int,
) : NotificationCenterEvent
data object ResetHome : NotificationCenterEvent data object ResetHome : NotificationCenterEvent
@ -125,22 +183,37 @@ sealed interface NotificationCenterEvent {
data object ResetInbox : NotificationCenterEvent data object ResetInbox : NotificationCenterEvent
data class CopyText(val value: String) : NotificationCenterEvent data class CopyText(
val value: String,
) : NotificationCenterEvent
data class ChangedLikedType(val value: Boolean) : NotificationCenterEvent data class ChangedLikedType(
val value: Boolean,
) : NotificationCenterEvent
data class ChangeSearchResultType(val value: SearchResultType, val screenKey: String?) : data class ChangeSearchResultType(
NotificationCenterEvent val value: SearchResultType,
val screenKey: String?,
) : NotificationCenterEvent
data class CommunitySubscriptionChanged(val value: CommunityModel) : NotificationCenterEvent data class CommunitySubscriptionChanged(
val value: CommunityModel,
) : NotificationCenterEvent
sealed interface ShareImageModeSelected : NotificationCenterEvent { sealed interface ShareImageModeSelected : NotificationCenterEvent {
data class ModeUrl(val url: String) : ShareImageModeSelected data class ModeUrl(
val url: String,
) : ShareImageModeSelected
data class ModeFile(val url: String, val source: String) : ShareImageModeSelected data class ModeFile(
val url: String,
val source: String,
) : ShareImageModeSelected
} }
data class AppIconVariantSelected(val value: Int) : NotificationCenterEvent data class AppIconVariantSelected(
val value: Int,
) : NotificationCenterEvent
sealed interface ProfileSideMenuAction : NotificationCenterEvent { sealed interface ProfileSideMenuAction : NotificationCenterEvent {
data object ManageAccounts : ProfileSideMenuAction data object ManageAccounts : ProfileSideMenuAction
@ -158,10 +231,13 @@ sealed interface NotificationCenterEvent {
data object Logout : ProfileSideMenuAction data object Logout : ProfileSideMenuAction
} }
data class ChangeUrlOpeningMode(val value: Int) : NotificationCenterEvent data class ChangeUrlOpeningMode(
val value: Int,
) : NotificationCenterEvent
data class ChangeCommunityVisibility(val value: CommunityVisibilityType) : data class ChangeCommunityVisibility(
NotificationCenterEvent val value: CommunityVisibilityType,
) : NotificationCenterEvent
data object FavoritesUpdated : NotificationCenterEvent data object FavoritesUpdated : NotificationCenterEvent
} }

View File

@ -9,6 +9,7 @@ sealed interface CommentPaginationSpecification {
val listingType: ListingType? = null, val listingType: ListingType? = null,
val otherInstance: String? = null, val otherInstance: String? = null,
val sortType: SortType = SortType.Active, val sortType: SortType = SortType.Active,
val includeDeleted: Boolean = false,
) : CommentPaginationSpecification ) : CommentPaginationSpecification
data class User( data class User(
@ -16,6 +17,7 @@ sealed interface CommentPaginationSpecification {
val name: String? = null, val name: String? = null,
val otherInstance: String? = null, val otherInstance: String? = null,
val sortType: SortType = SortType.New, val sortType: SortType = SortType.New,
val includeDeleted: Boolean = false,
) : CommentPaginationSpecification ) : CommentPaginationSpecification
data class Votes( data class Votes(

View File

@ -32,7 +32,9 @@ internal class DefaultCommentPaginationManager(
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher) private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
init { init {
notificationCenter.subscribe(NotificationCenterEvent.CommentUpdated::class).onEach { evt -> notificationCenter
.subscribe(NotificationCenterEvent.CommentUpdated::class)
.onEach { evt ->
handleCommentUpdate(evt.model) handleCommentUpdate(evt.model)
}.launchIn(scope) }.launchIn(scope)
} }
@ -68,8 +70,13 @@ internal class DefaultCommentPaginationManager(
itemList itemList
.orEmpty() .orEmpty()
.deduplicate() .deduplicate()
.filterDeleted() .let {
.also { if (specification.includeDeleted) {
it
} else {
it.filterDeleted()
}
}.also {
// deleted comments should not be counted // deleted comments should not be counted
canFetchMore = it.isNotEmpty() canFetchMore = it.isNotEmpty()
} }
@ -92,8 +99,13 @@ internal class DefaultCommentPaginationManager(
itemList itemList
.orEmpty() .orEmpty()
.deduplicate() .deduplicate()
.filterDeleted() .let {
.also { if (specification.includeDeleted) {
it
} else {
it.filterDeleted()
}
}.also {
canFetchMore = it.isNotEmpty() canFetchMore = it.isNotEmpty()
} }
} }

View File

@ -38,7 +38,9 @@ internal class DefaultPostPaginationManager(
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher) private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
init { init {
notificationCenter.subscribe(NotificationCenterEvent.PostUpdated::class).onEach { evt -> notificationCenter
.subscribe(NotificationCenterEvent.PostUpdated::class)
.onEach { evt ->
handlePostUpdate(evt.model) handlePostUpdate(evt.model)
}.launchIn(scope) }.launchIn(scope)
} }
@ -89,7 +91,8 @@ internal class DefaultPostPaginationManager(
val searching = !specification.query.isNullOrEmpty() val searching = !specification.query.isNullOrEmpty()
val (itemList, nextPage) = val (itemList, nextPage) =
if (searching) { if (searching) {
communityRepository.search( communityRepository
.search(
auth = auth, auth = auth,
communityId = specification.id, communityId = specification.id,
page = currentPage, page = currentPage,
@ -121,7 +124,13 @@ internal class DefaultPostPaginationManager(
.orEmpty() .orEmpty()
.deduplicate() .deduplicate()
.filterNsfw(specification.includeNsfw) .filterNsfw(specification.includeNsfw)
.filterDeleted() .let {
if (specification.includeDeleted) {
it
} else {
it.filterDeleted()
}
}
} }
is PostPaginationSpecification.MultiCommunity -> { is PostPaginationSpecification.MultiCommunity -> {
@ -155,7 +164,13 @@ internal class DefaultPostPaginationManager(
.orEmpty() .orEmpty()
.deduplicate() .deduplicate()
.filterNsfw(specification.includeNsfw) .filterNsfw(specification.includeNsfw)
.filterDeleted() .let {
if (specification.includeDeleted) {
it
} else {
it.filterDeleted()
}
}
} }
is PostPaginationSpecification.Votes -> { is PostPaginationSpecification.Votes -> {

View File

@ -23,6 +23,7 @@ sealed interface PostPaginationSpecification {
val query: String? = null, val query: String? = null,
val sortType: SortType = SortType.Active, val sortType: SortType = SortType.Active,
val includeNsfw: Boolean = true, val includeNsfw: Boolean = true,
val includeDeleted: Boolean = false,
) : PostPaginationSpecification ) : PostPaginationSpecification
data class User( data class User(
@ -31,6 +32,7 @@ sealed interface PostPaginationSpecification {
val otherInstance: String? = null, val otherInstance: String? = null,
val sortType: SortType = SortType.New, val sortType: SortType = SortType.New,
val includeNsfw: Boolean = true, val includeNsfw: Boolean = true,
val includeDeleted: Boolean = false,
) : PostPaginationSpecification ) : PostPaginationSpecification
data class Votes( data class Votes(

View File

@ -406,6 +406,28 @@ class DefaultCommentRepositoryTest {
} }
} }
@Test
fun whenRestore_thenInteractionsAreAsExpected() =
runTest {
val itemId = 1L
val token = "fake-token"
sut.restore(
commentId = itemId,
auth = token,
)
coVerify {
commentService.delete(
authHeader = token.toAuthHeader(),
form =
withArg { data ->
assertEquals(itemId, data.commentId)
assertFalse(data.deleted)
},
)
}
}
@Test @Test
fun whenReport_thenInteractionsAreAsExpected() = fun whenReport_thenInteractionsAreAsExpected() =
runTest { runTest {

View File

@ -583,6 +583,35 @@ class DefaultPostRepositoryTest {
form = form =
withArg { withArg {
assertEquals(postId, it.postId) assertEquals(postId, it.postId)
assertTrue(it.deleted)
},
)
}
}
@Test
fun whenRestore_thenInteractionsAreAsExpected() =
runTest {
coEvery {
postService.delete(
authHeader = any(),
form = any(),
)
} returns mockk(relaxed = true)
val postId = 1L
sut.restore(
id = postId,
auth = AUTH_TOKEN,
)
coVerify {
postService.delete(
authHeader = AUTH_TOKEN.toAuthHeader(),
form =
withArg {
assertEquals(postId, it.postId)
assertFalse(it.deleted)
}, },
) )
} }

View File

@ -101,7 +101,12 @@ interface CommentRepository {
suspend fun delete( suspend fun delete(
commentId: Long, commentId: Long,
auth: String, auth: String,
) ): CommentModel?
suspend fun restore(
commentId: Long,
auth: String,
): CommentModel?
suspend fun report( suspend fun report(
commentId: Long, commentId: Long,

View File

@ -320,9 +320,24 @@ internal class DefaultCommentRepository(
commentId = commentId, commentId = commentId,
deleted = true, deleted = true,
) )
services.comment.delete(authHeader = auth.toAuthHeader(), form = data) val res = services.comment.delete(authHeader = auth.toAuthHeader(), form = data)
Unit res.commentView?.toModel()
}.getOrDefault(Unit) }.getOrNull()
}
override suspend fun restore(
commentId: Long,
auth: String,
) = withContext(Dispatchers.IO) {
runCatching {
val data =
DeleteCommentForm(
commentId = commentId,
deleted = false,
)
val res = services.comment.delete(authHeader = auth.toAuthHeader(), form = data)
res.commentView.toModel()
}.getOrNull()
} }
override suspend fun report( override suspend fun report(

View File

@ -301,19 +301,40 @@ internal class DefaultPostRepository(
override suspend fun delete( override suspend fun delete(
id: Long, id: Long,
auth: String, auth: String,
): Unit = ) = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) {
runCatching { runCatching {
val data = val data =
DeletePostForm( DeletePostForm(
postId = id, postId = id,
deleted = true, deleted = true,
) )
val res =
services.post.delete( services.post.delete(
authHeader = auth.toAuthHeader(), authHeader = auth.toAuthHeader(),
form = data, form = data,
) )
}.getOrDefault(Unit) res.postView.toModel()
}.getOrNull()
}
override suspend fun restore(
id: Long,
auth: String,
): PostModel? =
withContext(Dispatchers.IO) {
runCatching {
val data =
DeletePostForm(
postId = id,
deleted = false,
)
val res =
services.post.delete(
authHeader = auth.toAuthHeader(),
form = data,
)
res.postView.toModel()
}.getOrNull()
} }
override suspend fun uploadImage( override suspend fun uploadImage(

View File

@ -97,7 +97,12 @@ interface PostRepository {
suspend fun delete( suspend fun delete(
id: Long, id: Long,
auth: String, auth: String,
) ): PostModel?
suspend fun restore(
id: Long,
auth: String,
): PostModel?
suspend fun uploadImage( suspend fun uploadImage(
auth: String, auth: String,

View File

@ -27,11 +27,20 @@ interface CommunityDetailMviModel :
data object LoadNextPage : Intent data object LoadNextPage : Intent
data class UpVotePost(val id: Long, val feedback: Boolean = false) : Intent data class UpVotePost(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class DownVotePost(val id: Long, val feedback: Boolean = false) : Intent data class DownVotePost(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class SavePost(val id: Long, val feedback: Boolean = false) : Intent data class SavePost(
val id: Long,
val feedback: Boolean = false,
) : Intent
data object HapticIndication : Intent data object HapticIndication : Intent
@ -39,11 +48,17 @@ interface CommunityDetailMviModel :
data object Unsubscribe : Intent data object Unsubscribe : Intent
data class DeletePost(val id: Long) : Intent data class DeletePost(
val id: Long,
) : Intent
data class MarkAsRead(val id: Long) : Intent data class MarkAsRead(
val id: Long,
) : Intent
data class Hide(val id: Long) : Intent data class Hide(
val id: Long,
) : Intent
data object Block : Intent data object Block : Intent
@ -51,35 +66,59 @@ interface CommunityDetailMviModel :
data object ClearRead : Intent data object ClearRead : Intent
data class StartZombieMode(val index: Int) : Intent data class StartZombieMode(
val index: Int,
) : Intent
data object PauseZombieMode : Intent data object PauseZombieMode : Intent
data class ModFeaturePost(val id: Long) : Intent data class ModFeaturePost(
val id: Long,
) : Intent
data class AdminFeaturePost(val id: Long) : Intent data class AdminFeaturePost(
val id: Long,
) : Intent
data class ModLockPost(val id: Long) : Intent data class ModLockPost(
val id: Long,
) : Intent
data class ModToggleModUser(val id: Long) : Intent data class ModToggleModUser(
val id: Long,
) : Intent
data object ToggleFavorite : Intent data object ToggleFavorite : Intent
data class Share(val url: String) : Intent data class Share(
val url: String,
) : Intent
data class SetSearch(val value: String) : Intent data class SetSearch(
val value: String,
) : Intent
data class ChangeSearching(val value: Boolean) : Intent data class ChangeSearching(
val value: Boolean,
) : Intent
data class Copy(val value: String) : Intent data class Copy(
val value: String,
) : Intent
data object WillOpenDetail : Intent data object WillOpenDetail : Intent
data object UnhideCommunity : Intent data object UnhideCommunity : Intent
data class SelectPreferredLanguage(val languageId: Long?) : Intent data class SelectPreferredLanguage(
val languageId: Long?,
) : Intent
data object DeleteCommunity : Intent data object DeleteCommunity : Intent
data class RestorePost(
val id: Long,
) : Intent
} }
data class UiState( data class UiState(
@ -123,15 +162,23 @@ interface CommunityDetailMviModel :
sealed interface Effect { sealed interface Effect {
data object Success : Effect data object Success : Effect
data class Failure(val message: String?) : Effect data class Failure(
val message: String?,
) : Effect
data class Error(val message: String?) : Effect data class Error(
val message: String?,
) : Effect
data object BackToTop : Effect data object BackToTop : Effect
data class ZombieModeTick(val index: Int) : Effect data class ZombieModeTick(
val index: Int,
) : Effect
data class TriggerCopy(val text: String) : Effect data class TriggerCopy(
val text: String,
) : Effect
data object Back : Effect data object Back : Effect
} }

View File

@ -1205,12 +1205,20 @@ class CommunityDetailScreen(
OptionId.Edit, OptionId.Edit,
LocalStrings.current.postActionEdit, LocalStrings.current.postActionEdit,
) )
if (post.deleted) {
this +=
Option(
OptionId.Restore,
LocalStrings.current.actionRestore,
)
} else {
this += this +=
Option( Option(
OptionId.Delete, OptionId.Delete,
LocalStrings.current.commentActionDelete, LocalStrings.current.commentActionDelete,
) )
} }
}
if (uiState.moderators.containsId(uiState.currentUserId)) { if (uiState.moderators.containsId(uiState.currentUserId)) {
this += this +=
Option( Option(
@ -1447,6 +1455,14 @@ class CommunityDetailScreen(
} }
} }
OptionId.Restore -> {
model.reduce(
CommunityDetailMviModel.Intent.RestorePost(
post.id,
),
)
}
else -> Unit else -> Unit
} }
}, },

View File

@ -157,15 +157,6 @@ class CommunityDetailViewModel(
) )
} }
}.launchIn(this) }.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.CommentRemoved::class)
.onEach { evt ->
val postId = evt.model.postId
uiState.value.posts.firstOrNull { it.id == postId }?.also {
val newPost = it.copy(comments = (it.comments - 1).coerceAtLeast(0))
handlePostUpdate(newPost)
}
}.launchIn(this)
val communityHandle = uiState.value.community.readableHandle val communityHandle = uiState.value.community.readableHandle
notificationCenter notificationCenter
.subscribe(NotificationCenterEvent.ChangeSortType::class) .subscribe(NotificationCenterEvent.ChangeSortType::class)
@ -374,6 +365,10 @@ class CommunityDetailViewModel(
CommunityDetailMviModel.Intent.DeleteCommunity -> { CommunityDetailMviModel.Intent.DeleteCommunity -> {
deleteCommunity() deleteCommunity()
} }
is CommunityDetailMviModel.Intent.RestorePost -> {
restorePost(intent.id)
}
} }
} }
@ -389,6 +384,7 @@ class CommunityDetailViewModel(
otherInstance = otherInstance, otherInstance = otherInstance,
query = currentState.searchText.takeIf { currentState.searching }, query = currentState.searchText.takeIf { currentState.searching },
includeNsfw = settingsRepository.currentSettings.value.includeNsfw, includeNsfw = settingsRepository.currentSettings.value.includeNsfw,
includeDeleted = true,
), ),
) )
updateState { updateState {
@ -860,4 +856,18 @@ class CommunityDetailViewModel(
} }
} }
} }
private fun restorePost(id: Long) {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
val newPost =
postRepository.restore(
id = id,
auth = auth,
)
if (newPost != null) {
handlePostUpdate(newPost)
}
}
}
} }

View File

@ -13,31 +13,65 @@ interface ProfileLoggedMviModel :
MviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect>, MviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect>,
ScreenModel { ScreenModel {
sealed interface Intent { sealed interface Intent {
data class ChangeSection(val section: ProfileLoggedSection) : Intent data class ChangeSection(
val section: ProfileLoggedSection,
) : Intent
data object Refresh : Intent data object Refresh : Intent
data object LoadNextPage : Intent data object LoadNextPage : Intent
data class DeletePost(val id: Long) : Intent data class DeletePost(
val id: Long,
) : Intent
data class DeleteComment(val id: Long) : Intent data class DeleteComment(
val id: Long,
) : Intent
data class Share(val url: String) : Intent data class Share(
val url: String,
) : Intent
data class UpVotePost(val id: Long, val feedback: Boolean = false) : Intent data class UpVotePost(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class DownVotePost(val id: Long, val feedback: Boolean = false) : Intent data class DownVotePost(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class SavePost(val id: Long, val feedback: Boolean = false) : Intent data class SavePost(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class UpVoteComment(val id: Long, val feedback: Boolean = false) : Intent data class UpVoteComment(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class DownVoteComment(val id: Long, val feedback: Boolean = false) : Intent data class DownVoteComment(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class SaveComment(val id: Long, val feedback: Boolean = false) : Intent data class SaveComment(
val id: Long,
val feedback: Boolean = false,
) : Intent
data object WillOpenDetail : Intent data object WillOpenDetail : Intent
data class RestorePost(
val id: Long,
) : Intent
data class RestoreComment(
val id: Long,
) : Intent
} }
data class UiState( data class UiState(

View File

@ -91,7 +91,8 @@ object ProfileLoggedScreen : Tab {
var commentIdToDelete by remember { mutableStateOf<Long?>(null) } var commentIdToDelete by remember { mutableStateOf<Long?>(null) }
LaunchedEffect(navigationCoordinator) { LaunchedEffect(navigationCoordinator) {
navigationCoordinator.onDoubleTabSelection.onEach { section -> navigationCoordinator.onDoubleTabSelection
.onEach { section ->
runCatching { runCatching {
if (section == TabNavigationSection.Profile) { if (section == TabNavigationSection.Profile) {
lazyListState.scrollToItem(0) lazyListState.scrollToItem(0)
@ -100,11 +101,15 @@ object ProfileLoggedScreen : Tab {
}.launchIn(this) }.launchIn(this)
} }
LaunchedEffect(notificationCenter) { LaunchedEffect(notificationCenter) {
notificationCenter.subscribe(NotificationCenterEvent.PostCreated::class).onEach { notificationCenter
.subscribe(NotificationCenterEvent.PostCreated::class)
.onEach {
model.reduce(ProfileLoggedMviModel.Intent.Refresh) model.reduce(ProfileLoggedMviModel.Intent.Refresh)
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.CommentCreated::class).onEach { notificationCenter
.subscribe(NotificationCenterEvent.CommentCreated::class)
.onEach {
model.reduce(ProfileLoggedMviModel.Intent.Refresh) model.reduce(ProfileLoggedMviModel.Intent.Refresh)
}.launchIn(this) }.launchIn(this)
} }
@ -169,8 +174,7 @@ object ProfileLoggedScreen : Tab {
.padding( .padding(
top = Spacing.xs, top = Spacing.xs,
bottom = Spacing.s, bottom = Spacing.s,
) ).fillMaxWidth(),
.fillMaxWidth(),
isModerator = uiState.user?.moderator == true, isModerator = uiState.user?.moderator == true,
) )
HorizontalDivider() HorizontalDivider()
@ -296,36 +300,39 @@ object ProfileLoggedScreen : Tab {
}, },
options = options =
buildList { buildList {
add( this +=
Option( Option(
OptionId.Share, OptionId.Share,
LocalStrings.current.postActionShare, LocalStrings.current.postActionShare,
),
) )
add( this +=
Option( Option(
OptionId.CrossPost, OptionId.CrossPost,
LocalStrings.current.postActionCrossPost, LocalStrings.current.postActionCrossPost,
),
) )
add( this +=
Option( Option(
OptionId.SeeRaw, OptionId.SeeRaw,
LocalStrings.current.postActionSeeRaw, LocalStrings.current.postActionSeeRaw,
),
) )
add( this +=
Option( Option(
OptionId.Edit, OptionId.Edit,
LocalStrings.current.postActionEdit, LocalStrings.current.postActionEdit,
),
) )
add( if (post.deleted) {
this +=
Option(
OptionId.Restore,
LocalStrings.current.actionRestore,
)
} else {
this +=
Option( Option(
OptionId.Delete, OptionId.Delete,
LocalStrings.current.commentActionDelete, LocalStrings.current.commentActionDelete,
),
) )
}
}, },
onOptionSelected = onOptionSelected =
rememberCallbackArgs(model) { optionId -> rememberCallbackArgs(model) { optionId ->
@ -369,6 +376,14 @@ object ProfileLoggedScreen : Tab {
} }
} }
OptionId.Restore -> {
model.reduce(
ProfileLoggedMviModel.Intent.RestorePost(
post.id,
),
)
}
else -> Unit else -> Unit
} }
}, },
@ -384,7 +399,8 @@ object ProfileLoggedScreen : Tab {
item { item {
Text( Text(
modifier = modifier =
Modifier.fillMaxWidth() Modifier
.fillMaxWidth()
.padding(top = Spacing.xs), .padding(top = Spacing.xs),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
text = LocalStrings.current.messageEmptyList, text = LocalStrings.current.messageEmptyList,
@ -490,11 +506,19 @@ object ProfileLoggedScreen : Tab {
OptionId.Edit, OptionId.Edit,
LocalStrings.current.postActionEdit, LocalStrings.current.postActionEdit,
) )
if (comment.deleted) {
this +=
Option(
OptionId.Restore,
LocalStrings.current.actionRestore,
)
} else {
this += this +=
Option( Option(
OptionId.Delete, OptionId.Delete,
LocalStrings.current.commentActionDelete, LocalStrings.current.commentActionDelete,
) )
}
}, },
onOptionSelected = onOptionSelected =
rememberCallbackArgs(model) { optionId -> rememberCallbackArgs(model) { optionId ->
@ -535,6 +559,14 @@ object ProfileLoggedScreen : Tab {
} }
} }
OptionId.Restore -> {
model.reduce(
ProfileLoggedMviModel.Intent.RestoreComment(
comment.id,
),
)
}
else -> Unit else -> Unit
} }
}, },
@ -549,7 +581,8 @@ object ProfileLoggedScreen : Tab {
item { item {
Text( Text(
modifier = modifier =
Modifier.fillMaxWidth() Modifier
.fillMaxWidth()
.padding(top = Spacing.xs), .padding(top = Spacing.xs),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
text = LocalStrings.current.messageEmptyList, text = LocalStrings.current.messageEmptyList,

View File

@ -48,20 +48,24 @@ class ProfileLoggedViewModel(
private val notificationCenter: NotificationCenter, private val notificationCenter: NotificationCenter,
private val hapticFeedback: HapticFeedback, private val hapticFeedback: HapticFeedback,
private val postNavigationManager: PostNavigationManager, private val postNavigationManager: PostNavigationManager,
) : ProfileLoggedMviModel, ) : DefaultMviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect>(
DefaultMviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect>(
initialState = ProfileLoggedMviModel.UiState(), initialState = ProfileLoggedMviModel.UiState(),
) { ),
ProfileLoggedMviModel {
init { init {
screenModelScope.launch { screenModelScope.launch {
updateState { it.copy(instance = apiConfigurationRepository.instance.value) } updateState { it.copy(instance = apiConfigurationRepository.instance.value) }
themeRepository.postLayout.onEach { layout -> themeRepository.postLayout
.onEach { layout ->
updateState { it.copy(postLayout = layout) } updateState { it.copy(postLayout = layout) }
}.launchIn(this) }.launchIn(this)
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
identityRepository.isLogged.drop(1).debounce(500).onEach { logged -> identityRepository.isLogged
.drop(1)
.debounce(500)
.onEach { logged ->
if (logged == true) { if (logged == true) {
updateState { updateState {
it.copy( it.copy(
@ -73,7 +77,8 @@ class ProfileLoggedViewModel(
refresh() refresh()
} }
}.launchIn(this) }.launchIn(this)
settingsRepository.currentSettings.onEach { settings -> settingsRepository.currentSettings
.onEach { settings ->
updateState { updateState {
it.copy( it.copy(
voteFormat = settings.voteFormat, voteFormat = settings.voteFormat,
@ -86,16 +91,19 @@ class ProfileLoggedViewModel(
) )
} }
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.PostUpdated::class).onEach { evt -> notificationCenter
.subscribe(NotificationCenterEvent.PostUpdated::class)
.onEach { evt ->
handlePostUpdate(evt.model) handlePostUpdate(evt.model)
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.PostDeleted::class).onEach { evt -> notificationCenter
handlePostDelete(evt.model.id) .subscribe(NotificationCenterEvent.Share::class)
}.launchIn(this) .onEach { evt ->
notificationCenter.subscribe(NotificationCenterEvent.Share::class).onEach { evt ->
shareHelper.share(evt.url) shareHelper.share(evt.url)
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.Logout::class).onEach { notificationCenter
.subscribe(NotificationCenterEvent.Logout::class)
.onEach {
delay(250) delay(250)
refreshUser() refreshUser()
}.launchIn(this) }.launchIn(this)
@ -195,6 +203,14 @@ class ProfileLoggedViewModel(
val state = postPaginationManager.extractState() val state = postPaginationManager.extractState()
postNavigationManager.push(state) postNavigationManager.push(state)
} }
is ProfileLoggedMviModel.Intent.RestorePost -> {
restorePost(intent.id)
}
is ProfileLoggedMviModel.Intent.RestoreComment -> {
restoreComment(intent.id)
}
} }
} }
@ -231,12 +247,14 @@ class ProfileLoggedViewModel(
PostPaginationSpecification.User( PostPaginationSpecification.User(
id = userId, id = userId,
sortType = SortType.New, sortType = SortType.New,
includeDeleted = true,
), ),
) )
commentPaginationManager.reset( commentPaginationManager.reset(
CommentPaginationSpecification.User( CommentPaginationSpecification.User(
id = userId, id = userId,
sortType = SortType.New, sortType = SortType.New,
includeDeleted = true,
), ),
) )
updateState { updateState {
@ -480,29 +498,51 @@ class ProfileLoggedViewModel(
} }
} }
private fun handlePostDelete(id: Long) {
screenModelScope.launch {
updateState {
it.copy(
posts = it.posts.filter { post -> post.id != id },
)
}
}
}
private fun deletePost(id: Long) { private fun deletePost(id: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
postRepository.delete(id = id, auth = auth) val newPost = postRepository.delete(id = id, auth = auth)
handlePostDelete(id) if (newPost != null) {
handlePostUpdate(newPost)
}
} }
} }
private fun deleteComment(id: Long) { private fun deleteComment(id: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
commentRepository.delete(id, auth) val newComment = commentRepository.delete(id, auth)
refresh() if (newComment != null) {
handleCommentUpdate(newComment)
}
}
}
private fun restorePost(id: Long) {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
val newPost =
postRepository.restore(
id = id,
auth = auth,
)
if (newPost != null) {
handlePostUpdate(newPost)
}
}
}
private fun restoreComment(id: Long) {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
val newComment =
commentRepository.restore(
commentId = id,
auth = auth,
)
if (newComment != null) {
handleCommentUpdate(newComment)
}
} }
} }
} }

View File

@ -22,23 +22,45 @@ interface PostDetailMviModel :
data object LoadNextPage : Intent data object LoadNextPage : Intent
data class FetchMoreComments(val parentId: Long) : Intent data class FetchMoreComments(
val parentId: Long,
) : Intent
data class UpVotePost(val feedback: Boolean = false) : Intent data class UpVotePost(
val feedback: Boolean = false,
) : Intent
data class DownVotePost(val feedback: Boolean = false) : Intent data class DownVotePost(
val feedback: Boolean = false,
) : Intent
data class SavePost(val post: PostModel, val feedback: Boolean = false) : Intent data class SavePost(
val post: PostModel,
val feedback: Boolean = false,
) : Intent
data class UpVoteComment(val commentId: Long, val feedback: Boolean = false) : Intent data class UpVoteComment(
val commentId: Long,
val feedback: Boolean = false,
) : Intent
data class DownVoteComment(val commentId: Long, val feedback: Boolean = false) : Intent data class DownVoteComment(
val commentId: Long,
val feedback: Boolean = false,
) : Intent
data class SaveComment(val commentId: Long, val feedback: Boolean = false) : Intent data class SaveComment(
val commentId: Long,
val feedback: Boolean = false,
) : Intent
data class ToggleExpandComment(val commentId: Long) : Intent data class ToggleExpandComment(
val commentId: Long,
) : Intent
data class DeleteComment(val commentId: Long) : Intent data class DeleteComment(
val commentId: Long,
) : Intent
data object DeletePost : Intent data object DeletePost : Intent
@ -50,25 +72,47 @@ interface PostDetailMviModel :
data object ModLockPost : Intent data object ModLockPost : Intent
data class ModDistinguishComment(val commentId: Long) : Intent data class ModDistinguishComment(
val commentId: Long,
) : Intent
data class ModToggleModUser(val id: Long) : Intent data class ModToggleModUser(
val id: Long,
) : Intent
data class Share(val url: String) : Intent data class Share(
val url: String,
) : Intent
data class Copy(val value: String) : Intent data class Copy(
val value: String,
) : Intent
data class SetSearch(val value: String) : Intent data class SetSearch(
val value: String,
) : Intent
data class ChangeSearching(val value: Boolean) : Intent data class ChangeSearching(
val value: Boolean,
) : Intent
data object NavigatePrevious : Intent data object NavigatePrevious : Intent
data object NavigateNext : Intent data object NavigateNext : Intent
data class NavigatePreviousComment(val currentIndex: Int) : Intent data class NavigatePreviousComment(
val currentIndex: Int,
) : Intent
data class NavigateNextComment(val currentIndex: Int) : Intent data class NavigateNextComment(
val currentIndex: Int,
) : Intent
data object RestorePost : Intent
data class RestoreComment(
val commentId: Long,
) : Intent
} }
data class UiState( data class UiState(
@ -109,10 +153,14 @@ interface PostDetailMviModel :
sealed interface Effect { sealed interface Effect {
data object Close : Effect data object Close : Effect
data class ScrollToComment(val index: Int) : Effect data class ScrollToComment(
val index: Int,
) : Effect
data object BackToTop : Effect data object BackToTop : Effect
data class TriggerCopy(val text: String) : Effect data class TriggerCopy(
val text: String,
) : Effect
} }
} }

View File

@ -384,12 +384,20 @@ class PostDetailScreen(
OptionId.Edit, OptionId.Edit,
LocalStrings.current.postActionEdit, LocalStrings.current.postActionEdit,
) )
if (uiState.post.deleted) {
this +=
Option(
OptionId.Restore,
LocalStrings.current.actionRestore,
)
} else {
this += this +=
Option( Option(
OptionId.Delete, OptionId.Delete,
LocalStrings.current.commentActionDelete, LocalStrings.current.commentActionDelete,
) )
} }
}
if (uiState.isModerator) { if (uiState.isModerator) {
this += this +=
Option( Option(
@ -654,6 +662,10 @@ class PostDetailScreen(
} }
} }
OptionId.Restore -> {
model.reduce(PostDetailMviModel.Intent.RestorePost)
}
else -> Unit else -> Unit
} }
}, },
@ -1309,12 +1321,20 @@ class PostDetailScreen(
OptionId.Edit, OptionId.Edit,
LocalStrings.current.postActionEdit, LocalStrings.current.postActionEdit,
) )
if (comment.deleted) {
this +=
Option(
OptionId.Restore,
LocalStrings.current.actionRestore,
)
} else {
this += this +=
Option( Option(
OptionId.Delete, OptionId.Delete,
LocalStrings.current.commentActionDelete, LocalStrings.current.commentActionDelete,
) )
} }
}
if (uiState.isModerator) { if (uiState.isModerator) {
this += this +=
Option( Option(
@ -1508,6 +1528,14 @@ class PostDetailScreen(
} }
} }
OptionId.Restore -> {
model.reduce(
PostDetailMviModel.Intent.RestoreComment(
comment.id,
),
)
}
else -> Unit else -> Unit
} }
}, },
@ -1603,12 +1631,20 @@ class PostDetailScreen(
OptionId.Edit, OptionId.Edit,
LocalStrings.current.postActionEdit, LocalStrings.current.postActionEdit,
) )
if (comment.deleted) {
this +=
Option(
OptionId.Restore,
LocalStrings.current.actionRestore,
)
} else {
this += this +=
Option( Option(
OptionId.Delete, OptionId.Delete,
LocalStrings.current.commentActionDelete, LocalStrings.current.commentActionDelete,
) )
} }
}
if (uiState.isModerator) { if (uiState.isModerator) {
this += this +=
Option( Option(
@ -1725,6 +1761,14 @@ class PostDetailScreen(
} }
} }
OptionId.Restore -> {
model.reduce(
PostDetailMviModel.Intent.RestoreComment(
comment.id,
),
)
}
else -> Unit else -> Unit
} }
}, },

View File

@ -133,11 +133,6 @@ class PostDetailViewModel(
.onEach { _ -> .onEach { _ ->
emitEffect(PostDetailMviModel.Effect.Close) emitEffect(PostDetailMviModel.Effect.Close)
}.launchIn(this) }.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.CommentRemoved::class)
.onEach { evt ->
handleCommentDelete(evt.model.id)
}.launchIn(this)
notificationCenter notificationCenter
.subscribe(NotificationCenterEvent.ChangeCommentSortType::class) .subscribe(NotificationCenterEvent.ChangeCommentSortType::class)
.onEach { evt -> .onEach { evt ->
@ -426,6 +421,9 @@ class PostDetailViewModel(
is PostDetailMviModel.Intent.NavigateNextComment -> is PostDetailMviModel.Intent.NavigateNextComment ->
navigateToNextComment(intent.currentIndex) navigateToNextComment(intent.currentIndex)
PostDetailMviModel.Intent.RestorePost -> restorePost()
is PostDetailMviModel.Intent.RestoreComment -> restoreComment(intent.commentId)
} }
} }
@ -451,6 +449,7 @@ class PostDetailViewModel(
postId = uiState.value.post.id, postId = uiState.value.post.id,
sortType = uiState.value.sortType, sortType = uiState.value.sortType,
otherInstance = otherInstance, otherInstance = otherInstance,
includeDeleted = true,
), ),
) )
updateState { updateState {
@ -761,29 +760,27 @@ class PostDetailViewModel(
private fun deleteComment(id: Long) { private fun deleteComment(id: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
commentRepository.delete(id, auth) val newComment = commentRepository.delete(id, auth)
handleCommentDelete(id) if (newComment != null) {
handleCommentUpdate(newComment)
refreshPost() refreshPost()
} }
} }
private fun handleCommentDelete(id: Long) {
screenModelScope.launch {
updateState { it.copy(comments = it.comments.filter { comment -> comment.id != id }) }
}
} }
private fun deletePost() { private fun deletePost() {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
val postId = uiState.value.post.id val postId = uiState.value.post.id
postRepository.delete(id = postId, auth = auth) val newPost = postRepository.delete(id = postId, auth = auth)
if (newPost != null) {
notificationCenter.send( notificationCenter.send(
event = NotificationCenterEvent.PostDeleted(uiState.value.post), event = NotificationCenterEvent.PostUpdated(newPost),
) )
emitEffect(PostDetailMviModel.Effect.Close) emitEffect(PostDetailMviModel.Effect.Close)
} }
} }
}
private fun toggleExpanded(comment: CommentModel) { private fun toggleExpanded(comment: CommentModel) {
screenModelScope.launch(Dispatchers.Main) { screenModelScope.launch(Dispatchers.Main) {
@ -1007,4 +1004,32 @@ class PostDetailViewModel(
} }
} }
} }
private fun restorePost() {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
val newPost =
postRepository.restore(
id = uiState.value.post.id,
auth = auth,
)
if (newPost != null) {
handlePostUpdate(newPost)
}
}
}
private fun restoreComment(id: Long) {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
val newComment =
commentRepository.restore(
commentId = id,
auth = auth,
)
if (newComment != null) {
handleCommentUpdate(newComment)
}
}
}
} }

View File

@ -15,37 +15,60 @@ interface PostListMviModel :
MviModel<PostListMviModel.Intent, PostListMviModel.UiState, PostListMviModel.Effect>, MviModel<PostListMviModel.Intent, PostListMviModel.UiState, PostListMviModel.Effect>,
ScreenModel { ScreenModel {
sealed interface Intent { sealed interface Intent {
data class Refresh(val hardReset: Boolean = false) : Intent data class Refresh(
val hardReset: Boolean = false,
) : Intent
data object LoadNextPage : Intent data object LoadNextPage : Intent
data class ChangeListing(val value: ListingType) : Intent data class ChangeListing(
val value: ListingType,
) : Intent
data class UpVotePost(val id: Long, val feedback: Boolean = false) : Intent data class UpVotePost(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class DownVotePost(val id: Long, val feedback: Boolean = false) : Intent data class DownVotePost(
val id: Long,
val feedback: Boolean = false,
) : Intent
data class SavePost(val id: Long, val feedback: Boolean = false) : Intent data class SavePost(
val id: Long,
data class HandlePostUpdate(val post: PostModel) : Intent val feedback: Boolean = false,
) : Intent
data object HapticIndication : Intent data object HapticIndication : Intent
data class DeletePost(val id: Long) : Intent data class DeletePost(
val id: Long,
) : Intent
data class Share(val url: String) : Intent data class Share(
val url: String,
) : Intent
data class MarkAsRead(val id: Long) : Intent data class MarkAsRead(
val id: Long,
) : Intent
data class Hide(val id: Long) : Intent data class Hide(
val id: Long,
) : Intent
data object ClearRead : Intent data object ClearRead : Intent
data class StartZombieMode(val index: Int) : Intent data class StartZombieMode(
val index: Int,
) : Intent
data object PauseZombieMode : Intent data object PauseZombieMode : Intent
data class Copy(val value: String) : Intent data class Copy(
val value: String,
) : Intent
data object WillOpenDetail : Intent data object WillOpenDetail : Intent
} }
@ -83,8 +106,12 @@ interface PostListMviModel :
sealed interface Effect { sealed interface Effect {
data object BackToTop : Effect data object BackToTop : Effect
data class ZombieModeTick(val index: Int) : Effect data class ZombieModeTick(
val index: Int,
) : Effect
data class TriggerCopy(val text: String) : Effect data class TriggerCopy(
val text: String,
) : Effect
} }
} }

View File

@ -48,21 +48,23 @@ class PostListViewModel(
private val imagePreloadManager: ImagePreloadManager, private val imagePreloadManager: ImagePreloadManager,
private val getSortTypesUseCase: GetSortTypesUseCase, private val getSortTypesUseCase: GetSortTypesUseCase,
private val postNavigationManager: PostNavigationManager, private val postNavigationManager: PostNavigationManager,
) : PostListMviModel, ) : DefaultMviModel<PostListMviModel.Intent, PostListMviModel.UiState, PostListMviModel.Effect>(
DefaultMviModel<PostListMviModel.Intent, PostListMviModel.UiState, PostListMviModel.Effect>(
initialState = PostListMviModel.UiState(), initialState = PostListMviModel.UiState(),
) { ),
PostListMviModel {
private var hideReadPosts = false private var hideReadPosts = false
init { init {
screenModelScope.launch { screenModelScope.launch {
apiConfigurationRepository.instance.onEach { instance -> apiConfigurationRepository.instance
.onEach { instance ->
updateState { updateState {
it.copy(instance = instance) it.copy(instance = instance)
} }
}.launchIn(this) }.launchIn(this)
identityRepository.isLogged.onEach { logged -> identityRepository.isLogged
.onEach { logged ->
refreshUser() refreshUser()
updateState { updateState {
it.copy(isLogged = logged ?: false) it.copy(isLogged = logged ?: false)
@ -70,11 +72,13 @@ class PostListViewModel(
updateAvailableSortTypes() updateAvailableSortTypes()
}.launchIn(this) }.launchIn(this)
themeRepository.postLayout.onEach { layout -> themeRepository.postLayout
.onEach { layout ->
updateState { it.copy(postLayout = layout) } updateState { it.copy(postLayout = layout) }
}.launchIn(this) }.launchIn(this)
settingsRepository.currentSettings.onEach { settings -> settingsRepository.currentSettings
.onEach { settings ->
updateState { updateState {
it.copy( it.copy(
blurNsfw = settings.blurNsfw, blurNsfw = settings.blurNsfw,
@ -94,35 +98,43 @@ class PostListViewModel(
} }
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.PostUpdated::class) notificationCenter
.subscribe(NotificationCenterEvent.PostUpdated::class)
.onEach { evt -> .onEach { evt ->
if (evt.model.deleted) {
handlePostDelete(evt.model)
} else {
handlePostUpdate(evt.model) handlePostUpdate(evt.model)
}
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.PostDeleted::class) notificationCenter
.onEach { evt -> .subscribe(NotificationCenterEvent.ChangeFeedType::class)
handlePostDelete(evt.model.id)
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.ChangeFeedType::class)
.onEach { evt -> .onEach { evt ->
if (evt.screenKey == "postList") { if (evt.screenKey == "postList") {
applyListingType(evt.value) applyListingType(evt.value)
} }
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.ChangeSortType::class) notificationCenter
.subscribe(NotificationCenterEvent.ChangeSortType::class)
.onEach { evt -> .onEach { evt ->
if (evt.screenKey == "postList") { if (evt.screenKey == "postList") {
applySortType(evt.value) applySortType(evt.value)
} }
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.Logout::class).onEach { notificationCenter
.subscribe(NotificationCenterEvent.Logout::class)
.onEach {
handleLogout() handleLogout()
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.InstanceSelected::class).onEach { notificationCenter
.subscribe(NotificationCenterEvent.InstanceSelected::class)
.onEach {
refresh(initial = true) refresh(initial = true)
delay(100) delay(100)
emitEffect(PostListMviModel.Effect.BackToTop) emitEffect(PostListMviModel.Effect.BackToTop)
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.BlockActionSelected::class) notificationCenter
.subscribe(NotificationCenterEvent.BlockActionSelected::class)
.onEach { evt -> .onEach { evt ->
val userId = evt.userId val userId = evt.userId
val communityId = evt.communityId val communityId = evt.communityId
@ -133,17 +145,24 @@ class PostListViewModel(
instanceId != null -> blockInstance(instanceId) instanceId != null -> blockInstance(instanceId)
} }
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.Share::class).onEach { evt -> notificationCenter
.subscribe(NotificationCenterEvent.Share::class)
.onEach { evt ->
shareHelper.share(evt.url) shareHelper.share(evt.url)
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.ResetHome::class).onEach { notificationCenter
.subscribe(NotificationCenterEvent.ResetHome::class)
.onEach {
onFirstLoad() onFirstLoad()
}.launchIn(this) }.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.CopyText::class).onEach { notificationCenter
.subscribe(NotificationCenterEvent.CopyText::class)
.onEach {
emitEffect(PostListMviModel.Effect.TriggerCopy(it.value)) emitEffect(PostListMviModel.Effect.TriggerCopy(it.value))
}.launchIn(this) }.launchIn(this)
zombieModeHelper.index.onEach { index -> zombieModeHelper.index
.onEach { index ->
if (uiState.value.zombieModeActive) { if (uiState.value.zombieModeActive) {
emitEffect(PostListMviModel.Effect.ZombieModeTick(index)) emitEffect(PostListMviModel.Effect.ZombieModeTick(index))
} }
@ -231,8 +250,7 @@ class PostListViewModel(
} }
PostListMviModel.Intent.HapticIndication -> hapticFeedback.vibrate() PostListMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
is PostListMviModel.Intent.HandlePostUpdate -> handlePostUpdate(intent.post) is PostListMviModel.Intent.DeletePost -> deletePost(intent.id)
is PostListMviModel.Intent.DeletePost -> handlePostDelete(intent.id)
is PostListMviModel.Intent.Share -> { is PostListMviModel.Intent.Share -> {
shareHelper.share(intent.url) shareHelper.share(intent.url)
} }
@ -485,11 +503,21 @@ class PostListViewModel(
} }
} }
private fun handlePostDelete(id: Long) { private fun deletePost(id: Long) {
screenModelScope.launch { screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
postRepository.delete(id = id, auth = auth) val newPost = postRepository.delete(id = id, auth = auth)
handlePostDelete(id) if (newPost != null) {
handlePostDelete(newPost)
}
}
}
private fun handlePostDelete(post: PostModel) {
screenModelScope.launch {
updateState {
it.copy(posts = it.posts.filter { p -> p.id != post.id })
}
} }
} }