From 7a2b09345fd30984de76a88b7cf4726f601f7c96 Mon Sep 17 00:00:00 2001 From: SkyD666 Date: Tue, 3 Sep 2024 23:53:50 +0800 Subject: [PATCH] [feature] Support resizing font size on reading page --- app/build.gradle.kts | 2 +- .../java/com/skyd/anivu/ext/PreferenceExt.kt | 2 + .../anivu/model/bean/article/ArticleBean.kt | 2 +- .../skyd/anivu/model/preference/Settings.kt | 4 + .../appearance/read/ReadTextSizePreference.kt | 26 ++++++ .../skyd/anivu/model/repository/RssHelper.kt | 2 +- .../skyd/anivu/ui/component/html/HtmlText.kt | 6 ++ .../com/skyd/anivu/ui/local/LocalValue.kt | 2 + .../skyd/anivu/ui/screen/read/ReadScreen.kt | 93 ++++++++++++++++++- app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 11 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/skyd/anivu/model/preference/appearance/read/ReadTextSizePreference.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 863ee468..e6580242 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,7 +22,7 @@ android { minSdk = 24 targetSdk = 35 versionCode = 24 - versionName = "2.1-alpha29" + versionName = "2.1-alpha30" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt b/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt index 61e0f060..0e48354b 100644 --- a/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt +++ b/app/src/main/java/com/skyd/anivu/ext/PreferenceExt.kt @@ -18,6 +18,7 @@ import com.skyd.anivu.model.preference.appearance.feed.FeedGroupExpandPreference import com.skyd.anivu.model.preference.appearance.feed.FeedListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.feed.FeedTopBarTonalElevationPreference import com.skyd.anivu.model.preference.appearance.media.MediaShowThumbnailPreference +import com.skyd.anivu.model.preference.appearance.read.ReadTextSizePreference import com.skyd.anivu.model.preference.appearance.search.SearchItemMinWidthPreference import com.skyd.anivu.model.preference.appearance.search.SearchListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.search.SearchTopBarTonalElevationPreference @@ -76,6 +77,7 @@ fun Preferences.toSettings(): Settings { articleItemMinWidth = ArticleItemMinWidthPreference.fromPreferences(this), searchItemMinWidth = SearchItemMinWidthPreference.fromPreferences(this), mediaShowThumbnail = MediaShowThumbnailPreference.fromPreferences(this), + readTextSize = ReadTextSizePreference.fromPreferences(this), // Update ignoreUpdateVersion = IgnoreUpdateVersionPreference.fromPreferences(this), diff --git a/app/src/main/java/com/skyd/anivu/model/bean/article/ArticleBean.kt b/app/src/main/java/com/skyd/anivu/model/bean/article/ArticleBean.kt index b0cb002e..99934bca 100644 --- a/app/src/main/java/com/skyd/anivu/model/bean/article/ArticleBean.kt +++ b/app/src/main/java/com/skyd/anivu/model/bean/article/ArticleBean.kt @@ -59,7 +59,7 @@ data class ArticleBean( @ColumnInfo(name = IS_FAVORITE_COLUMN) var isFavorite: Boolean = false, @ColumnInfo(name = CATEGORIES_COLUMN) - var catrgories: Categories? = null, + var categories: Categories? = null, ) : BaseBean, Parcelable { @Parcelize diff --git a/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt b/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt index 0b0ab65b..182c2bcd 100644 --- a/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt +++ b/app/src/main/java/com/skyd/anivu/model/preference/Settings.kt @@ -23,6 +23,7 @@ import com.skyd.anivu.model.preference.appearance.feed.FeedGroupExpandPreference import com.skyd.anivu.model.preference.appearance.feed.FeedListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.feed.FeedTopBarTonalElevationPreference import com.skyd.anivu.model.preference.appearance.media.MediaShowThumbnailPreference +import com.skyd.anivu.model.preference.appearance.read.ReadTextSizePreference import com.skyd.anivu.model.preference.appearance.search.SearchItemMinWidthPreference import com.skyd.anivu.model.preference.appearance.search.SearchListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.search.SearchTopBarTonalElevationPreference @@ -97,6 +98,7 @@ import com.skyd.anivu.ui.local.LocalProxyPassword import com.skyd.anivu.ui.local.LocalProxyPort import com.skyd.anivu.ui.local.LocalProxyType import com.skyd.anivu.ui.local.LocalProxyUsername +import com.skyd.anivu.ui.local.LocalReadTextSize import com.skyd.anivu.ui.local.LocalRssSyncBatteryNotLowConstraint import com.skyd.anivu.ui.local.LocalRssSyncChargingConstraint import com.skyd.anivu.ui.local.LocalRssSyncFrequency @@ -134,6 +136,7 @@ data class Settings( val articleItemMinWidth: Float = ArticleItemMinWidthPreference.default, val searchItemMinWidth: Float = SearchItemMinWidthPreference.default, val mediaShowThumbnail: Boolean = MediaShowThumbnailPreference.default, + val readTextSize: Float = ReadTextSizePreference.default, // Update val ignoreUpdateVersion: Long = IgnoreUpdateVersionPreference.default, // Behavior @@ -204,6 +207,7 @@ fun SettingsProvider( LocalArticleItemMinWidth provides settings.articleItemMinWidth, LocalSearchItemMinWidth provides settings.searchItemMinWidth, LocalMediaShowThumbnail provides settings.mediaShowThumbnail, + LocalReadTextSize provides settings.readTextSize, // Update LocalIgnoreUpdateVersion provides settings.ignoreUpdateVersion, // Behavior diff --git a/app/src/main/java/com/skyd/anivu/model/preference/appearance/read/ReadTextSizePreference.kt b/app/src/main/java/com/skyd/anivu/model/preference/appearance/read/ReadTextSizePreference.kt new file mode 100644 index 00000000..399a4898 --- /dev/null +++ b/app/src/main/java/com/skyd/anivu/model/preference/appearance/read/ReadTextSizePreference.kt @@ -0,0 +1,26 @@ +package com.skyd.anivu.model.preference.appearance.read + +import android.content.Context +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.floatPreferencesKey +import com.skyd.anivu.base.BasePreference +import com.skyd.anivu.ext.dataStore +import com.skyd.anivu.ext.put +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +object ReadTextSizePreference : BasePreference { + private const val READ_TEXT_SIZE = "readTextSize" + override val default = 16f + + val key = floatPreferencesKey(READ_TEXT_SIZE) + + fun put(context: Context, scope: CoroutineScope, value: Float) { + scope.launch(Dispatchers.IO) { + context.dataStore.put(key, value) + } + } + + override fun fromPreferences(preferences: Preferences): Float = preferences[key] ?: default +} \ No newline at end of file diff --git a/app/src/main/java/com/skyd/anivu/model/repository/RssHelper.kt b/app/src/main/java/com/skyd/anivu/model/repository/RssHelper.kt index 2605658c..67bae423 100644 --- a/app/src/main/java/com/skyd/anivu/model/repository/RssHelper.kt +++ b/app/src/main/java/com/skyd/anivu/model/repository/RssHelper.kt @@ -132,7 +132,7 @@ class RssHelper @Inject constructor( link = syndEntry.link, guid = syndEntry.uri, updateAt = Date().time, - catrgories = ArticleBean.Categories( + categories = ArticleBean.Categories( categories = syndEntry.categories.map { it.name }.filter { it.isNotBlank() } ) ), diff --git a/app/src/main/java/com/skyd/anivu/ui/component/html/HtmlText.kt b/app/src/main/java/com/skyd/anivu/ui/component/html/HtmlText.kt index b509c9b4..feca16b1 100644 --- a/app/src/main/java/com/skyd/anivu/ui/component/html/HtmlText.kt +++ b/app/src/main/java/com/skyd/anivu/ui/component/html/HtmlText.kt @@ -2,6 +2,7 @@ package com.skyd.anivu.ui.component.html import android.text.Html import android.text.method.LinkMovementMethod +import android.util.TypedValue import android.widget.TextView import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable @@ -13,6 +14,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.viewinterop.AndroidView import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY import androidx.core.text.parseAsHtml @@ -23,11 +26,13 @@ fun HtmlText( modifier: Modifier = Modifier, htmlFlags: Int = FROM_HTML_MODE_LEGACY, text: String, + fontSize: TextUnit = TextUnit.Unspecified, onImageClick: ((String) -> Unit)? = null, ) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val textColor = LocalContentColor.current + val textSize = with(LocalDensity.current) { fontSize.toPx() } var componentWidth by remember { mutableIntStateOf(0) } AndroidView( modifier = modifier.onGloballyPositioned { @@ -43,6 +48,7 @@ fun HtmlText( } }, update = { textView -> + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) textView.text = text.parseAsHtml( htmlFlags, imageGetter = ImageGetter( diff --git a/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt b/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt index 1869c305..3f2f86ef 100644 --- a/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt +++ b/app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt @@ -19,6 +19,7 @@ import com.skyd.anivu.model.preference.appearance.feed.FeedGroupExpandPreference import com.skyd.anivu.model.preference.appearance.feed.FeedListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.feed.FeedTopBarTonalElevationPreference import com.skyd.anivu.model.preference.appearance.media.MediaShowThumbnailPreference +import com.skyd.anivu.model.preference.appearance.read.ReadTextSizePreference import com.skyd.anivu.model.preference.appearance.search.SearchItemMinWidthPreference import com.skyd.anivu.model.preference.appearance.search.SearchListTonalElevationPreference import com.skyd.anivu.model.preference.appearance.search.SearchTopBarTonalElevationPreference @@ -90,6 +91,7 @@ val LocalShowArticlePullRefresh = compositionLocalOf { ShowArticlePullRefreshPre val LocalArticleItemMinWidth = compositionLocalOf { ArticleItemMinWidthPreference.default } val LocalSearchItemMinWidth = compositionLocalOf { SearchItemMinWidthPreference.default } val LocalMediaShowThumbnail = compositionLocalOf { MediaShowThumbnailPreference.default } +val LocalReadTextSize = compositionLocalOf { ReadTextSizePreference.default } // Update val LocalIgnoreUpdateVersion = compositionLocalOf { IgnoreUpdateVersionPreference.default } diff --git a/app/src/main/java/com/skyd/anivu/ui/screen/read/ReadScreen.kt b/app/src/main/java/com/skyd/anivu/ui/screen/read/ReadScreen.kt index ddf0a102..2c6e6d72 100644 --- a/app/src/main/java/com/skyd/anivu/ui/screen/read/ReadScreen.kt +++ b/app/src/main/java/com/skyd/anivu/ui/screen/read/ReadScreen.kt @@ -30,22 +30,29 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AttachFile import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.Download +import androidx.compose.material.icons.outlined.FormatSize +import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.PlayCircleOutline import androidx.compose.material.icons.outlined.Public import androidx.compose.material.icons.outlined.Share +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SuggestionChip import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -61,6 +68,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController @@ -83,6 +91,7 @@ import com.skyd.anivu.model.bean.article.ArticleBean import com.skyd.anivu.model.bean.article.ArticleWithEnclosureBean import com.skyd.anivu.model.bean.article.EnclosureBean import com.skyd.anivu.model.bean.article.RssMediaBean +import com.skyd.anivu.model.preference.appearance.read.ReadTextSizePreference import com.skyd.anivu.ui.activity.PlayActivity import com.skyd.anivu.ui.component.AniVuFloatingActionButton import com.skyd.anivu.ui.component.AniVuIconButton @@ -92,9 +101,11 @@ import com.skyd.anivu.ui.component.AniVuTopBarStyle import com.skyd.anivu.ui.component.dialog.WaitingDialog import com.skyd.anivu.ui.component.html.HtmlText import com.skyd.anivu.ui.component.rememberAniVuImageLoader +import com.skyd.anivu.ui.local.LocalReadTextSize import com.skyd.anivu.ui.screen.article.enclosure.EnclosureBottomSheet import com.skyd.anivu.ui.screen.article.enclosure.getEnclosuresList import com.skyd.anivu.util.ShareUtil +import java.util.Locale const val READ_SCREEN_ROUTE = "readScreen" @@ -117,7 +128,9 @@ fun ReadScreen(articleId: String, viewModel: ReadViewModel = hiltViewModel()) { val context = LocalContext.current val snackbarHostState = remember { SnackbarHostState() } + var openMoreMenu by rememberSaveable { mutableStateOf(false) } var openEnclosureBottomSheet by rememberSaveable { mutableStateOf?>(null) } + var openReadTextSizeSliderDialog by rememberSaveable { mutableStateOf(false) } val uiState by viewModel.viewState.collectAsStateWithLifecycle() val dispatcher = viewModel.getDispatcher(startWith = ReadIntent.Init(articleId)) @@ -128,7 +141,7 @@ fun ReadScreen(articleId: String, viewModel: ReadViewModel = hiltViewModel()) { snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, topBar = { AniVuTopBar( - style = AniVuTopBarStyle.CenterAligned, + style = AniVuTopBarStyle.Small, scrollBehavior = scrollBehavior, title = { Text(text = stringResource(R.string.read_screen_name)) }, actions = { @@ -164,6 +177,16 @@ fun ReadScreen(articleId: String, viewModel: ReadViewModel = hiltViewModel()) { imageVector = Icons.Outlined.Share, contentDescription = stringResource(R.string.share), ) + AniVuIconButton( + onClick = { openMoreMenu = true }, + imageVector = Icons.Outlined.MoreVert, + contentDescription = stringResource(R.string.more), + ) + MoreMenu( + expanded = openMoreMenu, + onDismissRequest = { openMoreMenu = false }, + onReadTextSizeClick = { openReadTextSizeSliderDialog = true }, + ) } ) }, @@ -243,6 +266,12 @@ fun ReadScreen(articleId: String, viewModel: ReadViewModel = hiltViewModel()) { dataList = openEnclosureBottomSheet.orEmpty() ) } + + if (openReadTextSizeSliderDialog) { + ReadTextSizeSliderDialog( + onDismissRequest = { openReadTextSizeSliderDialog = false }, + ) + } } } @@ -325,10 +354,11 @@ private fun Content( text = article.article.content.ifNullOfBlank { article.article.description.orEmpty() }, + fontSize = LocalReadTextSize.current.sp, onImageClick = { imageUrl -> openImageSheet = imageUrl } ) - article.article.catrgories?.let { catrgories -> - CategoryArea(catrgories) + article.article.categories?.let { categories -> + CategoryArea(categories) } if (openImageSheet != null) { @@ -342,6 +372,63 @@ private fun Content( } } +@Composable +private fun MoreMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + onReadTextSizeClick: () -> Unit, +) { + DropdownMenu(expanded = expanded, onDismissRequest = onDismissRequest) { + DropdownMenuItem( + text = { Text(text = stringResource(R.string.read_screen_text_size)) }, + leadingIcon = { + Icon( + imageVector = Icons.Outlined.FormatSize, + contentDescription = null, + ) + }, + onClick = { + onDismissRequest() + onReadTextSizeClick() + }, + ) + } +} + +@Composable +private fun ReadTextSizeSliderDialog( + modifier: Modifier = Modifier, + onDismissRequest: () -> Unit, +) { + ModalBottomSheet( + onDismissRequest = onDismissRequest, + sheetState = rememberModalBottomSheetState() + ) { + Column( + modifier = modifier.padding(bottom = 12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + val textSize = LocalReadTextSize.current + Text( + modifier = Modifier.padding(start = 16.dp), + text = String.format(Locale.getDefault(), "%.2f Sp", textSize), + style = MaterialTheme.typography.titleMedium, + ) + Spacer(modifier = Modifier.height(12.dp)) + Slider( + modifier = Modifier.padding(horizontal = 16.dp), + valueRange = 12f..50f, + value = textSize, + onValueChange = { + ReadTextSizePreference.put(context = context, scope = scope, value = it) + }, + ) + } + } +} + @Composable private fun RssMediaEpisode(modifier: Modifier = Modifier, rssMedia: RssMediaBean) { val episode = rssMedia.episode diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2fe8fed1..f93b4370 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -334,6 +334,7 @@ 进度指示 + 文字大小 已读 %d 项 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 792bc623..03df39b0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -341,6 +341,7 @@ Key Value Progress indicator + Text size Read %d item Read %d items