Skip to content

Latest commit

 

History

History
165 lines (144 loc) · 5.67 KB

File metadata and controls

165 lines (144 loc) · 5.67 KB

Stories for Jetpack Compose

A library to make your stories easy to create with Compose. All the logic about stories transitions and shown indicating is implemented. All you need is only to create UI components.

The UI design is entirely up to you - there's no need to pass parameters to expose content details.

UPD: Since the stable version 1.3.9, there is a also an ability to add video stories!

3

Setup

Add this code:

  • to your settings.gradle of the project:
dependencyResolutionManagement {
   repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
   repositories {
       google()
       mavenCentral()
       maven { url = uri("https://jitpack.io") }
   }
}
  • to your build.gradle of the module of usage:
dependencies {
   implementation("com.github.m2-oss.StoriesCompose:stories:1.3.9") // base functionality (mandatory)
   implementation("com.github.m2-oss.StoriesCompose:stories-video:1.3.9") // video stories (optional)
}

Quick Start

To create a list of previews:

  • prepare the data:
val STORIES_PREVIEW_LIST = listOf(
    UiStoriesPreviewData(
        id = "id1",
        imageData = R.drawable.ic_launcher_background,
        title = "1",
    ),
    UiStoriesPreviewData(
        id = "video1",
        imageData = R.drawable.ic_launcher_foreground,
        title = "video1",
    )
)
  • create the list:
@Composable
fun PreviewList(previews: List<UiStoriesPreviewData>, onClick: (String) -> Unit) {
    StoriesPreviewList(
        previews = previews,
        onClick = { onClick(it) }
    )
}

The result:

Screenshot_20260420_174714

To create container for stories:

  • prepare the data:
@Composable
private fun createData(
    storiesId: String,
    previews: List<UiStoriesPreviewData>
): UiStoriesData = UiStoriesData(
    storiesId = storiesId,
    stories = buildMap {
        val ids = previews.map { it.id }
        ids.forEach {
            put(
                it,
                buildList {
                    add(
                        if (it.contains("video")) {
                            UiSlidesData.Video(url = "https://cdn.m2.ru/assets/file-upload-server/20fb0b6ebd500608a682c9794a40ae7e.mp4")
                        } else {
                            UiSlidesData.Image(duration = 10_000L)
                        }
                    )
                }
            )
        }
    }
)
  • create the containers:
@Composable
fun Container(previews: List<UiStoriesPreviewData>, storiesId: String, onFinished: () -> Unit) {
    val data = createData(storiesId, previews)
    StoriesContainer(
        data = data,
        onFinished = onFinished
    ) { stories, slide, progressBar, playerHolder ->
        val player = (playerHolder as? ExoPlayerHolder)?.player
        Column(modifier = Modifier.fillMaxSize()) {
            val video = data.stories[stories]?.get(slide) is UiSlidesData.Video
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
                    .background(Color.Gray)
            ) {
                if (video) {
                    VideoContent(player)
                } else {
                    ImageContent(stories, slide, progressBar)
                }
            }
        }
    }
}

@Composable
private fun VideoContent(player: ExoPlayer?) {
    if (player == null) return

    Box(modifier = Modifier.fillMaxSize()) {
        ContentFrame(
            player = player,
            modifier = Modifier.fillMaxSize(),
            surfaceType = SURFACE_TYPE_TEXTURE_VIEW,
            contentScale = ContentScale.Fit
        )
    }
}

@Composable
private fun ImageContent(stories: String, slide: Int, progressBar: Dp) {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.LightGray)
            .offset(y = progressBar)
    ) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_background),
            contentDescription = null
        )
        Text(
            text = "$stories, $slide",
            modifier = Modifier.align(Alignment.Center)
        )
    }
}

The result:

4

Variability

You can use either both or one of the components. In second case you need to synchronize stories shown indicator manually because they both use the same entry point to maintain the logic (the preview component observes which stories were shown in the container one). To do so you need to use the repository contract. Create the repo you can with the factory. The samples of synchronization are here and here. Keep in mind that the interaction is asynchronous so that it must be on worker thread otherwise an exception will be thrown.