Initial Compose ReaderScreen (#799)
* Initial Compose ReaderScreen * Refine * Naming --------- Co-authored-by: GG Bond <whatever@tagkong.com>
This commit is contained in:
1 parent
2ff9df4c07
commit
ff4f76dfa5
13 files changed
+199
-28
No files matched your search
@@ -184,6 +184,7 @@ object Settings : DataStorePreferences(null) {
|
||||
val readingMode = intPref("pref_default_reading_mode_key", ReadingModeType.LEFT_TO_RIGHT.flagValue)
|
||||
val orientationMode = intPref("pref_default_orientation_type_key", OrientationType.FREE.flagValue)
|
||||
val showReaderSeekbar = boolPref("pref_show_reader_seekbar", true)
|
||||
var newReader by boolPref("new_compose_reader", false)
|
||||
|
||||
init {
|
||||
if ("CN" == Locale.getDefault().country) {
|
||||
|
||||
@@ -94,7 +94,7 @@ class Image private constructor(image: CoilImage, private val src: AutoCloseable
|
||||
size(targetWidth, targetHeight)
|
||||
scale(Scale.FILL)
|
||||
precision(Precision.INEXACT)
|
||||
if (isAtLeastO) {
|
||||
if (isAtLeastO && !Settings.newReader) {
|
||||
colorSpace(colorSpace)
|
||||
}
|
||||
maybeCropBorder(Settings.cropBorder.value)
|
||||
|
||||
@@ -53,6 +53,7 @@ import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadService
|
||||
import com.hippo.ehviewer.download.downloadDir
|
||||
import com.hippo.ehviewer.download.downloadLocation
|
||||
import com.hippo.ehviewer.ui.destinations.ReaderScreenDestination
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.LabeledCheckbox
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
@@ -62,6 +63,7 @@ import com.hippo.ehviewer.util.mapToLongArray
|
||||
import com.hippo.ehviewer.util.requestPermission
|
||||
import com.hippo.ehviewer.util.toEpochMillis
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
@@ -270,15 +272,24 @@ suspend fun removeFromFavorites(galleryInfo: BaseGalleryInfo) = doModifyFavorite
|
||||
localFavorited = EhDB.containLocalFavorites(galleryInfo.gid),
|
||||
)
|
||||
|
||||
fun Context.navToReader(info: BaseGalleryInfo, page: Int = -1) {
|
||||
val intent = Intent(this, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_EH
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, info)
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, page)
|
||||
startActivity(intent)
|
||||
context (Context, DestinationsNavigator)
|
||||
fun navToReader(
|
||||
info: BaseGalleryInfo,
|
||||
page: Int = -1,
|
||||
) {
|
||||
if (Settings.newReader) {
|
||||
navigate(ReaderScreenDestination(info, page))
|
||||
} else {
|
||||
val intent = Intent(this@Context, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_EH
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, info)
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, page)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun DialogState.doGalleryInfoAction(info: BaseGalleryInfo, context: Context) {
|
||||
context(DialogState, Context, DestinationsNavigator)
|
||||
suspend fun doGalleryInfoAction(info: BaseGalleryInfo) {
|
||||
val downloaded = DownloadManager.getDownloadState(info.gid) != DownloadInfo.STATE_INVALID
|
||||
val favorited = info.favoriteSlot != NOT_FAVORITED
|
||||
val items = buildList {
|
||||
@@ -300,14 +311,14 @@ suspend fun DialogState.doGalleryInfoAction(info: BaseGalleryInfo, context: Cont
|
||||
}
|
||||
}
|
||||
val selected = showSelectItemWithIcon(items, EhUtils.getSuitableTitle(info))
|
||||
with(context.findActivity<MainActivity>()) {
|
||||
with(findActivity<MainActivity>()) {
|
||||
when (selected) {
|
||||
0 -> navToReader(info)
|
||||
1 -> withUIContext {
|
||||
if (downloaded) {
|
||||
confirmRemoveDownload(info)
|
||||
} else {
|
||||
startDownload(context, false, info)
|
||||
startDownload(this@with, false, info)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
|
||||
import kotlin.contracts.contract
|
||||
|
||||
// Fuck U, Kotlin compiler!!!
|
||||
@Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS")
|
||||
inline fun <T1, T2, R> with(
|
||||
t1: T1,
|
||||
t2: T2,
|
||||
block: context(T1, T2)
|
||||
() -> R,
|
||||
): R {
|
||||
contract {
|
||||
callsInPlace(block, EXACTLY_ONCE)
|
||||
}
|
||||
return block(t1, t2)
|
||||
}
|
||||
|
||||
// Fuck U, Kotlin compiler!!!
|
||||
@Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS")
|
||||
inline fun <T1, T2, T3, R> with(
|
||||
t1: T1,
|
||||
t2: T2,
|
||||
t3: T3,
|
||||
block: context(T1, T2, T3)
|
||||
() -> R,
|
||||
): R {
|
||||
contract {
|
||||
callsInPlace(block, EXACTLY_ONCE)
|
||||
}
|
||||
return block(t1, t2, t3)
|
||||
}
|
||||
@@ -19,11 +19,11 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
private val intent = CustomTabsIntent.Builder().apply { setShowTitle(true) }.build()
|
||||
|
||||
@@ -36,8 +36,11 @@ fun Context.openBrowser(url: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun Context.jumpToReaderByPage(url: String, detail: GalleryDetail): Boolean {
|
||||
context(Context, DestinationsNavigator)
|
||||
fun Context.jumpToReaderByPage(
|
||||
url: String,
|
||||
detail: GalleryDetail,
|
||||
): Boolean {
|
||||
GalleryPageUrlParser.parse(url)?.let {
|
||||
if (it.gid == detail.gid) {
|
||||
navToReader(detail.galleryInfo, it.page)
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.hippo.ehviewer.ui.reader
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import coil3.BitmapImage
|
||||
import coil3.Image as CoilImage
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.archiveFile
|
||||
import com.hippo.ehviewer.gallery.ArchivePageLoader
|
||||
import com.hippo.ehviewer.gallery.EhPageLoader
|
||||
import com.hippo.ehviewer.ui.LockDrawer
|
||||
import com.hippo.ehviewer.ui.tools.Deferred
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import eu.kanade.tachiyomi.source.model.Page.State.DOWNLOAD_IMAGE
|
||||
import eu.kanade.tachiyomi.source.model.Page.State.ERROR
|
||||
import eu.kanade.tachiyomi.source.model.Page.State.LOAD_PAGE
|
||||
import eu.kanade.tachiyomi.source.model.Page.State.QUEUE
|
||||
import eu.kanade.tachiyomi.source.model.Page.State.READY
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1) {
|
||||
LockDrawer(true)
|
||||
val pageLoader = remember {
|
||||
val archive = DownloadManager.getDownloadInfo(info.gid)?.archiveFile
|
||||
archive?.let { ArchivePageLoader(it, info.gid, page) } ?: EhPageLoader(info, page)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
pageLoader.start()
|
||||
onDispose {
|
||||
pageLoader.stop()
|
||||
}
|
||||
}
|
||||
Deferred({ pageLoader.awaitReady() }) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
items(pageLoader.mPages) { page ->
|
||||
pageLoader.request(page.index)
|
||||
val state by page.status.collectAsState()
|
||||
when (state) {
|
||||
QUEUE, LOAD_PAGE -> {
|
||||
Box(modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT)) {
|
||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
}
|
||||
DOWNLOAD_IMAGE -> {
|
||||
Box(modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT)) {
|
||||
val progress by page.progressFlow.collectAsState()
|
||||
CircularProgressIndicator(
|
||||
progress = { progress / 100f },
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
}
|
||||
READY -> {
|
||||
val image = page.image!!.innerImage!!
|
||||
val painter = remember(image) { image.toPainter() }
|
||||
Image(
|
||||
painter = painter,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
)
|
||||
}
|
||||
ERROR -> {
|
||||
Box(modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT)) {
|
||||
Column(
|
||||
modifier = Modifier.align(Companion.Center),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(text = page.errorMsg.orEmpty())
|
||||
Button(onClick = { }) {
|
||||
Text(text = stringResource(id = R.string.action_retry))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoilImage.toPainter() = when (this) {
|
||||
is BitmapImage -> BitmapPainter(image = bitmap.asImageBitmap())
|
||||
else -> TODO()
|
||||
}
|
||||
|
||||
private const val DEFAULT_ASPECT = 1 / 1.4125f
|
||||
@@ -110,6 +110,7 @@ import com.hippo.ehviewer.ui.tools.SwipeToDismissBox2
|
||||
import com.hippo.ehviewer.ui.tools.delegateSnapshotUpdate
|
||||
import com.hippo.ehviewer.ui.tools.draggingHapticFeedback
|
||||
import com.hippo.ehviewer.ui.tools.rememberInVM
|
||||
import com.hippo.ehviewer.ui.with
|
||||
import com.hippo.ehviewer.util.findActivity
|
||||
import com.hippo.ehviewer.util.mapToLongArray
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -466,7 +467,7 @@ fun DownloadsScreen(navigator: DestinationsNavigator) {
|
||||
coroutineScope.launchIO {
|
||||
EhDB.putHistoryInfo(info.galleryInfo)
|
||||
}
|
||||
context.navToReader(info.galleryInfo)
|
||||
with(context, navigator) { navToReader(info.galleryInfo) }
|
||||
}
|
||||
Crossfade(targetState = gridView, label = "Downloads") { showGridView ->
|
||||
if (showGridView) {
|
||||
@@ -580,7 +581,7 @@ fun DownloadsScreen(navigator: DestinationsNavigator) {
|
||||
) {
|
||||
if (!selectMode) {
|
||||
onClick(Icons.Default.Shuffle) {
|
||||
context.navToReader(list.random().galleryInfo)
|
||||
with(context, navigator) { navToReader(list.random().galleryInfo) }
|
||||
}
|
||||
onClick(Icons.AutoMirrored.Default.Sort) {
|
||||
val selected = dialogState.showSingleChoice(
|
||||
|
||||
@@ -97,6 +97,7 @@ import com.hippo.ehviewer.ui.tools.snackBarPadding
|
||||
import com.hippo.ehviewer.ui.tools.toAnnotatedString
|
||||
import com.hippo.ehviewer.ui.tools.toBBCode
|
||||
import com.hippo.ehviewer.ui.tools.updateSpan
|
||||
import com.hippo.ehviewer.ui.with
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
import com.hippo.ehviewer.util.TextUrl
|
||||
import com.hippo.ehviewer.util.addTextToClipboard
|
||||
@@ -384,7 +385,7 @@ fun GalleryCommentsScreen(gid: Long, navigator: DestinationsNavigator) {
|
||||
}
|
||||
},
|
||||
onUrlClick = {
|
||||
if (!activity.jumpToReaderByPage(it, galleryDetail)) {
|
||||
if (!with(activity, navigator) { jumpToReaderByPage(it, galleryDetail) }) {
|
||||
if (!navigator.navWithUrl(it)) {
|
||||
activity.openBrowser(it)
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ import com.hippo.ehviewer.ui.tools.GalleryRatingBar
|
||||
import com.hippo.ehviewer.ui.tools.LocalDialogState
|
||||
import com.hippo.ehviewer.ui.tools.rememberInVM
|
||||
import com.hippo.ehviewer.ui.tools.rememberLambda
|
||||
import com.hippo.ehviewer.ui.with
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.AppHelper
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
@@ -243,7 +244,7 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
|
||||
true,
|
||||
)
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
context.navToReader(gi.findBaseInfo(), page)
|
||||
with(context, navigator) { navToReader(gi.findBaseInfo(), page) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,7 +408,7 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
|
||||
EhPreviewItem(
|
||||
galleryPreview = it,
|
||||
position = it.position,
|
||||
onClick = { context.navToReader(gd.galleryInfo, it.position) },
|
||||
onClick = { with(context, navigator) { navToReader(gd.galleryInfo, it.position) } },
|
||||
)
|
||||
}
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
@@ -460,9 +461,11 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
|
||||
onCardClick = ::onNavigateToCommentScene,
|
||||
onUserClick = ::onNavigateToCommentScene,
|
||||
onUrlClick = {
|
||||
if (!activity.jumpToReaderByPage(it, galleryDetail)) {
|
||||
if (!navigator.navWithUrl(it)) {
|
||||
activity.openBrowser(it)
|
||||
with(activity, navigator) {
|
||||
if (!jumpToReaderByPage(it, galleryDetail)) {
|
||||
if (!navWithUrl(it)) {
|
||||
openBrowser(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -811,7 +814,7 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
|
||||
DownloadInfo.STATE_FAILED -> stringResource(R.string.download_state_failed)
|
||||
else -> error("Invalid DownloadState!!!")
|
||||
}
|
||||
fun onReadButtonClick() = context.navToReader(galleryInfo.findBaseInfo(), startPage)
|
||||
fun onReadButtonClick() = with(context, navigator) { navToReader(galleryInfo.findBaseInfo(), startPage) }
|
||||
fun onCategoryChipClick() {
|
||||
val category = galleryInfo.category
|
||||
if (category == EhUtils.NONE || category == EhUtils.PRIVATE || category == EhUtils.UNKNOWN) {
|
||||
|
||||
@@ -132,6 +132,7 @@ import com.hippo.ehviewer.ui.tools.draggingHapticFeedback
|
||||
import com.hippo.ehviewer.ui.tools.foldToLoadResult
|
||||
import com.hippo.ehviewer.ui.tools.observed
|
||||
import com.hippo.ehviewer.ui.tools.rememberInVM
|
||||
import com.hippo.ehviewer.ui.with
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.util.findActivity
|
||||
import com.hippo.ehviewer.util.pickVisualMedia
|
||||
@@ -668,7 +669,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) {
|
||||
},
|
||||
onLongClick = {
|
||||
coroutineScope.launch {
|
||||
dialogState.doGalleryInfoAction(info, context)
|
||||
with(dialogState, context, navigator) { doGalleryInfoAction(info) }
|
||||
}
|
||||
},
|
||||
info = info,
|
||||
@@ -684,7 +685,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) {
|
||||
},
|
||||
onLongClick = {
|
||||
coroutineScope.launch {
|
||||
dialogState.doGalleryInfoAction(info, context)
|
||||
with(dialogState, context, navigator) { doGalleryInfoAction(info) }
|
||||
}
|
||||
},
|
||||
info = info,
|
||||
|
||||
@@ -21,7 +21,6 @@ import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingSource
|
||||
@@ -51,13 +50,15 @@ import com.hippo.ehviewer.ui.tools.getLimit
|
||||
import com.hippo.ehviewer.ui.tools.getOffset
|
||||
import com.hippo.ehviewer.ui.tools.launchInVM
|
||||
import com.hippo.ehviewer.ui.tools.rememberInVM
|
||||
import com.hippo.ehviewer.ui.with
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
fun GalleryPreviewScreen(galleryDetail: GalleryDetail, toNextPage: Boolean, navigator: NavController) {
|
||||
fun GalleryPreviewScreen(galleryDetail: GalleryDetail, toNextPage: Boolean, navigator: DestinationsNavigator) {
|
||||
LockDrawer(true)
|
||||
val context = LocalContext.current
|
||||
val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
@@ -139,7 +140,7 @@ fun GalleryPreviewScreen(galleryDetail: GalleryDetail, toNextPage: Boolean, navi
|
||||
) { index ->
|
||||
val item = data[index]
|
||||
EhPreviewItem(item, index) {
|
||||
context.navToReader(galleryDetail.galleryInfo, index)
|
||||
with(context, navigator) { navToReader(galleryDetail.galleryInfo, index) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
|
||||
import com.hippo.ehviewer.ui.tools.LocalDialogState
|
||||
import com.hippo.ehviewer.ui.tools.SwipeToDismissBox2
|
||||
import com.hippo.ehviewer.ui.tools.rememberInVM
|
||||
import com.hippo.ehviewer.ui.with
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
@@ -134,7 +135,7 @@ fun HistoryScreen(navigator: DestinationsNavigator) {
|
||||
},
|
||||
onLongClick = {
|
||||
coroutineScope.launchIO {
|
||||
dialogState.doGalleryInfoAction(info, context)
|
||||
with(dialogState, context, navigator) { doGalleryInfoAction(info) }
|
||||
}
|
||||
},
|
||||
info = info,
|
||||
|
||||
@@ -84,6 +84,10 @@ fun AdvancedScreen(navigator: DestinationsNavigator) {
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
) { paddingValues ->
|
||||
Column(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection).verticalScroll(rememberScrollState()).padding(paddingValues)) {
|
||||
SwitchPreference(
|
||||
title = "New Compose Reader [WIP!!!]",
|
||||
value = Settings::newReader,
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_advanced_save_parse_error_body),
|
||||
summary = stringResource(id = R.string.settings_advanced_save_parse_error_body_summary),
|
||||
|
||||
Reference in new issue
Block a user