diff --git a/CHANGELOG.md b/CHANGELOG.md
index abfdc7bd058..e53d4848283 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@
* Updates
* Add the ability to dismiss the low storage banner in download screen
([#3385](https://github.com/Automattic/pocket-casts-android/pull/3385))
+ * Disable Private Feed Sharing
+ ([#3395](https://github.com/Automattic/pocket-casts-android/pull/3395))
7.79
-----
diff --git a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabaseTest.kt b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabaseTest.kt
index 39b302644e6..70483ecdc44 100644
--- a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabaseTest.kt
+++ b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabaseTest.kt
@@ -174,6 +174,7 @@ class AppDatabaseTest {
// 102 to 103 added via auto migration
AppDatabase.MIGRATION_103_104,
AppDatabase.MIGRATION_104_105,
+ AppDatabase.MIGRATION_105_106,
)
.build()
// close the database and release any stream resources when the test finishes
diff --git a/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/PlayerHeaderFragment.kt b/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/PlayerHeaderFragment.kt
index fe9622d7205..48c15d3b599 100644
--- a/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/PlayerHeaderFragment.kt
+++ b/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/PlayerHeaderFragment.kt
@@ -274,6 +274,7 @@ class PlayerHeaderFragment : BaseFragment(), PlayerClickListener {
SnackbarMessage.EpisodeDownloadStarted -> LR.string.episode_queued_for_download
SnackbarMessage.EpisodeRemoved -> LR.string.episode_was_removed
SnackbarMessage.TranscriptNotAvailable -> LR.string.transcript_error_not_available
+ SnackbarMessage.ShareNotAvailable -> LR.string.sharing_is_not_available_for_private_podcasts
}
showSnackBar(text = getString(text))
}
diff --git a/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/shelf/PlayerShelf.kt b/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/shelf/PlayerShelf.kt
index 88d0c6ae896..b8c0b6d326a 100644
--- a/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/shelf/PlayerShelf.kt
+++ b/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/shelf/PlayerShelf.kt
@@ -112,7 +112,12 @@ fun PlayerShelf(
onShareClick = {
val podcast = playerViewModel.podcast ?: return@PlayerShelfContent
val episode = playerViewModel.episode as? PodcastEpisode ?: return@PlayerShelfContent
- shelfSharedViewModel.onShareClick(podcast, episode, ShelfItemSource.Shelf)
+
+ if (podcast.canShare) {
+ shelfSharedViewModel.onShareClick(podcast, episode, ShelfItemSource.Shelf)
+ } else {
+ shelfSharedViewModel.onShareNotAvailable(ShelfItemSource.Shelf)
+ }
},
onShowPodcast = {
shelfSharedViewModel.onShowPodcastOrCloudFiles(playerViewModel.podcast, ShelfItemSource.Shelf)
diff --git a/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/shelf/ShelfBottomSheetPage.kt b/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/shelf/ShelfBottomSheetPage.kt
index fc40a015ef3..4f9f061b9df 100644
--- a/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/shelf/ShelfBottomSheetPage.kt
+++ b/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/view/shelf/ShelfBottomSheetPage.kt
@@ -80,7 +80,12 @@ fun ShelfBottomSheetPage(
ShelfItem.Share -> {
val podcast = playerViewModel.podcast ?: return@MenuShelfItems
val episode = playerViewModel.episode as? PodcastEpisode ?: return@MenuShelfItems
- shelfSharedViewModel.onShareClick(podcast, episode, ShelfItemSource.OverflowMenu)
+
+ if (podcast.canShare) {
+ shelfSharedViewModel.onShareClick(podcast, episode, ShelfItemSource.OverflowMenu)
+ } else {
+ shelfSharedViewModel.onShareNotAvailable(ShelfItemSource.OverflowMenu)
+ }
}
ShelfItem.Podcast -> shelfSharedViewModel.onShowPodcastOrCloudFiles(playerViewModel.podcast, ShelfItemSource.OverflowMenu)
diff --git a/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/viewmodel/ShelfSharedViewModel.kt b/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/viewmodel/ShelfSharedViewModel.kt
index 2fa1f08cbc5..ac053d4fccd 100644
--- a/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/viewmodel/ShelfSharedViewModel.kt
+++ b/modules/features/player/src/main/java/au/com/shiftyjelly/pocketcasts/player/viewmodel/ShelfSharedViewModel.kt
@@ -172,6 +172,13 @@ class ShelfSharedViewModel @Inject constructor(
}
}
+ fun onShareNotAvailable(source: ShelfItemSource) {
+ trackShelfAction(ShelfItem.Share, source)
+ viewModelScope.launch {
+ _snackbarMessages.emit(SnackbarMessage.ShareNotAvailable)
+ }
+ }
+
fun onEpisodeRemoveClick(source: ShelfItemSource) {
trackShelfAction(ShelfItem.Download, source)
viewModelScope.launch {
@@ -365,6 +372,7 @@ class ShelfSharedViewModel @Inject constructor(
data object EpisodeDownloadStarted : SnackbarMessage
data object EpisodeRemoved : SnackbarMessage
data object TranscriptNotAvailable : SnackbarMessage
+ data object ShareNotAvailable : SnackbarMessage
}
sealed class TransitionState {
diff --git a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastFragment.kt b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastFragment.kt
index 40a64731b54..63351bd9985 100644
--- a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastFragment.kt
+++ b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastFragment.kt
@@ -84,6 +84,7 @@ import au.com.shiftyjelly.pocketcasts.views.helper.UiUtil
import au.com.shiftyjelly.pocketcasts.views.multiselect.MultiSelectBookmarksHelper.NavigationState
import au.com.shiftyjelly.pocketcasts.views.multiselect.MultiSelectHelper
import au.com.shiftyjelly.pocketcasts.views.multiselect.MultiSelectToolbar
+import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@@ -977,7 +978,16 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener {
private fun share() {
val podcast = viewModel.podcast.value ?: return
+
analyticsTracker.track(AnalyticsEvent.PODCAST_SCREEN_SHARE_TAPPED)
+
+ if (!podcast.canShare) {
+ (activity as? FragmentHostListener)?.snackBarView()?.let { snackBarView ->
+ Snackbar.make(snackBarView, getString(LR.string.sharing_is_not_available_for_private_podcasts), Snackbar.LENGTH_LONG).show()
+ }
+ return
+ }
+
if (FeatureFlag.isEnabled(Feature.REIMAGINE_SHARING)) {
SharePodcastFragment
.newInstance(podcast, SourceView.PODCAST_SCREEN)
diff --git a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/share/ShareListCreateViewModel.kt b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/share/ShareListCreateViewModel.kt
index 9d0a899be3f..c6e74e0ac85 100644
--- a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/share/ShareListCreateViewModel.kt
+++ b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/share/ShareListCreateViewModel.kt
@@ -39,7 +39,7 @@ class ShareListCreateViewModel @Inject constructor(
init {
viewModelScope.launch {
val podcasts = podcastManager.findPodcastsOrderByTitle()
- mutableState.value = mutableState.value.copy(podcasts = podcasts)
+ mutableState.value = mutableState.value.copy(podcasts = podcasts.filter { it.canShare })
}
}
diff --git a/modules/services/localization/src/main/res/values/strings.xml b/modules/services/localization/src/main/res/values/strings.xml
index 9910f9bb72b..95b3f0ba8b8 100644
--- a/modules/services/localization/src/main/res/values/strings.xml
+++ b/modules/services/localization/src/main/res/values/strings.xml
@@ -130,6 +130,7 @@
Error
Something went wrong. Please try again later.
Are you sure?
+ Sharing is not available for private podcasts
Back
Retry
Queue for later
diff --git a/modules/services/model/schemas/au.com.shiftyjelly.pocketcasts.models.db.AppDatabase/105.json b/modules/services/model/schemas/au.com.shiftyjelly.pocketcasts.models.db.AppDatabase/105.json
index ee7089154e2..462aa7875a9 100644
--- a/modules/services/model/schemas/au.com.shiftyjelly.pocketcasts.models.db.AppDatabase/105.json
+++ b/modules/services/model/schemas/au.com.shiftyjelly.pocketcasts.models.db.AppDatabase/105.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 105,
- "identityHash": "5081aaece3489e97782187c7cb1ab7e4",
+ "identityHash": "10131517fd9b7d158e054ece972adb8f",
"entities": [
{
"tableName": "bump_stats",
@@ -696,7 +696,7 @@
},
{
"tableName": "podcasts",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `added_date` INTEGER, `thumbnail_url` TEXT, `title` TEXT NOT NULL, `podcast_url` TEXT, `podcast_description` TEXT NOT NULL, `podcast_html_description` TEXT NOT NULL, `podcast_category` TEXT NOT NULL, `podcast_language` TEXT NOT NULL, `media_type` TEXT, `latest_episode_uuid` TEXT, `author` TEXT NOT NULL, `sort_order` INTEGER NOT NULL, `episodes_sort_order` INTEGER NOT NULL, `episodes_sort_order_modified` INTEGER, `latest_episode_date` INTEGER, `episodes_to_keep` INTEGER NOT NULL, `override_global_settings` INTEGER NOT NULL, `override_global_effects` INTEGER NOT NULL, `override_global_effects_modified` INTEGER, `start_from` INTEGER NOT NULL, `start_from_modified` INTEGER, `playback_speed` REAL NOT NULL, `playback_speed_modified` INTEGER, `volume_boosted` INTEGER NOT NULL, `volume_boosted_modified` INTEGER, `is_folder` INTEGER NOT NULL, `subscribed` INTEGER NOT NULL, `show_notifications` INTEGER NOT NULL, `show_notifications_modified` INTEGER, `auto_download_status` INTEGER NOT NULL, `auto_add_to_up_next` INTEGER NOT NULL, `auto_add_to_up_next_modified` INTEGER, `most_popular_color` INTEGER NOT NULL, `primary_color` INTEGER NOT NULL, `secondary_color` INTEGER NOT NULL, `light_overlay_color` INTEGER NOT NULL, `fab_for_light_bg` INTEGER NOT NULL, `link_for_dark_bg` INTEGER NOT NULL, `link_for_light_bg` INTEGER NOT NULL, `color_version` INTEGER NOT NULL, `color_last_downloaded` INTEGER NOT NULL, `sync_status` INTEGER NOT NULL, `exclude_from_auto_archive` INTEGER NOT NULL, `override_global_archive` INTEGER NOT NULL, `override_global_archive_modified` INTEGER, `auto_archive_played_after` INTEGER NOT NULL, `auto_archive_played_after_modified` INTEGER, `auto_archive_inactive_after` INTEGER NOT NULL, `auto_archive_inactive_after_modified` INTEGER, `auto_archive_episode_limit` INTEGER NOT NULL, `auto_archive_episode_limit_modified` INTEGER, `estimated_next_episode` INTEGER, `episode_frequency` TEXT, `grouping` INTEGER NOT NULL, `grouping_modified` INTEGER, `skip_last` INTEGER NOT NULL, `skip_last_modified` INTEGER, `show_archived` INTEGER NOT NULL, `show_archived_modified` INTEGER, `trim_silence_level` INTEGER NOT NULL, `trim_silence_level_modified` INTEGER, `refresh_available` INTEGER NOT NULL, `folder_uuid` TEXT, `licensing` INTEGER NOT NULL, `isPaid` INTEGER NOT NULL, `bundleuuid` TEXT, `bundlebundleUrl` TEXT, `bundlepaymentUrl` TEXT, `bundledescription` TEXT, `bundlepodcastUuid` TEXT, `bundlepaidType` TEXT, PRIMARY KEY(`uuid`))",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `added_date` INTEGER, `thumbnail_url` TEXT, `title` TEXT NOT NULL, `podcast_url` TEXT, `podcast_description` TEXT NOT NULL, `podcast_html_description` TEXT NOT NULL, `podcast_category` TEXT NOT NULL, `podcast_language` TEXT NOT NULL, `media_type` TEXT, `latest_episode_uuid` TEXT, `author` TEXT NOT NULL, `sort_order` INTEGER NOT NULL, `episodes_sort_order` INTEGER NOT NULL, `episodes_sort_order_modified` INTEGER, `latest_episode_date` INTEGER, `episodes_to_keep` INTEGER NOT NULL, `override_global_settings` INTEGER NOT NULL, `override_global_effects` INTEGER NOT NULL, `override_global_effects_modified` INTEGER, `start_from` INTEGER NOT NULL, `start_from_modified` INTEGER, `playback_speed` REAL NOT NULL, `playback_speed_modified` INTEGER, `volume_boosted` INTEGER NOT NULL, `volume_boosted_modified` INTEGER, `is_folder` INTEGER NOT NULL, `subscribed` INTEGER NOT NULL, `show_notifications` INTEGER NOT NULL, `show_notifications_modified` INTEGER, `auto_download_status` INTEGER NOT NULL, `auto_add_to_up_next` INTEGER NOT NULL, `auto_add_to_up_next_modified` INTEGER, `most_popular_color` INTEGER NOT NULL, `primary_color` INTEGER NOT NULL, `secondary_color` INTEGER NOT NULL, `light_overlay_color` INTEGER NOT NULL, `fab_for_light_bg` INTEGER NOT NULL, `link_for_dark_bg` INTEGER NOT NULL, `link_for_light_bg` INTEGER NOT NULL, `color_version` INTEGER NOT NULL, `color_last_downloaded` INTEGER NOT NULL, `sync_status` INTEGER NOT NULL, `exclude_from_auto_archive` INTEGER NOT NULL, `override_global_archive` INTEGER NOT NULL, `override_global_archive_modified` INTEGER, `auto_archive_played_after` INTEGER NOT NULL, `auto_archive_played_after_modified` INTEGER, `auto_archive_inactive_after` INTEGER NOT NULL, `auto_archive_inactive_after_modified` INTEGER, `auto_archive_episode_limit` INTEGER NOT NULL, `auto_archive_episode_limit_modified` INTEGER, `estimated_next_episode` INTEGER, `episode_frequency` TEXT, `grouping` INTEGER NOT NULL, `grouping_modified` INTEGER, `skip_last` INTEGER NOT NULL, `skip_last_modified` INTEGER, `show_archived` INTEGER NOT NULL, `show_archived_modified` INTEGER, `trim_silence_level` INTEGER NOT NULL, `trim_silence_level_modified` INTEGER, `refresh_available` INTEGER NOT NULL, `folder_uuid` TEXT, `licensing` INTEGER NOT NULL, `isPaid` INTEGER NOT NULL, `is_private` INTEGER NOT NULL, `bundleuuid` TEXT, `bundlebundleUrl` TEXT, `bundlepaymentUrl` TEXT, `bundledescription` TEXT, `bundlepodcastUuid` TEXT, `bundlepaidType` TEXT, PRIMARY KEY(`uuid`))",
"fields": [
{
"fieldPath": "uuid",
@@ -1094,6 +1094,12 @@
"affinity": "INTEGER",
"notNull": true
},
+ {
+ "fieldPath": "isPrivate",
+ "columnName": "is_private",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
{
"fieldPath": "singleBundle.uuid",
"columnName": "bundleuuid",
@@ -1859,7 +1865,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5081aaece3489e97782187c7cb1ab7e4')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '10131517fd9b7d158e054ece972adb8f')"
]
}
}
\ No newline at end of file
diff --git a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt
index 63e800c116c..094119ef12e 100644
--- a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt
+++ b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/AppDatabase.kt
@@ -876,6 +876,10 @@ abstract class AppDatabase : RoomDatabase() {
database.execSQL("ALTER TABLE podcasts ADD COLUMN podcast_html_description TEXT NOT NULL DEFAULT ''")
}
+ val MIGRATION_105_106 = addMigration(105, 106) { database ->
+ database.execSQL("ALTER TABLE podcasts ADD COLUMN is_private INTEGER NOT NULL DEFAULT 0")
+ }
+
fun addMigrations(databaseBuilder: Builder, context: Context) {
databaseBuilder.addMigrations(
addMigration(1, 2) { },
@@ -1271,6 +1275,7 @@ abstract class AppDatabase : RoomDatabase() {
// 102 to 103 added via auto migration
MIGRATION_103_104,
MIGRATION_104_105,
+ MIGRATION_105_106,
)
}
diff --git a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/entity/Podcast.kt b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/entity/Podcast.kt
index 5e22669a46d..cffa7dbcccc 100644
--- a/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/entity/Podcast.kt
+++ b/modules/services/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/entity/Podcast.kt
@@ -19,6 +19,8 @@ import au.com.shiftyjelly.pocketcasts.models.to.PlaybackEffects
import au.com.shiftyjelly.pocketcasts.models.to.PodcastGrouping
import au.com.shiftyjelly.pocketcasts.models.type.EpisodesSortType
import au.com.shiftyjelly.pocketcasts.models.type.TrimMode
+import au.com.shiftyjelly.pocketcasts.utils.featureflag.Feature
+import au.com.shiftyjelly.pocketcasts.utils.featureflag.FeatureFlag
import java.io.Serializable
import java.net.MalformedURLException
import java.net.URL
@@ -115,6 +117,7 @@ data class Podcast(
@ColumnInfo(name = "folder_uuid") internal var rawFolderUuid: String? = null,
@ColumnInfo(name = "licensing") var licensing: Licensing = Licensing.KEEP_EPISODES,
@ColumnInfo(name = "isPaid") var isPaid: Boolean = false,
+ @ColumnInfo(name = "is_private") var isPrivate: Boolean = false,
@Embedded(prefix = "bundle") var singleBundle: Bundle? = null,
@Ignore val episodes: MutableList = mutableListOf(),
) : Serializable {
@@ -171,6 +174,9 @@ data class Podcast(
val isSilenceRemoved: Boolean
get() = trimMode != TrimMode.OFF
+ val canShare: Boolean
+ get() = !FeatureFlag.isEnabled(Feature.SHARE_PODCAST_PRIVATE_NOT_AVAILABLE) || !isPrivate
+
val isUsingEffects: Boolean
get() = overrideGlobalEffects && (isSilenceRemoved || isVolumeBoosted || playbackSpeed != 1.0)
diff --git a/modules/services/servers/src/main/java/au/com/shiftyjelly/pocketcasts/servers/podcast/PodcastResponse.kt b/modules/services/servers/src/main/java/au/com/shiftyjelly/pocketcasts/servers/podcast/PodcastResponse.kt
index 573a01dda69..fa61044906e 100644
--- a/modules/services/servers/src/main/java/au/com/shiftyjelly/pocketcasts/servers/podcast/PodcastResponse.kt
+++ b/modules/services/servers/src/main/java/au/com/shiftyjelly/pocketcasts/servers/podcast/PodcastResponse.kt
@@ -46,6 +46,7 @@ data class PodcastInfo(
@field:Json(name = "category") val category: String?,
@field:Json(name = "audio") val audio: Boolean?,
@field:Json(name = "episodes") val episodes: List?,
+ @field:Json(name = "is_private") val isPrivate: Boolean?,
) {
fun toPodcast(): Podcast {
@@ -63,6 +64,7 @@ data class PodcastInfo(
episodes?.mapNotNull { it.toEpisode(uuid) }?.let { episodes ->
podcast.episodes.addAll(episodes)
}
+ podcast.isPrivate = isPrivate ?: false
return podcast
}
}
diff --git a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt
index 91730edfb33..24d9943288a 100644
--- a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt
+++ b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt
@@ -187,6 +187,14 @@ enum class Feature(
hasFirebaseRemoteFlag = true,
hasDevToggle = true,
),
+ SHARE_PODCAST_PRIVATE_NOT_AVAILABLE(
+ key = "share_podcast_private_not_available",
+ title = "Sharing is not available for private podcasts",
+ defaultValue = BuildConfig.DEBUG,
+ tier = FeatureTier.Free,
+ hasFirebaseRemoteFlag = false,
+ hasDevToggle = true,
+ ),
;
companion object {