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.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
@ -95,17 +96,19 @@ fun CommentCard(
) {
Box(
modifier =
Modifier.onClick(
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {},
).padding(
start =
indentAmount.takeIf {
it > 0 && comment.depth > 0
}?.let {
(it * comment.depth).dp + Spacing.xxxs
} ?: 0.dp,
),
Modifier
.onClick(
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {},
).padding(
start =
indentAmount
.takeIf {
it > 0 && comment.depth > 0
}?.let {
(it * comment.depth).dp + Spacing.xxxs
} ?: 0.dp,
),
) {
Column(
modifier =
@ -147,6 +150,14 @@ fun CommentCard(
style = MaterialTheme.typography.bodyMedium,
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 {
CustomizedContent(ContentFontClass.Body) {
CompositionLocalProvider(

View File

@ -5,7 +5,9 @@ data class Option(
val text: String,
)
sealed class OptionId(val value: Int) {
sealed class OptionId(
val value: Int,
) {
data object Share : OptionId(0)
data object Hide : OptionId(1)
@ -65,4 +67,6 @@ sealed class OptionId(val value: Int) {
data object Unsubscribe : OptionId(28)
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.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
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.input.pointer.pointerInput
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 com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
@ -90,26 +92,25 @@ fun PostCard(
val markRead = post.read && fadeRead
Box(
modifier =
modifier.then(
if (postLayout == PostLayout.Card) {
Modifier
.padding(horizontal = Spacing.xs)
.shadow(
elevation = 5.dp,
shape = RoundedCornerShape(CornerSize.l),
)
.clip(RoundedCornerShape(CornerSize.l))
.background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp),
)
.padding(vertical = Spacing.s)
} else {
Modifier
},
).onClick(
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {},
),
modifier
.then(
if (postLayout == PostLayout.Card) {
Modifier
.padding(horizontal = Spacing.xs)
.shadow(
elevation = 5.dp,
shape = RoundedCornerShape(CornerSize.l),
).clip(RoundedCornerShape(CornerSize.l))
.background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp),
).padding(vertical = Spacing.s)
} else {
Modifier
},
).onClick(
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {},
),
) {
if (postLayout != PostLayout.Compact) {
ExtendedPost(
@ -219,7 +220,10 @@ private fun CompactPost(
val customTabsHelper = remember { getCustomTabsHelper() }
val navigationCoordinator = remember { getNavigationCoordinator() }
val postLinkUrl =
post.url.orEmpty().takeIf { !it.looksLikeAnImage && !it.looksLikeAVideo }.orEmpty()
post.url
.orEmpty()
.takeIf { !it.looksLikeAnImage && !it.looksLikeAVideo }
.orEmpty()
Column(
modifier =
@ -268,97 +272,106 @@ private fun CompactPost(
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.spacedBy(Spacing.xs),
) {
CustomizedContent(ContentFontClass.Title) {
PostCardTitle(
modifier = Modifier.weight(0.75f),
text = post.title,
autoLoadImages = autoLoadImages,
markRead = markRead,
onClick = onClick,
onOpenCommunity = onOpenCommunity,
onOpenUser = onOpenCreator,
onOpenPost = onOpenPost,
onOpenImage = onOpenImage,
onDoubleClick = onDoubleClick,
onOpenWeb = onOpenWeb,
onLongClick = {
optionsMenuOpen.value = true
},
)
}
if (post.videoUrl.isNotEmpty()) {
PostCardVideo(
modifier =
Modifier
.weight(0.25f)
.padding(vertical = Spacing.xxs),
url = post.videoUrl,
blurred = blurNsfw && post.nsfw,
autoLoadImages = autoLoadImages,
onOpen =
rememberCallback {
if (postLinkUrl.isNotEmpty()) {
navigationCoordinator.handleUrl(
url = postLinkUrl,
openingMode = settings.urlOpeningMode.toUrlOpeningMode(),
uriHandler = uriHandler,
customTabsHelper = customTabsHelper,
onOpenWeb = onOpenWeb,
onOpenCommunity = onOpenCommunity,
onOpenPost = onOpenPost,
onOpenUser = onOpenCreator,
)
} else {
onClick?.invoke()
}
},
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 {
PostCardImage(
modifier =
Modifier
.weight(0.25f)
.then(
if (fullHeightImage) {
Modifier
} else {
Modifier.aspectRatio(1f)
},
)
.padding(vertical = Spacing.xs)
.clip(RoundedCornerShape(CornerSize.s)),
minHeight = Dp.Unspecified,
maxHeight = Dp.Unspecified,
imageUrl = post.imageUrl,
autoLoadImages = autoLoadImages,
loadButtonContent = @Composable {
Icon(imageVector = Icons.Default.Download, contentDescription = null)
},
blurred = blurNsfw && post.nsfw,
onImageClick =
rememberCallbackArgs { url ->
if (postLinkUrl.isNotEmpty()) {
navigationCoordinator.handleUrl(
url = postLinkUrl,
openingMode = settings.urlOpeningMode.toUrlOpeningMode(),
uriHandler = uriHandler,
customTabsHelper = customTabsHelper,
onOpenWeb = onOpenWeb,
onOpenCommunity = onOpenCommunity,
onOpenPost = onOpenPost,
onOpenUser = onOpenCreator,
)
} else {
onOpenImage?.invoke(url)
}
},
onDoubleClick = onDoubleClick,
onLongClick =
rememberCallback {
CustomizedContent(ContentFontClass.Title) {
PostCardTitle(
modifier = Modifier.weight(0.75f),
text = post.title,
autoLoadImages = autoLoadImages,
markRead = markRead,
onClick = onClick,
onOpenCommunity = onOpenCommunity,
onOpenUser = onOpenCreator,
onOpenPost = onOpenPost,
onOpenImage = onOpenImage,
onDoubleClick = onDoubleClick,
onOpenWeb = onOpenWeb,
onLongClick = {
optionsMenuOpen.value = true
},
)
)
}
if (post.videoUrl.isNotEmpty()) {
PostCardVideo(
modifier =
Modifier
.weight(0.25f)
.padding(vertical = Spacing.xxs),
url = post.videoUrl,
blurred = blurNsfw && post.nsfw,
autoLoadImages = autoLoadImages,
onOpen =
rememberCallback {
if (postLinkUrl.isNotEmpty()) {
navigationCoordinator.handleUrl(
url = postLinkUrl,
openingMode = settings.urlOpeningMode.toUrlOpeningMode(),
uriHandler = uriHandler,
customTabsHelper = customTabsHelper,
onOpenWeb = onOpenWeb,
onOpenCommunity = onOpenCommunity,
onOpenPost = onOpenPost,
onOpenUser = onOpenCreator,
)
} else {
onClick?.invoke()
}
},
)
} else {
PostCardImage(
modifier =
Modifier
.weight(0.25f)
.then(
if (fullHeightImage) {
Modifier
} else {
Modifier.aspectRatio(1f)
},
).padding(vertical = Spacing.xs)
.clip(RoundedCornerShape(CornerSize.s)),
minHeight = Dp.Unspecified,
maxHeight = Dp.Unspecified,
imageUrl = post.imageUrl,
autoLoadImages = autoLoadImages,
loadButtonContent = @Composable {
Icon(imageVector = Icons.Default.Download, contentDescription = null)
},
blurred = blurNsfw && post.nsfw,
onImageClick =
rememberCallbackArgs { url ->
if (postLinkUrl.isNotEmpty()) {
navigationCoordinator.handleUrl(
url = postLinkUrl,
openingMode = settings.urlOpeningMode.toUrlOpeningMode(),
uriHandler = uriHandler,
customTabsHelper = customTabsHelper,
onOpenWeb = onOpenWeb,
onOpenCommunity = onOpenCommunity,
onOpenPost = onOpenPost,
onOpenUser = onOpenCreator,
)
} else {
onOpenImage?.invoke(url)
}
},
onDoubleClick = onDoubleClick,
onLongClick =
rememberCallback {
optionsMenuOpen.value = true
},
)
}
}
}
PostCardFooter(
@ -439,12 +452,14 @@ private fun ExtendedPost(
val navigationCoordinator = remember { getNavigationCoordinator() }
val optionsMenuOpen = remember { mutableStateOf(false) }
val postLinkUrl =
post.url.orEmpty().takeIf {
it != post.imageUrl &&
it != post.videoUrl &&
!it.looksLikeAnImage &&
!it.looksLikeAVideo
}.orEmpty()
post.url
.orEmpty()
.takeIf {
it != post.imageUrl &&
it != post.videoUrl &&
!it.looksLikeAnImage &&
!it.looksLikeAVideo
}.orEmpty()
Column(
modifier =
@ -487,85 +502,56 @@ private fun ExtendedPost(
optionsMenuOpen.value = true
},
)
CustomizedContent(ContentFontClass.Title) {
PostCardTitle(
if (post.deleted) {
Text(
modifier =
Modifier.padding(
vertical = Spacing.xs,
horizontal = Spacing.s,
Modifier.fillMaxWidth().padding(
all = Spacing.s,
),
text = post.title,
markRead = markRead,
bolder = showBody,
autoLoadImages = autoLoadImages,
onOpenCommunity = onOpenCommunity,
onOpenUser = onOpenCreator,
onOpenPost = onOpenPost,
onOpenWeb = onOpenWeb,
onClick = onClick,
onOpenImage = onOpenImage,
onDoubleClick = onDoubleClick,
onLongClick =
rememberCallback {
optionsMenuOpen.value = true
},
)
}
if (post.videoUrl.isNotEmpty()) {
PostCardVideo(
modifier =
Modifier
.padding(
vertical = Spacing.xxs,
horizontal = if (fullWidthImage) 0.dp else Spacing.s,
),
url = post.videoUrl,
blurred = blurNsfw && post.nsfw,
autoLoadImages = autoLoadImages,
backgroundColor = backgroundColor,
onOpen = {
if (postLinkUrl.isNotEmpty()) {
navigationCoordinator.handleUrl(
url = postLinkUrl,
openingMode = settings.urlOpeningMode.toUrlOpeningMode(),
uriHandler = uriHandler,
customTabsHelper = customTabsHelper,
onOpenWeb = onOpenWeb,
onOpenCommunity = onOpenCommunity,
onOpenPost = onOpenPost,
onOpenUser = onOpenCreator,
)
} else {
onClick?.invoke()
}
},
text = LocalStrings.current.messageContentDeleted,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha),
)
} else {
PostCardImage(
modifier =
Modifier
.padding(
CustomizedContent(ContentFontClass.Title) {
PostCardTitle(
modifier =
Modifier.padding(
vertical = Spacing.xs,
horizontal = if (fullWidthImage) 0.dp else Spacing.s,
)
.then(
if (roundedCornerImage && !fullWidthImage) {
Modifier.clip(RoundedCornerShape(CornerSize.xl))
} else {
Modifier
},
).then(
if (fullHeightImage) {
Modifier
} else {
Modifier.heightIn(max = 200.dp)
},
horizontal = Spacing.s,
),
imageUrl = post.imageUrl,
blurred = blurNsfw && post.nsfw,
onImageClick =
rememberCallbackArgs { url ->
text = post.title,
markRead = markRead,
bolder = showBody,
autoLoadImages = autoLoadImages,
onOpenCommunity = onOpenCommunity,
onOpenUser = onOpenCreator,
onOpenPost = onOpenPost,
onOpenWeb = onOpenWeb,
onClick = onClick,
onOpenImage = onOpenImage,
onDoubleClick = onDoubleClick,
onLongClick =
rememberCallback {
optionsMenuOpen.value = true
},
)
}
if (post.videoUrl.isNotEmpty()) {
PostCardVideo(
modifier =
Modifier
.padding(
vertical = Spacing.xxs,
horizontal = if (fullWidthImage) 0.dp else Spacing.s,
),
url = post.videoUrl,
blurred = blurNsfw && post.nsfw,
autoLoadImages = autoLoadImages,
backgroundColor = backgroundColor,
onOpen = {
if (postLinkUrl.isNotEmpty()) {
navigationCoordinator.handleUrl(
url = postLinkUrl,
@ -578,69 +564,35 @@ private fun ExtendedPost(
onOpenUser = onOpenCreator,
)
} else {
onOpenImage?.invoke(url)
onClick?.invoke()
}
},
onDoubleClick = onDoubleClick,
autoLoadImages = autoLoadImages,
onLongClick = {
optionsMenuOpen.value = true
},
)
}
if (showBody) {
if (post.removed) {
Text(
modifier = Modifier.padding(horizontal = Spacing.s),
text = LocalStrings.current.messageContentRemoved,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha),
)
} else {
CustomizedContent(ContentFontClass.Body) {
PostCardBody(
modifier =
Modifier.padding(
top = Spacing.xxs,
start = Spacing.s,
end = Spacing.s,
PostCardImage(
modifier =
Modifier
.padding(
vertical = Spacing.xs,
horizontal = if (fullWidthImage) 0.dp else Spacing.s,
).then(
if (roundedCornerImage && !fullWidthImage) {
Modifier.clip(RoundedCornerShape(CornerSize.xl))
} else {
Modifier
},
).then(
if (fullHeightImage) {
Modifier
} else {
Modifier.heightIn(max = 200.dp)
},
),
text = post.text,
maxLines =
if (limitBodyHeight) {
settings.postBodyMaxLines
} else {
null
},
autoLoadImages = autoLoadImages,
markRead = markRead,
onClick = onClick,
onOpenCommunity = onOpenCommunity,
onOpenUser = onOpenCreator,
onOpenPost = onOpenPost,
onOpenImage = onOpenImage,
onOpenWeb = onOpenWeb,
onDoubleClick = onDoubleClick,
onLongClick = {
optionsMenuOpen.value = true
},
)
}
}
}
if (postLinkUrl.isNotEmpty()) {
PostLinkBanner(
modifier =
Modifier
.padding(
top = Spacing.s,
bottom = Spacing.xxs,
start = Spacing.s,
end = Spacing.s,
)
.onClick(
onClick = {
imageUrl = post.imageUrl,
blurred = blurNsfw && post.nsfw,
onImageClick =
rememberCallbackArgs { url ->
if (postLinkUrl.isNotEmpty()) {
navigationCoordinator.handleUrl(
url = postLinkUrl,
openingMode = settings.urlOpeningMode.toUrlOpeningMode(),
@ -651,11 +603,85 @@ private fun ExtendedPost(
onOpenPost = onOpenPost,
onOpenUser = onOpenCreator,
)
} else {
onOpenImage?.invoke(url)
}
},
onDoubleClick = onDoubleClick,
autoLoadImages = autoLoadImages,
onLongClick = {
optionsMenuOpen.value = true
},
)
}
if (showBody) {
if (post.removed) {
Text(
modifier = Modifier.padding(horizontal = Spacing.s),
text = LocalStrings.current.messageContentRemoved,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = ancillaryTextAlpha),
)
} else {
CustomizedContent(ContentFontClass.Body) {
PostCardBody(
modifier =
Modifier.padding(
top = Spacing.xxs,
start = Spacing.s,
end = Spacing.s,
),
text = post.text,
maxLines =
if (limitBodyHeight) {
settings.postBodyMaxLines
} else {
null
},
autoLoadImages = autoLoadImages,
markRead = markRead,
onClick = onClick,
onOpenCommunity = onOpenCommunity,
onOpenUser = onOpenCreator,
onOpenPost = onOpenPost,
onOpenImage = onOpenImage,
onOpenWeb = onOpenWeb,
onDoubleClick = onDoubleClick,
onLongClick = {
optionsMenuOpen.value = true
},
onDoubleClick = onDoubleClick ?: {},
),
url = postLinkUrl,
)
)
}
}
}
if (postLinkUrl.isNotEmpty()) {
PostLinkBanner(
modifier =
Modifier
.padding(
top = Spacing.s,
bottom = Spacing.xxs,
start = Spacing.s,
end = Spacing.s,
).onClick(
onClick = {
navigationCoordinator.handleUrl(
url = postLinkUrl,
openingMode = settings.urlOpeningMode.toUrlOpeningMode(),
uriHandler = uriHandler,
customTabsHelper = customTabsHelper,
onOpenWeb = onOpenWeb,
onOpenCommunity = onOpenCommunity,
onOpenPost = onOpenPost,
onOpenUser = onOpenCreator,
)
},
onDoubleClick = onDoubleClick ?: {},
),
url = postLinkUrl,
)
}
}
PostCardFooter(
modifier =

View File

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

View File

@ -426,4 +426,6 @@ internal val BgStrings =
override val settingsMediaList = "Качване на мултимедия"
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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 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 settingsHiddenPosts = "Hidden posts"
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 settingsHiddenPosts = "Kaŝitaj afiŝoj"
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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsHiddenPosts = "Post nascosti"
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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"Ż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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsHiddenPosts = "Ukryte posty"
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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"Добавить/удалить избранное в панели навигации"
override val messageContentDeleted = "Вы удалили этот контент"
override val actionRestore = "Восстановить"
}

View File

@ -422,4 +422,6 @@ internal val SeStrings =
override val settingsMediaList = "Mediauppladdningar"
override val settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"Додајте/уклоните фаворите у фиоци за навигацију"
override val messageContentDeleted = "Избрисали сте овај садржај"
override val actionRestore = "Ресторе"
}

View File

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

View File

@ -418,4 +418,6 @@ internal val TokStrings =
override val settingsMediaList = "sitelen linluwi"
override val settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"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 settingsEnableToggleFavoriteInNavDrawer =
"Додати/видалити вибране в панелі навігації"
override val messageContentDeleted = "Ви видалили цей вміст"
override val actionRestore = "Відновлення"
}

View File

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

View File

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

View File

@ -27,37 +27,62 @@ sealed interface NotificationCenterEvent {
val value: SortType,
val defaultForCommunity: Boolean,
val screenKey: String?,
) :
NotificationCenterEvent
) : NotificationCenterEvent
data class ChangeCommentSortType(val value: SortType, val screenKey: String?) :
NotificationCenterEvent
data class ChangeCommentSortType(
val value: SortType,
val screenKey: String?,
) : NotificationCenterEvent
data class ChangeFeedType(val value: ListingType, val screenKey: String?) :
NotificationCenterEvent
data class ChangeFeedType(
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) :
NotificationCenterEvent
data class ChangeContentFontSize(
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
@ -65,35 +90,58 @@ sealed interface 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) : NotificationCenterEvent
data class MultiCommunityCreated(
val model: MultiCommunityModel,
) : 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) : NotificationCenterEvent
data class ChangeCommentBarTheme(
val value: CommentBarTheme,
) : NotificationCenterEvent
data class BlockActionSelected(
val userId: Long? = null,
@ -101,11 +149,17 @@ sealed interface NotificationCenterEvent {
val instanceId: Long? = null,
) : 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(
val value: ActionOnSwipe,
@ -113,11 +167,15 @@ sealed interface NotificationCenterEvent {
val target: ActionOnSwipeTarget,
) : NotificationCenterEvent
data class ChangeSystemBarTheme(val value: UiBarTheme) : NotificationCenterEvent
data class ChangeSystemBarTheme(
val value: UiBarTheme,
) : NotificationCenterEvent
data object DraftDeleted : NotificationCenterEvent
data class ModeratorZoneActionSelected(val value: Int) : NotificationCenterEvent
data class ModeratorZoneActionSelected(
val value: Int,
) : NotificationCenterEvent
data object ResetHome : NotificationCenterEvent
@ -125,22 +183,37 @@ sealed interface 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?) :
NotificationCenterEvent
data class ChangeSearchResultType(
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 {
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 {
data object ManageAccounts : ProfileSideMenuAction
@ -158,10 +231,13 @@ sealed interface NotificationCenterEvent {
data object Logout : ProfileSideMenuAction
}
data class ChangeUrlOpeningMode(val value: Int) : NotificationCenterEvent
data class ChangeUrlOpeningMode(
val value: Int,
) : NotificationCenterEvent
data class ChangeCommunityVisibility(val value: CommunityVisibilityType) :
NotificationCenterEvent
data class ChangeCommunityVisibility(
val value: CommunityVisibilityType,
) : NotificationCenterEvent
data object FavoritesUpdated : NotificationCenterEvent
}

View File

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

View File

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

View File

@ -38,9 +38,11 @@ internal class DefaultPostPaginationManager(
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
init {
notificationCenter.subscribe(NotificationCenterEvent.PostUpdated::class).onEach { evt ->
handlePostUpdate(evt.model)
}.launchIn(scope)
notificationCenter
.subscribe(NotificationCenterEvent.PostUpdated::class)
.onEach { evt ->
handlePostUpdate(evt.model)
}.launchIn(scope)
}
override fun reset(specification: PostPaginationSpecification?) {
@ -89,16 +91,17 @@ internal class DefaultPostPaginationManager(
val searching = !specification.query.isNullOrEmpty()
val (itemList, nextPage) =
if (searching) {
communityRepository.search(
auth = auth,
communityId = specification.id,
page = currentPage,
sortType = specification.sortType,
resultType = SearchResultType.Posts,
query = specification.query.orEmpty(),
).mapNotNull {
(it as? SearchResult.Post)?.model
} to null
communityRepository
.search(
auth = auth,
communityId = specification.id,
page = currentPage,
sortType = specification.sortType,
resultType = SearchResultType.Posts,
query = specification.query.orEmpty(),
).mapNotNull {
(it as? SearchResult.Post)?.model
} to null
} else {
postRepository.getAll(
auth = auth,
@ -121,7 +124,13 @@ internal class DefaultPostPaginationManager(
.orEmpty()
.deduplicate()
.filterNsfw(specification.includeNsfw)
.filterDeleted()
.let {
if (specification.includeDeleted) {
it
} else {
it.filterDeleted()
}
}
}
is PostPaginationSpecification.MultiCommunity -> {
@ -155,7 +164,13 @@ internal class DefaultPostPaginationManager(
.orEmpty()
.deduplicate()
.filterNsfw(specification.includeNsfw)
.filterDeleted()
.let {
if (specification.includeDeleted) {
it
} else {
it.filterDeleted()
}
}
}
is PostPaginationSpecification.Votes -> {

View File

@ -23,6 +23,7 @@ sealed interface PostPaginationSpecification {
val query: String? = null,
val sortType: SortType = SortType.Active,
val includeNsfw: Boolean = true,
val includeDeleted: Boolean = false,
) : PostPaginationSpecification
data class User(
@ -31,6 +32,7 @@ sealed interface PostPaginationSpecification {
val otherInstance: String? = null,
val sortType: SortType = SortType.New,
val includeNsfw: Boolean = true,
val includeDeleted: Boolean = false,
) : PostPaginationSpecification
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
fun whenReport_thenInteractionsAreAsExpected() =
runTest {

View File

@ -583,6 +583,35 @@ class DefaultPostRepositoryTest {
form =
withArg {
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(
commentId: Long,
auth: String,
)
): CommentModel?
suspend fun restore(
commentId: Long,
auth: String,
): CommentModel?
suspend fun report(
commentId: Long,

View File

@ -320,9 +320,24 @@ internal class DefaultCommentRepository(
commentId = commentId,
deleted = true,
)
services.comment.delete(authHeader = auth.toAuthHeader(), form = data)
Unit
}.getOrDefault(Unit)
val res = services.comment.delete(authHeader = auth.toAuthHeader(), form = data)
res.commentView?.toModel()
}.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(

View File

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

View File

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

View File

@ -27,11 +27,20 @@ interface CommunityDetailMviModel :
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
@ -39,11 +48,17 @@ interface CommunityDetailMviModel :
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
@ -51,35 +66,59 @@ interface CommunityDetailMviModel :
data object ClearRead : Intent
data class StartZombieMode(val index: Int) : Intent
data class StartZombieMode(
val index: Int,
) : 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 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 UnhideCommunity : Intent
data class SelectPreferredLanguage(val languageId: Long?) : Intent
data class SelectPreferredLanguage(
val languageId: Long?,
) : Intent
data object DeleteCommunity : Intent
data class RestorePost(
val id: Long,
) : Intent
}
data class UiState(
@ -123,15 +162,23 @@ interface CommunityDetailMviModel :
sealed interface 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 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
}

View File

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

View File

@ -157,15 +157,6 @@ class CommunityDetailViewModel(
)
}
}.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
notificationCenter
.subscribe(NotificationCenterEvent.ChangeSortType::class)
@ -374,6 +365,10 @@ class CommunityDetailViewModel(
CommunityDetailMviModel.Intent.DeleteCommunity -> {
deleteCommunity()
}
is CommunityDetailMviModel.Intent.RestorePost -> {
restorePost(intent.id)
}
}
}
@ -389,6 +384,7 @@ class CommunityDetailViewModel(
otherInstance = otherInstance,
query = currentState.searchText.takeIf { currentState.searching },
includeNsfw = settingsRepository.currentSettings.value.includeNsfw,
includeDeleted = true,
),
)
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>,
ScreenModel {
sealed interface Intent {
data class ChangeSection(val section: ProfileLoggedSection) : Intent
data class ChangeSection(
val section: ProfileLoggedSection,
) : Intent
data object Refresh : 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 class RestorePost(
val id: Long,
) : Intent
data class RestoreComment(
val id: Long,
) : Intent
}
data class UiState(

View File

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

View File

@ -48,57 +48,65 @@ class ProfileLoggedViewModel(
private val notificationCenter: NotificationCenter,
private val hapticFeedback: HapticFeedback,
private val postNavigationManager: PostNavigationManager,
) : ProfileLoggedMviModel,
DefaultMviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect>(
) : DefaultMviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect>(
initialState = ProfileLoggedMviModel.UiState(),
) {
),
ProfileLoggedMviModel {
init {
screenModelScope.launch {
updateState { it.copy(instance = apiConfigurationRepository.instance.value) }
themeRepository.postLayout.onEach { layout ->
updateState { it.copy(postLayout = layout) }
}.launchIn(this)
themeRepository.postLayout
.onEach { layout ->
updateState { it.copy(postLayout = layout) }
}.launchIn(this)
@OptIn(FlowPreview::class)
identityRepository.isLogged.drop(1).debounce(500).onEach { logged ->
if (logged == true) {
identityRepository.isLogged
.drop(1)
.debounce(500)
.onEach { logged ->
if (logged == true) {
updateState {
it.copy(
posts = emptyList(),
comments = emptyList(),
)
}
refreshUser()
refresh()
}
}.launchIn(this)
settingsRepository.currentSettings
.onEach { settings ->
updateState {
it.copy(
posts = emptyList(),
comments = emptyList(),
voteFormat = settings.voteFormat,
autoLoadImages = settings.autoLoadImages,
preferNicknames = settings.preferUserNicknames,
fullHeightImages = settings.fullHeightImages,
fullWidthImages = settings.fullWidthImages,
showScores = settings.showScores,
showUnreadComments = settings.showUnreadComments,
)
}
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.PostUpdated::class)
.onEach { evt ->
handlePostUpdate(evt.model)
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.Share::class)
.onEach { evt ->
shareHelper.share(evt.url)
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.Logout::class)
.onEach {
delay(250)
refreshUser()
refresh()
}
}.launchIn(this)
settingsRepository.currentSettings.onEach { settings ->
updateState {
it.copy(
voteFormat = settings.voteFormat,
autoLoadImages = settings.autoLoadImages,
preferNicknames = settings.preferUserNicknames,
fullHeightImages = settings.fullHeightImages,
fullWidthImages = settings.fullWidthImages,
showScores = settings.showScores,
showUnreadComments = settings.showUnreadComments,
)
}
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.PostUpdated::class).onEach { evt ->
handlePostUpdate(evt.model)
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.PostDeleted::class).onEach { evt ->
handlePostDelete(evt.model.id)
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.Share::class).onEach { evt ->
shareHelper.share(evt.url)
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.Logout::class).onEach {
delay(250)
refreshUser()
}.launchIn(this)
}.launchIn(this)
if (uiState.value.initial) {
val userFromCache = identityRepository.cachedUser
@ -195,6 +203,14 @@ class ProfileLoggedViewModel(
val state = postPaginationManager.extractState()
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(
id = userId,
sortType = SortType.New,
includeDeleted = true,
),
)
commentPaginationManager.reset(
CommentPaginationSpecification.User(
id = userId,
sortType = SortType.New,
includeDeleted = true,
),
)
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) {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
postRepository.delete(id = id, auth = auth)
handlePostDelete(id)
val newPost = postRepository.delete(id = id, auth = auth)
if (newPost != null) {
handlePostUpdate(newPost)
}
}
}
private fun deleteComment(id: Long) {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
commentRepository.delete(id, auth)
refresh()
val newComment = commentRepository.delete(id, auth)
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 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
@ -50,25 +72,47 @@ interface PostDetailMviModel :
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 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(
@ -109,10 +153,14 @@ interface PostDetailMviModel :
sealed interface Effect {
data object Close : Effect
data class ScrollToComment(val index: Int) : Effect
data class ScrollToComment(
val index: Int,
) : Effect
data object BackToTop : Effect
data class TriggerCopy(val text: String) : Effect
data class TriggerCopy(
val text: String,
) : Effect
}
}

View File

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

View File

@ -133,11 +133,6 @@ class PostDetailViewModel(
.onEach { _ ->
emitEffect(PostDetailMviModel.Effect.Close)
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.CommentRemoved::class)
.onEach { evt ->
handleCommentDelete(evt.model.id)
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.ChangeCommentSortType::class)
.onEach { evt ->
@ -426,6 +421,9 @@ class PostDetailViewModel(
is PostDetailMviModel.Intent.NavigateNextComment ->
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,
sortType = uiState.value.sortType,
otherInstance = otherInstance,
includeDeleted = true,
),
)
updateState {
@ -761,15 +760,11 @@ class PostDetailViewModel(
private fun deleteComment(id: Long) {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
commentRepository.delete(id, auth)
handleCommentDelete(id)
refreshPost()
}
}
private fun handleCommentDelete(id: Long) {
screenModelScope.launch {
updateState { it.copy(comments = it.comments.filter { comment -> comment.id != id }) }
val newComment = commentRepository.delete(id, auth)
if (newComment != null) {
handleCommentUpdate(newComment)
refreshPost()
}
}
}
@ -777,11 +772,13 @@ class PostDetailViewModel(
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
val postId = uiState.value.post.id
postRepository.delete(id = postId, auth = auth)
notificationCenter.send(
event = NotificationCenterEvent.PostDeleted(uiState.value.post),
)
emitEffect(PostDetailMviModel.Effect.Close)
val newPost = postRepository.delete(id = postId, auth = auth)
if (newPost != null) {
notificationCenter.send(
event = NotificationCenterEvent.PostUpdated(newPost),
)
emitEffect(PostDetailMviModel.Effect.Close)
}
}
}
@ -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>,
ScreenModel {
sealed interface Intent {
data class Refresh(val hardReset: Boolean = false) : Intent
data class Refresh(
val hardReset: Boolean = false,
) : 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 HandlePostUpdate(val post: PostModel) : Intent
data class SavePost(
val id: Long,
val feedback: Boolean = false,
) : 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 class StartZombieMode(val index: Int) : Intent
data class StartZombieMode(
val index: Int,
) : Intent
data object PauseZombieMode : Intent
data class Copy(val value: String) : Intent
data class Copy(
val value: String,
) : Intent
data object WillOpenDetail : Intent
}
@ -83,8 +106,12 @@ interface PostListMviModel :
sealed interface 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,81 +48,93 @@ class PostListViewModel(
private val imagePreloadManager: ImagePreloadManager,
private val getSortTypesUseCase: GetSortTypesUseCase,
private val postNavigationManager: PostNavigationManager,
) : PostListMviModel,
DefaultMviModel<PostListMviModel.Intent, PostListMviModel.UiState, PostListMviModel.Effect>(
) : DefaultMviModel<PostListMviModel.Intent, PostListMviModel.UiState, PostListMviModel.Effect>(
initialState = PostListMviModel.UiState(),
) {
),
PostListMviModel {
private var hideReadPosts = false
init {
screenModelScope.launch {
apiConfigurationRepository.instance.onEach { instance ->
updateState {
it.copy(instance = instance)
}
}.launchIn(this)
identityRepository.isLogged.onEach { logged ->
refreshUser()
updateState {
it.copy(isLogged = logged ?: false)
}
updateAvailableSortTypes()
}.launchIn(this)
themeRepository.postLayout.onEach { layout ->
updateState { it.copy(postLayout = layout) }
}.launchIn(this)
settingsRepository.currentSettings.onEach { settings ->
updateState {
it.copy(
blurNsfw = settings.blurNsfw,
swipeActionsEnabled = settings.enableSwipeActions,
doubleTapActionEnabled = settings.enableDoubleTapAction,
voteFormat = settings.voteFormat,
autoLoadImages = settings.autoLoadImages,
preferNicknames = settings.preferUserNicknames,
fullHeightImages = settings.fullHeightImages,
fullWidthImages = settings.fullWidthImages,
actionsOnSwipeToStartPosts = settings.actionsOnSwipeToStartPosts,
actionsOnSwipeToEndPosts = settings.actionsOnSwipeToEndPosts,
showScores = settings.showScores,
fadeReadPosts = settings.fadeReadPosts,
showUnreadComments = settings.showUnreadComments,
)
}
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.PostUpdated::class)
.onEach { evt ->
handlePostUpdate(evt.model)
apiConfigurationRepository.instance
.onEach { instance ->
updateState {
it.copy(instance = instance)
}
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.PostDeleted::class)
.onEach { evt ->
handlePostDelete(evt.model.id)
identityRepository.isLogged
.onEach { logged ->
refreshUser()
updateState {
it.copy(isLogged = logged ?: false)
}
updateAvailableSortTypes()
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.ChangeFeedType::class)
themeRepository.postLayout
.onEach { layout ->
updateState { it.copy(postLayout = layout) }
}.launchIn(this)
settingsRepository.currentSettings
.onEach { settings ->
updateState {
it.copy(
blurNsfw = settings.blurNsfw,
swipeActionsEnabled = settings.enableSwipeActions,
doubleTapActionEnabled = settings.enableDoubleTapAction,
voteFormat = settings.voteFormat,
autoLoadImages = settings.autoLoadImages,
preferNicknames = settings.preferUserNicknames,
fullHeightImages = settings.fullHeightImages,
fullWidthImages = settings.fullWidthImages,
actionsOnSwipeToStartPosts = settings.actionsOnSwipeToStartPosts,
actionsOnSwipeToEndPosts = settings.actionsOnSwipeToEndPosts,
showScores = settings.showScores,
fadeReadPosts = settings.fadeReadPosts,
showUnreadComments = settings.showUnreadComments,
)
}
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.PostUpdated::class)
.onEach { evt ->
if (evt.model.deleted) {
handlePostDelete(evt.model)
} else {
handlePostUpdate(evt.model)
}
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.ChangeFeedType::class)
.onEach { evt ->
if (evt.screenKey == "postList") {
applyListingType(evt.value)
}
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.ChangeSortType::class)
notificationCenter
.subscribe(NotificationCenterEvent.ChangeSortType::class)
.onEach { evt ->
if (evt.screenKey == "postList") {
applySortType(evt.value)
}
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.Logout::class).onEach {
handleLogout()
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.InstanceSelected::class).onEach {
refresh(initial = true)
delay(100)
emitEffect(PostListMviModel.Effect.BackToTop)
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.BlockActionSelected::class)
notificationCenter
.subscribe(NotificationCenterEvent.Logout::class)
.onEach {
handleLogout()
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.InstanceSelected::class)
.onEach {
refresh(initial = true)
delay(100)
emitEffect(PostListMviModel.Effect.BackToTop)
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.BlockActionSelected::class)
.onEach { evt ->
val userId = evt.userId
val communityId = evt.communityId
@ -133,21 +145,28 @@ class PostListViewModel(
instanceId != null -> blockInstance(instanceId)
}
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.Share::class).onEach { evt ->
shareHelper.share(evt.url)
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.ResetHome::class).onEach {
onFirstLoad()
}.launchIn(this)
notificationCenter.subscribe(NotificationCenterEvent.CopyText::class).onEach {
emitEffect(PostListMviModel.Effect.TriggerCopy(it.value))
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.Share::class)
.onEach { evt ->
shareHelper.share(evt.url)
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.ResetHome::class)
.onEach {
onFirstLoad()
}.launchIn(this)
notificationCenter
.subscribe(NotificationCenterEvent.CopyText::class)
.onEach {
emitEffect(PostListMviModel.Effect.TriggerCopy(it.value))
}.launchIn(this)
zombieModeHelper.index.onEach { index ->
if (uiState.value.zombieModeActive) {
emitEffect(PostListMviModel.Effect.ZombieModeTick(index))
}
}.launchIn(this)
zombieModeHelper.index
.onEach { index ->
if (uiState.value.zombieModeActive) {
emitEffect(PostListMviModel.Effect.ZombieModeTick(index))
}
}.launchIn(this)
}
if (uiState.value.initial) {
@ -231,8 +250,7 @@ class PostListViewModel(
}
PostListMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
is PostListMviModel.Intent.HandlePostUpdate -> handlePostUpdate(intent.post)
is PostListMviModel.Intent.DeletePost -> handlePostDelete(intent.id)
is PostListMviModel.Intent.DeletePost -> deletePost(intent.id)
is PostListMviModel.Intent.Share -> {
shareHelper.share(intent.url)
}
@ -485,11 +503,21 @@ class PostListViewModel(
}
}
private fun handlePostDelete(id: Long) {
private fun deletePost(id: Long) {
screenModelScope.launch {
val auth = identityRepository.authToken.value.orEmpty()
postRepository.delete(id = id, auth = auth)
handlePostDelete(id)
val newPost = postRepository.delete(id = id, auth = auth)
if (newPost != null) {
handlePostDelete(newPost)
}
}
}
private fun handlePostDelete(post: PostModel) {
screenModelScope.launch {
updateState {
it.copy(posts = it.posts.filter { p -> p.id != post.id })
}
}
}