diff --git a/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt b/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt index 6bcdd76cb80..682cf761536 100644 --- a/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt +++ b/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt @@ -996,6 +996,10 @@ class MainActivity : return binding.snackbarFragment } + override fun setFullScreenDarkOverlayViewVisibility(visible: Boolean) { + binding.fullScreenDarkOverlayView.isVisible = visible + } + override fun onMiniPlayerHidden() { updateSnackbarPosition(miniPlayerOpen = false) settings.updateBottomInset(0) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index de02a102eca..8fdc761e374 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -66,6 +66,13 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + { adapter?.setBookmarks( @@ -1003,6 +1091,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { binding = null currentSnackBar?.dismiss() currentSnackBar = null + (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) } private fun archiveAllPlayed() { diff --git a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastTooltip.kt b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastTooltip.kt new file mode 100644 index 00000000000..f8cd5bac2f0 --- /dev/null +++ b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastTooltip.kt @@ -0,0 +1,136 @@ +package au.com.shiftyjelly.pocketcasts.podcasts.view.podcast + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Popup +import au.com.shiftyjelly.pocketcasts.compose.CallOnce +import au.com.shiftyjelly.pocketcasts.compose.components.TextH30 +import au.com.shiftyjelly.pocketcasts.compose.components.TextP40 +import au.com.shiftyjelly.pocketcasts.compose.theme +import au.com.shiftyjelly.pocketcasts.localization.R as LR + +@Composable +fun PodcastTooltip( + title: String, + subtitle: String, + offset: IntOffset, + onTooltipShown: () -> Unit, + onDismissRequest: () -> Unit, + onCloseButtonClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val tooltipColor = MaterialTheme.theme.colors.primaryUi01 + + CallOnce { + onTooltipShown.invoke() + } + + Popup( + alignment = Alignment.TopStart, + offset = offset, + onDismissRequest = onDismissRequest, + ) { + Box( + modifier = modifier.background(Color.Transparent).padding(16.dp).widthIn(max = 400.dp), + ) { + TooltipContent(tooltipColor, title, subtitle, onCloseButtonClick) + + TooltipArrow(tooltipColor) + } + } +} + +@Composable +private fun TooltipContent( + tooltipColor: Color, + title: String, + subtitle: String, + onCloseButtonClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Card( + backgroundColor = tooltipColor, + shape = RoundedCornerShape(8.dp), + elevation = 0.dp, + modifier = modifier, + ) { + Row( + modifier = Modifier.background(tooltipColor).padding(16.dp), + verticalAlignment = Alignment.Top, + ) { + Column( + modifier = Modifier.weight(1f), + ) { + TextH30( + text = title, + modifier = Modifier.padding(bottom = 4.dp), + ) + + TextP40( + text = subtitle, + fontSize = 12.sp, + color = MaterialTheme.theme.colors.primaryText02, + ) + } + + Spacer(modifier = Modifier.width(12.dp)) + + Icon( + imageVector = Icons.Default.Close, + contentDescription = stringResource(LR.string.close), + tint = MaterialTheme.theme.colors.primaryIcon02, + modifier = Modifier + .align(Alignment.Top) + .width(24.dp) + .clickable { + onCloseButtonClick.invoke() + }, + ) + } + } +} + +@Composable +private fun BoxScope.TooltipArrow(tooltipColor: Color) { + Canvas( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(end = 28.dp), + ) { + val triangleSize = 12.dp.toPx() + + drawPath( + path = Path().apply { + moveTo(0f, -2f) + lineTo(triangleSize, 0f) + lineTo(triangleSize / 2f, triangleSize) + close() + }, + color = tooltipColor, + ) + } +} diff --git a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/viewmodel/PodcastViewModel.kt b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/viewmodel/PodcastViewModel.kt index 8313a0deaf9..1fd7605aa11 100644 --- a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/viewmodel/PodcastViewModel.kt +++ b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/viewmodel/PodcastViewModel.kt @@ -60,6 +60,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch @@ -102,6 +103,8 @@ class PodcastViewModel private val _refreshState = MutableSharedFlow() val refreshState = _refreshState.asSharedFlow() + val shouldShowPodcastTooltip = MutableStateFlow(settings.showPodcastRefreshTooltip.value) + val groupedEpisodes: MutableLiveData>> = MutableLiveData() val signInState = userManager.getSignInState().toLiveData() @@ -592,6 +595,11 @@ class PodcastViewModel } } + fun hidePodcastRefreshTooltip() { + settings.showPodcastRefreshTooltip.set(false, updateModifiedAt = false) + shouldShowPodcastTooltip.value = false + } + private fun trackEpisodeBulkEvent(event: AnalyticsEvent, count: Int) { episodeAnalytics.trackBulkEvent( event, diff --git a/modules/features/podcasts/src/main/res/layout/fragment_podcast.xml b/modules/features/podcasts/src/main/res/layout/fragment_podcast.xml index dd1dcaa0c74..22121afd578 100644 --- a/modules/features/podcasts/src/main/res/layout/fragment_podcast.xml +++ b/modules/features/podcasts/src/main/res/layout/fragment_podcast.xml @@ -105,6 +105,12 @@ android:scaleType="centerCrop" tools:src="@tools:sample/avatars" /> + + diff --git a/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsEvent.kt b/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsEvent.kt index c5ddf9ff1c5..919a3e587bd 100644 --- a/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsEvent.kt +++ b/modules/services/analytics/src/main/java/au/com/shiftyjelly/pocketcasts/analytics/AnalyticsEvent.kt @@ -232,6 +232,8 @@ enum class AnalyticsEvent(val key: String) { PODCAST_SCREEN_REFRESH_EPISODE_LIST("podcast_screen_refresh_episode_list"), PODCAST_SCREEN_REFRESH_NEW_EPISODE_FOUND("podcast_screen_refresh_new_episode_found"), PODCAST_SCREEN_REFRESH_NO_EPISODES_FOUND("podcast_screen_refresh_no_episodes_found"), + PODCAST_REFRESH_EPISODE_TOOLTIP_SHOWN("podcast_refresh_episode_tooltip_shown"), + PODCAST_REFRESH_EPISODE_TOOLTIP_DISMISSED("podcast_refresh_episode_tooltip_dismissed"), /* Podcast Settings */ PODCAST_SETTINGS_FEED_ERROR_TAPPED("podcast_settings_feed_error_tapped"), diff --git a/modules/services/localization/src/main/res/values/strings.xml b/modules/services/localization/src/main/res/values/strings.xml index 113b0ba9148..33f54503aac 100644 --- a/modules/services/localization/src/main/res/values/strings.xml +++ b/modules/services/localization/src/main/res/values/strings.xml @@ -566,6 +566,8 @@ @string/unplayed Hide archived There was an error loading the podcast. + Fresh episodes, coming right up! + Pull down or use this menu to see if there\'s something new. Loading your podcasts Next episode any day now Next episode today diff --git a/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/Settings.kt b/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/Settings.kt index 300c2bbc38e..10bde7057ca 100644 --- a/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/Settings.kt +++ b/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/Settings.kt @@ -119,6 +119,8 @@ interface Settings { const val AUTOMOTIVE_CONNECTED_TO_MEDIA_SESSION = "automotive_connected_to_media_session" const val SHOW_REFERRALS_TOOLTIP = "show_referrals_tooltip" + + const val SHOW_PODCAST_REFRESH_TOOLTIP = "show_podcast_refresh_tooltip" } enum class NotificationChannel(val id: String) { @@ -556,6 +558,8 @@ interface Settings { val showReferralsTooltip: UserSetting + val showPodcastRefreshTooltip: UserSetting + val playerOrUpNextBottomSheetState: Flow fun updatePlayerOrUpNextBottomSheetState(state: Int) diff --git a/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/SettingsImpl.kt b/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/SettingsImpl.kt index 8c2af4d86d4..c8e44eecacc 100644 --- a/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/SettingsImpl.kt +++ b/modules/services/preferences/src/main/java/au/com/shiftyjelly/pocketcasts/preferences/SettingsImpl.kt @@ -1536,6 +1536,12 @@ class SettingsImpl @Inject constructor( sharedPrefs = sharedPreferences, ) + override val showPodcastRefreshTooltip: UserSetting = UserSetting.BoolPref( + sharedPrefKey = Settings.SHOW_PODCAST_REFRESH_TOOLTIP, + defaultValue = true, + sharedPrefs = sharedPreferences, + ) + override val referralClaimCode = UserSetting.StringPref( sharedPrefKey = "referralCode", defaultValue = "", diff --git a/modules/services/ui/src/main/java/au/com/shiftyjelly/pocketcasts/ui/helper/FragmentHostListener.kt b/modules/services/ui/src/main/java/au/com/shiftyjelly/pocketcasts/ui/helper/FragmentHostListener.kt index 7664cb2da73..b203a3729e3 100644 --- a/modules/services/ui/src/main/java/au/com/shiftyjelly/pocketcasts/ui/helper/FragmentHostListener.kt +++ b/modules/services/ui/src/main/java/au/com/shiftyjelly/pocketcasts/ui/helper/FragmentHostListener.kt @@ -21,6 +21,7 @@ interface FragmentHostListener { fun openPodcastPage(uuid: String, sourceView: String? = null) fun openCloudFiles() fun snackBarView(): View + fun setFullScreenDarkOverlayViewVisibility(visible: Boolean) fun showAccountUpgradeNow(autoSelectPlus: Boolean) fun updateStatusBar() fun getPlayerBottomSheetState(): Int