Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.wordpress.android.ui.postsrs

import org.wordpress.android.ui.postsrs.data.PostRsRestClient
import uniffi.wp_api.PostFormat
import uniffi.wp_api.PostStatus
import java.util.Date
Expand Down Expand Up @@ -51,6 +52,9 @@ data class PostRsSettingsUiState(
val editedFormat: PostFormat? = null,
val editedDate: Date? = null,
val editedAuthor: Long? = null,
val sitePostFormats: List<PostFormat> =
PostRsRestClient.DEFAULT_POST_FORMATS,
val isLoadingFormats: Boolean = false,
val isSaving: Boolean = false,
val dialogState: DialogState = DialogState.None,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ class PostRsSettingsViewModel @Inject constructor(
}

fun onFormatClicked() {
if (!hasFetchedFormats &&
!_uiState.value.isLoadingFormats
) {
resolveSitePostFormats()
}
_uiState.update {
it.copy(dialogState = DialogState.FormatDialog)
}
Expand Down Expand Up @@ -1111,6 +1116,38 @@ class PostRsSettingsViewModel @Inject constructor(
}
}

private var hasFetchedFormats = false

private fun resolveSitePostFormats() {
val site = site ?: return
viewModelScope.launch {
_uiState.update {
it.copy(isLoadingFormats = true)
}
@Suppress("TooGenericExceptionCaught")
try {
val formats =
withContext(Dispatchers.IO) {
restClient.fetchSitePostFormats(site)
}
_uiState.update {
it.copy(sitePostFormats = formats)
}
hasFetchedFormats = true
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
AppLog.w(
AppLog.T.POSTS,
"resolveSitePostFormats failed: ${e.message}"
)
}
_uiState.update {
it.copy(isLoadingFormats = false)
}
}
}

private fun formatDate(dateGmt: Date): String {
val fmt = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
Expand Down Expand Up @@ -1138,6 +1175,7 @@ class PostRsSettingsViewModel @Inject constructor(
editedFeaturedImageId = from.editedFeaturedImageId,
editedCategoryIds = from.editedCategoryIds,
editedTagIds = from.editedTagIds,
sitePostFormats = from.sitePostFormats,
)

private class PostApiRequestException(message: String) :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ import org.wordpress.android.util.SiteUtils
import rs.wordpress.api.kotlin.WpRequestResult
import uniffi.wp_api.AnyTermWithViewContext
import uniffi.wp_api.MediaListParams
import uniffi.wp_api.PostFormat
import uniffi.wp_api.TermCreateParams
import uniffi.wp_api.TermEndpointType
import uniffi.wp_api.TermListParams
import uniffi.wp_api.SparseThemeFieldWithViewContext
import uniffi.wp_api.SparseThemeWithViewContext
import uniffi.wp_api.ThemeListParams
import uniffi.wp_api.ThemeStatus
import uniffi.wp_api.ThemeSupports
import uniffi.wp_api.ThemeSupportsData
import uniffi.wp_api.UserListParams
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
Expand Down Expand Up @@ -44,7 +51,7 @@ class PostRsRestClient @Inject constructor(
/**
* Fetches media source URLs for the given [mediaIds] in a single
* network call using the `include` parameter, returning a map of
* media ID to Photon-optimised URL. IDs already in the local cache
* media ID to Photon-optimized URL. IDs already in the local cache
* are returned immediately without a network round-trip.
*
* @param widthDp target display width in dp for Photon resizing.
Expand Down Expand Up @@ -335,6 +342,64 @@ class PostRsRestClient @Inject constructor(
}
}

/**
* Fetches the post formats supported by the site's active
* theme. Returns [DEFAULT_POST_FORMATS] on failure or when
* the theme does not declare format support.
*/
suspend fun fetchSitePostFormats(
site: SiteModel,
): List<PostFormat> {
val client = wpApiClientProvider.getWpApiClient(site)
val response = client.request {
it.themes().filterListWithViewContext(
ThemeListParams(
status = ThemeStatus.Active
),
listOf(
SparseThemeFieldWithViewContext
.THEME_SUPPORTS
)
)
}
return when (response) {
is WpRequestResult.Success -> {
parsePostFormats(response.response.data)
?: DEFAULT_POST_FORMATS
}
else -> {
val msg =
(response
as? WpRequestResult.WpError<*>)
?.errorMessage
AppLog.w(
AppLog.T.POSTS,
"fetchSitePostFormats failed: $msg"
)
DEFAULT_POST_FORMATS
}
}
}

private fun parsePostFormats(
themes: List<SparseThemeWithViewContext>,
): List<PostFormat>? {
val slugs =
themes.firstOrNull()
?.themeSupports
?.get(ThemeSupports.Formats)
?.let { it as? ThemeSupportsData.VecString }
?.v1
?.takeIf { it.isNotEmpty() }
?: return null
return (listOf(PostFormat.Standard) +
slugs.map { slugToPostFormat(it) })
.distinct()
}

private fun slugToPostFormat(slug: String): PostFormat =
SLUG_TO_FORMAT[slug] ?: PostFormat.Custom(slug)

private fun toPhotonUrl(
site: SiteModel,
sourceUrl: String,
Expand Down Expand Up @@ -363,5 +428,21 @@ class PostRsRestClient @Inject constructor(
companion object {
internal const val AUTHORS_PER_PAGE: UInt = 20u
private const val PER_PAGE = 100u

private val SLUG_TO_FORMAT = mapOf(
"standard" to PostFormat.Standard,
"aside" to PostFormat.Aside,
"audio" to PostFormat.Audio,
"chat" to PostFormat.Chat,
"gallery" to PostFormat.Gallery,
"image" to PostFormat.Image,
"link" to PostFormat.Link,
"quote" to PostFormat.Quote,
"status" to PostFormat.Status,
"video" to PostFormat.Video,
)

val DEFAULT_POST_FORMATS =
SLUG_TO_FORMAT.values.toList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.key
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -1043,6 +1044,8 @@ private fun SettingsDialogs(
is DialogState.FormatDialog -> FormatDialog(
currentFormat = uiState.editedFormat
?: uiState.postFormat,
formats = uiState.sitePostFormats,
isLoading = uiState.isLoadingFormats,
onFormatSelected = onFormatSelected,
onDismiss = onDismissDialog,
)
Expand Down Expand Up @@ -1344,49 +1347,68 @@ private fun ExcerptDialog(
)
}

@Suppress("ForbiddenComment")
@Composable
private fun FormatDialog(
currentFormat: PostFormat?,
formats: List<PostFormat>,
isLoading: Boolean,
onFormatSelected: (PostFormat) -> Unit,
onDismiss: () -> Unit,
) {
// TODO: Replace hardcoded formats with site-supported
// formats once wordpress-rs exposes that API
val formats = listOf(
PostFormat.Standard,
PostFormat.Aside,
PostFormat.Audio,
PostFormat.Chat,
PostFormat.Gallery,
PostFormat.Image,
PostFormat.Link,
PostFormat.Quote,
PostFormat.Status,
PostFormat.Video,
)
if (isLoading) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
stringResource(
R.string
.post_rs_settings_format_dialog_title
)
)
},
text = {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
},
confirmButton = {},
)
return
}

val labels = formats.map { postFormatLabel(it) }
val currentIndex = formats.indexOfFirst {
it == currentFormat
}.coerceAtLeast(0)

val selectedIndex = rememberSaveable {
mutableIntStateOf(currentIndex)
}
key(formats) {
val selectedIndex = rememberSaveable {
mutableIntStateOf(currentIndex)
}

SingleChoiceAlertDialog(
title = stringResource(
R.string.post_rs_settings_format_dialog_title
),
options = labels,
selectedIndex = selectedIndex.intValue,
onOptionSelected = { selectedIndex.intValue = it },
onConfirm = {
onFormatSelected(formats[selectedIndex.intValue])
},
onDismiss = onDismiss,
confirmButtonText = stringResource(R.string.ok),
)
SingleChoiceAlertDialog(
title = stringResource(
R.string.post_rs_settings_format_dialog_title
),
options = labels,
selectedIndex = selectedIndex.intValue,
onOptionSelected = {
selectedIndex.intValue = it
},
onConfirm = {
onFormatSelected(
formats[selectedIndex.intValue]
)
},
onDismiss = onDismiss,
confirmButtonText = stringResource(
R.string.ok
),
)
}
}

@OptIn(ExperimentalMaterial3Api::class)
Expand Down
Loading