From 078949b868cdaf39b4523dfbf8466ac16b2621ae Mon Sep 17 00:00:00 2001 From: Eduarda Barbosa Date: Fri, 31 Jan 2025 12:08:09 -0300 Subject: [PATCH 1/9] implement tooltip --- .../podcasts/view/podcast/PodcastFragment.kt | 63 +++++++++ .../podcasts/view/podcast/PodcastTooltip.kt | 128 ++++++++++++++++++ .../src/main/res/layout/fragment_podcast.xml | 6 + .../src/main/res/values/strings.xml | 2 + 4 files changed, 199 insertions(+) create mode 100644 modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastTooltip.kt 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 64cb668b774..622a570e07b 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 @@ -8,8 +8,14 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.IntOffset import androidx.core.os.BundleCompat import androidx.core.os.bundleOf +import androidx.core.view.doOnNextLayout import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.updatePadding @@ -23,6 +29,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTracker import au.com.shiftyjelly.pocketcasts.analytics.SourceView +import au.com.shiftyjelly.pocketcasts.compose.AppTheme import au.com.shiftyjelly.pocketcasts.localization.extensions.getStringPlural import au.com.shiftyjelly.pocketcasts.models.entity.BaseEpisode import au.com.shiftyjelly.pocketcasts.models.entity.Bookmark @@ -90,6 +97,7 @@ import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.asObservable import kotlinx.coroutines.withContext @@ -168,6 +176,9 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { private var listState: Parcelable? = null + private var tooltipOffset by mutableStateOf(IntOffset.Zero) + private var showTooltip by mutableStateOf(false) + private var currentSnackBar: Snackbar? = null private val onScrollListener = object : RecyclerView.OnScrollListener() { @@ -735,6 +746,21 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { super.onViewCreated(view, savedInstanceState) binding?.setupMultiSelect() + binding?.composeTooltipHost?.setContent { + AppTheme(theme.activeTheme) { + if (showTooltip) { + PodcastTooltip( + title = stringResource(LR.string.podcast_feed_update_tooltip_title), + subtitle = stringResource(LR.string.podcast_feed_update_tooltip_subtitle), + offset = tooltipOffset, + onDismissRequest = { + showTooltip = false + }, + ) + } + } + } + viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.multiSelectBookmarksHelper.navigationState @@ -746,6 +772,43 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { } } } + + binding?.episodesRecyclerView?.doOnNextLayout { + showTooltipOnEpisodeOptions() + } + } + + private fun showTooltipOnEpisodeOptions() { + lifecycleScope.launch { + delay(500) + + val headerPositionInList = 2 // See: au.com.shiftyjelly.pocketcasts.podcasts.view.podcast.PodcastAdapter.setEpisodes + + val viewHolder = binding?.episodesRecyclerView?.findViewHolderForAdapterPosition(headerPositionInList) + as? PodcastAdapter.EpisodeHeaderViewHolder + + val anchorView = viewHolder?.binding?.btnEpisodeOptions + anchorView?.let { showTooltipAbove(it) } + } + } + + private fun showTooltipAbove(view: View) { + val anchorLocation = IntArray(2) + view.getLocationOnScreen(anchorLocation) + + val composeLocation = IntArray(2) + requireView().findViewById(R.id.composeTooltipHost) + .getLocationOnScreen(composeLocation) + + val anchorX = anchorLocation[0] - composeLocation[0] + (view.width / 2) + var anchorY = anchorLocation[1] - composeLocation[1] - 350 + + if (anchorY < 0) { + anchorY = 0 + } + + tooltipOffset = IntOffset(anchorX, anchorY) + showTooltip = true } private fun onShareBookmarkClick() { 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..5a51dbbbb64 --- /dev/null +++ b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastTooltip.kt @@ -0,0 +1,128 @@ +package au.com.shiftyjelly.pocketcasts.podcasts.view.podcast + +import androidx.compose.foundation.Canvas +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.components.TextH40 +import au.com.shiftyjelly.pocketcasts.compose.components.TextH70 +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, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, +) { + val tooltipColor = MaterialTheme.theme.colors.primaryUi01 + + Popup( + alignment = Alignment.TopStart, + offset = offset, + onDismissRequest = onDismissRequest, + ) { + Box( + modifier = modifier.padding(24.dp).widthIn(max = 400.dp), + ) { + TooltipContent(tooltipColor, title, subtitle, onDismissRequest) + + TooltipArrow(tooltipColor) + } + } +} + +@Composable +private fun TooltipContent( + tooltipColor: Color, + title: String, + subtitle: String, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, +) { + Card( + backgroundColor = tooltipColor, + elevation = 8.dp, + shape = RoundedCornerShape(8.dp), + modifier = modifier, + ) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.Top, + ) { + Column( + modifier = Modifier.weight(1f), + ) { + TextH40( + text = title, + modifier = Modifier.padding(bottom = 4.dp), + ) + + TextH70( + 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 { + onDismissRequest.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/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/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 From 70e4a1160057e46ec3276beb134ddb6782e567db Mon Sep 17 00:00:00 2001 From: Eduarda Barbosa Date: Thu, 6 Feb 2025 13:47:54 -0300 Subject: [PATCH 2/9] hide tooltip when tap on it --- .../podcasts/view/podcast/PodcastFragment.kt | 23 +++++++++++-------- .../podcasts/view/podcast/PodcastTooltip.kt | 7 +++--- .../podcasts/viewmodel/PodcastViewModel.kt | 8 +++++++ .../pocketcasts/preferences/Settings.kt | 4 ++++ .../pocketcasts/preferences/SettingsImpl.kt | 6 +++++ 5 files changed, 36 insertions(+), 12 deletions(-) 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 622a570e07b..c4e3b461b30 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 @@ -8,6 +8,8 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -177,7 +179,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { private var listState: Parcelable? = null private var tooltipOffset by mutableStateOf(IntOffset.Zero) - private var showTooltip by mutableStateOf(false) + private var tooltipEnabled by mutableStateOf(false) private var currentSnackBar: Snackbar? = null @@ -748,13 +750,15 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { binding?.composeTooltipHost?.setContent { AppTheme(theme.activeTheme) { - if (showTooltip) { + val shouldShow by viewModel.shouldShowPodcastTooltip.collectAsState() + AnimatedVisibility(visible = shouldShow && tooltipEnabled) { PodcastTooltip( title = stringResource(LR.string.podcast_feed_update_tooltip_title), subtitle = stringResource(LR.string.podcast_feed_update_tooltip_subtitle), offset = tooltipOffset, - onDismissRequest = { - showTooltip = false + onDismissRequest = {}, + onCloseButtonClick = { + viewModel.hidePodcastRefreshTooltip() }, ) } @@ -774,11 +778,11 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { } binding?.episodesRecyclerView?.doOnNextLayout { - showTooltipOnEpisodeOptions() + configureTooltip() } } - private fun showTooltipOnEpisodeOptions() { + private fun configureTooltip() { lifecycleScope.launch { delay(500) @@ -797,8 +801,9 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { view.getLocationOnScreen(anchorLocation) val composeLocation = IntArray(2) - requireView().findViewById(R.id.composeTooltipHost) - .getLocationOnScreen(composeLocation) + val tooltipComposeView = binding?.composeTooltipHost ?: return + + tooltipComposeView.getLocationOnScreen(composeLocation) val anchorX = anchorLocation[0] - composeLocation[0] + (view.width / 2) var anchorY = anchorLocation[1] - composeLocation[1] - 350 @@ -808,7 +813,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { } tooltipOffset = IntOffset(anchorX, anchorY) - showTooltip = true + tooltipEnabled = true } private fun onShareBookmarkClick() { 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 index 5a51dbbbb64..e16c4f65a87 100644 --- 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 @@ -37,6 +37,7 @@ fun PodcastTooltip( subtitle: String, offset: IntOffset, onDismissRequest: () -> Unit, + onCloseButtonClick: () -> Unit, modifier: Modifier = Modifier, ) { val tooltipColor = MaterialTheme.theme.colors.primaryUi01 @@ -49,7 +50,7 @@ fun PodcastTooltip( Box( modifier = modifier.padding(24.dp).widthIn(max = 400.dp), ) { - TooltipContent(tooltipColor, title, subtitle, onDismissRequest) + TooltipContent(tooltipColor, title, subtitle, onCloseButtonClick) TooltipArrow(tooltipColor) } @@ -61,7 +62,7 @@ private fun TooltipContent( tooltipColor: Color, title: String, subtitle: String, - onDismissRequest: () -> Unit, + onCloseButtonClick: () -> Unit, modifier: Modifier = Modifier, ) { Card( @@ -99,7 +100,7 @@ private fun TooltipContent( .align(Alignment.Top) .width(24.dp) .clickable { - onDismissRequest.invoke() + onCloseButtonClick.invoke() }, ) } 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/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 = "", From 18fd26607ece156dbb1c07601bd5c0f0c7a8c557 Mon Sep 17 00:00:00 2001 From: Eduarda Barbosa Date: Thu, 6 Feb 2025 14:28:50 -0300 Subject: [PATCH 3/9] hide tooltip behind feature flag --- .../pocketcasts/podcasts/view/podcast/PodcastFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c4e3b461b30..55ac7baeadb 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 @@ -751,7 +751,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { binding?.composeTooltipHost?.setContent { AppTheme(theme.activeTheme) { val shouldShow by viewModel.shouldShowPodcastTooltip.collectAsState() - AnimatedVisibility(visible = shouldShow && tooltipEnabled) { + AnimatedVisibility(visible = shouldShow && tooltipEnabled && FeatureFlag.isEnabled(Feature.PODCAST_FEED_UPDATE)) { PodcastTooltip( title = stringResource(LR.string.podcast_feed_update_tooltip_title), subtitle = stringResource(LR.string.podcast_feed_update_tooltip_subtitle), @@ -784,7 +784,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { private fun configureTooltip() { lifecycleScope.launch { - delay(500) + delay(1.seconds) // Delay to wait the recyclerview to be configured val headerPositionInList = 2 // See: au.com.shiftyjelly.pocketcasts.podcasts.view.podcast.PodcastAdapter.setEpisodes From ff7e4fe5a2ec8cd9d62ef5cf48cf40003c97c452 Mon Sep 17 00:00:00 2001 From: Eduarda Barbosa Date: Thu, 6 Feb 2025 14:45:22 -0300 Subject: [PATCH 4/9] add tracks --- .../pocketcasts/podcasts/view/podcast/PodcastFragment.kt | 4 ++++ .../pocketcasts/podcasts/view/podcast/PodcastTooltip.kt | 6 ++++++ .../com/shiftyjelly/pocketcasts/analytics/AnalyticsEvent.kt | 2 ++ 3 files changed, 12 insertions(+) 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 55ac7baeadb..a55613e9c40 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 @@ -756,8 +756,12 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { title = stringResource(LR.string.podcast_feed_update_tooltip_title), subtitle = stringResource(LR.string.podcast_feed_update_tooltip_subtitle), offset = tooltipOffset, + onTooltipShown = { + analyticsTracker.track(AnalyticsEvent.PODCAST_REFRESH_EPISODE_TOOLTIP_SHOWN) + }, onDismissRequest = {}, onCloseButtonClick = { + analyticsTracker.track(AnalyticsEvent.PODCAST_REFRESH_EPISODE_TOOLTIP_DISMISSED) viewModel.hidePodcastRefreshTooltip() }, ) 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 index e16c4f65a87..259ad75821a 100644 --- 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 @@ -26,6 +26,7 @@ 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.TextH40 import au.com.shiftyjelly.pocketcasts.compose.components.TextH70 import au.com.shiftyjelly.pocketcasts.compose.theme @@ -36,12 +37,17 @@ 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, 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"), From 84b8428794c0395982232b44ebd17e924b606bd9 Mon Sep 17 00:00:00 2001 From: Eduarda Barbosa Date: Thu, 6 Feb 2025 15:11:54 -0300 Subject: [PATCH 5/9] set tooltip overlay --- .../au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt | 4 ++++ app/src/main/res/layout/activity_main.xml | 7 +++++++ .../pocketcasts/AutomotiveSettingsActivity.kt | 3 +++ .../podcasts/view/podcast/PodcastFragment.kt | 10 +++++++--- .../pocketcasts/ui/helper/FragmentHostListener.kt | 1 + 5 files changed, 22 insertions(+), 3 deletions(-) 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..aaec0f14543 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" /> + + Date: Fri, 7 Feb 2025 10:37:36 -0300 Subject: [PATCH 6/9] minor ui improvements --- app/src/main/res/layout/activity_main.xml | 2 +- .../podcasts/view/podcast/PodcastFragment.kt | 2 +- .../podcasts/view/podcast/PodcastTooltip.kt | 15 ++++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index aaec0f14543..8fdc761e374 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -66,7 +66,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - Date: Fri, 7 Feb 2025 12:36:56 -0300 Subject: [PATCH 7/9] fix issue when has low internet connection --- .../podcasts/view/podcast/PodcastFragment.kt | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) 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 02a356a7422..764a50d9764 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 @@ -8,15 +8,16 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.core.os.BundleCompat import androidx.core.os.bundleOf -import androidx.core.view.doOnNextLayout import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.updatePadding @@ -178,7 +179,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { private var listState: Parcelable? = null private var tooltipOffset by mutableStateOf(IntOffset.Zero) - private var tooltipEnabled by mutableStateOf(false) + private var canShowTooltip by mutableStateOf(false) private var currentSnackBar: Snackbar? = null @@ -749,8 +750,15 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { binding?.composeTooltipHost?.setContent { AppTheme(theme.activeTheme) { - val shouldShow by viewModel.shouldShowPodcastTooltip.collectAsState() - if (shouldShow && tooltipEnabled && FeatureFlag.isEnabled(Feature.PODCAST_FEED_UPDATE)) { + val shouldShowPodcastTooltip by viewModel.shouldShowPodcastTooltip.collectAsState() + + var show by remember { mutableStateOf(true) } + + LaunchedEffect(canShowTooltip) { + show = canShowTooltip && FeatureFlag.isEnabled(Feature.PODCAST_FEED_UPDATE) && shouldShowPodcastTooltip + } + + if (show) { PodcastTooltip( title = stringResource(LR.string.podcast_feed_update_tooltip_title), subtitle = stringResource(LR.string.podcast_feed_update_tooltip_subtitle), @@ -761,7 +769,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { }, onDismissRequest = { (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) - tooltipEnabled = false + canShowTooltip = false }, onCloseButtonClick = { (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) @@ -769,6 +777,8 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { viewModel.hidePodcastRefreshTooltip() }, ) + } else { + (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) } } } @@ -784,10 +794,6 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { } } } - - binding?.episodesRecyclerView?.doOnNextLayout { - configureTooltip() - } } private fun configureTooltip() { @@ -821,7 +827,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { } tooltipOffset = IntOffset(anchorX, anchorY) - tooltipEnabled = true + canShowTooltip = true } private fun onShareBookmarkClick() { @@ -977,6 +983,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { podcast = state.podcast, context = requireContext(), ) + configureTooltip() } PodcastTab.BOOKMARKS -> { adapter?.setBookmarks( @@ -1079,6 +1086,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { binding = null currentSnackBar?.dismiss() currentSnackBar = null + (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) } private fun archiveAllPlayed() { From 0f0bbfbac64e06f5c3b989cb7eec1d6664c249fe Mon Sep 17 00:00:00 2001 From: Eduarda Barbosa Date: Fri, 7 Feb 2025 12:40:16 -0300 Subject: [PATCH 8/9] hide tooltip when tap out of it --- .../pocketcasts/podcasts/view/podcast/PodcastFragment.kt | 1 + 1 file changed, 1 insertion(+) 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 764a50d9764..1d8afaa477d 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 @@ -769,6 +769,7 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { }, onDismissRequest = { (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) + viewModel.hidePodcastRefreshTooltip() canShowTooltip = false }, onCloseButtonClick = { From 34a6e8f4eb111df61c3fd495423cfa1fc4806626 Mon Sep 17 00:00:00 2001 From: Eduarda Barbosa Date: Fri, 7 Feb 2025 13:42:56 -0300 Subject: [PATCH 9/9] collapse header in order to show tooltip in small devices --- .../podcasts/view/podcast/PodcastAdapter.kt | 4 ++-- .../podcasts/view/podcast/PodcastFragment.kt | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastAdapter.kt b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastAdapter.kt index e762e9a19c0..e051f7931c4 100644 --- a/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastAdapter.kt +++ b/modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/podcast/PodcastAdapter.kt @@ -446,10 +446,10 @@ class PodcastAdapter( } } - fun setPodcast(podcast: Podcast) { + fun setPodcast(podcast: Podcast, forceHeaderExpanded: Boolean? = null) { // expand the podcast description and details if the user hasn't subscribed if (this.podcast.uuid != podcast.uuid) { - headerExpanded = !podcast.isSubscribed + headerExpanded = forceHeaderExpanded ?: !podcast.isSubscribed ratingsViewModel.loadRatings(podcast.uuid) ratingsViewModel.refreshPodcastRatings(podcast.uuid) onHeaderSummaryToggled(headerExpanded, false) 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 1d8afaa477d..bb420cc5b9c 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 @@ -754,8 +754,8 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { var show by remember { mutableStateOf(true) } - LaunchedEffect(canShowTooltip) { - show = canShowTooltip && FeatureFlag.isEnabled(Feature.PODCAST_FEED_UPDATE) && shouldShowPodcastTooltip + LaunchedEffect(canShowTooltip, shouldShowPodcastTooltip) { + show = canShowTooltip && shouldShowPodcastTooltip && FeatureFlag.isEnabled(Feature.PODCAST_FEED_UPDATE) } if (show) { @@ -768,14 +768,11 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { analyticsTracker.track(AnalyticsEvent.PODCAST_REFRESH_EPISODE_TOOLTIP_SHOWN) }, onDismissRequest = { - (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) - viewModel.hidePodcastRefreshTooltip() - canShowTooltip = false + hideTooltip() }, onCloseButtonClick = { - (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) analyticsTracker.track(AnalyticsEvent.PODCAST_REFRESH_EPISODE_TOOLTIP_DISMISSED) - viewModel.hidePodcastRefreshTooltip() + hideTooltip() }, ) } else { @@ -831,6 +828,12 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { canShowTooltip = true } + private fun hideTooltip() { + (activity as? FragmentHostListener)?.setFullScreenDarkOverlayViewVisibility(false) + viewModel.hidePodcastRefreshTooltip() + canShowTooltip = false + } + private fun onShareBookmarkClick() { lifecycleScope.launch { val (podcast, episode, bookmark) = viewModel.getSharedBookmark() ?: return@launch @@ -948,7 +951,8 @@ class PodcastFragment : BaseFragment(), Toolbar.OnMenuItemClickListener { binding?.toolbar?.setBackgroundColor(backgroundColor) binding?.headerBackgroundPlaceholder?.setBackgroundColor(backgroundColor) - adapter?.setPodcast(podcast) + val forceHeaderExpanded = !viewModel.shouldShowPodcastTooltip.value && FeatureFlag.isEnabled(Feature.PODCAST_FEED_UPDATE) + adapter?.setPodcast(podcast, forceHeaderExpanded = forceHeaderExpanded) viewModel.archiveEpisodeLimit()