Skip to content

Commit 0055448

Browse files
authored
[PF-190, PF-192]: Integrate Project Story Components into PreLaunchProjectPageActivity (#2539)
Co-authored-by: Tony Teate <4317686+tonyteate@users.noreply.github.com>
1 parent 8ab01ef commit 0055448

12 files changed

Lines changed: 334 additions & 66 deletions

File tree

app/src/main/graphql/projectStory.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ fragment storyRichTextComponentFragment on RichTextComponent {
6363
iframeUrl
6464
width
6565
height
66+
providerName
6667
html
6768
}
6869
... on RichTextPhoto {

app/src/main/java/com/kickstarter/features/projectstory/ProjectStoryViewModel.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModelProvider
66
import androidx.lifecycle.viewModelScope
77
import com.kickstarter.features.projectstory.data.StoriedProject
88
import com.kickstarter.libs.Environment
9+
import com.kickstarter.libs.featureflag.StatsigGateKey
910
import com.kickstarter.libs.utils.extensions.isProjectUri
1011
import kotlinx.coroutines.CoroutineDispatcher
1112
import kotlinx.coroutines.Job
@@ -22,13 +23,15 @@ data class ProjectStoryUiState(
2223
val storiedProject: StoriedProject? = null,
2324
)
2425

26+
private object StatsigGateException : Exception()
2527
class ProjectStoryViewModel(
2628
private val environment: Environment,
2729
testDispatcher: CoroutineDispatcher? = null
2830
) : ViewModel() {
2931

3032
private val scope = viewModelScope + (testDispatcher ?: EmptyCoroutineContext)
31-
private val apolloClient = environment.apolloClientV2()!!
33+
private val apolloClient = requireNotNull(environment.apolloClientV2())
34+
private val statsigClient = requireNotNull(environment.statsigClient())
3235

3336
private val _projectStoryUiState = MutableStateFlow(ProjectStoryUiState())
3437
val projectStoryUiState = _projectStoryUiState.asStateFlow()
@@ -53,6 +56,11 @@ class ProjectStoryViewModel(
5356
}
5457

5558
fun fetchProject() {
59+
if (!statsigClient.isReady.value || !statsigClient.checkGate(StatsigGateKey.ANDROID_PRELAUNCH_PROJECT_STORY.key)) {
60+
_projectStoryUiState.value = ProjectStoryUiState(isLoading = false, error = StatsigGateException)
61+
return
62+
}
63+
5664
if (projectStoryUiState.value.storiedProject?.project?.slug() == projectSlug) {
5765
Timber.d("Project already fetched for slug: $projectSlug")
5866
return

app/src/main/java/com/kickstarter/features/projectstory/data/RichText.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ sealed interface RichTextItem {
6363
val __typename: String,
6464
val type: String,
6565
val iframeUrl: String,
66+
val width: Int,
67+
val height: Int,
68+
val providerName: String,
69+
val html: String
6670
) : RichTextItem
6771
data class ListOpen(
6872
val __typename: String,

app/src/main/java/com/kickstarter/features/projectstory/ui/ProjectStoryCaptionedImage.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import coil.imageLoader
3737
import coil.request.ImageRequest
3838
import coil.size.SizeResolver
3939
import com.kickstarter.ui.compose.designsystem.KSTheme
40+
import com.kickstarter.ui.compose.designsystem.KSTheme.colors
4041
import com.kickstarter.ui.compose.designsystem.grey_04
4142
import org.joda.time.DateTime
4243

@@ -191,7 +192,7 @@ fun ProjectStoryCaptionedImage(
191192
.fillMaxWidth()
192193
.testTag(ProjectStoryCaptionedImageTestTag.CAPTION.name),
193194
text = caption,
194-
color = if (link.isNullOrBlank()) Color.Unspecified else StoryTheme.InlineStyles.link.color,
195+
color = if (link.isNullOrBlank()) colors.kds_support_700 else StoryTheme.InlineStyles.link.color,
195196
fontStyle = FontStyle.Italic,
196197
textDecoration = if (link.isNullOrBlank()) null else TextDecoration.Underline,
197198
textAlign = TextAlign.Center,

app/src/main/java/com/kickstarter/features/projectstory/ui/ProjectStoryComponents.kt

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
1111
import androidx.compose.runtime.remember
1212
import androidx.compose.ui.Modifier
1313
import androidx.compose.ui.layout.LocalPinnableContainer
14+
import androidx.compose.ui.layout.PinnableContainer
1415
import androidx.compose.ui.text.AnnotatedString
1516
import androidx.compose.ui.text.Bullet
1617
import androidx.compose.ui.text.LinkAnnotation
@@ -30,16 +31,17 @@ import coil.compose.AsyncImagePainter
3031
import com.kickstarter.features.projectstory.data.RichTextItem
3132
import com.kickstarter.libs.utils.Secrets
3233
import com.kickstarter.libs.utils.extensions.getEnvironment
34+
import com.kickstarter.ui.compose.designsystem.KSTheme.colors
3335
import com.kickstarter.ui.compose.designsystem.kds_create_700
3436
import timber.log.Timber
3537

3638
object StoryTheme {
3739
object Typography {
3840
val paragraph = TextStyle.Default.merge(fontSize = 16.sp)
39-
val heading1 = TextStyle.Default.merge(fontSize = 28.sp)
40-
val heading2 = TextStyle.Default.merge(fontSize = 26.sp)
41-
val heading3 = TextStyle.Default.merge(fontSize = 24.sp)
42-
val heading4 = TextStyle.Default.merge(fontSize = 22.sp)
41+
val heading1 = TextStyle.Default.merge(fontSize = 32.sp)
42+
val heading2 = TextStyle.Default.merge(fontSize = 30.sp)
43+
val heading3 = TextStyle.Default.merge(fontSize = 28.sp)
44+
val heading4 = TextStyle.Default.merge(fontSize = 20.sp)
4345
}
4446

4547
object InlineStyles {
@@ -120,7 +122,7 @@ fun RichTextItemTextComponent(item: RichTextItem.Text) {
120122
else -> baseAnnotatedString
121123
}
122124

123-
Text(annotatedString, style = textStyle)
125+
Text(annotatedString, color = colors.kds_support_700, style = textStyle)
124126
}
125127

126128
private fun parseRichTextChildrenOfRichText(children: List<RichTextItem.Text.ChildParagraph>): AnnotatedString {
@@ -149,14 +151,6 @@ private fun parseRichTextChildrenOfRichText(children: List<RichTextItem.Text.Chi
149151
)
150152
}
151153

152-
/* Join all sibling text with a space _except_ if the text starts with certain
153-
* kinds of punctuation. This is to handle a peculiarity of how the server-side parser
154-
* deals w/ spaces, and is likely to be changed on the server-side in the near future. */
155-
val firstCharacter = text.firstOrNull()
156-
if (index != 0 && firstCharacter.needsLeadingSpace()) {
157-
append(" ")
158-
}
159-
160154
when {
161155
linkAnnotation != null -> {
162156
withLink(linkAnnotation) {
@@ -204,7 +198,7 @@ fun WebViewComponent(url: String) {
204198
Timber.d("WebViewComponent($url)")
205199
lateinit var context: Context
206200

207-
val pinnedHandle = LocalPinnableContainer.current?.pin()
201+
val pinnedHandle: PinnableContainer.PinnedHandle? = LocalPinnableContainer.current?.pin()
208202
Timber.d("pinnedHandle: $pinnedHandle")
209203

210204
@SuppressLint("SetJavaScriptEnabled")
@@ -224,10 +218,11 @@ fun WebViewComponent(url: String) {
224218
}
225219
},
226220
update = {
227-
if (it.url.isNullOrEmpty()) {
221+
val wv = it
222+
if (wv.url.isNullOrEmpty()) {
228223
val baseUrl = context.getEnvironment()?.webEndpoint() ?: Secrets.WebEndpoint.PRODUCTION
229224
val additionalHeaders = mapOf("Referer" to baseUrl)
230-
it.loadUrl(url, additionalHeaders)
225+
wv.loadUrl(url, additionalHeaders)
231226
}
232227
},
233228
onRelease = {

app/src/main/java/com/kickstarter/libs/featureflag/StatsigClient.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import timber.log.Timber
3333

3434
enum class StatsigGateKey(val key: String) {
3535
ANDROID_VIDEO_FEED("android_video_feed"),
36-
ANDROID_PRELAUNCH_SOCIAL_SHARE("android_social_share")
36+
ANDROID_PRELAUNCH_SOCIAL_SHARE("android_social_share"),
37+
ANDROID_PRELAUNCH_PROJECT_STORY("android_pre-launch_project_story"),
3738
}
3839

3940
object StatsigExperiments {

app/src/main/java/com/kickstarter/services/transformers/extensions/RichTextTransformers.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ fun StoryRichTextComponentFragment.toRichTextComponent(): RichTextComponent {
115115
RichTextItem.Oembed(
116116
fragmentItem.onRichTextOembed.__typename,
117117
fragmentItem.onRichTextOembed.type,
118-
fragmentItem.onRichTextOembed.iframeUrl
118+
fragmentItem.onRichTextOembed.iframeUrl,
119+
fragmentItem.onRichTextOembed.width,
120+
fragmentItem.onRichTextOembed.height,
121+
fragmentItem.onRichTextOembed.providerName,
122+
fragmentItem.onRichTextOembed.html,
119123
)
120124
}
121125
else -> null

app/src/main/java/com/kickstarter/ui/activities/PreLaunchProjectPageActivity.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,28 @@ class PreLaunchProjectPageActivity : ComponentActivity() {
9797
val context = LocalContext.current
9898
val projectState = viewModel.project().subscribeAsState(initial = null)
9999
val similarProjectsState = similarProjectsViewModel.similarProjectsUiState.collectAsState()
100+
val projectStoryState = projectStoryViewModel.projectStoryUiState.collectAsState()
100101
var shareData: SocialShareData? by remember { mutableStateOf(null) }
101102

102103
LaunchedEffect(projectState.value) {
103104
projectState.value?.let { project ->
104105
similarProjectsViewModel.provideProject(project)
105-
// project.slug()?.let {
106-
// projectStoryViewModel.provideProjectSlug(it)
107-
// projectStoryViewModel.fetchProject()
108-
// }
106+
}
107+
}
108+
109+
val projectSlug = projectState.value?.slug()
110+
111+
LaunchedEffect(projectSlug) {
112+
projectSlug?.let { slug ->
113+
projectStoryViewModel.provideProjectSlug(slug)
114+
projectStoryViewModel.fetchProject()
109115
}
110116
}
111117

112118
PreLaunchProjectPageScreen(
113119
projectState = projectState,
114120
similarProjectsState = similarProjectsState,
121+
projectStoryState = projectStoryState,
115122
leftOnClickAction = { finish() },
116123
rightOnClickAction = {
117124
projectState.value?.let { this.viewModel.inputs.bookmarkButtonClicked() }

app/src/main/java/com/kickstarter/ui/activities/compose/PreLaunchProjectPageScreen.kt

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import android.annotation.SuppressLint
44
import android.content.res.Configuration
55
import androidx.compose.foundation.background
66
import androidx.compose.foundation.layout.Box
7+
import androidx.compose.foundation.layout.Spacer
78
import androidx.compose.foundation.layout.aspectRatio
89
import androidx.compose.foundation.layout.fillMaxSize
910
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.height
1012
import androidx.compose.foundation.layout.padding
1113
import androidx.compose.foundation.layout.systemBarsPadding
1214
import androidx.compose.foundation.lazy.LazyColumn
15+
import androidx.compose.foundation.lazy.items
1316
import androidx.compose.foundation.shape.RoundedCornerShape
1417
import androidx.compose.material.icons.Icons
1518
import androidx.compose.material.icons.filled.LocationOn
@@ -34,6 +37,11 @@ import androidx.compose.ui.unit.dp
3437
import androidx.constraintlayout.compose.ConstraintLayout
3538
import androidx.constraintlayout.compose.Dimension
3639
import com.kickstarter.R
40+
import com.kickstarter.features.projectstory.ProjectStoryUiState
41+
import com.kickstarter.features.projectstory.data.RichTextItem
42+
import com.kickstarter.features.projectstory.ui.RichTextItemPhotoComponent
43+
import com.kickstarter.features.projectstory.ui.RichTextItemTextComponent
44+
import com.kickstarter.features.projectstory.ui.WebViewComponent
3745
import com.kickstarter.libs.utils.extensions.toHtml
3846
import com.kickstarter.mock.factories.ProjectFactory
3947
import com.kickstarter.models.Project
@@ -62,6 +70,7 @@ import com.kickstarter.ui.toolbars.compose.ToolbarIconToggleButton
6270
import com.kickstarter.ui.toolbars.compose.TopToolBar
6371
import com.kickstarter.ui.views.compose.KsCreatorLayout
6472
import com.kickstarter.viewmodels.projectpage.SimilarProjectsUiState
73+
import timber.log.Timber
6574

6675
@Composable
6776
@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@@ -71,7 +80,8 @@ fun PreLaunchProjectPageScreenPreview() {
7180
val project = ProjectFactory.backedProject()
7281
val projectState = remember { mutableStateOf(null) }
7382
val similarProjectsState = remember { mutableStateOf(SimilarProjectsUiState()) }
74-
PreLaunchProjectPageScreen(projectState, similarProjectsState)
83+
val projectStoryState = remember { mutableStateOf(ProjectStoryUiState()) }
84+
PreLaunchProjectPageScreen(projectState, similarProjectsState, projectStoryState)
7585
}
7686
}
7787

@@ -93,6 +103,7 @@ enum class PreLaunchProjectPageScreenTestTag() {
93103
fun PreLaunchProjectPageScreen(
94104
projectState: State<Project?>,
95105
similarProjectsState: State<SimilarProjectsUiState>,
106+
projectStoryState: State<ProjectStoryUiState>,
96107
leftOnClickAction: () -> Unit = {},
97108
rightOnClickAction: () -> Unit = {},
98109
middleRightClickAction: () -> Unit = {},
@@ -102,6 +113,8 @@ fun PreLaunchProjectPageScreen(
102113
numberOfFollowers: String? = null
103114
) {
104115
val project = projectState.value
116+
val story = projectStoryState.value.storiedProject?.story
117+
105118
Scaffold(
106119
modifier = Modifier.systemBarsPadding(),
107120
topBar = {
@@ -129,7 +142,7 @@ fun PreLaunchProjectPageScreen(
129142
modifier = Modifier
130143
.padding(contentPadding)
131144
.fillMaxSize()
132-
.background(colors.kds_support_100)
145+
.background(colors.preLaunchProjectPage.background)
133146
) {
134147
val (lazyColumn, buttonCardLayout) = createRefs()
135148
val screenPadding = dimensionResource(id = R.dimen.activity_horizontal_margin)
@@ -256,19 +269,78 @@ fun PreLaunchProjectPageScreen(
256269
}
257270
}
258271

259-
item {
260-
val spcMarginTop = dimensionResource(id = R.dimen.grid_3)
261-
val spcMarginBottom = dimensionResource(id = R.dimen.grid_27)
272+
if (story?.items != null && story.items.isNotEmpty()) {
273+
item {
274+
Spacer(Modifier.height(dimensionResource(id = R.dimen.grid_2)))
275+
}
276+
}
262277

278+
items(story?.items ?: listOf<RichTextItem>(), contentType = { it::class.simpleName }) { item ->
263279
Box(
264-
modifier = Modifier
265-
.padding(top = spcMarginTop, bottom = spcMarginBottom)
266-
.testTag(SIMILAR_PROJECTS_CONTAINER.name)
280+
modifier = Modifier.padding(horizontal = screenPadding, vertical = dimensionResource(id = R.dimen.grid_1))
267281
) {
268-
SimilarProjectsComponent(
269-
uiState = similarProjectsState,
270-
onClick = onSimilarProjectClick
271-
)
282+
when (item) {
283+
is RichTextItem.Text -> {
284+
when (item) {
285+
is RichTextItem.Text.Paragraph -> {
286+
val childPhoto = item.children?.firstOrNull { it is RichTextItem.Photo } as? RichTextItem.Photo
287+
when {
288+
childPhoto != null -> {
289+
val link = item.link
290+
RichTextItemPhotoComponent(childPhoto, link)
291+
}
292+
else -> RichTextItemTextComponent(item)
293+
}
294+
}
295+
else -> {
296+
RichTextItemTextComponent(item)
297+
}
298+
}
299+
}
300+
is RichTextItem.Photo -> {
301+
Box(
302+
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp) // .defaultMinSize(minHeight = 200.dp)
303+
) {
304+
RichTextItemPhotoComponent(item)
305+
}
306+
}
307+
is RichTextItem.Oembed -> {
308+
Timber.d("RichTextItem.Oembed item: $item")
309+
if (item.iframeUrl.isNotEmpty()) {
310+
val aspectRatio = if (item.width > 0 && item.height > 0) {
311+
item.width.toFloat() / item.height.toFloat()
312+
} else {
313+
16f / 9f
314+
}
315+
Box(
316+
modifier = Modifier.fillMaxWidth().aspectRatio(aspectRatio)
317+
) {
318+
WebViewComponent(item.iframeUrl)
319+
}
320+
}
321+
}
322+
else -> {}
323+
}
324+
}
325+
}
326+
327+
if (!projectStoryState.value.isLoading &&
328+
(projectStoryState.value.storiedProject != null || projectStoryState.value.error != null)
329+
) {
330+
item {
331+
val spcMarginTop = dimensionResource(id = R.dimen.grid_3)
332+
val spcMarginBottom = dimensionResource(id = R.dimen.grid_27)
333+
334+
Box(
335+
modifier = Modifier
336+
.padding(top = spcMarginTop, bottom = spcMarginBottom)
337+
.testTag(SIMILAR_PROJECTS_CONTAINER.name)
338+
) {
339+
SimilarProjectsComponent(
340+
uiState = similarProjectsState,
341+
onClick = onSimilarProjectClick
342+
)
343+
}
272344
}
273345
}
274346
}

0 commit comments

Comments
 (0)