Skip to content
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

End of Year: Add modal structure #413

Merged
merged 4 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar
import androidx.compose.ui.res.stringResource
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
Expand All @@ -26,6 +27,10 @@ import au.com.shiftyjelly.pocketcasts.account.PromoCodeUpgradedFragment
import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsEvent
import au.com.shiftyjelly.pocketcasts.analytics.AnalyticsTrackerWrapper
import au.com.shiftyjelly.pocketcasts.analytics.FirebaseAnalyticsTracker
import au.com.shiftyjelly.pocketcasts.compose.AppTheme
import au.com.shiftyjelly.pocketcasts.compose.bottomsheet.BottomSheetContentState
import au.com.shiftyjelly.pocketcasts.compose.bottomsheet.BottomSheetContentState.Content.Button
import au.com.shiftyjelly.pocketcasts.compose.bottomsheet.ModalBottomSheet
import au.com.shiftyjelly.pocketcasts.databinding.ActivityMainBinding
import au.com.shiftyjelly.pocketcasts.discover.view.DiscoverFragment
import au.com.shiftyjelly.pocketcasts.filters.FiltersFragment
Expand Down Expand Up @@ -249,6 +254,10 @@ class MainActivity :
handleIntent(intent, savedInstanceState)

updateSystemColors()

if (BuildConfig.END_OF_YEAR_ENABLED) {
setupEndOfYearLaunchBottomSheet()
}
}

override fun onStart() {
Expand Down Expand Up @@ -446,6 +455,27 @@ class MainActivity :
showBottomSheet(UpNextFragment.newInstance(source = source))
}

private fun setupEndOfYearLaunchBottomSheet() {
binding.modalBottomSheet.setContent {
AppTheme(themeType = theme.activeTheme) {
ModalBottomSheet(
showOnLoad = true,
content = BottomSheetContentState.Content(
titleText = stringResource(LR.string.end_of_year_launch_modal_title),
summaryText = stringResource(LR.string.end_of_year_launch_modal_summary),
primaryButton = Button.Primary(
label = stringResource(LR.string.end_of_year_launch_modal_primary_button_title),
onClick = {}
),
secondaryButton = Button.Secondary(
label = stringResource(LR.string.end_of_year_launch_modal_secondary_button_title),
),
)
)
}
}
}

@OptIn(DelicateCoroutinesApi::class)
@Suppress("DEPRECATION")
private fun setupPlayerViews() {
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
android:clickable="true"
android:translationZ="200dp"/>

<androidx.compose.ui.platform.ComposeView
android:id="@+id/modalBottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:translationZ="200dp"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

<au.com.shiftyjelly.pocketcasts.views.component.RadioactiveLineView
Expand Down
3 changes: 3 additions & 0 deletions base.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ android {
buildConfigField "String", "SETTINGS_ENCRYPT_SECRET", "\"${project.settingsEncryptSecret}\""
buildConfigField "String", "SHARING_SERVER_SECRET", "\"${project.sharingServerSecret}\""

// Feature Flags
buildConfigField "boolean", "END_OF_YEAR_ENABLED", "false"

testInstrumentationRunner project.testInstrumentationRunner
testApplicationId "au.com.shiftyjelly.pocketcasts.test" + project.name.replace("-", "_")
vectorDrawables.useSupportLibrary = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package au.com.shiftyjelly.pocketcasts.compose.bottomsheet

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import au.com.shiftyjelly.pocketcasts.compose.AppThemeWithBackground
import au.com.shiftyjelly.pocketcasts.compose.components.TextH40
import au.com.shiftyjelly.pocketcasts.compose.components.TextP50
import au.com.shiftyjelly.pocketcasts.compose.preview.ThemePreviewParameterProvider
import au.com.shiftyjelly.pocketcasts.compose.theme
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme

private val outlinedBorder: BorderStroke
@Composable
get() = BorderStroke(2.dp, MaterialTheme.colors.primary)

class BottomSheetContentState(
val content: Content,
) {
data class Content(
val titleText: String,
val summaryText: String,
val primaryButton: Button.Primary,
val secondaryButton: Button.Secondary? = null,
) {
sealed interface Button {
val label: String
val onClick: (() -> Unit)?

data class Primary(
override val label: String,
override val onClick: () -> Unit,
) : Button

data class Secondary(
override val label: String,
override val onClick: (() -> Unit)? = null,
mchowning marked this conversation as resolved.
Show resolved Hide resolved
) : Button
}
}
}

@Composable
fun BottomSheetContent(
state: BottomSheetContentState,
onDismiss: () -> Unit,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.fillMaxWidth()
.wrapContentWidth(unbounded = false)
.wrapContentHeight(unbounded = true)
.padding(24.dp)
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
val content = state.content

Spacer(modifier = modifier.height(16.dp))

TextH40(
text = content.titleText,
color = MaterialTheme.theme.colors.primaryText01,
)

Spacer(modifier = modifier.height(16.dp))

TextP50(
text = content.summaryText,
style = MaterialTheme.typography.body1,
color = MaterialTheme.theme.colors.primaryText02,
textAlign = TextAlign.Center
)

Spacer(modifier = modifier.height(16.dp))

Button(onClick = content.primaryButton.onClick) {
Text(text = content.primaryButton.label)
}

Spacer(modifier = modifier.height(8.dp))

if (content.secondaryButton != null) {
OutlinedButton(
border = outlinedBorder,
onClick = {
onDismiss.invoke()
content.secondaryButton.onClick?.invoke()
}
) {
Text(text = content.secondaryButton.label)
}
}
}
}
}

@Preview(showBackground = true)
@Composable
private fun BottomSheetContentPreview(
@PreviewParameter(ThemePreviewParameterProvider::class) themeType: Theme.ThemeType,
) {
AppThemeWithBackground(themeType) {
BottomSheetContent(
state = BottomSheetContentState(
content = BottomSheetContentState.Content(
titleText = "Heading",
summaryText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.",
primaryButton = BottomSheetContentState.Content.Button.Primary(
label = "Confirm",
onClick = {}
),
secondaryButton = BottomSheetContentState.Content.Button.Secondary(
label = "Not now",
),
)
),
onDismiss = {}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package au.com.shiftyjelly.pocketcasts.compose.bottomsheet

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterialApi::class)
Copy link
Contributor Author

@ashiagr ashiagr Oct 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used ModalBottomSheetLayout which is experimental right now and I had to opt-in for ExperimentalMaterialApi. It made things really simple, I also tested it works on older Android versions (API level 23). But let me know if it's a deal breaker.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine. With compose still relatively young I don't think we should be too hesitant about using experimental apis when they make sense because a lot of fairly fundamental stuff still seems to be experimental.

@Composable
fun ModalBottomSheet(
showOnLoad: Boolean = false,
content: BottomSheetContentState.Content,
) {
val sheetState = rememberModalBottomSheetState(
initialValue = if (showOnLoad) ModalBottomSheetValue.Expanded else ModalBottomSheetValue.Hidden,
)
val coroutineScope = rememberCoroutineScope()

BackHandler(sheetState.isVisible) {
hideBottomSheet(coroutineScope, sheetState)
}

ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
BottomSheetContent(
state = BottomSheetContentState(content),
onDismiss = {
hideBottomSheet(coroutineScope, sheetState)
}
)
},
sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
scrimColor = Color.Black.copy(alpha = .25f),
content = {}
)
}

@OptIn(ExperimentalMaterialApi::class)
fun hideBottomSheet(coroutineScope: CoroutineScope, sheetState: ModalBottomSheetState) {
coroutineScope.launch {
sheetState.hide()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,16 @@ fun TextP50(
modifier: Modifier = Modifier,
color: Color? = null,
maxLines: Int? = null,
style: TextStyle? = null
style: TextStyle? = null,
textAlign: TextAlign? = null,
) {
TextP50(
text = AnnotatedString(text),
modifier = modifier,
color = color,
maxLines = maxLines,
style = style
style = style,
textAlign = textAlign,
)
}

Expand All @@ -147,7 +149,8 @@ fun TextP50(
modifier: Modifier = Modifier,
color: Color? = null,
maxLines: Int? = null,
style: TextStyle? = null
style: TextStyle? = null,
textAlign: TextAlign? = null,
) {
Text(
text = text,
Expand All @@ -157,6 +160,7 @@ fun TextP50(
maxLines = maxLines ?: Int.MAX_VALUE,
overflow = TextOverflow.Ellipsis,
style = style ?: LocalTextStyle.current,
textAlign = textAlign,
modifier = modifier
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1377,4 +1377,11 @@
<string name="server_files_storage_limit_exceeded">You have exceeded the storage limit for your account.</string>
<string name="server_files_title_required">Title is required.</string>

<!-- End of Year -->

<string name="end_of_year_launch_modal_title">Your Year in Podcasts</string>
<string name="end_of_year_launch_modal_summary">See your top podcasts, categories, listening stats and more. Share with friends and shout out your favorite creators!</string>
<string name="end_of_year_launch_modal_primary_button_title">View my 2022</string>
<string name="end_of_year_launch_modal_secondary_button_title">Not Now</string>

</resources>