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 {