diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/model/opml/OPMLTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/model/opml/OPMLTest.kt index 416b22ef0..1790a7e8f 100644 --- a/app/src/androidTest/java/com/nononsenseapps/feeder/model/opml/OPMLTest.kt +++ b/app/src/androidTest/java/com/nononsenseapps/feeder/model/opml/OPMLTest.kt @@ -758,6 +758,7 @@ class OPMLTest : DIAware { UserSettings.SETTING_IMG_ONLY_WIFI -> "true" UserSettings.SETTING_IMG_SHOW_THUMBNAILS -> "false" UserSettings.SETTING_DEFAULT_OPEN_ITEM_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB + UserSettings.SETTING_OPEN_ITEM_IN_READER_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB UserSettings.SETTING_TEXT_SCALE -> "1.6" UserSettings.SETTING_IS_MARK_AS_READ_ON_SCROLL -> "true" UserSettings.SETTING_READALOUD_USE_DETECT_LANGUAGE -> "true" @@ -838,6 +839,7 @@ private val sampleFile: List = + diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt index fb9bc1113..a624ab077 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt @@ -333,6 +333,10 @@ class Repository( fun setLinkOpener(value: LinkOpener) = settingsStore.setLinkOpener(value) + val itemInReaderOpener = settingsStore.itemInReaderOpener + + fun setItemInReaderOpener(value: LinkOpener) = settingsStore.setItemInReaderOpener(value) + val syncFrequency = settingsStore.syncFrequency fun setSyncFrequency(value: SyncFrequency) = settingsStore.setSyncFrequency(value) diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt index 0c0939aa0..a3e8f1736 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt @@ -389,6 +389,30 @@ class SettingsStore( ).apply() } + private val _itemInReaderOpener = + MutableStateFlow( + linkOpenerFromString( + sp.getStringNonNull( + PREF_OPEN_ITEM_IN_READER_WITH, + PREF_VAL_OPEN_WITH_CUSTOM_TAB, + ), + ), + ) + val itemInReaderOpener = _itemInReaderOpener.asStateFlow() + + fun setItemInReaderOpener(value: LinkOpener) { + _itemInReaderOpener.value = value + sp + .edit() + .putString( + PREF_OPEN_ITEM_IN_READER_WITH, + when (value) { + LinkOpener.CUSTOM_TAB -> PREF_VAL_OPEN_WITH_CUSTOM_TAB + LinkOpener.DEFAULT_BROWSER -> PREF_VAL_OPEN_WITH_BROWSER + }, + ).apply() + } + private val _openAdjacent = MutableStateFlow(sp.getBoolean(PREF_OPEN_ADJACENT, true)) val openAdjacent = _openAdjacent.asStateFlow() @@ -626,6 +650,7 @@ const val PREF_IMG_SHOW_THUMBNAILS = "pref_img_show_thumbnails" */ const val PREF_DEFAULT_OPEN_ITEM_WITH = "pref_default_open_item_with" const val PREF_OPEN_LINKS_WITH = "pref_open_links_with" +const val PREF_OPEN_ITEM_IN_READER_WITH = "pref_open_item_in_reader_with" const val PREF_OPEN_ADJACENT = "pref_open_adjacent" const val PREF_PAGING_MODE = "pref_paging_mode" @@ -695,6 +720,7 @@ enum class UserSettings( SETTING_IMG_SHOW_THUMBNAILS(key = PREF_IMG_SHOW_THUMBNAILS), SETTING_DEFAULT_OPEN_ITEM_WITH(key = PREF_DEFAULT_OPEN_ITEM_WITH), SETTING_OPEN_LINKS_WITH(key = PREF_OPEN_LINKS_WITH), + SETTING_OPEN_ITEM_IN_READER_WITH(key = PREF_OPEN_ITEM_IN_READER_WITH), SETTING_OPEN_ADJACENT(key = PREF_OPEN_ADJACENT), SETTING_PAGING_MODE(key = PREF_PAGING_MODE), SETTING_ANIMATED_PAGING(key = PREF_ANIMATED_PAGING), diff --git a/app/src/main/java/com/nononsenseapps/feeder/model/opml/OPMLImporter.kt b/app/src/main/java/com/nononsenseapps/feeder/model/opml/OPMLImporter.kt index a1dd643c2..7f8b21ae7 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/model/opml/OPMLImporter.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/model/opml/OPMLImporter.kt @@ -85,6 +85,10 @@ open class OPMLImporter( settingsStore.setLinkOpener( linkOpenerFromString(value), ) + UserSettings.SETTING_OPEN_ITEM_IN_READER_WITH -> + settingsStore.setItemInReaderOpener( + linkOpenerFromString(value), + ) UserSettings.SETTING_TEXT_SCALE -> settingsStore.setTextScale(value.toFloatOrNull() ?: 1.0f) UserSettings.SETTING_IS_MARK_AS_READ_ON_SCROLL -> settingsStore.setIsMarkAsReadOnScroll(value.toBoolean()) UserSettings.SETTING_READALOUD_USE_DETECT_LANGUAGE -> settingsStore.setUseDetectLanguage(value.toBoolean()) diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/feedarticle/ArticleScreen.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/feedarticle/ArticleScreen.kt index a6a88f47a..2e32e6f9a 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/feedarticle/ArticleScreen.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/feedarticle/ArticleScreen.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.nononsenseapps.feeder.R +import com.nononsenseapps.feeder.archmodel.LinkOpener import com.nononsenseapps.feeder.archmodel.TextToDisplay import com.nononsenseapps.feeder.db.room.ID_UNSET import com.nononsenseapps.feeder.model.LocaleOverride @@ -159,6 +160,11 @@ fun ArticleScreen( activityLauncher.openLinkInCustomTab(link, toolbarColor) } }, + onOpenInBrowser = { + viewState.articleLink?.let { link -> + activityLauncher.openLinkInBrowser(link) + } + }, onFeedTitleClick = { onNavigateToFeed(viewState.articleFeedId) }, @@ -192,6 +198,7 @@ fun ArticleScreen( onMarkAsUnread: () -> Unit, onShare: () -> Unit, onOpenInCustomTab: () -> Unit, + onOpenInBrowser: () -> Unit, onFeedTitleClick: () -> Unit, onShowToolbarMenu: (Boolean) -> Unit, ttsOnPlay: () -> Unit, @@ -256,13 +263,35 @@ fun ArticleScreen( } } - PlainTooltipBox(tooltip = { Text(stringResource(id = R.string.open_in_web_view)) }) { + PlainTooltipBox( + tooltip = { + Text( + stringResource( + when (viewState.itemInReaderOpener) { + LinkOpener.DEFAULT_BROWSER -> R.string.open_article_in_default_browser + else -> R.string.open_article_in_custom_tab + }, + ), + ) + }, + ) { IconButton( - onClick = onOpenInCustomTab, + onClick = { + when (viewState.itemInReaderOpener) { + LinkOpener.DEFAULT_BROWSER -> onOpenInBrowser() + else -> onOpenInCustomTab() + } + }, ) { Icon( Icons.Default.OpenInBrowser, - contentDescription = stringResource(id = R.string.open_in_web_view), + contentDescription = + stringResource( + when (viewState.itemInReaderOpener) { + LinkOpener.DEFAULT_BROWSER -> R.string.open_article_in_default_browser + else -> R.string.open_article_in_custom_tab + }, + ), ) } } diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/feedarticle/ArticleViewModel.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/feedarticle/ArticleViewModel.kt index 53b78a58f..3cdce4b6d 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/feedarticle/ArticleViewModel.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/feedarticle/ArticleViewModel.kt @@ -130,6 +130,7 @@ class ArticleViewModel( ttsStateHolder.availableLanguages, repository.openAISettings, openAiSummary, + repository.itemInReaderOpener, ) { params -> val article = params[0] as Article? val textToDisplay = params[1] as TextToDisplay @@ -144,6 +145,7 @@ class ArticleViewModel( val showSummarize = (params[8] as OpenAISettings).isValid && !article?.link.isNullOrEmpty() val openAiSummary = (params[9] as OpenAISummaryState) + val itemInReaderOpener = params[10] as LinkOpener ArticleState( useDetectLanguage = useDetectLanguage, @@ -156,6 +158,7 @@ class ArticleViewModel( articleFeedId = article?.feedId ?: ID_UNSET, textToDisplay = textToDisplay, linkOpener = linkOpener, + itemInReaderOpener = itemInReaderOpener, pubDate = article?.pubDate, author = article?.author, enclosure = article?.enclosure ?: Enclosure(), @@ -481,6 +484,7 @@ private data class ArticleState( override val articleFeedId: Long = ID_UNSET, override val textToDisplay: TextToDisplay = TextToDisplay.CONTENT, override val linkOpener: LinkOpener = LinkOpener.CUSTOM_TAB, + override val itemInReaderOpener: LinkOpener = LinkOpener.CUSTOM_TAB, override val pubDate: ZonedDateTime? = null, override val author: String? = null, override val enclosure: Enclosure = Enclosure(), @@ -508,6 +512,7 @@ interface ArticleScreenViewState { val articleFeedId: Long val textToDisplay: TextToDisplay val linkOpener: LinkOpener + val itemInReaderOpener: LinkOpener val pubDate: ZonedDateTime? val author: String? val enclosure: Enclosure diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/settings/Settings.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/settings/Settings.kt index a36b7c694..dd1ec2c44 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/settings/Settings.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/settings/Settings.kt @@ -180,6 +180,8 @@ fun SettingsScreen( onItemOpenerChange = settingsViewModel::setItemOpener, currentLinkOpenerValue = viewState.linkOpener, onLinkOpenerChange = settingsViewModel::setLinkOpener, + currentItemInReaderOpenerValue = viewState.itemInReaderOpener, + onItemInReaderOpenerChange = settingsViewModel::setItemInReaderOpener, currentSyncFrequencyValue = viewState.syncFrequency, onSyncFrequencyChange = settingsViewModel::setSyncFrequency, batteryOptimizationIgnoredValue = viewState.batteryOptimizationIgnored, @@ -265,6 +267,8 @@ private fun SettingsScreenPreview() { onItemOpenerChange = {}, currentLinkOpenerValue = LinkOpener.DEFAULT_BROWSER, onLinkOpenerChange = {}, + currentItemInReaderOpenerValue = LinkOpener.DEFAULT_BROWSER, + onItemInReaderOpenerChange = {}, currentSyncFrequencyValue = SyncFrequency.EVERY_12_HOURS, onSyncFrequencyChange = {}, batteryOptimizationIgnoredValue = false, @@ -339,6 +343,8 @@ fun SettingsList( onItemOpenerChange: (ItemOpener) -> Unit, currentLinkOpenerValue: LinkOpener, onLinkOpenerChange: (LinkOpener) -> Unit, + currentItemInReaderOpenerValue: LinkOpener, + onItemInReaderOpenerChange: (LinkOpener) -> Unit, currentSyncFrequencyValue: SyncFrequency, onSyncFrequencyChange: (SyncFrequency) -> Unit, batteryOptimizationIgnoredValue: Boolean, @@ -703,6 +709,22 @@ fun SettingsList( .width(dimens.maxContentWidth), ) + MenuSetting( + title = stringResource(id = R.string.open_item_in_reader_with), + currentValue = currentItemInReaderOpenerValue.asLinkOpenerOption(), + values = + immutableListHolderOf( + LinkOpener.CUSTOM_TAB.asLinkOpenerOption(), + LinkOpener.DEFAULT_BROWSER.asLinkOpenerOption(), + ), + onSelection = { + onItemInReaderOpenerChange(it.linkOpener) + }, + modifier = + Modifier + .width(dimens.maxContentWidth), + ) + val notCompactScreen = LocalConfiguration.current.smallestScreenWidthDp >= 600 if (notCompactScreen) { diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/settings/SettingsViewModel.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/settings/SettingsViewModel.kt index 13ead961d..24bf20735 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/compose/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/compose/settings/SettingsViewModel.kt @@ -100,6 +100,10 @@ class SettingsViewModel( repository.setLinkOpener(value) } + fun setItemInReaderOpener(value: LinkOpener) { + repository.setItemInReaderOpener(value) + } + fun setSyncFrequency(value: SyncFrequency) = applicationCoroutineScope.launch { repository.setSyncFrequency(value) @@ -248,6 +252,7 @@ class SettingsViewModel( repository.font, repository.isPagingMode, repository.isAnimatedPaging, + repository.itemInReaderOpener, ) { params: Array -> @Suppress("UNCHECKED_CAST") SettingsViewState( @@ -287,6 +292,7 @@ class SettingsViewModel( font = params[30] as FontSelection, isPagingMode = params[31] as Boolean, isAnimatedPaging = params[32] as Boolean, + itemInReaderOpener = params[33] as LinkOpener, ) }.collect { _viewState.value = it @@ -350,6 +356,7 @@ data class SettingsViewState( val font: FontSelection = SystemDefault, val isPagingMode: Boolean = false, val isAnimatedPaging: Boolean = false, + val itemInReaderOpener: LinkOpener = LinkOpener.CUSTOM_TAB, ) data class UIFeedSettings( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2e1addbd5..df621c09c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,7 +100,8 @@ Unable to open article in custom tab Use app default Open items by default with - Open links with + Open links in reader with + Open articles via button in reader with Reader Article list Open link in web view diff --git a/app/src/test/java/com/nononsenseapps/feeder/archmodel/SettingsStoreTest.kt b/app/src/test/java/com/nononsenseapps/feeder/archmodel/SettingsStoreTest.kt index ad006caba..3b0c128b3 100644 --- a/app/src/test/java/com/nononsenseapps/feeder/archmodel/SettingsStoreTest.kt +++ b/app/src/test/java/com/nononsenseapps/feeder/archmodel/SettingsStoreTest.kt @@ -320,6 +320,17 @@ class SettingsStoreTest : DIAware { assertEquals(LinkOpener.DEFAULT_BROWSER, store.linkOpener.value) } + @Test + fun itemInReaderOpener() { + store.setItemInReaderOpener(LinkOpener.DEFAULT_BROWSER) + + verify { + sp.edit().putString(PREF_OPEN_ITEM_IN_READER_WITH, PREF_VAL_OPEN_WITH_BROWSER).apply() + } + + assertEquals(LinkOpener.DEFAULT_BROWSER, store.itemInReaderOpener.value) + } + @Ignore("scheduling of jobs is not mocked") @Test fun syncFrequency() { diff --git a/app/src/test/java/com/nononsenseapps/feeder/model/opml/OpmlParserTest.kt b/app/src/test/java/com/nononsenseapps/feeder/model/opml/OpmlParserTest.kt index 3ab33ca2c..f2f506c58 100644 --- a/app/src/test/java/com/nononsenseapps/feeder/model/opml/OpmlParserTest.kt +++ b/app/src/test/java/com/nononsenseapps/feeder/model/opml/OpmlParserTest.kt @@ -66,6 +66,7 @@ class OpmlParserTest : DIAware { UserSettings.SETTING_IMG_ONLY_WIFI -> "true" UserSettings.SETTING_IMG_SHOW_THUMBNAILS -> "false" UserSettings.SETTING_DEFAULT_OPEN_ITEM_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB + UserSettings.SETTING_OPEN_ITEM_IN_READER_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB UserSettings.SETTING_TEXT_SCALE -> "1.6" UserSettings.SETTING_IS_MARK_AS_READ_ON_SCROLL -> "true" UserSettings.SETTING_READALOUD_USE_DETECT_LANGUAGE -> "true" @@ -116,6 +117,7 @@ class OpmlParserTest : DIAware { settingsStore.setLoadImageOnlyOnWifi(true) settingsStore.setShowThumbnails(false) settingsStore.setItemOpener(ItemOpener.CUSTOM_TAB) + settingsStore.setItemInReaderOpener(LinkOpener.CUSTOM_TAB) settingsStore.setTextScale(1.6f) settingsStore.setIsMarkAsReadOnScroll(true) settingsStore.setUseDetectLanguage(true) diff --git a/app/src/test/java/com/nononsenseapps/feeder/model/opml/OpmlWriterKtTest.kt b/app/src/test/java/com/nononsenseapps/feeder/model/opml/OpmlWriterKtTest.kt index f602addb6..5e7c0342b 100644 --- a/app/src/test/java/com/nononsenseapps/feeder/model/opml/OpmlWriterKtTest.kt +++ b/app/src/test/java/com/nononsenseapps/feeder/model/opml/OpmlWriterKtTest.kt @@ -128,6 +128,7 @@ class OpmlWriterKtTest { + @@ -195,6 +196,7 @@ class OpmlWriterKtTest { UserSettings.SETTING_SHOW_TITLE_UNREAD_COUNT -> "true" UserSettings.SETTING_MAX_ITEM_COUNT_PER_FEED -> "200" UserSettings.SETTING_DEFAULT_OPEN_ITEM_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB + UserSettings.SETTING_OPEN_ITEM_IN_READER_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB UserSettings.SETTING_OPENAI_KEY -> "test-api-key" UserSettings.SETTING_OPENAI_MODEL_ID -> "gpt-4o-mini" UserSettings.SETTING_OPENAI_URL -> "https://api.openai.com"