-
Notifications
You must be signed in to change notification settings - Fork 210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate share fragment to Jetpack Compose #1185
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,124 +4,264 @@ import android.os.Bundle | |
import android.view.LayoutInflater | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import androidx.core.view.isVisible | ||
import androidx.compose.foundation.background | ||
import androidx.compose.foundation.clickable | ||
import androidx.compose.foundation.interaction.MutableInteractionSource | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.foundation.layout.wrapContentSize | ||
import androidx.compose.foundation.shape.RoundedCornerShape | ||
import androidx.compose.material.Divider | ||
import androidx.compose.material.MaterialTheme | ||
import androidx.compose.material.Surface | ||
import androidx.compose.material.Text | ||
import androidx.compose.material.ripple.rememberRipple | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.collectAsState | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.draw.clip | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.platform.ComposeView | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.text.font.FontFamily | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.DpSize | ||
import androidx.compose.ui.unit.dp | ||
import androidx.compose.ui.unit.sp | ||
import androidx.fragment.app.activityViewModels | ||
import androidx.lifecycle.asFlow | ||
import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTrackerWrapper | ||
import au.com.shiftyjelly.pocketcasts.analytics.SourceView | ||
import au.com.shiftyjelly.pocketcasts.compose.AppTheme | ||
import au.com.shiftyjelly.pocketcasts.compose.theme | ||
import au.com.shiftyjelly.pocketcasts.models.entity.BaseEpisode | ||
import au.com.shiftyjelly.pocketcasts.models.entity.Podcast | ||
import au.com.shiftyjelly.pocketcasts.models.entity.PodcastEpisode | ||
import au.com.shiftyjelly.pocketcasts.player.databinding.FragmentShareBinding | ||
import au.com.shiftyjelly.pocketcasts.player.viewmodel.PlayerViewModel | ||
import au.com.shiftyjelly.pocketcasts.repositories.podcast.SharePodcastHelper | ||
import au.com.shiftyjelly.pocketcasts.repositories.podcast.SharePodcastHelper.ShareType | ||
import au.com.shiftyjelly.pocketcasts.ui.helper.StatusBarColor | ||
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme | ||
import au.com.shiftyjelly.pocketcasts.views.extensions.applyColor | ||
import au.com.shiftyjelly.pocketcasts.views.fragments.BaseDialogFragment | ||
import com.google.android.material.bottomsheet.BottomSheetBehavior | ||
import com.google.android.material.bottomsheet.BottomSheetDialog | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import io.reactivex.disposables.Disposable | ||
import javax.inject.Inject | ||
import au.com.shiftyjelly.pocketcasts.localization.R as IR | ||
|
||
@AndroidEntryPoint | ||
class ShareFragment : BaseDialogFragment() { | ||
|
||
@Inject lateinit var analyticsTracker: AnalyticsTrackerWrapper | ||
override val statusBarColor: StatusBarColor? = null | ||
|
||
private lateinit var composeView: ComposeView | ||
private val viewModel: PlayerViewModel by activityViewModels() | ||
private var binding: FragmentShareBinding? = null | ||
private var disposable: Disposable? = null | ||
|
||
override fun onPause() { | ||
super.onPause() | ||
|
||
disposable?.dispose() | ||
} | ||
|
||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { | ||
val binding = FragmentShareBinding.inflate(inflater, container, false) | ||
this.binding = binding | ||
|
||
val podcast = viewModel.podcast | ||
val episode = viewModel.episode | ||
return ComposeView(requireContext()).also { | ||
composeView = it | ||
} | ||
} | ||
|
||
binding.buttonSharePodcast.setOnClickListener { | ||
if (podcast != null) { | ||
SharePodcastHelper( | ||
podcast, | ||
null, | ||
null, | ||
requireContext(), | ||
ShareType.PODCAST, | ||
SourceView.PLAYER, | ||
analyticsTracker | ||
).showShareDialogDirect() | ||
@Suppress("DEPRECATION") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
override fun onActivityCreated(savedInstanceState: Bundle?) { | ||
super.onActivityCreated(savedInstanceState) | ||
composeView.setContent { | ||
val podcast = viewModel.podcast | ||
val episode = viewModel.episode | ||
val listData = viewModel.listDataLive.asFlow().collectAsState(initial = null) | ||
listData.value?.let { | ||
ShareScreen( | ||
podcast = viewModel.podcast, | ||
episode = viewModel.episode, | ||
backgroundColor = Color(it.podcastHeader.backgroundColor), | ||
onSharePodcast = { | ||
if (podcast != null) { | ||
SharePodcastHelper( | ||
podcast, | ||
null, | ||
null, | ||
requireContext(), | ||
SharePodcastHelper.ShareType.PODCAST, | ||
SourceView.PLAYER, | ||
analyticsTracker | ||
).showShareDialogDirect() | ||
} | ||
}, | ||
onShareEpisode = { | ||
if (podcast != null && episode is PodcastEpisode) { | ||
SharePodcastHelper( | ||
podcast, | ||
episode, | ||
null, | ||
requireContext(), | ||
SharePodcastHelper.ShareType.EPISODE, | ||
SourceView.PLAYER, | ||
analyticsTracker | ||
).showShareDialogDirect() | ||
} | ||
}, | ||
onShareCurrentPosition = { | ||
if (podcast != null && episode is PodcastEpisode) { | ||
SharePodcastHelper( | ||
podcast, | ||
episode, | ||
episode.playedUpTo, | ||
requireContext(), | ||
SharePodcastHelper.ShareType.CURRENT_TIME, | ||
SourceView.PLAYER, | ||
analyticsTracker | ||
).showShareDialogDirect() | ||
} | ||
}, | ||
onShareOpenFileIn = { | ||
if (podcast != null && episode is PodcastEpisode) { | ||
SharePodcastHelper( | ||
podcast, | ||
episode, | ||
episode.playedUpTo, | ||
requireContext(), | ||
SharePodcastHelper.ShareType.EPISODE_FILE, | ||
SourceView.PLAYER, | ||
analyticsTracker | ||
).sendFile() | ||
} | ||
} | ||
) | ||
} | ||
close() | ||
} | ||
binding.buttonShareEpisode.setOnClickListener { | ||
if (podcast != null && episode is PodcastEpisode) { | ||
SharePodcastHelper( | ||
podcast, | ||
episode, | ||
null, | ||
requireContext(), | ||
ShareType.EPISODE, | ||
SourceView.PLAYER, | ||
analyticsTracker | ||
).showShareDialogDirect() | ||
viewModel.playingEpisodeLive.observe(viewLifecycleOwner) { (_, backgroundColor) -> | ||
applyColor(theme, backgroundColor) | ||
} | ||
close() | ||
} | ||
binding.buttonShareCurrentPosition.setOnClickListener { | ||
if (podcast != null && episode is PodcastEpisode) { | ||
SharePodcastHelper( | ||
podcast, | ||
episode, | ||
episode.playedUpTo, | ||
requireContext(), | ||
ShareType.CURRENT_TIME, | ||
SourceView.PLAYER, | ||
analyticsTracker | ||
).showShareDialogDirect() | ||
(dialog as? BottomSheetDialog)?.behavior?.state = BottomSheetBehavior.STATE_EXPANDED | ||
} | ||
} | ||
|
||
@Composable | ||
private fun ShareScreen( | ||
podcast: Podcast?, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As we are passing |
||
episode: BaseEpisode?, | ||
backgroundColor: Color, | ||
onSharePodcast: () -> Unit, | ||
onShareEpisode: () -> Unit, | ||
onShareCurrentPosition: () -> Unit, | ||
onShareOpenFileIn: () -> Unit, | ||
) { | ||
Surface( | ||
modifier = Modifier | ||
.fillMaxWidth(), | ||
color = backgroundColor | ||
) { | ||
Column( | ||
modifier = Modifier | ||
.padding(bottom = 24.dp) | ||
) { | ||
Pill( | ||
modifier = Modifier | ||
.align(Alignment.CenterHorizontally) | ||
) | ||
if (podcast != null) { | ||
ShareButton( | ||
modifier = Modifier | ||
.padding(top = 20.dp), | ||
label = IR.string.share_podcast, | ||
onClick = onSharePodcast | ||
) | ||
} | ||
close() | ||
} | ||
binding.buttonOpenFileIn.setOnClickListener { | ||
if (podcast != null && episode is PodcastEpisode) { | ||
SharePodcastHelper( | ||
podcast, | ||
episode, | ||
episode.playedUpTo, | ||
requireContext(), | ||
ShareType.EPISODE_FILE, | ||
SourceView.PLAYER, | ||
analyticsTracker | ||
).sendFile() | ||
Divider( | ||
color = Color.LightGray.copy(0.3f), | ||
thickness = 1.dp, | ||
) | ||
Divider(thickness = 1.dp) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate divider? There's one above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okay |
||
if (podcast != null && episode is BaseEpisode) { | ||
ShareButton( | ||
label = IR.string.podcast_share_episode, | ||
onClick = onShareEpisode | ||
) | ||
Divider( | ||
color = Color.LightGray.copy(0.3f), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should consider to use |
||
thickness = 1.dp, | ||
) | ||
ShareButton( | ||
label = IR.string.podcast_share_current_position, | ||
onClick = onShareCurrentPosition | ||
) | ||
} | ||
if (podcast != null && episode is BaseEpisode && episode.isDownloaded) { | ||
ShareButton(label = IR.string.podcast_share_open_file_in, onClick = onShareOpenFileIn) | ||
} | ||
close() | ||
} | ||
|
||
binding.sharePodcast.isVisible = podcast != null | ||
binding.shareEpisode.isVisible = episode != null | ||
binding.shareCurrentPosition.isVisible = episode != null | ||
binding.openFileIn.isVisible = episode != null && episode.isDownloaded | ||
|
||
return binding.root | ||
} | ||
} | ||
|
||
@Suppress("DEPRECATION") | ||
override fun onActivityCreated(savedInstanceState: Bundle?) { | ||
super.onActivityCreated(savedInstanceState) | ||
@Composable | ||
private fun ShareButton( | ||
label: Int, | ||
onClick: () -> Unit, | ||
modifier: Modifier = Modifier | ||
) { | ||
Box( | ||
modifier = modifier | ||
.fillMaxWidth() | ||
.clickable( | ||
interactionSource = remember { MutableInteractionSource() }, | ||
indication = rememberRipple(color = Color.LightGray) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should consider to use |
||
) { onClick() } | ||
.height(64.dp) | ||
.padding(start = 32.dp) | ||
.wrapContentSize(Alignment.CenterStart) | ||
) { | ||
Text( | ||
text = stringResource(id = label), | ||
color = MaterialTheme.theme.colors.playerContrast01, | ||
fontFamily = FontFamily.SansSerif, | ||
fontSize = 16.sp | ||
) | ||
} | ||
} | ||
|
||
viewModel.playingEpisodeLive.observe(viewLifecycleOwner) { (_, backgroundColor) -> | ||
applyColor(theme, backgroundColor) | ||
} | ||
private val PillSize = DpSize(width = 48.dp, height = 4.dp) | ||
private val PillCornerRadius = 10.dp | ||
|
||
(dialog as? BottomSheetDialog)?.behavior?.state = BottomSheetBehavior.STATE_EXPANDED | ||
} | ||
@Composable | ||
private fun Pill( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There can be reusable components that can be extracted out like this This will help us decide the architecture for the rest of the "Playback Effects" dialogs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, the pill can be reused. Regarding the Options Dialog, it would work but one concern: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Could we pass a custom option component (which is only shown on a single screen) to the @mchowning, @geekygecko any thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Mzazi25 👋 I had a brief discussion with the team, with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure thing. Let me work on that |
||
modifier: Modifier = Modifier, | ||
) { | ||
Box( | ||
modifier = modifier | ||
.size(PillSize) | ||
.clip(RoundedCornerShape(PillCornerRadius)) | ||
.background(MaterialTheme.theme.colors.primaryText02) | ||
) | ||
} | ||
|
||
private fun close() { | ||
dismiss() | ||
@Preview | ||
@Composable | ||
private fun ShareScreenPrev( | ||
theme: Theme.ThemeType = Theme.ThemeType.DARK | ||
) { | ||
AppTheme(themeType = theme) { | ||
ShareScreen( | ||
podcast = Podcast(), | ||
episode = null, | ||
backgroundColor = Color.DarkGray, | ||
onSharePodcast = {}, | ||
onShareEpisode = { }, | ||
onShareCurrentPosition = {}, | ||
onShareOpenFileIn = {} | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we're not using
disposable
anywhere. Can we remove the variable andonPause
override?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing