feat(reading): support for specifying the composition of shared content (#660)

This commit is contained in:
Ash 2024-03-26 11:45:44 +08:00 committed by GitHub
parent d88a542bf7
commit 2771989489
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 153 additions and 36 deletions

View File

@ -82,6 +82,7 @@ fun Preferences.toSettings(): Settings {
pullToSwitchArticle = PullToSwitchArticlePreference.fromPreference(this),
openLink = OpenLinkPreference.fromPreferences(this),
openLinkSpecificBrowser = OpenLinkSpecificBrowserPreference.fromPreferences(this),
sharedContent = SharedContentPreference.fromPreferences(this),
// Languages
languages = LanguagesPreference.fromPreferences(this),

View File

@ -81,6 +81,7 @@ data class Settings(
val pullToSwitchArticle: PullToSwitchArticlePreference = PullToSwitchArticlePreference.default,
val openLink: OpenLinkPreference = OpenLinkPreference.default,
val openLinkSpecificBrowser: OpenLinkSpecificBrowserPreference = OpenLinkSpecificBrowserPreference.default,
val sharedContent: SharedContentPreference = SharedContentPreference.default,
// Languages
val languages: LanguagesPreference = LanguagesPreference.default,
@ -195,10 +196,9 @@ val LocalInitialFilter =
val LocalArticleListSwipeEndAction = compositionLocalOf { SwipeEndActionPreference.default }
val LocalArticleListSwipeStartAction = compositionLocalOf { SwipeStartActionPreference.default }
val LocalPullToSwitchArticle = compositionLocalOf { PullToSwitchArticlePreference.default }
val LocalOpenLink =
compositionLocalOf<OpenLinkPreference> { OpenLinkPreference.default }
val LocalOpenLinkSpecificBrowser =
compositionLocalOf { OpenLinkSpecificBrowserPreference.default }
val LocalOpenLink = compositionLocalOf<OpenLinkPreference> { OpenLinkPreference.default }
val LocalOpenLinkSpecificBrowser = compositionLocalOf { OpenLinkSpecificBrowserPreference.default }
val LocalSharedContent = compositionLocalOf<SharedContentPreference> { SharedContentPreference.default }
// Languages
val LocalLanguages =
@ -287,6 +287,7 @@ fun SettingsProvider(
LocalPullToSwitchArticle provides settings.pullToSwitchArticle,
LocalOpenLink provides settings.openLink,
LocalOpenLinkSpecificBrowser provides settings.openLinkSpecificBrowser,
LocalSharedContent provides settings.sharedContent,
// Languages
LocalLanguages provides settings.languages,

View File

@ -0,0 +1,61 @@
package me.ash.reader.infrastructure.preference
import android.content.Context
import android.content.Intent
import androidx.compose.runtime.Stable
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.orNotEmpty
import me.ash.reader.ui.ext.put
sealed class SharedContentPreference(val value: Int) : Preference() {
object OnlyLink : SharedContentPreference(0)
object TitleAndLink : SharedContentPreference(1)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch {
context.dataStore.put(
DataStoreKeys.SharedContent,
value
)
}
}
@Stable
fun toDesc(context: Context): String =
when (this) {
OnlyLink -> context.getString(R.string.only_link)
TitleAndLink -> context.getString(R.string.title_and_link)
}
fun share(context: Context, title: String?, link: String?) {
when (this) {
OnlyLink -> share(context, link.orEmpty())
TitleAndLink -> share(context, title.orNotEmpty { it + "\n" } + link.orEmpty())
}
}
private fun share(context: Context, content: String) {
context.startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_TEXT, content)
type = "text/plain"
}, context.getString(R.string.share)))
}
companion object {
val default = OnlyLink
val values = listOf(OnlyLink, TitleAndLink)
fun fromPreferences(preferences: Preferences): SharedContentPreference =
when (preferences[DataStoreKeys.SharedContent.key]) {
0 -> OnlyLink
1 -> TitleAndLink
else -> default
}
}
}

View File

@ -66,16 +66,6 @@ fun Context.showToastLong(message: String?) {
showToast(message, Toast.LENGTH_LONG)
}
fun Context.share(content: String) {
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
putExtra(
Intent.EXTRA_TEXT,
content,
)
type = "text/plain"
}, getString(R.string.share)))
}
fun Context.openURL(
url: String?,
openLink: OpenLinkPreference,

View File

@ -3,7 +3,14 @@ package me.ash.reader.ui.ext
import android.content.Context
import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.doublePreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
@ -439,6 +446,12 @@ sealed class DataStoreKeys<T> {
get() = stringPreferencesKey("openLppSpecificBrowser")
}
object SharedContent : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("sharedContent")
}
// Languages
object Languages : DataStoreKeys<Int>() {

View File

@ -35,3 +35,6 @@ fun String.md5(): String =
.toString(16).padStart(32, '0')
fun String?.decodeHTML(): String? = this?.run { Html.fromHtml(this).toString() }
fun String?.orNotEmpty(l: (value: String) -> String): String =
if (this.isNullOrBlank()) "" else l(this)

View File

@ -1,7 +1,13 @@
package me.ash.reader.ui.page.home.flow
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
@ -9,11 +15,18 @@ import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.DoneAll
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
@ -28,11 +41,22 @@ import me.ash.reader.R
import me.ash.reader.domain.model.article.ArticleWithFeed
import me.ash.reader.domain.model.general.Filter
import me.ash.reader.domain.model.general.MarkAsReadConditions
import me.ash.reader.infrastructure.preference.*
import me.ash.reader.infrastructure.preference.LocalFlowArticleListDateStickyHeader
import me.ash.reader.infrastructure.preference.LocalFlowArticleListFeedIcon
import me.ash.reader.infrastructure.preference.LocalFlowArticleListTonalElevation
import me.ash.reader.infrastructure.preference.LocalFlowFilterBarFilled
import me.ash.reader.infrastructure.preference.LocalFlowFilterBarPadding
import me.ash.reader.infrastructure.preference.LocalFlowFilterBarStyle
import me.ash.reader.infrastructure.preference.LocalFlowFilterBarTonalElevation
import me.ash.reader.infrastructure.preference.LocalFlowTopBarTonalElevation
import me.ash.reader.infrastructure.preference.LocalSharedContent
import me.ash.reader.ui.component.FilterBar
import me.ash.reader.ui.component.base.*
import me.ash.reader.ui.component.base.DisplayText
import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.component.base.RYExtensibleVisibility
import me.ash.reader.ui.component.base.RYScaffold
import me.ash.reader.ui.component.base.SwipeRefresh
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.share
import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.home.HomeViewModel
@ -55,6 +79,7 @@ fun FlowPage(
val filterBarFilled = LocalFlowFilterBarFilled.current
val filterBarPadding = LocalFlowFilterBarPadding.current
val filterBarTonalElevation = LocalFlowFilterBarTonalElevation.current
val sharedContent = LocalSharedContent.current
val context = LocalContext.current
val homeUiState = homeViewModel.homeUiState.collectAsStateValue()
@ -126,9 +151,7 @@ fun FlowPage(
val onShare: ((ArticleWithFeed) -> Unit)? = remember {
{ articleWithFeed ->
with(articleWithFeed.article) {
context.share(
arrayOf(title, link).filter { it.isNotBlank() }.joinToString(separator = "\n")
)
sharedContent.share(context, title, link)
}
}
}

View File

@ -22,9 +22,9 @@ import androidx.compose.ui.zIndex
import androidx.navigation.NavHostController
import me.ash.reader.R
import me.ash.reader.infrastructure.preference.LocalReadingPageTonalElevation
import me.ash.reader.infrastructure.preference.LocalSharedContent
import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.component.base.RYExtensibleVisibility
import me.ash.reader.ui.ext.share
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.common.RouteName
@ -40,6 +40,7 @@ fun TopBar(
) {
val context = LocalContext.current
val tonalElevation = LocalReadingPageTonalElevation.current
val sharedContent = LocalSharedContent.current
Box(
modifier = Modifier
@ -78,10 +79,7 @@ fun TopBar(
contentDescription = stringResource(R.string.share),
tint = MaterialTheme.colorScheme.onSurface,
) {
context.share(title
?.takeIf { it.isNotBlank() }
?.let { it + "\n" } + link
)
sharedContent.share(context, title, link)
}
}, colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(tonalElevation.value.dp),

View File

@ -1,6 +1,5 @@
package me.ash.reader.ui.page.settings.accounts
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@ -60,12 +59,8 @@ class AccountViewModel @Inject constructor(
fun exportAsOPML(accountId: Int, callback: (String) -> Unit = {}) {
viewModelScope.launch(defaultDispatcher) {
try {
callback(opmlService.saveToString(accountId,
_accountUiState.value.exportOPMLMode == ExportOPMLMode.ATTACH_INFO))
} catch (e: Exception) {
Log.e("FeedsViewModel", "exportAsOpml: ", e)
}
callback(opmlService.saveToString(accountId,
_accountUiState.value.exportOPMLMode == ExportOPMLMode.ATTACH_INFO))
}
}

View File

@ -31,7 +31,9 @@ import me.ash.reader.infrastructure.preference.LocalInitialPage
import me.ash.reader.infrastructure.preference.LocalOpenLink
import me.ash.reader.infrastructure.preference.LocalOpenLinkSpecificBrowser
import me.ash.reader.infrastructure.preference.LocalPullToSwitchArticle
import me.ash.reader.infrastructure.preference.LocalSharedContent
import me.ash.reader.infrastructure.preference.OpenLinkPreference
import me.ash.reader.infrastructure.preference.SharedContentPreference
import me.ash.reader.infrastructure.preference.SwipeEndActionPreference
import me.ash.reader.infrastructure.preference.SwipeStartActionPreference
import me.ash.reader.ui.component.base.DisplayText
@ -57,6 +59,7 @@ fun InteractionPage(
val pullToSwitchArticle = LocalPullToSwitchArticle.current
val openLink = LocalOpenLink.current
val openLinkSpecificBrowser = LocalOpenLinkSpecificBrowser.current
val sharedContent = LocalSharedContent.current
val scope = rememberCoroutineScope()
val isOpenLinkSpecificBrowserItemEnabled = remember(openLink) {
openLink == OpenLinkPreference.SpecificBrowser
@ -67,6 +70,7 @@ fun InteractionPage(
var swipeEndDialogVisible by remember { mutableStateOf(false) }
var openLinkDialogVisible by remember { mutableStateOf(false) }
var openLinkSpecificBrowserDialogVisible by remember { mutableStateOf(false) }
var sharedContentDialogVisible by remember { mutableStateOf(false) }
RYScaffold(
containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface,
@ -158,6 +162,17 @@ fun InteractionPage(
}
},
) {}
Subtitle(
modifier = Modifier.padding(horizontal = 24.dp),
text = stringResource(R.string.share),
)
SettingItem(
title = stringResource(R.string.shared_content),
desc = sharedContent.toDesc(context),
onClick = {
sharedContentDialogVisible = true
},
) {}
}
item {
Spacer(modifier = Modifier.height(24.dp))
@ -264,4 +279,18 @@ fun InteractionPage(
}
)
RadioDialog(
visible = sharedContentDialogVisible,
title = stringResource(R.string.shared_content),
options = SharedContentPreference.values.map {
RadioDialogOption(
text = it.toDesc(context),
selected = it == sharedContent,
) {
it.put(context, scope)
}
},
) {
sharedContentDialogVisible = false
}
}

View File

@ -427,4 +427,7 @@
<string name="save">Save</string>
<string name="image_saved">Image saved</string>
<string name="permission_denied">Permission denied</string>
<string name="shared_content">Shared content</string>
<string name="only_link">Only link</string>
<string name="title_and_link">Title and link</string>
</resources>