# Storyteller Kotlin SDK – AI Context This file aggregates the public Storyteller Kotlin SDK documentation into a single plain-text file for use with AI coding assistants (Claude, ChatGPT, etc.). ## How to Use This File - Paste this file into your AI assistant along with your own project code when working with the Storyteller Kotlin SDK. - Internal markdown links like `[Working with Users](Users.md)` refer to sections labelled `## Source: Users.md` later in this file; they are not clickable but indicate related sections. - Image references have been removed; only text, headings, and code examples are kept. - When in doubt, prefer using public APIs and patterns described here or visible in the client’s codebase. ## Integration Rules for AI 1. Respect the client app’s existing architecture and Gradle setup; adapt examples to their structure. 2. Prefer Storyteller public APIs and cross-check important behaviors against the official docs at https://docs.getstoryteller.com/android. 3. Do not add optional parameters, fields, or configuration flags unless they are clearly required by the user’s scenario. 4. When generating code, include brief comments that link back to the relevant section of this documentation when appropriate. 5. Avoid inventing new APIs; use only the functions, types, and patterns described here or present in the user’s code. ## Included Source Sections The sections below are grouped by the original documentation file they came from: - GettingStarted.md – Quickstart Guide and installation - AdditionalMethods.md – Extra Storyteller SDK methods - Users.md – Working with users and identities - StorytellerLists.md – Main list components and usage - StorytellerListViews.md – Legacy ListViews API - StorytellerClipsFragment.md – Embedded Clips fragment usage - StorytellerHome.md – Storyteller Home integration - Themes.md – Theming and customization - StorytellerDelegates.md – Delegate interfaces and callbacks - StorytellerModule.md – Module configuration and setup - NavigatingToApp.md – Deep links back into your app - Analytics.md – Analytics events and tracking - Ads.md – Ads integration details - PrivacyAndTracking.md – Privacy and tracking configuration - DeepLinking.md – Deep link behavior and configuration - OpenPlayer.md – Opening and controlling the player - Search.md – Search integration - StorytellerBrightcove.md – Brightcove module integration - Cards.md – Cards feature documentation - OpenSourceLicenses.md – Open source license information - AI.md – Using docs with AI tools - Changelog.md – Release notes and behavior changes - javadoc.md – Links to API reference (Dokka) --- ## Source: GettingStarted.md # Quickstart Guide This is a developers' guide for setting up Storyteller for native Android apps. This guide will cover the basic technical steps for initializing the Storyteller SDK, authenticating a user, and adding a `StorytellerStoriesRowView` to your app. ## Resources - [Storyteller Showcase App](https://github.com/getstoryteller/storyteller-showcase-android) ### Showcase examples - [Compose — SDK init in `ShowcaseApp`](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/main/src/main/java/com/getstoryteller/storytellershowcaseapp/ShowcaseApp.kt#L39) - [Compose — `Storyteller.initialize` in `StorytellerServiceImpl`](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L40) - [XML — SDK init in `ShowcaseApp`](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ShowcaseApp.kt#L40) - [XML — `Storyteller.initialize` in `StorytellerServiceImpl`](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L43) ## How to Add the SDK to Your Project Before you can add the Android SDK to your app, you will need to obtain an API Key. This is a secret key used to authenticate the SDK in your app. Throughout this document it will be marked as `[APIKEY]`. ### SDK Dependencies Currently the Android SDK contains the following [dependencies](https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk/Storyteller/sdk/11.0.4/sdk-11.0.4.pom). ### R8 / ProGuard If your app uses R8, the rules are added automatically. ### SDK Installation The Android SDK can be included in your project using [Gradle](https://gradle.org/). It is recommended to use [Android Studio](https://developer.android.com/studio). If you are having problems with configuring your build, check out the [Android Studio guide](https://developer.android.com/studio/build) or [Gradle guides](https://gradle.org/guides/). 1. Add the maven repository for the SDK in the _Project_ `build.gradle` file (`MyAwesomeApp/build.gradle`), under the `allprojects` section > Note: make sure it is added to `allprojects`, and not `buildscript` ```groovy ... allprojects { repositories { google() mavenCentral() maven { url 'https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk' } } } ``` 1. Modify the app _Module_ `build.gradle` file (`MyAwesomeApp/app/build.gradle`) ```groovy ... dependencies { def storyteller_version = "11.0.4" implementation(group: "Storyteller", name: "sdk", version: "$storyteller_version") } ``` 1. Sync your project with Gradle files - Android Studio ## SDK Initialization Before using the Android SDK in your app, you need to initialize it with an API key. ### Adding an API Key Use the `initialize(apiKey: String, userInput: StorytellerUserInput? = null, eventTrackingOptions: StorytellerEventTrackingOptions = StorytellerEventTrackingOptions(), onSuccess: () -> Unit = {}, onFailure: (Error) -> Unit = {})` public method to manually initialize the SDK at runtime. This will authenticate the SDK on the Storyteller API and configure it with the corresponding settings. - `apiKey`: (**Required**) the API key you wish to initialize the SDK with - `userInput` : details of the user to be authenticated (this should be a unique user ID. If this is not set, the default value is used) - `eventTrackingOptions`: privacy and tracking configuration options (defaults to all tracking enabled) - `onSuccess`: callback for successful completion - `onFailure`: callback for failed completion with error Usage: ```kotlin Storyteller.initialize( apiKey = "[APIKEY]", userInput = StorytellerUserInput("unique-user-id"), eventTrackingOptions = StorytellerEventTrackingOptions( enablePersonalization = true, enableStorytellerTracking = true // ... other options ), onSuccess = { // onSuccess action }, onFailure = { error -> // onFailure action } ) ``` Initialization errors: - `InitializationError`: when the context is `null` - `InvalidAPIKeyError`: when an invalid API key is used - `NetworkError`: when the call to load the settings for the SDK fails (i.e. a non-success HTTP status code is returned) - `NetworkTimeoutError`: when the call to load the settings for the SDK times out - `JSONParseError`: when a malformed settings response is received from the server > Note: Please be aware that this method is asynchronous ## Authenticate a User For more information about Users and External IDs, please see [Working with Users](Users.md) ## Adding a StorytellerStoriesRow Composable 1. At first we will need a nesting composable. You can use any composable layout you want: `Box`, `Column`, `LazyColumn`, etc. ```kotlin @Composable fun MainScreen() { Box() { // ... } } ``` 1. Storyteller Composables Now it's time to add the Storyteller Composables to your app. The Storyteller Composables are the building blocks of the Storyteller SDK. ```kotlin StorytellerStoriesRow( modifier = Modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), // data model with the configuration for your Composables. We will describe how to use StorytellerDataModel below. delegate = listViewDelegate, // delegate for the Composables. We will describe how to use StorytellerListViewDelegate below. state = rememberStorytellerRowState() // state for the Composables. We will describe how to use StorytellerRowState below. ) ``` ## XML Views (Legacy) Prefered way to add Storyteller Lists is to use Composables. Storyteller still supports XML/Views and the guide can be found [in the StorytellerListViews documentation](StorytellerListViews.md). ## Source: AdditionalMethods.md # Additional Methods ## Table of Contents - [Setting the global StorytellerDelegate](#setting-the-global-storytellerdelegate) - [Static Attributes](#static-attributes) - [Static Methods](#static-methods) This page covers additional helper methods and properties. ## Showcase examples - [Compose — `Storyteller.isSearchEnabled` (`StorytellerTopAppBar`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/main/StorytellerTopAppBar.kt#L87) - [Compose — `Storyteller.dismissPlayer` before navigation (`ShowcaseStorytellerDelegate`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ShowcaseStorytellerDelegate.kt#L121) - [XML — `Storyteller.dismissPlayer` before navigation (`ShowcaseStorytellerDelegate`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ShowcaseStorytellerDelegate.kt#L86) ## Setting the global StorytellerDelegate Please see the dedicated [Storyteller Delegates](StorytellerDelegates.md) page. ```kotlin Storyteller.storytellerDelegate = myCustomStorytellerDelegate ``` ## Static Attributes ### isInitialized `isInitialized` is a boolean static property which is set to `true` if Storyteller was initialized successfully. ```kotlin val initialized = Storyteller.isInitialized ``` ### isSearchEnabled `isSearchEnabled` is a boolean static property which is set to `true` if the Search feature is enabled in the Storyteller configuration. ```kotlin val searchEnabled = Storyteller.isSearchEnabled ``` ### isPlayerVisible `isPlayerVisible` is a boolean property which is set to `true` when a Story or Clip player is opened, `false` when the Story or Clip player is dismissed. ```kotlin val isStoryPlayerVisible = Storyteller.isPlayerVisible ``` ### version The `version` is a static String property which holds the SDK version. ```kotlin val storytellerVersion = Storyteller.version ``` ### currentUserId ⚠️ **DEPRECATED** > **⚠️ DEPRECATED:** This property is deprecated and will be removed in version 11.0.0 for VPPA compliance. User IDs should not be exposed to integrators to prevent matching with video viewing data. This method now returns an empty string and will be completely removed in a future version. `currentUserId` was a static String property that previously returned the current user ID. For VPPA compliance, this property has been deprecated and now returns an empty string. ```kotlin // DEPRECATED - Do not use val userId = Storyteller.currentUserId // Always returns "" ``` ### user Allows setting custom attributes for audience targeting. Please see [User Customization](Users.md) ```kotlin Storyteller.user.setCustomAttribute('location', 'New York'); ``` ## Static Methods ### openSearch ```kotlin fun openSearch(activity: Activity) ``` This method opens the Search screen. Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Search activity ### openSheet ```kotlin fun openSheet(activity: AppCompatActivity, sheetId: String, onError: (StorytellerError) -> Unit = {}) ``` This call opens a Sheet with a given ID. Parameters: - `activity` - this is the Activity that will be used to launch the Sheet - `sheetId` - this is a Sheet's ID, if it is `null` then `onError` will be called - `onError` - this is called when there is an issue with opening a Sheet (e.g. the sheetId is a blank string) ### dismissPlayer `dismissPlayer(Boolean, String?)`: force closes the currently open Story or Player Views. If no Player is open when this is called, it has no effect. `animated`: the flag indicating if close animation should be triggered. Defaults to `true`. `reason`: the reason why the Story Page was force closed. This will be used to populate the `dismissedReason` parameter of the corresponding `onUserActivityOccurred` callback. If this is set to `null` the `onUserActivityOccurred` callback will not be triggered. `onCompletion`: a callback that will be called when the player is dismissed. This is useful when you want to perform an action after the player is dismissed. ```kotlin Storyteller.dismissPlayer(true, "reason") ``` ### resumePlayer `resumePlayer()`: resumes the currently open Story or Player Views. If no Player is open when this is called, it has no effect. ```kotlin Storyteller.resumePlayer() ``` ### getStoriesCount ```kotlin suspend fun getStoriesCount(categoryIds: List): Int ``` This method returns the count of stories available in the specified categories. It can be used to determine if a particular category or set of categories contains any Stories before rendering a UI component. Parameters: - `categoryIds` - a list of category IDs to check for stories Return value: - Returns the total number of stories available in the specified categories, or 0 if Storyteller is not initialized or the categoryIds list is empty ### getClipsCount ```kotlin suspend fun getClipsCount(categoryIds: List): Int ``` This method returns the count of clips available in the specified categories. It can be used to determine if a particular category or set of categories contains any Clips before rendering a UI component. Parameters: - `categoryIds` - a list of category IDs to check for clips Return value: - Returns the total number of clips available in the specified categories, or 0 if Storyteller is not initialized or the categoryIds list is empty ## Source: Users.md # Working with Users User IDs can be used for reporting purposes, storing the read status of Clips and Stories, followed categories and user preferences. By default, the Storyteller SDK creates an `externalId` for users when the SDK is first initialized. Each time `null` value is passed to `Storyteller.initialize(userInput = null)` SDK will handle user management and the default `externalId` is stored until the user uninstalls the app. **However, if you have a user account system then you may wish to set your own user IDs within the Storyteller SDK. The `externalId` should be an identifier that is unique per user and does not change. Therefore, using something like the user's email address is not a good choice for an `externalId` as the user may change it at some point in the future. However, using a unique UUID/GUID would be a good choice as it is guaranteed to not change over time.** > Note: Storyteller SDK is not storing any user IDs passed to initialize. We follow VPPA compliance and the user IDs are hashed during the App runtime ## Showcase examples - [Compose — user ID, custom attributes, locale (`AccountScreen`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/account/AccountScreen.kt#L65) - [Compose — initialization + user attribute updates (`StorytellerServiceImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L108) - [XML — login + verify flow (user ID)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/login/LoginViewModel.kt#L65) - [XML — initialization + user attribute updates (`StorytellerServiceImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L83) ## Setting a User ID In order to supply a `externalId` to the Storyteller SDK, call the following method: ```kotlin Storyteller.initialize( apiKey = "[APIKEY]", useInput = StorytellerUserInput("unique-externalId"), onSuccess = { // onSuccess action }, onFailure = { error -> // onFailure action } ) ``` This should be called as soon as the value for the `externalId` is known in your app. For example, this could be on app start or when a user logs in. ## Current User Information You can access the following information about the current user via the following properties: ```kotlin Storyteller.currentUserId // Returns the current user id Storyteller.currentApiKey // Returns the current api key ``` ## Changing Users If you use login in your app and wish to allow users to logout and log back in as a new user (or proceed as an anonymous user) then when a user logs out you should call `initialize` again, specifying a new `externalId`. Note that this will reset the local store of which Pages the user has viewed. ## Setting the User's Locale In order to set the locale to the Storyteller SDK, call the following method: ```kotlin Storyteller.user.setLocale(locale: String?) ``` Ensure the locale parameter uses an ISO 639-1 two-letter if available, otherwise, use an ISO 639-2 three-letter language code for precise language specification: ```kotlin Storyteller.user.setLocale("fr") Storyteller.user.setLocale("ace") ``` To clear or reset the locale settings, simply pass `null` as the parameter: ```kotlin Storyteller.user.setLocale(null) ``` ## User Customization ### Custom Attributes One of the features of Storyteller is the ability to set custom attributes for requests. These custom attributes can be used to provide additional information about a request, such as the user's location or the device they are using. It can be also used for audience targeting. ### How to Use To set a custom attribute, you can use the `setCustomAttribute()` method of the `Storyteller.user` object. This method takes two parameters: the key of the custom attribute and its value. For example, to set a custom attribute for the user's location, you can use the following code: ```kotlin Storyteller.user.setCustomAttribute('location', 'New York') ``` This will add a custom attribute to all requests made by the user, with the key "location" and the value "New York". You can set multiple custom attributes by calling setCustomAttribute() multiple times with different keys and values. Only String values are allowed. > Setting custom attributes should be done after Storyteller SDK is initialized. To remove custom attribute, you can use the `removeCustomAttribute()` method of the `Storyteller.user` object. This method takes one parameter: the key of the custom attribute to remove. For example, to remove a custom attribute for the user's location, you can use the following code ```kotlin Storyteller.user.removeCustomAttribute("location") ``` ## Followed Categories Storyteller provides a mechanism for managing followed categories associated with clips. ### How to Use #### Set followed categories To set a followed category, call the `addFollowedCategory()` function for a singular category or `addFollowedCategories()` for a collection. Access these methods through the `Storyteller.user` object. For instance, to designate a followed category attribute for the user, implement the following code: ```kotlin Storyteller.user.addFollowedCategory("location") ``` or ```kotlin Storyteller.user.addFollowedCategories(listOf("location", "city", "country")) ``` These categories are stored locally and used to decide whether the plus or tick icon is displayed in the clips UI. > Setting followable categories attributes should be done after Storyteller SDK is initialized. #### Remove followable category To remove a followed category, use the `removeFollowedCategory()` method of the `Storyteller.user` object. This method requires a single parameter—the key of the followed category to be removed. For example, to remove a followable category related to the user's location, utilize the following code: ```kotlin Storyteller.user.removeFollowedCategory("location") ``` This function accepts a singular string parameter, removing the specified category from the locally stored list. ## Storyteller-Managed Following The SDK handles following activity internally and automatically syncs with Storyteller's servers. This simplifies integration by removing the need for custom following logic in your app. This means that all of the above methods on `Storyteller.user` are no-ops in this mode. Also the `categoryFollowActionTaken` is not called. ### Common methods To check if a specific category is being followed, use the `isCategoryFollowed()` method. To retrieve a list of all currently followed categories, use the `getFollowedCategories()` method. ```kotlin val isFollowing: Boolean = Storyteller.user.isCategoryFollowed("location") val followedCategories: Set = Storyteller.user.followedCategories ``` ## Source: StorytellerLists.md # Storyteller Jetpack Compose SDK This is a developers' guide for setting up Storyteller Compose SDK for native Android apps. This guide will cover the basic technical steps for integrating Storyteller Compose SDK into your app. *Note: While this guide focuses on the Jetpack Compose implementation, corresponding traditional Android Views (e.g., `StorytellerStoriesRowView`, `StorytellerStoriesGridView`, `StorytellerClipsRowView`, `StorytellerClipsGridView`) also exist and can be used in XML layouts or programmatically. Their configuration attributes and delegate usage are similar to their Compose counterparts.* ## Getting Started ### Resources - [Storyteller Android Compose Sample App](https://github.com/getstoryteller/storyteller-sample-android/tree/main/app/src/main/java/com/example/storytellerSampleAndroid/compose) - [Storyteller Showcase App (Compose)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/main/src/main/java/com/getstoryteller/storytellershowcaseapp/ShowcaseApp.kt#L24) ### Showcase examples - [Lists + Cards feed (`HomeScreen`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/home/HomeScreen.kt#L164) - [Storyteller list rendering (`StorytellerItem`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/storyteller/StorytellerItem.kt#L58) ### How to Add the SDK to Your Project Before you can add the Android SDK to your app, you will need to obtain an API Key. This is a secret key used to authenticate the SDK in your app. Throughout this document it will be marked as `[APIKEY]`. #### SDK Installation The Compose SDK can be included in your project using [Gradle](https://gradle.org/). It is recommended to use [Android Studio](https://developer.android.com/studio). If you are having problems with configuring your build, check out the [Android Studio guide](https://developer.android.com/studio/build) or [Gradle guides](https://gradle.org/guides/). > Note: Starting from 9.3.0 SDK version Compose is now part of the SDK and there is no need to add separate Compose dependency. The steps to add the SDK are [in the Getting Started documentation](GettingStarted.md) #### Usage 1. At first we will need a nesting composable. You can use any composable layout you want: `Box`, `Column`, `LazyColumn`, etc. ```kotlin @Composable fun MainScreen() { Box() { // ... } } ``` 1. Storyteller Composables Now it's time to add the Storyteller Composables to your app. The Storyteller Composables are the building blocks of the Storyteller SDK. ```kotlin StorytellerStoriesRow( modifier = Modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), // data model with the configuration for your Composables. We will describe how to use StorytellerDataModel below. delegate = listViewDelegate, // delegate for the Composables. We will describe how to use StorytellerListViewDelegate below. state = rememberStorytellerRowState() // state for the Composables. We will describe how to use StorytellerRowState below. ) ``` We have the following **composable types** available: - StorytellerStoriesRow - StorytellerStoriesGrid - StorytellerClipsRow - StorytellerClipsGrid 1. Using the Storyteller Composables You can always modify your Composables during recomposition. Reload Data: Just like normal composable lists, they have a list state, so as Storyteller lists, they have a state, and we will use it to reload the data. There is an extra parameter `state` in the Composables, which is set by default. Let's see how it's used: ```kotlin val state = rememberStorytellerRowState() // or rememberStorytellerGridState() for StorytellerStoriesGrid or StorytellerClipsGrid StorytellerStoriesRow( // ... state = state ) // At any point in your code you can call `reloadData` method, maybe after doing PTR or after some event. LaunchedEffect(Unit) { state.reloadData() } ``` 1. Setting categories and collections, applying themes and styles You can use `StorytellerStoriesDataModel` object for configuring your Stories Composables. It has the following properties: ```kotlin StorytellerStoriesDataModel( categories = listOf("category1", "category2"), // list of stories categories for the Composable. If you want to use all categories, you can pass an empty list. theme = UiTheme(), // theme for the Composable. A global theme will be set if no value will be passed here. uiStyle = StorytellerListViewStyle.LIGHT, // a style of the Composable. It can be AUTO, LIGHT or DARK. AUTO will represent the theme of the app. displayLimit = 6, // a limit of the stories to be displayed in the Composable. Default is Int.MAX_VALUE. cellType = StorytellerListViewCellType.SQUARE, // a cell type of the Composable. It can be SQUARE or ROUND. visibleTiles = null, // a number of visible tiles for rows. Default is null and turned off. If the value is set for greater than 0, the visible tiles will be turned on. The height of the row will be calculated based on the number of visible tiles and if set to eg. 3.5 it will always show three and a half tile independently of the screen size. offset = 1, // the offset to start displaying stories from a specific position in the collection. Default is 0 (start from beginning). context = hashMapOf("placementId" to "home_stories", "location" to "Home"), // Optional analytics context containing integrator-defined key-value pairs. See [Analytics](Analytics.md#context) for more details. ) ``` > Note: `StorytellerStoriesGrid` supports only `cellType = StorytellerListViewCellType.SQUARE`. You can use `StorytellerClipsDataModel` object for configuring your Clips Composables. It has the following properties: ```kotlin StorytellerClipsDataModel( collection = "collection1", // a string representing your collection of Clips. theme = UiTheme(), // theme for the Composable. A global theme will be set if no value will be passed here. uiStyle = StorytellerListViewStyle.LIGHT, // a style of the Composable. It can be AUTO, LIGHT or DARK. AUTO will represent the theme of the app. displayLimit = 6, // a limit of the clips to be displayed in the Composable. Default is Int.MAX_VALUE. cellType = StorytellerListViewCellType.SQUARE, // a cell type of the Composable. It can be SQUARE or ROUND. visibleTiles = null, // a number of visible tiles for rows. Default is null and turned off. If the value is set for greater than 0, the visible tiles will be turned on. The height of the row will be calculated based on the number of visible tiles and if set to eg. 3.5 it will always show three and a half tile independently of the screen size. offset = 1, // the offset to start displaying clips from a specific position in the collection. Default is 0 (start from beginning). context = hashMapOf("placementId" to "home_clips", "location" to "Home"), // Optional analytics context containing integrator-defined key-value pairs. See [Analytics](Analytics.md#context) for more details. ) ``` > Note: `StorytellerClipsGrid` supports only `cellType = StorytellerListViewCellType.SQUARE`. ## Storyteller Compose ListView Delegate The `StorytellerListViewDelegate` will provide you necessary callbacks for the state of data inside you Composables. This delegate is used to track when data load starts, when it completes, when a tile is tapped, and when player was dismissed by user. We recommend using `remember` for the delegate's object to avoid creating new ones during recomposition. ```kotlin val listViewDelegate by remember("your_item_id") { val value = object : StorytellerListViewDelegate { override fun onPlayerDismissed() { // handle player dismissed event } override fun onDataLoadStarted() { // handle loading started event } override fun onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int) { // handle data load complete event } override fun onTileTapped(tileType: StorytellerTileType) { // handle tile tap event - called before the player Activity is opened // tileType can be either StorytellerTileType.Story or StorytellerTileType.Clip } } mutableStateOf( value ) } StorytellerStoriesRow( delegate = listViewDelegate, // ... ) ``` ### StorytellerTileType The `StorytellerTileType` is a sealed class that represents the type of tile that was tapped. It contains information about the tapped item and can be one of two types: #### StorytellerTileType.Story Represents a Story tile that was tapped. **Properties:** - `id: String` - The unique identifier of the story - `categories: List` - List of categories that this story belongs to #### StorytellerTileType.Clip Represents a Clip tile that was tapped. **Properties:** - `id: String` - The unique identifier of the clip - `collectionId: String` - The collection ID that this clip belongs to - `categories: List` - List of categories that this clip belongs to **Example usage:** ```kotlin override fun onTileTapped(tileType: StorytellerTileType) { when (tileType) { is StorytellerTileType.Story -> { val storyId = tileType.id val categories = tileType.categories // Handle story tile tap Log.d("TileTap", "Story tapped: $storyId, categories: $categories") } is StorytellerTileType.Clip -> { val clipId = tileType.id val collectionId = tileType.collectionId val categories = tileType.categories // Handle clip tile tap Log.d("TileTap", "Clip tapped: $clipId from collection: $collectionId, categories: $categories") } } } ``` ## StorytellerStoriesGrid The `StorytellerStoriesGrid` is a Composable with same functionality as the `StorytellerStoriesGridView` view from the Storyteller SDK. ### Usage in Compose ```kotlin //Stories Grid StorytellerStoriesGrid( modifier = modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), delegate = listViewDelegate, state = rememberStorytellerGridState(), isEnabled = true ) //Stories Scrollable Grid StorytellerStoriesGrid( modifier = modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), delegate = listViewDelegate, state = rememberStorytellerGridState(), isScrollable = true, isEnabled = true ) ``` ### Parameters - `modifier` - a `Modifier` object that will be applied to the `StorytellerStoriesGrid` composable. - `delegate` - a `StorytellerListViewDelegate` object that will be used to handle data load events and player dismissed event. - `state` - a `StorytellerGridState` object that can be used to reload data and it has a reference to the actual grid state. + the `rememberStorytellerGridState()` has two parameters: - `lazyGridState` - a `LazyGridState` object that will be used to handle the state of the grid. - `enablePullToRefresh` - a `Boolean` flag that indicates if the pull to refresh should be enabled. it's off if the grid is not scrollable. and it's off by default if the grid is scrollable. - `isScrollable` - a `Boolean` flag that indicates if the grid is scrollable, if `true` the grid can be put in a non scrollable container. if `false` the grid can be put in a scrollable container, but a display limit should be set wisely as it wil layout all tiles in the grid. - `isEnabled` - a `Boolean` flag that indicates if the tile click should be enabled. ## StorytellerStoriesRow The `StorytellerStoriesRow` is a Composable with same functionality as the `StorytellerStoriesRowView` view from the Storyteller SDK. ### Usage in Compose ```kotlin StorytellerStoriesRow( modifier = modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), delegate = listViewDelegate, state = rememberStorytellerRowState() ) ``` ## StorytellerClipsGrid The `StorytellerClipsGridView` is a Composable with same functionality as the `StorytellerClipsGridView` view from the Storyteller SDK. ### Usage in Compose ```kotlin //Clips Grid StorytellerClipsGrid( modifier = modifier, dataModel = StorytellerClipsDataModel(collection = "collection"), delegate = listViewDelegate, state = rememberStorytellerGridState(), isScrollable = false, isEnabled = true ) //Clips Scrollable Grid StorytellerClipsGrid( modifier = modifier, dataModel = StorytellerClipsDataModel(collection = "collection"), delegate = listViewDelegate, state = rememberStorytellerGridState(), isScrollable = true, isEnabled = true ) ``` ### Parameters - `modifier` - a `Modifier` object that will be applied to the `StorytellerClipsGrid` composable. - `delegate` - a `StorytellerListViewDelegate` object that will be used to handle data load events and player dismissed event. - `state` - a `StorytellerGridState` object that can be used to reload data and it has a reference to the actual grid state. + the `rememberStorytellerGridState()` has two parameters: - `lazyGridState` - a `LazyGridState` object that will be used to handle the state of the grid. - `enablePullToRefresh` - a `Boolean` flag that indicates if the pull to refresh should be enabled. it's off if the grid is not scrollable. and it's off by default if the grid is scrollable. - `isScrollable` - a `Boolean` flag that indicates if the grid is scrollable, if `true` the grid can be put in a non scrollable container. if `false` the grid can be put in a scrollable container, but a display limit should be set wisely as it will layout all tiles in the grid. - `isEnabled` - a `Boolean` flag that indicates if the tile click should be enabled. - `searchInput` - an optional `SearchInput` object that can be used to filter clips in the grid. - `isVisible` - a `Boolean` flag that indicates if the grid should be visible. Defaults to true. ## StorytellerClipsRow The `StorytellerClipsRow` is a Composable with same functionality as the `StorytellerClipsRowView` view from the Storyteller SDK. ### Usage in Compose ```kotlin StorytellerClipsRow( modifier = modifier, dataModel = StorytellerClipsDataModel(collection = "collection"), delegate = listViewDelegate, state = rememberStorytellerRowState() ) ``` ## Source: StorytellerListViews.md # What is StorytellerListView (Legacy) `StorytellerListView` is a base abstract class for views displaying lists of Stories or Clips. Soryteller Views are considered Legacy and the support may be removed in near future. Please prefer using [Composables instead](StorytellerLists.md). ## Showcase examples - [XML — lists screen (`DashboardFragment`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/dashboard/DashboardFragment.kt#L78) - [XML — RecyclerView ViewHolders (`ViewHolders`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/dashboard/adapter/ViewHolders.kt#L38) The Storyteller SDK offers two implementations of this abstract class: - `StorytellerStoriesView` - `StorytellerClipsView` Each of these classes has their corresponding rows and grid views as the following: For Stories - `StorytellerStoriesRowView` - `StorytellerStoriesGridView` For Clips - `StorytellerClipsRowView` - `StorytellerClipsGridView` this abstract class contains all abstract attributes and methods that are common to all `StorytellerListView` implementations. ## How to improve the performance of RecyclerView? If you use the `StorytellerViews` in a `RecyclerView`, using the `setViewHolderParent` method is not mandatory but can significantly improve the UI performance. Inside the `RecyclerView.Adapter`: ```kotlin ... override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val itemView = LayoutInflater.from(parent.context).inflate(R.layout.my_item, parent, false) return MyViewHolder(itemView, parent) } ... ``` Inside the `RecyclerView.ViewHolder`: ```kotlin class MyViewHolder( private val view: View, private val parent: ViewGroup, ): RecyclerView.ViewHolder(view) { private val binding = ListStoryRowBinding.bind(view) fun onBind(item: Item) { binding.storytellerView.setViewHolderParent(parent) ... } } ``` ## Adding a StorytellerStoriesRowView The `StorytellerStoriesRowView` can be added to your app using XML layout or in code. ### XML Add a `com.storyteller.ui.list.StorytellerStoriesRowView` element to your layout ```xml ``` ### Code 1. Create a `StorytellerStoriesRowView` instance ```kotlin val storyRowView = StorytellerStoriesRowView(context) ``` 1. Add the created view instance to the view hierarchy ```kotlin view.addView(storyRowView) ``` ### Recommendations for `StorytellerListView` Hosting Activity Please keep in mind that Stories will open in the activity that displays a status bar only if there is a cutout notch present on the device. In other cases, Stories will open in the activity without a status bar. Status bar visibility changes can have an impact on shared element transitions between Storyteller List View and Story Viewing Activity. For that reason, we recommend hosting activities to use LAYOUT_STABLE flags as in the code snippet below ```kotlin // Storyteller hosting activity onCreate method override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ~~~~~~~ window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE // ~~~~~~~ } ``` ## Further Reading ### Additional Storyteller Methods `Storyteller` has some helper methods which may be useful, see [AdditionalMethods](AdditionalMethods.md) for more details. ### Implementing Storyteller Callbacks `StorytellerDelegate` has callbacks for events which can be implemented, see [StorytellerDelegate](StorytellerDelegates.md) for more details. ### Implementing StorytellerListView Callbacks `StorytellerListView` has callbacks for events which can be implemented, see [StorytellerListViewDelegate](StorytellerDelegates.md) for more details. ## Configuring a StorytellerStoriesView ### Attributes This sections describes attributes common to `StorytellerStoriesRowView` and `StorytellerStoriesGridView`. Notes are used to mark if there is a difference in property interpretation. #### delegate The `delegate` is the `StorytellerListViewDelegate` instance for `StorytellerListView` callbacks (see [Implementing StorytellerListViewDelegate methods](StorytellerDelegates.md)). #### StorytellerStoriesView.ListConfiguration The `ListConfiguration` class is used to configure the `StorytellerStoriesRowView` and `StorytellerStoriesGridView` classes. It is a data class that inherits from `StorytellerListConfiguration`. Usage: ```kotlin val storytellerStoriesRowView = StorytellerStoriesRowView(context) storytellerStoriesRowView.configuration = StorytellerStoriesView.ListConfiguration( theme = customTheme, uiStyle = StorytellerListViewStyle.AUTO, displayLimit = 10, cellType = StorytellerListViewCellType.SQUARE, categories = listOf("category1", "category2", "categoryX"), offset = 1, context = hashMapOf("placementId" to "home_stories", "location" to "Home"), ) ``` ##### cellType The `cellType` is the style of the cell. This can either be `ROUND(0)` or `SQUARE(1)`. The default value is `SQUARE(1)`. ##### categories The `categories` attribute is a list of Categories that the list view should show to the user. The default value is an empty list. The `categories` property can be used to show specific Stories content in the row or grid by supplying a list of valid Categories as strings. Categories can be defined in the CMS. If no Categories are assigned then the content from the default or "Home" Category is displayed. ##### theme The `theme` parameter is used to set the [Theme](Themes.md) for the `StorytellerListView`. If theme is set to null, then theme set as`Storyteller.theme` global property is used. The theme determines how the items within the List View are presented as well as various features of the player once it is launched. ##### uiStyle The `uiStyle` adjusts whether Storyteller renders in light mode, dark mode or follows the system setting. `uiStyle` takes the following values: - `StorytellerListViewStyle.AUTO` - default value, the StorytellerListView will adjust its color scheme automatically according to the current system UI mode - `StorytellerListViewStyle.LIGHT` - force the StorytellerListView to use the light theme - `StorytellerListViewStyle.DARK` - force the StorytellerListView to use the dark theme ##### displayLimit The `displayLimit` is the maximum amount of tiles that can be shown in the list. ##### offset The `offset` is the offset to start displaying stories from a specific position in the collection. Default is 0 (start from beginning). ##### context The `context` parameter is an optional `StorytellerAnalyticsContext` (which is a `HashMap`) containing integrator-defined key-value pairs for analytics attribution. This context will be included with all analytics events originating from this UI instance. See [Analytics](Analytics.md#context) for more details. #### Dynamic Scaling Item tiles will scale dynamically to fit the row or grid view. The base size of the tiles are: - 100dp width x 150dp height for square tiles - 76dp width x 105dp height for round tiles The base size will be used when rendering the row if the dimensions of the row view cannot determine its constraints. The exact dimensions of the row view will depend on how it is defined in the layout view hierarchy, including its parents. In general, tiles will maintain its base proportion and scale up or down to meet view hierarchy constrains. For the sake of simplicity, examples will be provided using `square` as `100dp/150dp = 2/3` proportions to make it easier to do calculations. ##### Example 1 ```xml ``` The final item tile size will be ≈ `133dp x 200dp`, as `200 dp[h] * 2/3 [w/h] ≈ 133dp` ##### Example 2 ```xml ``` The final item tile size will be ≈ 133dp x 200dp. This is because the `StorytellerStoriesRowView` is wrapped in a parent view with a height of 200dp and has its size attributes set to `match_parent`. The system, therefore, expands the row view to fill the height of its parent and scales the width of the tiles accordingly. ##### Example 3 ```xml ``` In this case, the final item tile size will be the height of the window. This happens because the `StorytellerStoriesRowView` is defined as the top-most view and `android:layout_height="match_parent"` has been set on the view. The system, therefore, expands the row view to fill the window height - and so the resulting item tiles will scale to fit the window height also. ##### Example 4 ```xml ... ... ``` In this case, the final item tile size will be 100dp x 150dp (the base size). Since the `StorytellerStoriesRowView` has been defined as the child of another view, setting `android:layout_height="wrap_content"` makes the item tile size 100dp x 150dp (base size). #### XML Attributes can also be applied in XML. ```xml ``` ### Methods #### reloadData The `reloadData` method starts loading fresh data for all Stories from the API. On completion, it updates the Story data, starts prefetching content and updates the read status of the Stories. The `onDataLoadStarted` and `onDataLoadComplete` methods on the [StorytellerListViewDelegate](StorytellerDelegates.md) are called accordingly (the latter with appropriate data depending on the result of the API requests). ```kotlin val storytellerStoriesRowView = StorytellerStoriesRowView(context) storytellerStoriesRowView.reloadData() ``` ## Configuring a StorytellerClipsView ### Attributes This sections describes attributes common to `StorytellerClipsRowView` and `StorytellerClipsGridView`. Notes are used to mark if there is a difference in property interpretation #### StorytellerListViewDelegate - `delegate`: the `StorytellerListViewDelegate` instance for `StorytellerClipsView` callbacks (see [Implementing StorytellerListViewDelegate methods](StorytellerDelegates.md)) #### StorytellerClipsView.ListConfiguration The `ListConfiguration` class is used to configure the `StorytellerClipsRowView` and `StorytellerClipsGridView` classes. It is a data class that inherits from `StorytellerListConfiguration`. `StorytellerClipsView.ListConfiguration` class attributes: Example: ```kotlin val storytellerClipsRowView = StorytellerClipRowView(context) storytellerClipsRowView.configuration = StorytellerClipsView.ListConfiguration( theme = customTheme, uiStyle = StorytellerListViewStyle.AUTO, displayLimit = 10, cellType = StorytellerListViewCellType.SQUARE, collection = "collectionId", context = hashMapOf("placementId" to "home_clips", "location" to "Home"), ) ``` - `collection`: the Collection that the list view should show to the user. The default value is `null`.The `collection` property is used to show specific Clips content in the row or grid by supplying a single Collection as string. Collections can be defined in the CMS. If no Collection is assigned, then no Clips content is displayed. - `theme`: This parameter is used to set the [Theme](Themes.md) for the `StorytellerClipsView`. If theme is set to `null`, then theme set as`Storyteller.theme` global property is used. The theme determines how the items within the List View are presented as well as various features of the player once it is launched. - `displayLimit`: only display up to this number of tiles in the list. - `uiStyle`: adjust whether Storyteller renders in light mode, dark mode or follows the system setting. `uiStyle` takes the following values: - `StorytellerListViewStyle.AUTO` - default value, the StorytellerClipsView will adjust its color scheme automatically according to the current system UI mode - `StorytellerListViewStyle.LIGHT` - force the StorytellerClipsView to use the light theme - `StorytellerListViewStyle.DARK` - force the StorytellerClipsView to use the dark theme - `context`: Optional `StorytellerAnalyticsContext` (a `HashMap`) containing integrator-defined key-value pairs for analytics attribution. This context will be included with all analytics events originating from this UI instance. See [Analytics](Analytics.md#context) for more details. #### Feed Title The feed title in the Clips player can be configured in the CMS for the Collection. This can be a custom title or image. ### Dynamic Scaling Item tiles will scale dynamically to fit the row or grid view. The base size of the tiles are: - 100dp width x 150dp height for square tiles - 76dp width x 105dp height for round tiles The base size will be used when rendering the row if the dimensions of the row view cannot determine its constraints. The exact dimensions of the row view will depend on how it is defined in the layout view hierarchy, including its parents. In general, tiles will maintain its base proportions and scale up or down to meet view hierarchy constrains. For the sake of simplicity, examples will be provided using `square` as `100dp/150dp = 2/3` proportions to make it easier to do calculations. #### Example 1 ```xml ``` The final item tile size will be ≈ `133dp x 200dp`, as `200 dp[h] * 2/3 [w/h] ≈ 133dp` #### Example 2 ```xml ``` The final item tile size will be ≈ 133dp x 200dp. This is because the `StorytellerRowView` is wrapped in a parent view with a height of 200dp and has its size attributes set to `match_parent`. The system, therefore, expands the row view to fill the height of its parent and scales the width of the tiles accordingly. #### Example 3 ```xml ``` In this case, the final item tile size will be the height of the window. This happens because the `StorytellerRowView` is defined as the top-most view and `android:layout_height="match_parent"` has been set on the view. The system, therefore, expands the row view to fill the window height - and so the resulting item tiles will scale to fit the window height also. #### Example 4 ```xml ... ... ``` In this case, the final item tile size will be `100dp x 150dp` (the base size). Since the `StorytellerRowView` has been defined as the child of another view, setting `android:layout_height="wrap_content"` makes the item tile size `100dp x 150dp` (base size). ### XML Views can also be applied in XML. ```xml ``` ### Methods #### reloadData The `reloadData` method starts loading fresh data for all Clips from the API. On completion, it updates the Clips data, starts prefetching content and updates the read status of the Clips. The `onDataLoadStarted` and `onDataLoadComplete` methods on the [StorytellerListViewDelegate](StorytellerDelegates.md) are called accordingly (the latter with appropriate data depending on the result of the API requests). ```kotlin val storytellerClipsRowView = StorytellerClipsRowView(context) storytellerClipsRowView.reloadData() ``` ## Source: StorytellerClipsFragment.md # StorytellerClipsFragment `StorytellerClipsFragment` is a fragment that can be used to embed a clip in your own activities. Like a regular Android Fragment, it can be configured from an XML layout or instantiated programmatically and attached to the host's (another fragment or activity) FragmentManager via a fragment transaction. > Clips are delivered in a 9:16 aspect ratio. While `StorytellerClipsFragment` will do content > cropping in a way that all given space is taken by the content (all area is consumed, cropping is > applied to avoid content distortion). If you want to present content without > cropping, `StorytellerClipsFragment` should reside > in a container that maintains a 9:16 aspect ratio. ## Showcase examples - [Compose — embedded clips (`MomentsScreen`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/moments/MomentsScreen.kt#L160) - [XML — embedded clips (`EmbeddedClipFragment`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/embedded/EmbeddedClipFragment.kt#L74) ## Using StorytellerClipsFragment from xml layout `StorytellerClipsFragment`can be used directly from the layout XML files. The usage is identical to typical Android fragments. You need to specify the fully qualified fragment class name in the android:name property and the collection ID using app:storyteller_collection_id_property. Note that in this example, a constraint is applied to the dimensions' proportions so the clip content is not cropped at all. app:storyteller_initial_category is optional and can be used to set the initial category of the collection to be viewed. If this is used. The clip will be first loaded with the collection. The selected category will then be navigated to automatically. ```xml ``` ## Using StorytellerClipsFragment programmatically `StorytellerClipsFragment` can also be used programmatically. To embed a clip fragment programmatically, you need to follow these steps: 1. Create a new fragment instance using the `StorytellerClipsFragment.create(collectionId: String, context: StorytellerAnalyticsContext)` method. a. Optionally, you can also set the `initialCategory` property to a string value to automatically navigate to a selected category. 2. Create a fragment transaction that would add this fragment to the fragment container view. 3. Commit the created transaction. See the following snippet illustrating attaching a fragment in the `Activity.onCreate` method: ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // An example using viewBinding feature; see view binding for reference. val binding = ActivityClipFragmentHostBinding.inflate(layoutInflater) setContentView(binding.root) val storytellerClipsFragment = StorytellerClipsFragment.create( collectionId = "yourCollectionId", context = hashMapOf("placementId" to "embedded_clips", "location" to "ClipsScreen") ) val transaction = supportFragmentManager.beginTransaction() transaction.add( binding.fragmentHost.id, storytellerClipsFragment ) transaction.commit() } ``` ## Controlling playback in StorytellerClipsFragment `StorytellerClipsFragment` by default starts playback as soon as it is attached to the host and visible. Standard Android Fragment lifecycle events typically handle automatic pausing and resuming (e.g., when the app goes to the background), complementing the manual control provided by the `shouldPlay` property. ```kotlin // finding the fragment by id using the fragment manager val storytellerClipsFragment = supportFragmentManager.findFragmentById(binding.fragmentHost.id) as StorytellerClipsFragment storytellerClipsFragment.shouldPlay = false // stops playback storytellerClipsFragment.shouldPlay = true // starts playback ``` `StorytellerClipsFragment` also contains `canGoBack:Boolean` property which can be used to check if the fragment can go back from the current Category or is it at the top level. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create( collection = "yourCollectionId", topLevelBackEnabled = true, context = hashMapOf("placementId" to "embedded_clips", "location" to "ClipsScreen") ) val canGoBack = storytellerClipsFragment.canGoBack ``` `StorytellerClipsFragment` also contains `listener` property which can be used to control playback and handle top level back button press. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create( collection = "yourCollectionId", topLevelBackEnabled = true, context = hashMapOf("placementId" to "embedded_clips", "location" to "ClipsScreen") ) storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener { override fun onTopLevelBackPressed(): Boolean { // will be invoked when top level back button is pressed return true // true if fragment needs to be stopped / host should handle back } override fun onDataLoadStarted() { // will be invoked when data load starts } override fun onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int){ // will be invoked when data load completes or fails } } storytellerClipsFragment.reloadData() ``` ## External back button handling `StorytellerClipsFragment` does not show the top level back button by default. You can control the behaviour by setting `topLevelBackEnabled` property and setting `onTopLevelBackPressed` according to your needs. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create( collection = "yourCollectionId", topLevelBackEnabled = true, context = hashMapOf("placementId" to "embedded_clips", "location" to "ClipsScreen") ) storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener { override fun onTopLevelBackPressed(): Boolean { // will be invoked when top level back button is pressed return true // true if fragment needs to be stopped } } ``` `StorytellerClipsFragment` also contains `canGoBack:Boolean` property which can be used to check if the fragment can go back from the current Category or is it at the top level. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create( collection = "yourCollectionId", topLevelBackEnabled = true, context = hashMapOf("placementId" to "embedded_clips", "location" to "ClipsScreen") ) val canGoBack = storytellerClipsFragment.canGoBack ``` ## Initial Category To set the `StorytellerClipsFragment` to start with a specific category, you can set the `initialCategory` property. Programmatical Usage ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create( collection = "yourCollectionId", initialCategory = "yourCategory", context = hashMapOf("placementId" to "embedded_clips", "location" to "ClipsScreen") ) ``` XML Usage ```xml ``` If the category is found in the collection, the clip will be loaded with the collection and the selected category. The category will be navigated to automatically. If the category is not found in the collection or is invalid, it will be ignored. ## Reload Data `StorytellerClipsFragment` in order to reload data you can call `reloadData()` method. This method will make a request to the backend to fetch the latest data. If this method is called when there are category filters applied, then it will go back one level. If there are no category filters applied this method will reload data. Loading state can be observed by setting `listener` property and overriding it's `onDataLoadStart` and `onDataLoadComplete` methods. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create( collection = "yourCollectionId", topLevelBackEnabled = true, context = hashMapOf("placementId" to "embedded_clips", "location" to "ClipsScreen") ) storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener { override fun onTopLevelBackPressed(): Boolean { // will be invoked when top level back button is pressed return true // true if fragment needs to be stopped / host should handle back } override fun onDataLoadStarted() { // will be invoked when data load starts } override fun onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int){ // will be invoked when data load completes or fails } } storytellerClipsFragment.reloadData() ``` ## Inset Management One of the usecase of `StorytellerClipsFragment` is to embed it in a view that is laid behind status bar. While that will increase size of the screen available for the content, this arrangement can potentially causing the clip title and other UI elements to be obscured by the status bar. To avoid this problem you can set `StorytellerClipsFragment.topInset` property to the height of the status bar. This will cause the fragment to offset the title and the top buttons by the given amount of pixels. Similarly if the bottom part of the view is obscured by the Navbar, you can set `StorytellerClipsFragment.bottomInset` property to the height of the navigation bar. The recommended approach is to use this property together with `onApplyWindowInsets` callback. ```kotlin ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, winInsets -> val inset = winInsets.getInsets(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars()) storytellerClipsFragment.topInset = inset.top storytellerClipsFragment.bottomInset = inset.bottom WindowInsetsCompat.CONSUMED } ``` ## Compose Integration `StorytellerClipsFragment` can be used in Jetpack Compose using the `StorytellerEmbeddedClips` composable. ```kotlin class DemoComposeEmbeddedClipsActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val collection = intent.getStringExtra("collection") ?: "" val widthPercentage = intent.getIntExtra("width", 100) / 100F val heightPercentage = intent.getIntExtra("height", 80) / 100F val topLevelBack = intent.getBooleanExtra("topLevelBack", false) val initialCategory = intent.getStringExtra("category") ?: "" enableEdgeToEdge() setContent { val state = rememberStorytellerEmbeddedClipsState( collectionId = collection, topLevelBack = topLevelBack, initialCategory = initialCategory, context = hashMapOf("placementId" to "embedded_clips_compose", "location" to "ClipsScreen") ) StorytellerEmbeddedClips( modifier = Modifier .fillMaxWidth(widthPercentage) .fillMaxHeight(heightPercentage), state = state, ) } } companion object { fun start(context: Context, collection: String, category: String?, width: Int, height: Int, topLevelBack: Boolean) { // add params to intent Intent(context, DemoComposeEmbeddedClipsActivity::class.java).apply { putExtra("collection", collection) putExtra("category", intialCategory) putExtra("width", width) putExtra("height", height) putExtra("topLevelBack", topLevelBack) context.startActivity(this) } } } } ``` ### StorytellerEmbeddedClipsState `rememberStorytellerEmbeddedClipsState` is a composable function that creates a `StorytellerEmbeddedClipsState` object that holds the state of the `StorytellerEmbeddedClips` composable. ```kotlin val state = rememberStorytellerEmbeddedClipsState( collectionId = "collection", topLevelBack = topLevelBack, context = hashMapOf("placementId" to "embedded_clips_compose", "location" to "ClipsScreen") ) StorytellerEmbeddedClips( modifier = Modifier, state = state ) ``` `StorytellerEmbeddedClipsState` contains `canGoBack` property that can be used to check if the fragment can go back from the current Category or is it at the top level. ```kotlin val state = rememberStorytellerEmbeddedClipsState( collectionId = "collection", topLevelBack = topLevelBack, context = hashMapOf("placementId" to "embedded_clips_compose", "location" to "ClipsScreen") ) val canGoBack = state.canGoBack // true if user can navigate back ``` `StorytellerEmbeddedClipsState` contains `goBack()` which will move the content to previous Category if the user is not at the top level. ```kotlin val state = rememberStorytellerEmbeddedClipsState( collectionId = collection, topLevelBack = topLevelBack, context = hashMapOf("placementId" to "embedded_clips_compose", "location" to "ClipsScreen") ) state.goBack() // navigate to previous category programmatically ``` ## Source: StorytellerHome.md # Storyteller Home `StorytellerHome` is a component which allows multiple Stories and Clips Rows or Grids to be embedded in a single screen in your application in a list. ## How to Use The code samples below show how to use the `StorytellerHome` component in both `Compose` and `Fragment`. It includes a pull-to-refresh (PTR) mechanism, but you can also use the `reloadData` method to refresh data on your side. *Defaults: If `theme` is not provided, the global theme set via `Storyteller.theme` will be used. If `uiStyle` is not provided, it defaults to `StorytellerListViewStyle.AUTO`, which follows the system's light/dark mode setting.* `context` - optional context data that will be included in analytics callbacks for attribution. This allows you to track which sources drive engagement with your home content. See [Analytics](Analytics.md#context) for more details. ### Compose ```kotlin // without reload data outside sdk StorytellerHome( homeId = "home", theme = yourTheme, uiStyle = StorytellerListViewStyle.AUTO, context = hashMapOf("placementId" to "home_screen", "location" to "Home"), modifier = Modifier ) // with reload data outside sdk val state = rememberStorytellerHomeState() StorytellerHome ( homeId = "home", theme = yourTheme, uiStyle = StorytellerListViewStyle.AUTO, context = hashMapOf("placementId" to "home_screen", "location" to "Home"), // Optional analytics context state = state, modifier = Modifier ) // ... // call for reload data outside sdk suspend fun reloadData() { state.reloadData() } ``` ### Fragment ```kotlin val fragment = StorytellerHomeFragment.create( homeId = "home", context = hashMapOf("placementId" to "home_screen", "location" to "Home"), // Optional analytics context // theme = yourTheme, // Optional: Defaults to Storyteller.theme // uiStyle = StorytellerListViewStyle.AUTO, // Optional: Defaults to AUTO ) // call for reload data outside sdk fragment.reloadData() ``` ## Source: Themes.md # Custom Themes The appearance of the SDK can be customized by setting the `Storyteller.theme` global property or `StorytellerListView.theme` view property. This requires a `UiTheme` configuration object. > Note: global theme should be set before any of the Storyteller list views are inflated. > Note: this property will be used as fallback theming style used to render Story items in lists and activities launched from list. > See [StorytellerLists](StorytellerLists.md) or [StorytellerListViews](StorytellerListViews.md) (legacy). ## Showcase examples - [Compose — theme building (`GetConfigurationUseCase`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/domain/GetConfigurationUseCase.kt#L60) - [XML — theme selection (`GetHomeScreenUseCase`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/domain/GetHomeScreenUseCase.kt#L21) UiTheme is a data structure containing tree of the parameters. While it's possible to manually declare a whole structure of this data object, for convenience it's recommended to use UiTheme DSL for that purpose. ## Defining a Theme A `Theme` contains various settings which change visual aspects of the Storyteller SDK. All properties are optional. In general, it should be possible to obtain a custom look by only using the `colors` properties - however, if you require more fine-grained control, that is also available. ### Colors The `colors` property on theme is used to establish a set of base colors for the SDK to use. | Colors | Description | Default Value | Dark Value | | :----------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | :--------- | | `theme.colors.primary` | Default accent color used throughout the UI. Usually the primary brand color. Used for: Unread Indicators, loading progress bars and spinners on Android. | #1C62EB | | | `theme.colors.success` | Used to indicate correct answers in Quizzes. | #3BB327 | | | `theme.colors.alert` | Used to indicate wrong answers in Quizzes and for the 'Live' indicator on Live Stories. | #E21219 | | | `theme.colors.white.primary` | Used for Story names on rectangular tiles and in the player. Also used for all primary text in dark mode by default. | #ffffff | | | `theme.colors.white.secondary` | Used for secondary text in dark mode. | rgba({theme.colors.white.primary}, 85%) | | | `theme.colors.white.tertiary` | Used for tertiary text in dark mode, e.g. the time stamp in the player header. Also used for the selected Poll option border. | rgba({theme.colors.white.primary}, 70%) | | | `theme.colors.black.primary` | Used for primary text in light mode by default. | #1A1A1A | | | `theme.colors.black.secondary` | Used for secondary text in light mode. | rgba({theme.colors.black.primary}, 85%) | | | `theme.colors.black.tertiary` | Used for tertiary text and minor UI elements in light mode. | rgba({theme.colors.black.primary}, 70%) | | ### Font Use the `font` property to set a custom font for use throughout the UI. | Font | Description | Default Value | Dark Value | | :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | :--------- | | `theme.font` | Font to be used throughout the UI, defaults to the system font on each platform or inherits the container font on web. Font definition requires access to different weights to work properly. | System Font | | ### Primitives The `primitives` object contains base values which are used throughout the UI. | Primitives | Description | Default Value | Dark Value | | :------------------------------ | :------------------------------------------------------------------------------- | :------------ | :--------- | | `theme.primitives.cornerRadius` | Default corner radius used for Rectangular Tiles, Buttons and Poll/Quiz answers. | 8 dp | | ### Lists The `lists` customize properties of the various list types available from the SDK. | Lists | Description | Default Value | Dark Value | | :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------- | :--------------------------- | | `theme.lists.title` | The style of the title on Story or Clip tile. | 8 dp | | | `theme.lists.row.tileSpacing` | The space between each Story and Clip Tile in a row. | 8 dp | | | `theme.lists.row.startInset` | The space before the first tile in a row. | 12 dp | | | `theme.lists.row.endInset` | The space after the last tile in a row. | 12 dp | | | `theme.lists.grid.tileSpacing` | The space between Story and Clip Tiles in a grid, both vertically and horizontally. | 8 dp | | | `theme.lists.grid.columns` | Number of columns in the grid. | 2 dp | | | `theme.lists.grid.topInset` | The space at the top of the first row in a grid. | 0 dp | | | `theme.lists.grid.bottomInset` | The space at the bottom of the last row in a grid. | 0 dp | | | `theme.lists.backgroundColor` | Required for outline on Live chip and fade to the side of the row | {theme.colors.white.primary} | {theme.colors.black.primary} | | `theme.lists.animateTilesOnReorder` | This option allows you to enable animation in lists when updating items | true | true | | `theme.lists.enablePlayerOpen` | Controls whether the SDK opens the player when a tile is tapped. When set to `false`, the SDK will not open the player and the app must handle tile taps via `StorytellerDelegates.onTileTapped(tileType: StorytellerTileType)` | true | true | ### Gradient The `Gradient` data class allows for the creation of a color gradient, with options to customize both the colors and the positions at which the gradient starts and ends. | Property | Default Value | Data Type | Description | | --------------- | ------------- | ------------------ | -------------------------------------------------- | | `startColor` | `null` | `ColorInt` | The color where the gradient begins. | | `endColor` | `null` | `ColorInt` | The color where the gradient ends. | | `startPosition` | `null` | `GradientPosition` | The position indicating where the gradient starts. | | `endPosition` | `null` | `GradientPosition` | The position indicating where the gradient ends. | #### Enum: GradientPosition Defines positions for starting and ending points of the gradient. | Value | Data Type | Description | | ------------------------------- | --------- | ----------------------------------------- | | `GradientPosition.BottomLeft` | `enum` | Bottom left corner of the gradient area. | | `GradientPosition.BottomCenter` | `enum` | Bottom center edge of the gradient area. | | `GradientPosition.BottomRight` | `enum` | Bottom right corner of the gradient area. | | `GradientPosition.CenterLeft` | `enum` | Center left edge of the gradient area. | | `GradientPosition.CenterCenter` | `enum` | Center of the gradient area. | | `GradientPosition.CenterRight` | `enum` | Center right edge of the gradient area. | | `GradientPosition.TopLeft` | `enum` | Top left corner of the gradient area. | | `GradientPosition.TopCenter` | `enum` | Top center edge of the gradient area. | | `GradientPosition.TopRight` | `enum` | Top right corner of the gradient area. | ### Story and Clip Tiles The `tiles property` can be used to customize the appearance of the Story and Clip Tiles. | Story and Clip Tiles | Description | Default Value | Dark Value | | :-------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------ | :---------------------------- | | `theme.tiles.chip.textSize` | Text size for the New Indicator and Live Indicator. | 11 sp | | | `theme.tiles.chip.borderColor` | Border color for the New Indicator and Live Indicator. | null | | | `theme.tiles.chip.show` | Show or hide the New/Live chip indicator on tiles. | true | | | `theme.tiles.title.textSize` | Size of the Story Title on a Tile. | 11 sp | | | `theme.tiles.title.lineHeight` | Line height of the Story Title on a Tile. | 13 sp | | | `theme.tiles.title.alignment` | Alignment of the text in a tile, can be Gravity.START/CENTER/END | center | | | `theme.tiles.circularTile.title.unreadTextColor` | Text color of Circular Story and Clip Tile Titles in unread state | {theme.colors.black.primary} | {theme.colors.white.primary} | | `theme.tiles.circularTile.title.readTextColor` | Text color of Circular Story and Clip Tile Titles in read state | {theme.colors.black.tertiary} | {theme.colors.white.tertiary} | | `theme.tiles.circularTile.unreadIndicatorColor` | The color of the ring around Circular Story and Clip Tiles. | {theme.colors.primary} | | | `theme.tiles.circularTile.readIndicatorColor` | The color of the ring around Circular Story and Clip Tiles in read state | #C5C5C5 (taken from app layout) | | | `theme.tiles.circularTile.unreadIndicatorBorderColor` | The color of the border of the ring around Circular Story and Clip Tiles | null | | | `theme.tiles.circularTile.readIndicatorBorderColor` | The color of the border of the ring around Circular Story and Clip Tiles in read state | null | | | `theme.tiles.circularTile.unreadBorderWidth` | Width of Circular Story and Clip Tile ring border in unread state | 2dp | | | `theme.tiles.circularTile.readBorderWidth` | Width of Circular Story and Clip Tile ring border in read state | 2dp | | | `theme.tiles.circularTile.unreadIndicatorGradient` | The gradient of the ring around a circular tile when the story or the clip is unread. If set, overrides `circularTile.unreadIndicatorColor`. | null | | | `theme.tiles.circularTile.liveChip.unreadImage` | Image resource to be used in place of default unread Live Indicator. If set, overrides `theme.tiles.circularTile.liveChip.unreadBackgroundGradient`. | null | | | `theme.tiles.circularTile.liveChip.unreadBackgroundGradient` | The gradient of the ring around a live tile and background of the Live Indicator. If set, overrides `circularTile.liveChip.unreadBackgroundColor`. | null | | | `theme.tiles.circularTile.liveChip.unreadBackgroundColor` | Background color of the Live Indicator when the story contains unread pages or the clip has not been viewed. | {theme.colors.alert} | | | `theme.tiles.circularTile.liveChip.unreadBorderColor` | Border color of the Live Indicator when the story contains unread pages or the clip has not been viewed. | null | | | `theme.tiles.circularTile.liveChip.unreadTextColor` | Text color of the Live Indicator when the story contains unread pages or the clip has not been viewed. | {theme.colors.white.primary} | | | `theme.tiles.circularTile.liveChip.readImage` | Image resource to be used in place of default read Live Indicator. | null | | | `theme.tiles.circularTile.liveChip.readBackgroundColor` | Background color of the Live Indicator when all pages have been read or the clip has been viewed. | {theme.colors.black.tertiary} | | | `theme.tiles.circularTile.liveChip.readBorderColor` | Border color of the Live Indicator when all pages have been read or the clip has been viewed. | null | | | `theme.tiles.circularTile.liveChip.readTextColor` | Text color of the Live Indicator when all story pages have been read or the clip has been viewed. | {theme.colors.white.primary} | | | `theme.tiles.rectangularTile.title.textColor` | Text color of the Story Title in Rectangular Tiles. | {theme.colors.white.primary} | | | `theme.tiles.rectangularTile.chip.alignment` | Alignment of the New Indicator and Live Indicator in Rectangular Tiles, can be start, center or end. | end | | | `theme.tiles.rectangularTile.padding` | Internal padding for Rectangular Story and Clip Tiles, creates space between Story Name or New Indicator and tile edge. | 8 dp | | | `theme.tiles.rectangularTile.unreadIndicator.image` | Image resource to be used in place of default New Indicator on Rectangular Tiles. | null | | | `theme.tiles.rectangularTile.unreadIndicator.backgroundColor` | Background color of the New Indicator. | {theme.colors.primary} | | | `theme.tiles.rectangularTile.unreadIndicator.borderColor` | Border color of the New Indicator. | null | | | `theme.tiles.rectangularTile.unreadIndicator.textColor` | The text color of the unread indicator for a rectangular tile. | {theme.colors.white.primary} | | | `theme.tiles.rectangularTile.unreadIndicator.textSize` | Text size for the New Indicator. | 11 sp | | | `theme.tiles.rectangularTile.liveChip.unreadImage` | Image resource to be used in place of default unread Live Indicator. If set, overrides `theme.tiles.circularTile.liveChip.unreadBackgroundGradient`. | null | | | `theme.tiles.rectangularTile.liveChip.unreadBackgroundGradient` | The gradient of the ring around a live tile and background of the Live Indicator. If set, overrides `circularTile.liveChip.unreadBackgroundColor`. | null | | | `theme.tiles.rectangularTile.liveChip.unreadBackgroundColor` | Background color of the Live Indicator when the story contains unread pages or the clip has not been viewed. | {theme.colors.alert} | | | `theme.tiles.rectangularTile.liveChip.unreadTextColor` | Text color of the Live Indicator when the story contains unread pages or the clip has not been viewed. | {theme.colors.white.primary} | | | `theme.tiles.rectangularTile.liveChip.readImage` | Image resource to be used in place of default read Live Indicator. | null | | | `theme.tiles.rectangularTile.liveChip.readBackgroundColor` | Background color of the Live Indicator when all pages have been read or the clip has been viewed. | {theme.colors.black.tertiary} | | | `theme.tiles.rectangularTile.liveChip.readBorderColor` | Border color of the Live Indicator when all pages have been read or the clip has been viewed. | null | | | `theme.tiles.rectangularTile.liveChip.unreadBorderColor` | Border color of the Live Indicator when the story contains unread pages or the clip has not been viewed. | null | | | `theme.tiles.rectangularTile.liveChip.readTextColor` | Text color of the Live Indicator when all story pages have been read or the clip has been viewed. | {theme.colors.white.primary} | | ### Player The `player` property is used to customize properties relating to the Stories and Clips Player. | Player | Description | Default Value | Dark Value | | :----------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------- | :--------- | | `theme.player.showStoryIcon` | Shows the circular Story icon before the Story Name in the Player. | FALSE | | | `theme.player.showTimestamp` | Shows a timestamp after the Story Name in the Player, eg "2h" to show the Story was published 2 hours ago. | TRUE | | | `theme.player.showShareButton` | Shows the Share button in the Player, applies to all Page types and Engagement Units. Setting to FALSE entirely disables sharing in Storyteller. | TRUE | | | `theme.player.liveChip.image` | Image used in place of Live Chip before Live Story or Clip Titles. If set, it overrides `liveChip.backgroundGradient` | null | | | `theme.player.liveChip.backgroundGradient` | Background gradient of the badge for Live Story or Clip. If set, it overrides `liveChip.backgroundColor` | null | | | `theme.player.liveChip.backgroundColor` | Background color of the Live chip Story or Clip | {theme.colors.alert} | | | `theme.player.liveChip.textColor` | Text color used for badge label for Live Story or Clip | {theme.colors.white.primary} | | | `theme.player.liveChip.borderColor` | Color used for border of badge label for Live Story or Clip | null | | | `theme.player.icons.share` | Share button image to be used in place of default share icon. | default icon | | | `theme.player.icons.refresh` | Refresh button image to be used in place of default refresh icon. | default icon | | | `theme.player.icons.close` | Close button image to be used in place of default close icon. | default icon | | | `theme.player.icons.like.initial` | Initial like button image to be used in place of default initial like icon. | default icon | | | `theme.player.icons.like.liked` | Liked button image to be used in place of default liked icon. | default icon | | | `theme.player.showLikeButton` | Shows the Like button on Clips. | TRUE | | ### Buttons The `buttons` property applies customizations to buttons which appear throughout the UI. | Buttons | Description | Default Value | Dark Value | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------ | :--------- | | `theme.buttons.backgroundColor` | Background color of buttons including: share buttons at the end of Quizzes, primary action buttons in Clips, and action buttons in the Following empty state. | {theme.colors.white.primary} | | | `theme.buttons.textColor` | Text color of buttons including: share buttons at the end of Quizzes, primary action buttons in Clips, and action buttons in the Following empty state. | {theme.colors.black.primary} | | | `theme.buttons.textCase` | Sets the text case for the button on the Instructions Screen and share buttons at the end of Quizzes. (TextCase.UPPER/DEFAULT/LOWER). | default | | | `theme.buttons.cornerRadius` | Sets the corner radius for buttons including: Instructions Screen button, share buttons at the end of Quizzes, primary action buttons in Clips, and action buttons in the Following empty state. Any value greater than half the height of the button will create a pill shape. | {theme.primitives.cornerRadius} | | ### Instructions Use the `instructions` property to customize the appearance of the instructions screen. | Instructions | Description | Default Value | Dark Value | | :------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :----------------------------- | | `theme.instructions.show` | Show the Instructions Screen the first time a user opens Storyteller. Set to FALSE to entirely hide the Instructions screen. | TRUE | | | `theme.instructions.headingColor` | Heading color of the text used on the Instructions Screen. | {theme.colors.black.primary} | {theme.colors.white.primary} | | `theme.instructions.headingTextCase` | Sets the text case for the heading on the Instructions Screen. (TextCase.UPPER/DEFAULT/LOWER). | TextCaseTheme.DEFAULT | | | `theme.instructions.headingFont` | Font to be used on the Instuctions Screen for headers, defaults to the system font | System font | | | `theme.instructions.subHeadingColor` | Subheading color of the text used on the Instructions Screen. | {theme.colors.black.secondary} | {theme.colors.white.secondary} | | `theme.instructions.backgroundColor` | Background color of the Instructions Screen. | {theme.colors.white.primary} | {theme.colors.black.primary} | | `theme.instructions.icons` | A set of icons used for each instruction on the Instructions Screen | default set of icons | | | `theme.instructions.button.backgroundColor` | Background color of the button used on the Instructions Screen. | {theme.colors.black.primary} | {theme.colors.white.primary} | | `theme.instructions.button.textColor` | Text color of the button used on the Instructions Screen. | {theme.colors.white.primary} | {theme.colors.black.primary} | ### Engagement Units The `engagementUnits` property can be used to customize properties relating to Polls and Quizzes. | Engagement Units | Description | Default Value | Dark Value | | :----------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------- | :--------- | | `theme.engagementUnits.poll.answerTextColor` | Answer text color used in Poll Answers. | {theme.colors.black.primary} | | | `theme.engagementUnits.poll.percentBarColor` | Background color of the Percentage Bar in Poll Answers. | #CDD0DC | | | `theme.engagementUnits.poll.selectedAnswerBorderColor` | Border color added to the selected Poll Answer. Inherits `colors.primary` | {theme.colors.primary} | | | `theme.engagementUnits.poll.answeredMessageTextColor` | Color of the vote count or "Thanks for Voting!" message shown to users. | {theme.colors.white.primary} | | | `theme.engagementUnits.poll.selectedAnswerBorderImage` | Border image used for the selected Poll Answer. Overwrites selectedAnswerBorderColor and can be used to create a shimmer animation as the border image is rotated in when an answer is selected. | null | | | `theme.engagementUnits.poll.showPercentBarBackground` | Adds a striped background under the percentage bar in Poll Answers. | FALSE | | | `theme.engagementUnits.triviaQuiz.correctColor` | Color used for correct answers in Trivia Quizzes. | {theme.colors.success} | | | `theme.engagementUnits.triviaQuiz.incorrectColor` | Color used for incorrect answers in Trivia Quizzes. | {theme.colors.alert} | | ### Search The `search` property applies customizations to the `Search` component. | Title | Description | Default Value | Dark Value | | ----------------------- | ------------------------------------------------- | ------------------- | ------------------- | | `theme.search.heading` | Styling of the `Filters` title in the Filter View | `theme.lists.title` | `theme.lists.title` | | `theme.search.backIcon` | Image to be used as a back icon in the Search UI | default icon | | ### Home | Title | Description | Default Value | Dark Value | | ---------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------- | ---------- | | `theme.home.headerTitle.font` | Font to be used throughout the UI, defaults to the system font | System font | | | `theme.home.headerTitle.textSize` | Size of the Home Header Title | 22sp | | | `theme.home.headerTitle.lineHeight` | Line height of the Home Header Title | 25sp | | | `theme.home.headerTitle.textCase` | Sets the text case for the Home Header Title. (TextCase.UPPER/DEFAULT/LOWER). | TextCaseTheme.DEFAULT | | | `theme.home.headerTitle.textColor` | Text color of the Home Title | colors.black.primary | | | `theme.home.circularTitle.textSize` | The size of the title for Circular tiles | lists.title | | | `theme.home.circularTitle.lineHeight` | The line height of the title for Circular tiles | lists.title | | | `theme.home.gridTitle.textSize` | The size of the title for tiles in Grid | 16sp | | | `theme.home.gridTitle.lineHeight` | The line height of the title for Grid tiles | 22sp | | | `theme.home.singletonTitle.textSize` | The size of the title for Singleton tile | 22sp | | | `theme.home.singletonTitle.lineHeight` | The line height of the title for Singleton tiles | 28sp | | | `theme.home.rectangularTitle.smallTitle.textSize` | The size of the title for small Rectangular row tiles | lists.title | | | `theme.home.rectangularTitle.smallTitle.lineHeight` | The title line height for the small Rectangular row tiles | lists.title | | | `theme.home.rectangularTitle.mediumTitle.textSize` | The size of the title for medium Rectangular row tiles | 16sp | | | `theme.home.rectangularTitle.mediumTitle.lineHeight` | The title line height for the medium Rectangular row tiles | 22sp | | | `theme.home.rectangularTitle.largeTitle.textSize` | The size of the title for large Rectangular row tiles | 18sp | | | `theme.home.rectangularTitle.largeTitle.lineHeight` | The title line height for the large Rectangular row tiles | 24sp | | ### Theme Builder Initialization Theme builder is initialized by the `buildTheme` method lambda passed after the method acts in the builder scope. ```kotlin import com.storyteller.domain.theme.builders.buildTheme import com.storyteller.domain.theme.builders.ofHexCode Storyteller.theme = buildTheme { light.colors.primary = ofHexCode("#FF00FF") } ``` ### Accessing Properties in the Builder Scope There are 2 equivalent ways of accessing builder properties: - By scopes ```kotlin buildTheme { light { instructions { button { backgroundColor = ofHexCode("#00FF00") } } } } ``` - By properties ```kotlin buildTheme { light.instructions.button.backgroundColor = ofHexCode("#00FF00") } ``` Both approaches produce the same effect. ### Light and Dark Builder Variants In the builder context two variants are present: - light - dark Although they have an identical structure, they are build with the different set of fallbacks. For instance, default `theme.tiles.circularTile.title.unreadTextColor` will fallback to the default value of `theme.colors.black.primary` in the light mode or `theme.colors.white.primary` in the dark mode. The selection of active themes will be done using current phone UI mode and `StorytellerListView.uiStyle` property value. See [StorytellerListViews](StorytellerListViews.md) for more details. For coding convenience, if you do not intent use light and dark mode and relay on default fallback you can use `from` inline method to copy already set values from one theme to the other. ```kotlin buildTheme { light { colors { primary = ofHexCode("#FF00FF") success = ofHexCode("#00FF00") } } dark from light } ``` The above code will set all properties of dark to the current state of the light builder. This method is useful to avoid lengthy typing - a common parameter can be assigned one and copied to the other variant. ### Setting Properties of Particular Type #### Colors Color properties are expected to be Android @ColorInt. They can be initialized with anything that return such type e.g they can be resolved color from resources, Color.argb() Color.BLUE and so on. For convenience, `ofHexColor(string)` method is provided - it accepts 6 or 8 hex digits prefixed by the `#` ```kotlin val red = ofHexColor("#FF0000") val semiTransparentRed = ofHexColor("#55FF0000") ``` > Note: when using resources colors mind that they are resolved at the moment of building theme, **NOT** at the moment of accessing. #### Drawables For the properties accepting StorytellerDrawable type. You can use `drawableRes` helper method to set a drawable resource. ```kotlin val myTheme = buildTheme { light.engagementUnits.poll.selectedAnswerBorderImage = drawableRes(R.drawable.gradient_border) } ``` #### Fonts To support multiple weights for fonts, a font family xml resource is required. The SDK will automatically select a font for the appropriate weight when needed. - Creating a font family resource `custom_font.xml` ```xml ``` - Setting the font family as custom font property `theme.font` using the helper method `fontRes` ```kotlin font = fontRes(R.font.custom_font) ``` ## Example ```kotlin val storyRowView = StoryRowView(context) storyRowView.theme = buildTheme { light { colors { primary = ofHexCode("#FF0000") success = ofHexCode("#00FF00") alert = ofHexCode("#C50511") } font = fontRes(R.font.custom_font) lists { row { tileSpacing = 8 startInset = 12 endInset = 12 } grid { tileSpacing = 8 columns = 2 } enablePlayerOpen = false // Handle tile taps manually via StorytellerDelegates } tiles { title { textSize = 11 lineHeight = 13 alignment = Gravity.START } circularTile { unreadIndicatorGradient = UiTheme.Theme.Gradient( startColor = getColor(R.color.gradient_start), endColor = getColor(R.color.gradient_end), startPosition = UiTheme.Theme.Gradient.GradientPosition.CenterLeft, endPosition = UiTheme.Theme.Gradient.GradientPosition.CenterRight, ) liveChip { unreadBackgroundGradient = UiTheme.Theme.Gradient( startColor = getColor(R.color.gradient_start), endColor = getColor(R.color.gradient_end), startPosition = UiTheme.Theme.Gradient.GradientPosition.CenterLeft, endPosition = UiTheme.Theme.Gradient.GradientPosition.CenterRight, ) readTextColor = getColor(R.color.read_live_text) unreadTextColor = getColor(R.color.unread_live_text) } } rectangularTile { padding = 8 unreadIndicator.alignment = Gravity.END unreadIndicator.textColor = ofHexCode("#000FF") unreadIndicator.textSize = 11 liveChip { readTextColor = getColor(R.color.read_live_text) unreadTextColor = getColor(R.color.unread_live_text) } } } buttons.cornerRadius = 24 buttons.textCase = TextCase.UPPER instructions { icons { forward = drawableRes(R.drawable.ic_forward_light) pause = drawableRes(R.drawable.ic_pause_light) back = drawableRes(R.drawable.ic_back_light) move = drawableRes(R.drawable.ic_move_light) } } home { title { font = fontRes(R.font.font) textCase = TextCaseTheme.LOWER textSize = 28 lineHeight = 28 textColor = getColor(R.color.rams_storyteller_primary) } } } dark from light } ``` ## Source: StorytellerDelegates.md # Implementing Storyteller Delegate Callbacks ## Table of Contents - [StorytellerDelegate](#storytellerdelegate) - [StorytellerListViewDelegate](#storytellerlistviewdelegate) A `StorytellerDelegate` has methods for managing `Storyteller` events. It is used as global object for handling all events when opening a Story Player with and without the `StorytellerStoriesRowView` or `StorytellerStoriesGridView`. Please see the dedicated `StorytellerListViewDelegate` below for handling events related to rows and grids. ## Showcase examples - [Compose — global delegate (`ShowcaseStorytellerDelegate`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ShowcaseStorytellerDelegate.kt#L32) - [Compose — list delegate example (`PageItemStorytellerDelegate`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/storyteller/PageItemStorytellerDelegate.kt#L14) - [XML — global delegate (`ShowcaseStorytellerDelegate`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ShowcaseStorytellerDelegate.kt#L28) - [XML — list delegate example (`StorytellerViewDelegate`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/dashboard/adapter/StorytellerViewDelegate.kt#L13) ## StorytellerDelegate To use global `StorytellerDelegate`, implement the `StorytellerDelegate` interface by overriding the required methods and set it in `Storyteller` object. Example: ```kotlin Storyteller.storytellerDelegate = myCustomStorytellerDelegate ``` ### onUserActivityOccured The `onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: UserActivityData)` method is is called when an analytics event is triggered. See the dedicated [Analytics](Analytics.md) page for more information on analytic events. ### getAd The `getAd(adRequestInfo: StorytellerAdRequestInfo, onComplete: (StorytellerAd) -> Unit = {}, onError: () -> Unit)` method is called when the tenant is configured to request ads from the containing app and the SDK requires ad data from the containing app. For more information on how to supply ads to the Storyteller SDK, see the dedicated [Ads](Ads.md) page. ### userNavigatedToApp The `userNavigatedToApp(url: String)` method is called when a user taps on an action button on a Page which should direct the user to a specific place within the integrating app. More information on `In App` links [Navigating to App](NavigatingToApp.md). For more information on deep linking, see the dedicated [Deep Linking](DeepLinking.md) page. ## Analytics The callback `onUserActivityOccurred` provides analytics events and corresponding data triggered internally by the SDK. This information can be used in your app. The following parameters are passed to the callback method: - `type` - type of event that occurred, as a `StorytellerUserActivity.EventType` enum - `data` - an object containing data about the event which occurred Example: ```kotlin ... override fun onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: UserActivityData) { if (type == StorytellerUserActivity.EventType.OPENED_STORY) { // Retrieve the story id value val openStoryId = data.storyId // Retrieve the story title value val openStoryTitle = data.storyTitle // Report retrieved values from your app } } ``` For a detailed discussion of all the relevant events and properties please see the dedicated [Analytics](Analytics.md) page. ## Client Ads By implementing `getAd`, you can provide custom ad data for the SDK to render, this is only applicable when the ad configuration is set to `Integrating App` in the CMS. Ad data can be obtained asynchronously, and should be provided using the `onComplete` closure parameter. Example: ```kotlin ... override fun getAd(adRequestInfo: StorytellerAdRequestInfo, onComplete: (StorytellerAd) -> Unit = {}, onError: () -> Unit) { // Action to get some ads val ad = getMyAd() // Convert the ad to a StorytellerAd val storytellerAd = convertToStorytellrAd(ad) // Provide the ad to the SDK onComplete(ad) } ``` For a detailed discussion of all the relevant considerations, please see the dedicated [Ads](Ads.md) page. ### configureWebView This method allows you to configure the WebView with custom settings or actions when the Storyteller SDK, is to display a WebView. It takes three parameters: the WebView to configure, an optional URL string, and an optional favicon Bitmap. This method is called from the `onPageStarted` method of a custom `WebViewClient` when a new page starts loading in the WebView. #### Parameters - `view: WebView` - The WebView instance to be configured. - `url: String?` - (Optional) The URL string associated with the WebView page that is being loaded. - `favicon: Bitmap?` - (Optional) The favicon Bitmap to be displayed for the WebView page. #### Example Usage To intercept and customize WebViews created by the Storyteller SDK, you can implement your own version of the `configureWebView` method. Here is an example of how to do this: ```kotlin override fun configureWebView( view: WebView, url: String?, favicon: Bitmap? ) { // Your custom configuration code goes here // The following line shows a simple alert in the WebView with the message "test webview javascript". view.evaluateJavascript("javascript: alert('test webview javascript');", null) } ``` ## Followable Categories The method `fun categoryFollowActionTaken(category: Category, isFollowing: Boolean)` is invoked when a user adds or removes a category from within SDK's UI. The callback method receives the following parameters: - `category` - An object representing the clip category - `isFollowing` - A boolean value indicating whether the user is following or unfollowing the specified category ### customScreenForCategory The `fun customScreenForCategory(): @Composable (StorytellerFollowableCategoryCustomScreen) -> Unit` method is called when a user navigates to a followable category screen. The callback receives a `StorytellerFollowableCategoryCustomScreen` object. with the following parameters: - `pendingModifier` - a `Modifier` object that is used to apply custom styling to the screen, this must be applied to the root composable of your custom screen - `category` - a `Category` object that is the category that the user is navigating to - `onBackClicked` - a `() -> Unit` a callback that should be called when the user clicks the back button from the custom screen, this is to let Storyteller know that the user has navigated back to the previous screen. If this is not called, the user will be stuck on the custom screen and unable to navigate back to the previous screen. Example: ```kotlin override fun customScreenForCategory(): @Composable() ((StorytellerFollowableCategoryCustomScreen) -> Unit) { return { (pendingModifier, category, onBackClicked) -> Scaffold( modifier = pendingModifier, topBar = { CenterAlignedTopAppBar( title = { Text("${category.displayTitle}") }, navigationIcon = { IconButton(onClick = onBackClicked) { Icon( imageVector = Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back" ) } } ) } ) { paddingValues -> Surface( modifier = Modifier.padding(paddingValues) ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.Center ) { Text("Custom layout for ${category.displayTitle}") } } } } } ``` Alongside the `customScreenForCategory` callback, the `Storyteller` object also has a `useCustomScreenForCategory` property that is a `(Category) -> Boolean` callback. This is to allow the developer to conditionally show the custom screen for a specific category or default to the original implementation of Followable Categories by Storyteller. The callback must return a `Boolean` value, which determines whether the custom screen should be shown for the given category. Example: ```kotlin Storyteller.useCustomScreenForCategory = { if (it.type == "custom") { // return true if you want to show the custom followable category screen true } else { // return false if you want to show the default followable category screen false } } ``` The `customScreenForCategory` will only be called if the `useCustomScreenForCategory` callback returns `true` for the given category. Otherwise, if it's not set or returns `false`, the default implementation of the Followable Categories by Storyteller will be used. ### bottomSheetScreen The `fun bottomSheetScreen(urlProvider: () -> String, onDismiss: () -> Unit): @Composable () -> Unit` method is called when the deep link includes the query parameter `shouldUseModal=true`. - `urlProvider` A callback that provides the URL to be loaded in the WebView or used for other purposes. - `onDismiss` A callback invoked when the user dismisses the bottom sheet. Example: ```kotlin override fun bottomSheetScreen( urlProvider: () -> String, onDismiss: () -> Unit ): @Composable (() -> Unit) { return { DemoBottomSheet(urlProvider, onDismiss) } } ``` ## StorytellerListViewDelegate A `StorytellerListViewDelegate` has methods for managing `StorytellerStoriesRowView` and `StorytellerStoriesGridView` events. ### How to Use To use `StorytellerListViewDelegate`, implement the `StorytellerListViewDelegate` interface and override the required methods: #### onDataLoadStarted The `onDataLoadStarted()` method is called when the network request to load data for all Stories has started. #### onDataLoadComplete The `onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int)` method is called when the data loading network request is complete. | Property | Description | | ----------- | ------------------------------------------------------- | | `success` | This confirms whether or not the request was successful | | `error` | The HTTP error if the request was not successful | | `dataCount` | The number of Stories loaded | #### onTileTapped The `onTileTapped(tileType: StorytellerTileType)` method is called when a user taps on a tile inside a row or grid. This callback is executed before the player Activity is opened. | Property | Description | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tileType` | A sealed class that contains tile information. Can be either `StorytellerTileType.Story` (with storyId and categories) or `StorytellerTileType.Clip` (with clipId, collectionId, and categories). Categories are `List`. | Example: ```kotlin override fun onTileTapped(tileType: StorytellerTileType) { when (tileType) { is StorytellerTileType.Story -> { // Handle story tile tap val storyId = tileType.id val categories = tileType.categories } is StorytellerTileType.Clip -> { // Handle clip tile tap val clipId = tileType.id val collectionId = tileType.collectionId val categories = tileType.categories } } } ``` > Note: When `theme.lists.enablePlayerOpen` is set to `false`, this callback becomes the primary way to handle tile interactions, as the SDK will not automatically open the player. #### onPlayerDismissed The `onPlayerDismissed()` method is called when user closes Storyteller Player. Example: ```kotlin val StorytellerStoriesRowView = StorytellerStoriesRowView() StorytellerStoriesRowView.delegate = myDelegate ``` #### Error Handling By using the callback function `onDataLoadComplete` and the data it provides, you can handle the current state of the `StorytellerStoriesRowView` appropriately in your app. > Note: `dataCount` is the total number of Stories in the existing `StorytellerStoriesRowView` at any given time Example: ```kotlin ... override fun onDataLoadComplete(success: Boolean, error: StorytellerError?, dataCount: Int) { if (success) { // stories data has been loaded successfully // dataCount is the current total number of stories, including newly added/removed data } else if (error != null) { // an error has occurred, unwrap the error value for more information // dataCount is the total number of stories before loading began } } ``` Another example: ```kotlin ... override fun onDataLoadComplete(success: Boolean, error: StorytellerError?, dataCount: Int) { if (error != null && dataCount == 0) { // stories have failed to load with error and there is no data to show // you may wish to hide the `StorytellerStoriesRowView` instance here StorytellerStoriesRowView.visibility = View.GONE } } ``` ## Source: StorytellerModule.md # StorytellerModule The StorytellerModule module is a interface you can adopt to handle the fetching of ads and recording of user activity events from Storyteller. It contains the following methods: ## Showcase examples - [Compose — initializing modules (`StorytellerServiceImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L92) - [XML — initializing modules (`StorytellerServiceImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L70) ## Methods ```kotlin abstract fun onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: UserActivityData) abstract fun getAd( adContext: AdContext, adRequestInfo: StorytellerAdRequestInfo, onComplete: (StorytellerAd) -> Unit, onError: (String) -> Unit ) ``` ### Analytics The callback `onUserActivityOccurred` provides analytics events and corresponding data to be triggered internally by the SDK. This information can be used in your app. The following parameters are passed to the callback method: - `type` - type of event that occurred, as a `StorytellerUserActivity.EventType` enum - `data` - an object containing data about the event which occurred Example: ```kotlin override fun onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: UserActivityData) { when (type) { StorytellerUserActivity.EventType.AD_ACTION_BUTTON_TAPPED -> onAdAction(data) StorytellerUserActivity.EventType.OPENED_AD -> onAdStart(data) StorytellerUserActivity.EventType.FINISHED_AD -> onAdEnd(data) else -> Log.d("StorytellerModule", "Unhandled event type: $type") } } ``` See the [Analytics](Analytics.md) page for more information. ### Ads By implementing `getAd`, you can provide custom ad data for the SDK to render. This is only applicable when the ad configuration is set to `Integrating App` in the CMS. Ad data can be obtained asynchronously. The onComplete callback should be called when the ad data is ready, and the onError callback should be called if an error occurs. Example: ```kotlin override fun getAd( adContext: AdContext, adRequestInfo: StorytellerAdRequestInfo, onComplete: (StorytellerAd) -> Unit, onError: (String) -> Unit ) { val ad = fetchAd(adContext, adRequestInfo) if (ad != null) { onComplete(ad) } else { onError("Failed to fetch ad") } } ``` For a detailed discussion of all the relevant considerations, please see the dedicated [Ads](Ads.md) page. ## Technical Consideration Use of a custom StorytellerModule is optional. If you do not implement this interface, the SDK will use the default implementation see [Ads](Ads.md). It must be provided to the SDK when initializing the Storyteller SDK using the `Storyteller.modules` method to see Gam Ads. ## Source: NavigatingToApp.md # Navigating to App A `StorytellerDelegate` has method for managing in app navigation. It can be used for custom handling of `deeplink` URLs which can configured per page in CMS. This method will be called when user presses on the action button with the `deeplink` navigation type. ## Showcase examples - [Compose — `userNavigatedToApp` (`ShowcaseStorytellerDelegate`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ShowcaseStorytellerDelegate.kt#L117) - [XML — `userNavigatedToApp` (`ShowcaseStorytellerDelegate`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ShowcaseStorytellerDelegate.kt#L82) ## How to Use To use global `userNavigatedToApp`, implement the `StorytellerDelegate` interface by overriding the required methods and set it in `Storyteller` object. `userNavigatedToApp(url: String)` is the method responsible for handling these `deeplink` URLs. ## Navigation to the Integrating App The callback `userNavigatedToApp` is called when a user taps on an action button on a page which has its link type set to `deeplink`. In this case, your app will be passed a URL which has been entered in the Storyteller CMS and your app is then responsible for parsing this URL and following it to the correct location within your app. Example: ```kotlin override fun userNavigatedToApp(url: String) { // parse the url and navigate to the destination } ``` ## Source: Analytics.md # Analytics ## Table of Contents - [Event Types](#event-types) - [Story Events](#story-events) - [Clip Events](#clip-events) - [Sheet Events](#sheet-events) - [Card Events](#card-events) - [Ad Events](#ad-events) - [Playback Events](#playback-events) - [Search Events](#search-events) - [Event Data](#event-data) ## Showcase examples - [Compose — map `onUserActivityOccurred` to Amplitude](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ShowcaseStorytellerDelegate.kt#L136) - [XML — map `onUserActivityOccurred` to Amplitude](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ShowcaseStorytellerDelegate.kt#L101) ## Event Types These are the various events which are triggered from within the SDK. Each event is a member of the `StorytellerUserActivity.EventType` enumeration. Please see the [StorytellerDelegates](StorytellerDelegates.md) page for details on how to bind to these events. In the below discussion, "completing" a Page refers to allowing the timer to expire - so this would correspond to watching all of an image or a Poll Page for the duration set for it (default 15s) or watching all of a video. ## Story Events ### Opened Story This event is recorded in the following scenarios: - When a user taps on a list item to open a Story - When a Story is loaded because the previous Story finished - When a Story is loaded because the user tapped to skip the last Page of the previous Story - When a user swipes left on a Story to go to the next Story - When a user swipes right on a Story to go to the previous Story - When a user is sent directly to a Story via a deep link Whenever an Opened Story event occurs, event data for `storyID`, `storyTitle`, `storyIndex`, `storyPageCount`, `storyReadStatus`, `storyPlaybackMode`, `openedReason`, `currentCategory`, `collection`, `categories`, `captionsEnabled`, `actionLinkId`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Opened Page This event is recorded as soon as a user sees content for a Page. Opened Page is one of the most important events to track as it is equal to a video start, one of the most important measures of engagement. By tracking this event, you can monitor valuable information about user engagement with your app. Whenever an Opened Page event occurs, event data for `pageID`, `pageIndex`, `pageType`, `storyPlaybackMode`, `pageHasAction`, `pageActionType`, `pageActionText`, `pageActionUrl`, `actionLinkId`, `contentLength`, `storyID`, `storyTitle`, `storyIndex`, `storyPageCount`, `currentCategory`, `collection`, `openedReason`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Dismissed Story This event is recorded in the following scenarios: - When a user taps the close button to dismiss the Story - When a user swipes down to dismiss the Story - When a user taps back on their device UI to dismiss the Story (Android only) - When a user taps to skip the last Page of the final Story - this dismisses the Story and exits the Story View - When a user swipes left on the final Story to dismiss the Story - When a user swipes right on the first Story to dismiss the Story - When a user completes the final Page of the final Story and the Story view is dismissed Whenever a Dismissed Story event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `pageType` `dismissedReason`, `durationViewed`, `currentCategory`, `collection`, `pagesViewedCount`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Skipped Story This event is recorded when: - a user swipes left to go to the next Story - a user skips the last Page of a Story to go to the next Story Whenever a Skipped Story event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `currentCategory`, `collection`, `pageType`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Skipped Page This event is recorded when a user taps to go to the next Page before completing the current Page. Whenever a Skipped Page event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `currentCategory`, `collection`, `pageType`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Completed Story This event is recorded at the same time as `OpenedPage` for the final Page in a Story. Whenever a Completed Story event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `currentCategory`, `collection`, `pageType`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Completed Page This event is recorded when a user watches a Page to completion (i.e. the timer for that Page finishes). Whenever a Completed Page event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `pageType`, `currentCategory`, `collection`, `contentLength`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Action Button Tapped This event is recorded when a user taps on an action button on a Page to open a link. Whenever a Action Button Tapped event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `pageType`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `collection`, `actionText`, `pageActionUrl`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Share Button Tapped This event is recorded when a user taps the share button on a Page. Whenever a Share Button Tapped event occurs, event data for `shareMethod`, `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `currentCategory`, `collection`, `pageType`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Previous Story This event is recorded when: - a user swipes right to go to the previous Story (unless this is the first Story - in which case DismissedStory is fired instead) - a user taps back on the first Page in a Story (and this is not the first Page in the first Story) Whenever a Previous Story event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `currentCategory`, `collection`, `pageType`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Previous Page This event is recorded when a user taps back to go to a previous Page in the Story. Whenever a Previous Page event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `currentCategory`, `collection`, `pageType`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Share Success This event is recorded when a user selects a sharing method from the system dialog. Note: this event is only available for Android API level 22 and above. Whenever a Share Success event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` `storyPlaybackMode`, `pageID`, `pageIndex`, `pageType`, `currentCategory`, `collection`, `shareMethod`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `isMuted` and `context` is returned. > Note 1: on Android API level 31 and above, `shareMethod` is not supported and will be `null` for this event. > Note 2: sharing to clipboard does not trigger Share Success event, only when an app is selected for sharing the event will be fired. ### Voted Poll This event is recorded when a user votes in a poll. Whenever a Voted Poll event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `pageId`, `pageIndex`, `pageType`, `currentCategory`, `collection`, `pollAnswerId`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Trivia Quiz Question Answered This event is recorded when a user submits a Quiz answer. Whenever a Trivia Quiz Question Answered event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPlaybackMode`, `pageID`, `pageIndex`, `pageType`, `triviaQuizId`, `triviaQuizTitle`, `triviaQuizQuestionId`, `currentCategory`, `collection`, `triviaQuizAnswerId`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Trivia Quiz Completed This event is recorded when a user completes a Quiz. Whenever a Trivia Quiz Completed event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `storyPlaybackMode`, `pageID`, `pageIndex`, `pageType`, `triviaQuizId`, `triviaQuizTitle`, `currentCategory`, `collection`, `triviaQuizScore`, `actionLinkId`, `captionsEnabled`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Muted Story This event is recorded when a user taps the mute button to mute audio while viewing a Story. Whenever a Muted Story event occurs, event data for `captionsEnabled`, `categories`, `categoryDetails`, `currentCategory`, `contentLength`, `storyDisplayTitle`, `openedReason`, `storyId`, `storyIndex`, `pageActionText`, `pageActionUrl`, `actionLinkId`, `pageHasAction`, `pageActionType`, `pageId`, `pageIndex`, `pageTitle`, `pageType`, `searchFilter`, `searchTerm`, `searchSort`, `storyPageCount`, `storyPlaybackMode`, `storyTitle`, `isMuted` and `context` is returned. ### Unmuted Story This event is recorded when a user taps the mute button to unmute audio while viewing a Story. Whenever an Unmuted Story event occurs, event data for `captionsEnabled`, `categories`, `categoryDetails`, `currentCategory`, `contentLength`, `storyDisplayTitle`, `openedReason`, `storyId`, `storyIndex`, `pageActionText`, `pageActionUrl`, `actionLinkId`, `pageHasAction`, `pageActionType`, `pageId`, `pageIndex`, `pageTitle`, `pageType`, `searchFilter`, `searchTerm`, `searchSort`, `storyPageCount`, `storyPlaybackMode`, `storyTitle`, `isMuted` and `context` is returned. ## Clip Events ### Opened Clip This event is recorded when: - a user taps on a row or grid item to open a Clip - a user swipes up to the next Clip - a user swipes down to the previous Clip - a user is sent directly to a Clip via a call to `openCollection` - a user is sent directly to a Clip via a deep link - a user dismisses the last category (by pressing back) and returns to the top level collection Whenever a Opened Clip event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `clipHasAction`, `clipActionType`, `openedReason`, `collection`, `categories`, `contentLength`, `clipFeedType`, `categoryDetails`, `captionsEnabled`, `actionLinkId`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Dismissed Clip This event is recorded when: - a user taps on the back button in the top-left to exit the Clips player (and is at the top of the stack of Clip Categories) - a user taps back on their device UI to navigate back (Android only) This event does not fire when the user is not at the top of the Clip Category stack. Whenever a Dismissed Clip event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `dismissedReason`, `durationViewed`, `clipsViewed`, `collection`, `categories`, `clipFeedType`, `loopsViewed`, `captionsEnabled`, `actionLinkId`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Next Clip This event is recorded when a user swipes up to go to the next Clip. Whenever a Next Clip event occurs, event data for `clipId`, `clipTitle`, `collection`, `categories`, `clipFeedType`, `clipIndex`, `clipCollectionCount`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Previous Clip This event is recorded when a user swipes down to go to the previous Clip. Whenever a Previous Clip event occurs, event data for `clipId`, `clipTitle`, `collection`, `categories`, `clipFeedType`, `clipIndex`, `clipCollectionCount`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Completed Loop This event is recorded when a user completes a loop of a Clip. Whenever a Completed Loop event occurs, event data for `clipId`, `clipTitle`, `collection`, `categories`, `clipFeedType`, `clipIndex`, `clipCollectionCount`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `completionType`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. > Note: For live clips this event never will be fired. ### Action Button Tapped This event is recorded when: - a user taps the primary action button at the bottom of a Clip - a user taps any secondary action buttons above the clip title - a user swipes left on a Clip to open the relevant action Whenever an Action Button Tapped event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `clipHasAction`, `clipActionType`, `clipActionText`, `collection`, `categories`, `clipFeedType`, `actionText`, `clipActionUrl`, `captionsEnabled`, `actionLinkId`, `actionClass`, `actionIndex`, `tappedClipActionText`, `tappedClipActionUrl`, `tappedClipActionType`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Share Button Tapped This event is recorded when a user taps the share button on a Clip. Whenever a Share Button Tapped event occurs, event data for `clipId`, `clipTitle`, `collection`, `categories`, `clipFeedType`, `clipIndex`, `clipCollectionCount`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Share Success This event is recorded when a user selects a sharing method from the system dialog. Whenever a Share Success event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `shareMethod`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. > Note: on Android API level 31 and above, `shareMethod` is not supported and will be `null` for this event. ### Paused Clip This event is recorded when a user taps on the screen whilst a Clip is playing to pause the Clip. Tt does not fire when a Clip is paused automatically by sharing or following an action. Whenever a Paused Clip event occurs, event data for `clipId`, `clipTitle`, `collection`, `categories`, `clipFeedType`, `clipIndex`, `clipCollectionCount`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. > Note: For live clips this event never will be fired, as pausing/resuming live clips is not supported. ### Resumed Clip This event is recorded when a user taps a Clip that is paused to resume playback. It does not fire when a Clip is resumed automatically. Whenever a Resumed Clip event occurs, event data for `clipId`, `clipTitle`, `collection`, `categories`, `clipFeedType`, `clipIndex`, `clipCollectionCount`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. > Note: For live clips this event never will be fired, as pausing/resuming live clips is not supported. ### Liked Clip This event is recorded when a user likes a Clip by tapping the like button when they do not currently like the Clip. Whenever a Liked Clip event occurs, event data for `clipId`, `clipTitle`, `collection`, `categories`, `clipFeedType`, `clipIndex`, `clipCollectionCount`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Unliked Clip This event is recorded when a user unlikes a Clip by tapping the like button when they currently like the Clip. Whenever a Unliked Clip event occurs, event data for `clipId`, `clipTitle`, `collection`, `categories`, `clipFeedType`, `clipIndex`, `clipCollectionCount`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Finished Clip This event is recorded when: - Dismissed Clip is fired - Next Clip is fired - Previous Clip is fired - At the same time as OpenedCategory - (iOS specific triggers exist for loop completion, For You/Following switching) Whenever a Finished Clip event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `loopsViewed`, `durationViewed`, `collection`, `categories`, `clipFeedType`, `contentLength`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `completionType`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Opened Category This event is recorded when: - a user taps a category at the bottom of the Clips Player to open a new Category - a user opens a category by navigating back from another category This event is not fired when opening the top level of a collection - only when navigating to a specific category. Whenever a Opened Category event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `categoryId`, `categoryName`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Dismissed Category This event is recorded when: - a user navigates back using the back button (or system back on Android) to the previous category. This event does not fire when dismissing the top level collection Whenever a Dismissed Category event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `categoryId`, `categoryName`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Follow Category This event is recorded when: - a user taps the plus button under the follow category button of the Clips Player - a user taps the follow button at the top right of the Followable Category screen and the category was not followed Whenever a Follow Category event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `categoryId`, `categoryName`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Unfollow Category This event is recorded when: - a user taps the checkmark button under the follow category button of the Clips Player - a user taps the follow button at the top right of the Followable Category screen and the category was followed Whenever a Unfollow Category event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `categoryId`, `categoryName`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Followable Category Tapped This event is recorded when: - a user taps the follow category button of the Clips Player to open the Followable Category screen - a user swipes left from the right edge of the screen on a Clip with a Followable Category and opens the Followable Category screen Whenever a Followable Category Tapped event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `categoryId`, `categoryName`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Enable Captions This event is recorded when: - a user taps the button to enable Closed Captions in the Clips Player Whenever an Enable Captions event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `categoryId`, `categoryName`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Disable Captions This event is recorded when: - a user taps the button to disable Closed Captions in the Clips Player Whenever a Disable Captions event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `categoryId`, `categoryName`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Followable Category Limit Shown This event is recorded when: - a user attempts to follow a category but has reached their following limit and the limit dialog is displayed Whenever a Followable Category Limit Shown event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `followableCategoryLimitActionText`, `followableCategoryLimitActionUrl`, `followableCategoryLimitDialogue`, `location`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Followable Category Limit Action Button Tapped This event is recorded when: - a user taps the action button in the followable category limit dialog Whenever a Followable Category Limit Action Button Tapped event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `followableCategoryLimitActionText`, `followableCategoryLimitActionUrl`, `followableCategoryLimitDialogue`, `location`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Followable Category Limit Dismissed This event is recorded when: - a user dismisses or closes the followable category limit dialog without taking action Whenever a Followable Category Limit Dismissed event occurs, event data for `clipId`, `clipTitle`, `clipIndex`, `clipCollectionCount`, `collection`, `categories`, `clipFeedType`, `captionsEnabled`, `actionLinkId`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `followableCategoryLimitActionText`, `followableCategoryLimitActionUrl`, `followableCategoryLimitDialogue`, `location`, `eyebrow`, `metadata`, `isMuted` and `context` is returned. ### Muted Clip This event is recorded when a user taps the mute button to mute audio while viewing a Clip. Whenever a Muted Clip event occurs, event data for `captionsEnabled`, `actionLinkId`, `categories`, `categoryDetails`, `clipHasAction`, `clipActionText`, `clipActionUrl`, `clipActionType`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `clipId`, `clipIndex`, `clipTitle`, `collection`, `contentLength`, `searchFilter`, `searchTerm`, `searchSort`, `clipFeedType`, `collectionClipCount`, `isLive`, `isMuted` and `context` is returned. ### Unmuted Clip This event is recorded when a user taps the mute button to unmute audio while viewing a Clip. Whenever an Unmuted Clip event occurs, event data for `captionsEnabled`, `actionLinkId`, `categories`, `categoryDetails`, `clipHasAction`, `clipActionText`, `clipActionUrl`, `clipActionType`, `clipHasSecondaryActions`, `clipSecondaryActionsText`, `clipSecondaryActionUrls`, `clipSecondaryActionTypes`, `clipId`, `clipIndex`, `clipTitle`, `collection`, `contentLength`, `searchFilter`, `searchTerm`, `searchSort`, `clipFeedType`, `collectionClipCount`, `isLive`, `isMuted` and `context` is returned. ## Sheet Events ### Opened Sheet This event is recorded in the following scenarios: - When a user taps on an action button on a clip or story to open a Sheet - When a Sheet is opened via a deep link - When a Sheet is opened via the `openSheet` method Whenever a user taps on an action button on a clip or story to open a Sheet the Opened Sheet event occurs, event data for `sheetId`, `sheetTitle`, `sheetSize`, `openedReason`, `storyId`, `storyTitle`, `storyIndex`, `storyPageCount`, `storyPlaybackMode`, `currentCategory`, `collection`, `categories`, `pageID`, `pageIndex`, `pageType`, `pageHasAction`, `pageActionType`, `pageActionText`, `pageActionUrl`, `clipId`, `clipTitle`, `clipIndex`, `clipHasAction`, `clipActionType`, `clipActionText`, `clipActionUrl`, `captionsEnabled`, `actionLinkId`, `metadata`, `isMuted` and `context` is returned. Whenever a Sheet is opened via a deep link or via the `openSheet` method the Opened Sheet event occurs, event data for `sheetId`, `sheetTitle`, `sheetSize`, `openedReason`, `metadata`, `isMuted` and `context` is returned. ### Dismissed Sheet This event is recorded when a user closes a Sheet by: - Tapping outside of the Sheet (for 50% height setting Sheets) - Dragging the sheet down beyond a threshold - Tapping the back button - Tapping the system back button Whenever a user dismisses the Sheet on a clip or story the Dismissed Sheet event occurs, event data for `sheetId`, `sheetTitle`, `sheetSize`, `storyId`, `storyTitle`, `storyIndex`, `storyPageCount`, `storyPlaybackMode`, `currentCategory`, `collection`, `categories`, `pageID`, `pageIndex`, `pageType`, `pageHasAction`, `pageActionType`, `pageActionText`, `pageActionUrl`, `clipId`, `clipTitle`, `clipIndex`, `clipHasAction`, `clipActionType`, `clipActionText`, `clipActionUrl`, `captionsEnabled`, `actionLinkId`, `metadata`, `isMuted` and `context` is returned. Whenever a user dismisses the Sheet that is opened via a deep link or via the `openSheet` method the Dismissed Sheet event occurs, event data for `sheetId`, `sheetTitle`, `sheetSize`, `openedReason`, `metadata`, `isMuted` and `context` is returned. ## Card Events ### Card Tapped This event is recorded when: - a user taps on the Storyteller Card view Whenever a Card Tapped event occurs, event data for `cardId`, `cardActionType`, `cardActionUrl`, `cardAspectRatio`, `cardBackgroundType`, `cardCollectionId`, `cardIndex`, `cardSubtitle`, `cardTitle`, `categories`, `categoryDetails`, `hasButton`, and `context` is returned. ### Card Viewed This event is recorded when: - the background image of the card closest to the center of the screen loads. For video cards, this event is triggered when the video starts playing. Whenever a Card Viewed event occurs, event data for `cardId`, `cardActionType`, `cardActionUrl`, `cardAspectRatio`, `cardBackgroundType`, `cardCollectionId`, `cardIndex`, `cardSubtitle`, `cardTitle`, `categories`, `categoryDetails`, `hasButton`, and `context` is returned. ## Ad Events ### Opened Ad #### Stories This event is recorded when: - an ad is loaded because the previous Story finished - a user swipes left on a Story to go to the next Story and an ad appears - a user swipes right on a Story to go to the previous Story and an ad appears Whenever a Opened Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `openedReason`, `adView`, `contentLength`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `categories`, `pageActionUrl`, `isMuted` and `context` is returned. #### Clips This event is recorded when: - a user swipes up on a clip to go to the next clip and an ad should appear next - a user swipes down on a clip to go to the previous clip and an ad should appear next - an ad completes a loop and begins playing again from the start Information about Ad View is only sent to client delegates on an OpenedAd event. Whenever a Opened Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `openedReason`, `adView`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Dismissed Ad #### Stories This event is recorded when a user: - taps close to dismiss the ad - swipes down to dismiss the ad - taps back on their device UI to dismiss the ad (Android only) - taps to skip the ad if the ad is the last Page in the current set of Stories - swipes left on the ad to skip it if the ad is the last Page in the current set of Stories - completes the ad if the ad is the last Page in the current set of Stories Whenever a Dismissed Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `dismissedReason`, `durationViewed`, `pagesViewed`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `categories`, `pageActionUrl`, `isMuted` and `context` is returned. #### Clips This event is recorded when a user: - taps the back button to exit the clips player when an ad is being shown - (Android only) presses the system back button to exit the clips player when an ad is being shown Whenever a Dismissed Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `dismissedReason`, `durationViewed`, `pagesViewed`, `clipsViewed`, `loopsViewed`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Skipped Ad #### Stories This event is recorded when a user: - swipes left to go to the next Story before completing the current ad - taps to go to the next Page on an ad before completing the current ad Whenever a Skipped Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `categories`, `pageActionUrl`, `isMuted` and `context` is returned. #### Clips This event is recorded when: - a user swipes up to go to the next clip - a user swipes down to go the previous clip Whenever a Skipped Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Ad Action Button Tapped #### Stories This event is recorded when a user taps on an action button on an ad to open a link. Whenever a Ad Action Button Tapped event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `categories`, `actionText`, `pageActionUrl`, `isMuted` and `context` is returned. #### Clips This event is recorded when a user: - taps the action button at the bottom of an ad displayed in clips - swipes left on a clip to open the relevant action Whenever a Ad Action Button Tapped event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Previous Ad This event is recorded when a user: - swipes right to go to the previous Story when viewing an ad - taps back to go to the previous Page when viewing an ad Whenever a Previous Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `categories`, `pageActionUrl`, `isMuted` and `context` is returned. ### Finished Ad #### Stories This event is recorded at the same time as Dismissed Ad, Skipped Ad, Previous Ad and Viewed Ad Page Complete and gives an easier way to determine when an ad finishes for any reason. Whenever a Finished Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `adView`, `contentLength`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `categories`, `pageActionUrl`, `isMuted` and `context` is returned. #### Clips This event is recorded at the same time as DismissedAd, or SkippedAd. Whenever a Finished Ad event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `adView`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Paused Ad #### Stories This event is recorded when a user pauses a Page within an ad by pressing and holding on the Page. Whenever a Paused Ad Page event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `categories`, `pageActionUrl`, `isMuted` and `context` is returned. #### Clips This event is recorded when a user pauses a clip which is an ad by tapping the screen. It does not fire when a clip is paused automatically for sharing or following an action Whenever a Paused Ad Clip event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Resumed Ad #### Stories This event is recorded when a user resumes playing a Page within an ad by releasing their long press which paused the ad. Whenever a Resumed Ad Page event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `pageActionText`, `currentCategory`, `categories`, `pageActionUrl`, `isMuted` and `context` is returned. #### Clips This event is fired when a clip which is an ad is paused and a user taps the screen to resume playback. It does not fire when a clip is resumed automatically Whenever a Resumed Ad Clip event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Viewed Ad Page First Quartile This event is recorded when a user reaches 1/4 of the way through the duration of a page or clip which is an ad. For clip ads (which loop), it fires for each loop of the clip. Whenever a Viewed Ad Page First Quartile event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Viewed Ad Page Midpoint This event is recorded when a user reaches halfway through the duration of a page or clip which is an ad. For clip ads (which loop), it fires for each loop of the clip. Whenever a Viewed Ad Page Midpoint event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Viewed Ad Page Third Quartile This event is recorded when a user reaches 3/4 of the way through the duration of a page or clip which is an ad. For clip ads (which loop), it fires for each loop of the clip. Whenever a Viewed Ad Page Third Quartile event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Viewed Ad Page Complete This event is recorded when a user reaches the end of the duration for a page or clip which is an ad. For clip ads (which loop), it fires for each loop of the clip. Whenever a Viewed Ad Page Complete event occurs, event data for `advertiserName`, `adId`, `adType`, `adPlacement`, `adFormat`, `adResponseIdentifier`, `pageType`, `contentLength`, `pageHasAction`, `pageActionType`, `clipHasAction`, `clipActionType`, `pageActionText`, `clipActionText`, `pageActionUrl`, `clipActionUrl`, `currentCategory`, `collection`, `clipIndex`, `clipCollectionCount`, `isMuted` and `context` is returned. ### Muted Ad This event is recorded when a user taps the mute button to mute audio while viewing an Ad. #### Stories Whenever a Muted Ad event occurs in Stories, event data for `advertiserName`, `adId`, `adType`, `adFormat`, `adResponseIdentifier`, `adView`, `captionsEnabled`, `categories`, `categoryDetails`, `currentCategory`, `contentLength`, `storyDisplayTitle`, `openedReason`, `storyIndex`, `pageActionText`, `pageActionUrl`, `pageHasAction`, `pageActionType`, `searchFilter`, `searchTerm`, `searchSort`, `storyPageCount`, `storyTitle`, `isMuted` and `context` is returned. #### Clips Whenever a Muted Ad event occurs in Clips, event data for `advertiserName`, `adId`, `adType`, `adFormat`, `adResponseIdentifier`, `categories`, `contentLength`, `openedReason`, `pageActionText`, `pageActionUrl`, `pageHasAction`, `pageActionType`, `searchFilter`, `searchTerm`, `searchSort`, `clipFeedType`, `collectionClipCount`, `isMuted` and `context` is returned. ### Unmuted Ad This event is recorded when a user taps the mute button to unmute audio while viewing an Ad. #### Stories Whenever an Unmuted Ad event occurs in Stories, event data for `advertiserName`, `adId`, `adType`, `adFormat`, `adResponseIdentifier`, `adView`, `captionsEnabled`, `categories`, `categoryDetails`, `currentCategory`, `contentLength`, `storyDisplayTitle`, `openedReason`, `storyIndex`, `pageActionText`, `pageActionUrl`, `pageHasAction`, `pageActionType`, `searchFilter`, `searchTerm`, `searchSort`, `storyPageCount`, `storyTitle`, `isMuted` and `context` is returned. #### Clips Whenever an Unmuted Ad event occurs in Clips, event data for `advertiserName`, `adId`, `adType`, `adFormat`, `adResponseIdentifier`, `categories`, `contentLength`, `openedReason`, `pageActionText`, `pageActionUrl`, `pageHasAction`, `pageActionType`, `searchFilter`, `searchTerm`, `searchSort`, `clipFeedType`, `collectionClipCount`, `isMuted` and `context` is returned. ## Playback Events ### Ready to Play This event is called once per video Page at the point when the video player has been loaded. Whenever a Ready to Play event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `pageID`, `pageIndex`, `pageType`, `contentLength`, `actionLinkId`, `isLive`, `metadata` and `context` is returned. ### Media Started This event is called once per video Page at the point when the video starts to play for the first time. Whenever a Media Started event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `pageID`, `pageIndex`, `pageType`, `contentLength`, `actionLinkId`, `isLive`, `metadata` and `context` is returned. ### Buffering Started This event is called on video Pages whenever the video starts to buffer. Whenever a Buffering Started event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `pageID`, `pageIndex`, `pageType`, `contentLength`, `actionLinkId`, `isInitialBuffering`, `isLive`, `metadata` and `context` is returned. ### Buffering Ended This event is called on video Pages whenever the video finishes buffering > Note: There should be at most one Ready to Play event and one Media Started event for every Page. There may be multiple Buffering Started/Buffering Ended pairs of events for an individual Page. There may not always be a Buffering Ended event for every Buffering Started event as the user may choose to exit the Page during buffering. Whenever a Buffering Ended event occurs, event data for `storyId`, `storyTitle`, `storyIndex`, `pageID`, `pageIndex`, `pageType`, `contentLength`, `actionLinkId`, `isInitialBuffering`, `timeSinceBufferingBegan`, `isLive`, `metadata` and `context` is returned. ## Search Events ### Opened Search The openedSearch event is recorded when: - A user taps on Search from a Story within the Story player - A user taps on Search from a Clip within the Clip player ### Dismissed Search The dismissedSearch event is recorded when a user taps the 'X' button to exit the Search interface. ### Performed Search The performedSearch event is recorded when: - A user taps the 'Search' icon after entering a term in the Search bar (whether manually or by tapping the 'arrow' icon beside a Search suggestion to populate the search bar) - A user taps the 'Search' icon beside a Search suggestion. The Search is then performed with the suggestion as the term. - A user taps 'Apply filters' from the filters interface. ### Opened Filters The openedFilters event is recorded when, after a user has performed as Search, they press the 'Filter' icon to bring up the filter interface. > Note that filters can only be applied after the initial search has been performed. ### Dismissed Filters This event is recorded when a user, after calling up the 'Filters' interface, swipes down to exit the interface without applying any. ### Used Suggestion The usedSuggestion event is recorded when: - A user taps the 'arrow' icon beside a Search suggestion to populate the Search bar. This does not trigger any other event, and the user may amend the Search bar input before performing a Search. - A user taps the 'Search' icon beside a Search suggestion. This simultaneously triggers a PerformedSearch event, above. ## Event Data For each event, data is returned with details about the story and Page involved as well as some extra properties with more information about what the user has done. The data is returned as a `UserActivityData` class with the following properties: ### Story ID The `storyId: String?` is the ID of the Story for which the event occurred. ### Story Title The `storyTitle: String?` is the title of the Story for which the event occurred. ### Story Display Title The `storyDisplayTitle: String?` is the display title of the Story for which the event occurred. ### Story Index The `storyIndex: Int?` is the index of the Story for which the event occurred in the row from which it was opened at the point it was opened - this is only included on `OpenedStory` events. Note: this value is 1-based. ### Story Page Count The `storyPageCount: Int?` is the number of Pages in the Story. ### Story Read Status The `storyReadStatus: String?` is whether the Story was read or unread at the point the Story was opened - this is only included on `OpenedStory` events. Note this will either be `read` or `unread`. ### Page ID The `pageId: String?` is the ID of the Page for which the event occurred. ### Page Index The `pageIndex: Int?` is the index of the Page in the Story for which the event occurred. Note: this value is 1-based. ### Page Type The `pageType: String?` is the type of the Page associated with the event. This can have the value `image`, `video` or `poll`. ### Story Playback Mode The `storyPlaybackMode: String?` value states if the Story was opened during the list or in the single Story mode (Storyteller static method.) This is included for all events. The values for this are either `list` or `singleStory`. ### Page Has Action The `pageHasAction: Bool?` value states whether the Page associated with the event contains an action. ### Page Action Type The `pageActionType: String?` is the type of the action on the page. ### Page Action Text The `pageActionText: String?` is the text call to action if the Page has an Action. ### Page Action URL The `pageActionUrl: String?` is the URL for the link if the Page has an Action. ### Opened Reason The `openedReason: String?` value states how the user opened a Story or Clip. The possible values for this are `storyRowTap` (the user tapped the Story in the Story row); `deepLink` (openStory or openPage was called to open the Story); `swipe` (the user swiped left or right to change the current Story); `automaticPlayback` (the user completed the previous Page); `tap` (the user tapped on the previous Page to navigate to this Page); `clipListTap` (the user tapped the Clip in a Clip list) and `loop` (a Clip has completed a full playback cycle and started playing again). `openedReason` is only included on `OpenedStory` and `OpenedClip` events. ### Dismissed Reason The `dismissedReason: String?` value states the way the user dismissed a Story or Clip. The possible values for this are `closeButtonTapped` (the user tapped close to dismiss the Story); `swipedDown` (the user swiped down to dismiss the Story); `swipedFirstStory` (the user swiped the first Story to dismiss it); `swipedFinalStory` (the user swiped the final Story to dismiss it); `skippedFinalPage` (the user tapped to skip the final Page of the final Story); `backTapped` (the user tapped their device back button to dismiss the Story view); `completedFinalPage` (the user completed the final Page of the final Story) and `backButtonTapped` (the user tapped the back button to dismiss the Clip). `dismissedReason` is only included on `DismissedStory` and `DismissedClip` events. ### Duration Viewed The `durationViewed: Float?` is the duration the user viewed the Story or Clip for in milliseconds. This is measured from the most recent `OpenedStory` or `OpenedClip` event with an Opened Reason of `storyRowTap`, `deepLink` or `clipsListTap`. This timer is reset after any `DismissedStory` or `DismissedClip` events. For `FinishedClip`, Duration Viewed is the the duration the user viewed the Clips player for in milliseconds. This is measured from the most recent `OpenedClip` event with an Opened Reason of `swipe`. ### Pages Viewed Count The `pagesViewedCount: Int?` is the total number of Pages a user has viewed since the most recent `OpenedStory` event with an Opened Reason of `storyRowTap` or `deepLink`. This count is reset after any `DismissedStory` events. ### Content Length The `contentLength: Long?` is the total duration of the Page content in seconds. ### Ad ID The `adId: String?` is the ad ID if an event is associated with an ad. ### Share Method The `shareMethod: String?` is the component name of the app which the user has selected for sharing. > Note: on Android API level 31 and above, `shareMethod` is not supported and will be `null` for this event. ### Ad View The `adView: View?` is the view the ad is rendered in. This is only included for `OpenedAd` and `FinishedAd` events. ### Advertisers Name The `advertiserName: String?` is the name of the advertiser for a particular ad. This is only included for ad events. ### Ad Type The `adType: String?` tells the source component of the ad. It can be either `stories` or `clips`. This is only included for ad events. ### Ad Placement The `adPlacement: String?` tells the placement of the ad. It can be either `Between Stories`, `Between Pages` or `Between Clips`. This is only included for ad events. ### Ad Format The `adFormat: String?` represents the format of the Ad that was displayed. Possible values can be `customNative`, `native`, `banner`. This is only included for ad events. ### Ad Response Identifier The `adResponseIdentifier: String?` represents the response identifier attached to the ad that was received from the ad provider. Used for debugging ad targeting. This is only included for ad events. ### Is Initial Buffering The `isInitialBuffering: Boolean?` value is returned if the buffering happens at the start of playback for that Page. This is only included for `BufferingStarted` and `BufferingEnded` events. ### Time Since Buffering Began The `timeSinceBufferingBegan: Long?` value is the duration the current buffering lasted for in milliseconds. This is only included for `BufferingEnded` events. ### Categories The `categories: [String]?` value is the list of categories assigned to the Story for which the event occurred. This is only included on `OpenedStory` events. ### Poll Answer The `pollAnswerId: String?` is the ID of the answer the user selected when voting. This is only included on `VotedPoll` events. ### Trivia Quiz Answer ID The `triviaQuizAnswerId: String?` is the ID of the selected trivia quiz answer. This is only included on `TriviaQuizQuestionAnswered` events. ### Trivia Quiz ID The `triviaQuizId: String?` is the ID of the Trivia Quiz that was completed or answered. This is only included on `TriviaQuizQuestionAnswered` and `TriviaQuizCompleted` events. ### Trivia Quiz Question ID The `triviaQuizQuestionId: String?` is the ID of the Trivia Quiz question which was answered. This is only included on `TriviaQuizQuestionAnswered` events. ### Trivia Quiz Title The `triviaQuizTitle: String?` is the title of the Trivia Quiz that was completed or answered. This is only included on `TriviaQuizQuestionAnswered` and `TriviaQuizCompleted` events. ### Trivia Quiz Score The`triviaQuizScore: Int?` value is the score of the trivia quiz that was completed. This is only included on `TriviaQuizCompleted` events. ### Clip ID The `clipId: String?` is the ID of the Clip for which the event occurred. ### Clip Title The `clipTitle: String?`is the title of the Clip for which the event occurred. ### Clip Index The `clipIndex: Int?` is the index of the Clip in the row or grid at the point it was selected or the index of the Clip in the feed at the point it was viewed. ### Clip Collection Count The `clipCollectionCount: Int?` amount of Clips in the collection. ### Clips Viewed The `clipsViewed: Int?` value is the total number of Clips a user has viewed since the most recent OpenedClip event with an Opened Reason of `clipListTap` or `deepLink`. This count should be reset after any DismissedClip events. ### Loops Viewed The `loopsViewed: Int?` is for DismissedClip, the total number of loops (plays of an individual Clip) a user has viewed since the most recent OpenedClip event with an Opened Reason of `clipListTap` or `deepLink`. This count should be reset after any DismissedClip events. For FinishedClip, the total number of loops. ### Clip Has Action The `clipHasAction: Bool?` value is whether the Clip associated with the event contains a primary action. ### Clip Action Type The `clipActionType String?` is the type of the action on the clip. ### Clip Action Text The `clipActionText String?` is the text call to action if the Clip associated with the event has a primary Action link. ### Clip Action URL The `clipActionUrl: String?` is the URL linked to from the action if a Clip associated with the event has a primary Action. ### Action Class The `actionClass: String?` identifies whether the action button that was tapped is a primary or secondary action. Possible values are `primary` and `secondary`. This is only included for `ClipActionButtonTapped` events. ### Action Index The `actionIndex: Int?` is the 1-based index of the secondary action that was tapped. This is only included for `ClipActionButtonTapped` events when a secondary action is tapped. ### Tapped Clip Action Text The `tappedClipActionText: String?` is the text of the specific action button (primary or secondary) that was tapped. This is only included for `ClipActionButtonTapped` events. ### Tapped Clip Action URL The `tappedClipActionUrl: String?` is the URL of the specific action button (primary or secondary) that was tapped. This is only included for `ClipActionButtonTapped` events. ### Tapped Clip Action Type The `tappedClipActionType: String?` is the type of the specific action button (primary or secondary) that was tapped. This is only included for `ClipActionButtonTapped` events. ### Has Secondary Actions The `clipHasSecondaryActions: Boolean?` indicates whether the clip has any secondary actions. This is included in all Clip Analytics Events. ### Secondary Actions Text The `clipSecondaryActionsText: [String]?` is an array of all the text CTAs on secondary actions for the clip. This is included in all Clip Analytics Events when the clip has secondary actions. ### Secondary Action URLs The `clipSecondaryActionUrls: [String]?` is an array of all the URLs on secondary actions for the clip. This is included in all Clip Analytics Events when the clip has secondary actions. ### Secondary Action Types The `clipSecondaryActionTypes: [String]?` is an array of all the types of secondary actions for the clip. This is included in all Clip Analytics Events when the clip has secondary actions. ### Is Live The `isLive: Boolean?` set to `true` for Live Clips. ### Action Text The `actionText` property has exactly the same value as "Page Action Text" or "Clip Action Text" and is only reported for Action Button Tapped ### Action Link ID The `actionLinkId: String?` is a unique identifier for the action link associated with pages or clips. This property is included in analytics events where actions are present and allows tracking specific action links across the platform. The action link ID can be used to identify which specific CTA (call-to-action) was interacted with or displayed, enabling more granular analytics and attribution for content performance. ### Collection The `collection` is the ID of the Collection if a Story or Clip is being played from a Collection. ### Category Details The `categoryDetails` is a list of Category Detail objects. This includes the name, ID, type and placement of the Category. ### Category Name The `categoryName` of the category being navigated to or dismissed ### Category ID The `categoryId` of the category being navigated to or dismissed. ### Current Category The `currentCategory` is the Category for the row that is currently being interacted with. The information provided from this is the Category title, ID and placement. This is only included on Story and Ad events. ### Search From The `searchFrom` indicates whether the Search for which the event is recorded was opened from Clips or Stories. ### Is Suggestion The `isSuggestion` indicates whether the Search for which the event is recorded used a suggested Search. > Note that when filters are opened (event), the Is Suggestion value should reflect what was used for the initial search performed before opening filters. ### Initial Input The `initialInput` this property tracks the input at the moment the suggestion was used. ### Search Filter The `searchFilter` property includes the content type and date posted used by search filter. ### Search Sort The `searchSort` field indicates method by which the relevant Story / Clip's search results were sorted. ### Search Term The `searchTerm` by which the Clips / Stories were searched, either entered in the search bar by the user or selected / filled from search suggestions. ### Captions Enabled The `captionsEnabled: Boolean?` value is set to `true` if captions are enabled. ### Is Muted The `isMuted: Boolean?` value indicates the current mute state of the audio. This is set to `true` when audio is muted and `false` when audio is unmuted. This property is included in mute/unmute events for Stories, Clips, and Ads. ### Sheet ID The `sheetId` is the ID of the Sheet for which the event occurred. ### Sheet Title The `sheetTitle` is the title of the Sheet for which the event occurred. ### Sheet Size The `sheetSize` is the height setting of the Sheet. This can have the value `50`, `75` or `100` (representing % of screen height). ### Card ID The `cardId: String?` is the ID of the Card for which the event occurred. This is included for `CardTapped` event. ### Card Action Type The `cardActionType: String?` is the type of the action on the Card. This is included for `CardTapped` event. ### Card Action URL The `cardActionUrl: String?` is the URL for the link if the Card has an action. This is included for `CardTapped` event. ### Card Aspect Ratio The `cardAspectRatio: String?` is the aspect ratio of the Card. This is included for `CardTapped` event. ### Card Background Type The `cardBackgroundType: String?` is the type of background on the Card. This can have the value `image` or `video`. This is included for `CardTapped` event. ### Card Collection ID The `cardCollectionId: String?` is the ID of the collection the Card belongs to. This is included for `CardTapped` event. ### Card Index The `cardIndex: Int?` is the index of the Card in the collection. Note: this value is 1-based. This is included for `CardTapped` event. ### Card Subtitle The `cardSubtitle: String?` is the subtitle text displayed on the Card. This is included for `CardTapped` event. ### Card Title The `cardTitle: String?` is the title text displayed on the Card. This is included for `CardTapped` event. ### Metadata The `metadata: Map?` is a map containing custom metadata key-value pairs associated with the content. This is included for Story, Clip, Playback, and Sheet events. The metadata comes from the Story or Clip associated with the event and can contain custom fields provided via the Storyteller API. ### Followable Category Limit Action Text The `followableCategoryLimitActionText: String?` is the text displayed on the action button in the followable category limit dialog. This is only included for followable category limit events. ### Followable Category Limit Action URL The `followableCategoryLimitActionUrl: String?` is the URL that the action button in the followable category limit dialog will navigate to when tapped. This is only included for followable category limit events. ### Followable Category Limit Dialogue The `followableCategoryLimitDialogue: String?` is the main text content displayed in the followable category limit dialog explaining why the user cannot follow more categories. This is only included for followable category limit events. ### Location The `location: String?` indicates where the followable category limit dialog was displayed. For clip-related limit events, this value is `"On Clip"`. This is only included for followable category limit events. ### Eyebrow The `eyebrow: String?` is the subtitle text displayed above the title for Stories and Clips tiles. It is also present in Stories and Clips Player assuming it is set in CMS. ### Completion Type The `completionType: String?` indicates how the user finished watching a Clip or completed a loop. This property is included in `finishedClip` and `completedLoop` events. Possible values include: - `natural` - The clip or loop completed naturally by playing to the end - `scrubbed` - The user used the progress bar to scrub/seek to the end This helps track whether users are actively engaging with the full content or skipping through it using the progress bar. ### Context The `context: StorytellerAnalyticsContext?` contains custom context data that was provided when configuring the SDK row/grid/card component. This allows integrators to track the context of where the analytics event occurred. This property is included in all Story, Clip, Ad, Media Playback, Sheet, and Card analytics events. ## Source: Ads.md # Ads ## Table of Contents + [Introduction](#introduction) * [Storyteller First Party Ads](#storyteller-first-party-ads) * [Storyteller GAM SDK](#storyteller-gam-sdk) + [Basic Setup](#basic-setup) + [Setup with the dynamic Ad Unit changes](#setup-with-the-dynamic-ad-unit-changes) + [Setup with the additional parameters](#setup-with-the-additional-parameters) + [StorytellerAdRequestInfo](#storytelleradrequestinfo) ## Introduction The Storyteller SDK supports displaying ads that can be created in the Storyteller CMS (First Party Ads), as well as Ads from Google Ad Manager via an SDK extension developed by Storyteller and Ads from other sources via custom implementation provided by the integrator. Which source of ads is used can be configured on your behalf by a member of the Storyteller Delivery Team. ### Storyteller First Party Ads If your tenant is configured to use Storyteller First Party Ads, which can be managed in the Storyteller CMS, then no changes to the Storyteller integration code are necessary. The Ads code is managed entirely within the Storyteller SDK. ### Storyteller GAM SDK To use Ads from Google Ad Manager in Storyteller, first reach out to your Storyteller contact and they will assist you with setting up Google Ad Manager to traffic ads to Storyteller. You will then need to use the Storyteller Google Ad Manager SDK extension to fetch the ads from Google Ad Manager. To use this extension, first install it using Gradle. Ensure you have the following Maven repository added to your `settings.gradle`: ```groovy maven { url = uri("https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk") } ``` Then add the following reference to your version catalog file: ```toml storyteller-ads = { module = "Storyteller:ads", version.ref = "storyteller" } ``` And finally reference this in your `build.gradle`: ```groovy implementation(libs.storyteller.ads) ``` #### Basic Setup Now initialize the extension as follows: ```kotlin import com.storyteller.modules.ads.StorytellerGamModule import com.storyteller.domain.ads.entities.CustomNativeTemplateIds val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply { init( adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo -> "/33813572/storyteller" }, ) } fun initializeStoryteller() { Storyteller.modules = listOf(storytellerGamModule) //initialize code } ``` You will need to supply the following parameters: | Parameter Name | Description | |--------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `{ storytellerAdRequestInfo -> adUnitId` } | The lambda which returns desired `adUnitId`. This can be used for dynamic `adUnitId` changes depending on `storytellerAdRequestInfo` content. If you do not need dynamic adUnit changes simply put the static ad unit as a return value. | Then pass the newly created instance of the extension to the `modules` property on the `Storyteller` instance: ```kotlin Storyteller.modules = listOf(storytellerGamModule) ``` ### Showcase examples - [Compose — GAM module init (`StorytellerServiceImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L64) - [Compose — ad unit + template IDs (`StorytellerGoogleAdInfo`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ads/entity/StorytellerGoogleAdInfo.kt#L13-L14) - [XML — GAM module init (`StorytellerServiceImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L64) - [XML — ad unit + template IDs (`StorytellerGoogleAdInfo`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/ads/entity/StorytellerGoogleAdInfo.kt#L13-L14) #### Setup with the dynamic Ad Unit changes Example for dynamic Ad Unit changes when we want to use different adUnit for Stories and Clips: ```kotlin import com.storyteller.modules.ads.StorytellerGamModule import com.storyteller.domain.ads.entities.CustomNativeTemplateIds val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply { init( adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo -> when (storytellerAdRequestInfo) { is StorytellerAdRequestInfo.StoriesAdRequestInfo -> "/33813572/storyteller/stories" else -> "/33813572/storyteller/clips" } }, ) } fun initializeStoryteller() { Storyteller.modules = listOf(storytellerGamModule) //initialize code } ``` #### Setup with the additional parameters You can also supply optional parameters `templateIds` and `keyValuePairs` if needed: ```kotlin import com.storyteller.modules.ads.StorytellerGamModule import com.storyteller.domain.ads.entities.StorytellerCustomNativeTemplateIds val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply { init( adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo -> when (storytellerAdRequestInfo) { is StorytellerAdRequestInfo.StoriesAdRequestInfo -> "/33813572/storyteller/stories" else -> "/33813572/storyteller/clips" } }, templateIds = StorytellerCustomNativeTemplateIds("12102683", "12269089"), keyValuePairs = { emptyMap() }, ) } fun initializeStoryteller() { Storyteller.modules = listOf(storytellerGamModule) //initialize code } ``` | Parameter Name | Description | |--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `templateIds` | If you have worked with the Storyteller Delivery team to setup Custom Native Ads, you will need to supply their IDs here. If you are only using Stories (but not Clips) it is only necessary to supply one property of this struct. | | `keyValuePairs` | A function that is called each time we request a new ad. The Storyteller GAM SDK passes a default set of KVPs to GAM to allow targeting based on the content of the Stories/Clips the user is viewing. If you have any additional parameters that you need to be able to target by, these should be passed here. **By default, Storytelles always includes the following KVPs:** `stApiKey`(current API key), `stCollection`(the identifier of the clips collection where the ad will be displayed), `stClipCategories`(list of categories for the current clip associated with the item for ad targeting), `stNextClipCategories`(list of categories for the next clip associated with the item for ad targeting), `stAdIndex`(count of the Ad position from the start of the stCollection). | #### StorytellerAdRequestInfo The `StorytellerAdRequestInfo` sealed class has two subclasses: **`ClipsAdRequestInfo`** Used when an Ad is requested for display in a Clips Player. The properties include: + `collection` - The identifier of the clips collection where the ad will be displayed + `nextClipCategories` - List of categories for the next clip associated with the item for ad targeting + `adIndex` - Count of the Ad position from the start of the Collection + `itemInfo` - Metadata about the item context for ad targeting **`StoriesAdRequestInfo`** Used when an Ad is requested for display in a Stories Player. The properties include: + `placement` - The identifier of the stories placement where the ad will be displayed + `categories` - List of category identifiers for ad targeting and filtering + `adIndex` - Count of the Ad position from the start of the Collection + `itemInfo` - Metadata about the item context for ad targeting #### ItemInfo Each request class includes an `ItemInfo` object that contains: + `categories` - List of categories associated with the item for ad targeting ## Source: PrivacyAndTracking.md # Privacy and Tracking The `eventTrackingOptions` parameter customizes Storyteller's analytics and tracking behavior during SDK initialization. It allows certain features to be disabled based on user privacy choices. It is an object of type `StorytellerEventTrackingOptions` and by default, all of its properties are enabled: ```kotlin data class StorytellerEventTrackingOptions( val enablePersonalization: Boolean = true, val enableStorytellerTracking: Boolean = true, val enableUserActivityTracking: Boolean = true, val enableAdTracking: Boolean = true, val enableFullVideoAnalytics: Boolean = true, val enableRemoteViewingStore: Boolean = true ) // Set during initialization Storyteller.initialize( apiKey = "[APIKEY]", userInput = StorytellerUserInput("unique-user-id"), eventTrackingOptions = StorytellerEventTrackingOptions(/*your setup here*/) ) // Read current settings (read-only) val currentOptions = Storyteller.eventTrackingOptions ``` > **Note:** Starting from version 11.0.0, `eventTrackingOptions` can only be set during SDK initialization and cannot be modified afterwards. To change these settings, you must call `Storyteller.initialize()` again with new options. ## Showcase examples - [Compose — privacy toggles (`AccountViewModel`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/account/AccountViewModel.kt#L88) - [Compose — stored tracking preferences (`SessionRepositoryImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/SessionRepositoryImpl.kt#L51) - [XML — re-initialize with `eventTrackingOptions` (`StorytellerServiceImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L95) ## User Personalization When `enablePersonalization` is enabled, user attributes and the user's ID are included on requests to Storyteller's servers to allow us to personalize the content returned ## Storyteller tracking When `enableStorytellerTracking` is enabled, we will record analytics events on our servers. *Note* that some events are necessary for user functionality and will still be transmitted (but not stored) even when this setting is off. ## User Activity tracking When `enableUserActivityTracking` is enabled, we will call the Storyteller delegate's method `onUserActivityOccurred()`, which allows integrating apps to record our analytics events on their own systems. ## Ads tracking When `enableAdTracking` is disabled, ad-related events will not be tracked through `onUserActivityOccurred()` Storyteller delegate method and on our servers. Additionally, only necessary fields like Ad Unit Id and Custom Template Id's will be included in GAM requests. ## Full Video Analytics When `enableFullVideoAnalytics` is enabled, detailed video information (story titles, clip titles, story IDs, clip IDs, etc.) is included in analytics events sent to the `onUserActivityOccurred()` delegate method. When disabled, sensitive video data fields are nullified for VPPA (Video Privacy Protection Act) compliance: - `storyId`, `storyTitle`, `storyDisplayTitle` - `clipId`, `clipTitle` - `pageId`, `pageTitle`, `itemTitle`, `containerTitle` - `cardId`, `cardTitle`, `cardSubtitle` This setting allows integrators to comply with video privacy regulations while still receiving engagement analytics events. All other analytics data (user interactions, durations, event types, etc.) continues to be provided. ## Remote Viewing Store When `enableRemoteViewingStore` is enabled (default), the SDK operates normally with full user activity tracking and remote storage capabilities. When `enableRemoteViewingStore` is disabled, the SDK operates in a privacy-enhanced mode designed to address VPPA (Video Privacy Protection Act) compliance concerns: - **No User ID Storage**: User IDs are never stored locally or sent to backend services - **Local-Only User Activity**: All user viewing activity is stored locally on the device and never synchronized with remote servers - **No Remote User Activity Fetch**: The SDK will not attempt to fetch user activity data from remote servers This mode is particularly useful for clients who need enhanced privacy protection while still maintaining local user experience features like viewing history and recommendations. Example for enhanced VPPA compliance: ```kotlin Storyteller.initialize( apiKey = "[APIKEY]", userInput = StorytellerUserInput("unique-user-id"), eventTrackingOptions = StorytellerEventTrackingOptions( enablePersonalization = true, enableStorytellerTracking = true, enableUserActivityTracking = true, enableAdTracking = true, enableFullVideoAnalytics = false, // Nullifies sensitive video data enableRemoteViewingStore = false // Disables remote user ID storage and tracking ) ) ``` ## Disabled Functional Features The `disabledFunctionalFeatures` property allows you to selectively disable specific functional areas of the SDK while maintaining others. This provides granular control over which user interactions are tracked and stored. When a functional feature is disabled: - The SDK will not make API requests for that particular item (even if `enableRemoteViewingStore` is true) - The SDK will not store the relevant activity locally on device - The UI will behave as if the feature is disabled from the server Available functional features that can be disabled: - **All** - Disables everything including any future cases - **PageReadStatus** - Disables Page Read/Unread feature - **ClipViewedStatus** - Disables Clips Viewed/Not Viewed feature - **ClipLikes** - Disables Clip Likes and Unlikes - **ClipShares** - Disables Clip Share tracking and storage (but not the act of sharing) ### UI Behavior When Features Are Disabled **Pages**: When PageReadStatus is disabled, all rows act as if Read/Unread tracking is disabled from the server. **Clips**: When ClipViewedStatus is disabled, all entry points act as if Viewed/Not Viewed tracking is disabled from the server. **Clip Likes**: When disabled, users can like/unlike clips with UI feedback, but if they swipe away and return, the clip will appear unliked again. **Clip Shares**: When disabled, users can share clips and see count updates, but if they swipe away and return, the original count will be displayed. Example configuration: ```kotlin Storyteller.initialize( apiKey = "[APIKEY]", userInput = StorytellerUserInput("unique-user-id"), eventTrackingOptions = StorytellerEventTrackingOptions( enablePersonalization = true, enableStorytellerTracking = true, enableUserActivityTracking = true, enableAdTracking = true, enableFullVideoAnalytics = true, enableRemoteViewingStore = true, disabledFunctionalFeatures = listOf( StorytellerDisabledFunctionalFeature.ClipLikes, StorytellerDisabledFunctionalFeature.ClipViewedStatus ) ) ) ``` ## enableEventTracking/disableEventTracking (Legacy) > Note: This method will be removed in version 11.0.0. Please use `eventTrackingOptions` instead. The `disableEventTracking()` method will disable storing analytics events on Storyteller servers. By default, event tracking is enabled. Note that some events are necessary for user functionality and will still be made (but not stored) when event tracking is disabled. Event tracking can be enabled again by calling `enableEventTracking()` Example: ```kotlin Storyteller.enableEventTracking() ``` ```kotlin Storyteller.disableEventTracking() ``` ## Source: DeepLinking.md # Deep Linking The Storyteller SDK makes it possible to handle the deep links. The implementing app should follow the official [Android guideline](https://developer.android.com/training/app-links/deep-linking). ## Showcase examples - [Compose — deep link handling (`DeeplinkHandler`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/DeeplinkHandler.kt#L39) - [XML — deep link handling (`MainActivity`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/xml/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/MainActivity.kt#L43) In order to enable it separate intent filters need to be added to the implementing app's `AndroidManifest.xml`. ```xml ``` In the above, `[tenant_name]` is the lower case variant of your tenant's name. For example, if the tenant name is "MyTenant", then `[tenant_name]` should be "mytenant". Each link should be added as a separate intent-filter as in the example above. If Android system starting from Android 11 will fail to auto-verify any of the links which should be auto-verified it will disable the verification for all of them. This will result in the app not being able to handle any of the deep links in the Manifest. > Note: Please note that the last intent filter above for "[tenant_name]stories" does not have android:autoVerify="true" attribute. This is because the auto-verification is not supported for custom non https schemes. The deep link can be handled by using `Storyteller.openDeepLink` static method which opens the link. This can be done automatically by using `openDeepLink` or manually using Story or Page ID (`openStory` and `openPage` respectively). Example 1: ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) val data: Uri? = intent?.data if (data != null && Storyteller.isStorytellerDeepLink(data.toString()) { //open link automatically Storyteller.openDeepLink(this, data.toString()) } } ``` > Note: Starting from Android 6.0+, a Digital Asset Links file is required to properly handle `https` deep links. It can be generated inside the Storyteller CMS and > it requires you to add your app's package name and SHA-256 fingerprint of certificate the app was signed with. For more information on the Digital Asset Links file, see the [Android developer documentation](https://developer.android.com/studio/write/app-link-indexing#associatesite) To generate the Digital Asset Links file in the Storyteller CMS: 1. Navigate to the 'Apps' section on the left-hand side 2. Click on the 'New App' button 3. Fill out the 'Package Name' and 'SHA 256 Cert Fingerprint' - Package Name - this is the application ID declared in the app's `build.gradle` file. This value should be the same as the "App Store Identifier" entered above. E.g. `com.example.myapp.` - SHA 256 Cert Fingerprint - this is the SHA256 fingerprint of your app's signing certificate. You can use the following command to generate the fingerprint via the Java keytool: `keytool -list -v -keystore my-release-key.keystore`. Or, if using the Google Play Store Signing, the SHA256 signature can be downloaded using the Google Play Console: + Login to the Play Console + Select the app you want to configure + Go to Setup → App integrity + Copy the SHA-256 value under "App signing certificate" + This value should be entered into your Storyteller App's form without the "SHA-256: " prefix. E.g.`15:8D:E9:83:C5:73...` After saving your app, the Digital Asset Links file for your tenant can be viewed at the following URLs: `https://yourtenantname.ope.nstori.es/assetlinks.json` and `https://yourtenantname.ope.nstori.es/.well-known/assetlinks.json` **Note: the file may take up to 5 minutes to appear after saving your App.** ## Deep Link Handling ### Open Deep Link The `openDeepLink` function in the Storyteller SDK handles different types of deep links. There are 3 different ways in which it does this ```kotlin fun openDeepLink(activity: Activity, url: String, onError: ((StorytellerError) -> Unit)) ``` This call makes Storyteller open the provided deeplink (showing the requested Page/Story). The method returns `true` if the URL is a Storyteller deeplink Parameters: - `activity` - Activity. - `url` - this is the Deep Link URL. - `onError` - this is called when there is any issue with opening the deep link (e.g. the requested content is no longer available) #### Story Category - For story category deep links, the openDeepLink method identifies and processes links based on the following structure: - The link contains either `/open/category/` or `/go/category/` - The link includes a category identifier following the `/category/` part of the path - The category ID is extracted from the deep link and the `openCategory` function is called with the extracted category ID - eg: `"https://[tenantname].shar.estori.es/go/category/123456"` or `"https://[tenantname].shar.estori.es/open/category/123456"` #### Clip collection - For clip collection deep links, the openDeepLink method identifies and processes links based on the following structure: - The link contains either `/open/clip`, `/go/clip`, `/open/clips`, or `/go/clips` - The link includes a unique identifier (UUID) in the path - The link must include a `collectionId` query parameter - An optional `categoryId` query parameter can be included to specify a category to open in the Clip Collection as an initial category (`initialCategory` of the openCollection method) - The information extracted will be used to call `openCollection()` - eg: `"https://[tenantname].shar.estori.es/open/clip/((clipId))?collectionId=abcd1234?categoryId=123456"` #### Story Deep link - For story deep links, the openDeepLink method identifies and processes links based on the following structure: - The link is confirmed to be a URI object - It extracts storyId or pageId from the deep link via segment positions. One must be present - It proceeds to call `openPage()` or `openStory()` with either the storyId or pageId, - eg openStory: `https://[tenantname].shar.estori.es/go/story/((storyId))` or openPage: `https://[tenantname].shar.estori.es/go/page/((pageId))` #### Sheet - For sheet deep link, the openDeepLink method identifies and processes links based on the following structure: - The link contains either `/open/sheet/` or `/go/sheet/` - It extracts `sheetId` from the deep link via segment positions. One must be present - eg: `"https://[tenantname].shar.estori.es/go/sheet/123456"` or `"https://[tenantname].ope.nstori.es/open/sheet/123456"` ## Manual Deep Link Handling It is generally recommended to author your own deeplink and extract the required data. This is to not upset the current state and deeplink integration of your app and to allow for more control over the deeplink handling then use `openStory()`/`openPage()`/`openCollection()`/`openCategory()`/`openSheet()` to pass the relevant data to the SDK, see the [Open Player](OpenPlayer.md) (or [AdditionalMethods](AdditionalMethods.md)) documentation for more information. ### isStorytellerDeepLink `isStorytellerDeepLink(String)` checks if the string is a valid Storyteller deep link. ```kotlin Storyteller.isStorytellerDeepLink("stories://open/9329bed2-2311-69b8-cbcf-39fcf8d8af21/f6445df7-bd79-71de-cdfb-39fd071568a1") ``` ## Source: OpenPlayer.md # Open Player ## Showcase examples - [Compose — opening a category (`ImageActionItem`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/home/ImageActionItem.kt#L60) ## Open Stories This call opens a Story with a given ID. The call will only open that individual Story. ```kotlin fun openStory(activity: Activity, storyId: String? = null, onError: (StorytellerError) -> Unit = {}) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `storyId` - this is a Story's ID, if it is `null` then `onError` will be called - `onError` - this is called when there is an issue with opening a Story (e.g. the requested content is no longer available) ## Open Pages This call opens a Page with a given ID. The call will only open that individual Story containing that page. ```kotlin fun openPage(activity: Activity, pageId: String? = null, onError: (StorytellerError) -> Unit = {}) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `pageId` - this is a Page's ID, if it is `null` then `onError` will be called - `onError` - this is called when there is an issue with opening a Story (e.g. the requested content is no longer available) ## Open Collection ```kotlin fun openCollection( activity: Activity, configuration: StorytellerClipCollectionConfiguration, titleDrawable: Drawable? = null, onError: (StorytellerError) -> Unit = {} ) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `configuration` - this is a Clip Collection configuration, see below - `titleDrawable` - this is a Drawable that will be used as the title of the Player (optional) - `onError` - this is called when there is an issue with opening the Collection (e.g. the requested Collection is not available) `StorytellerClipCollectionConfiguration` parameters: - `collectionId` - this is a Clip Collection's ID (required) - `categoryId` - this is a Category ID (optional), if provided, this category will be opened, if the category is not available or not found it will be ignored - `clipId` - this is a Clip ID to open (optional) ## Open Category This call opens a Story category. If Story ID is not supplied, the first Story in the collection will be opened. ```kotlin fun openCategory( activity: Activity, category: String, storyId: String? = null, onError: (StorytellerError) -> Unit = {} ) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `category` - this is a Story category - `storyId` - this is a Story ID (optional) - `onError` - this is called when there is an issue with opening the Category (e.g. the requested Category is not available) ### openStoryByExternalId This call opens a Story by external ID. ```kotlin fun openStoryByExternalId( context: Activity, externalId: String? = null, onError: (StorytellerError) -> Unit = {} ) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `externalId` - external Id to open a Story - `onError` - this is called when there is an issue with opening the Category (e.g. the requested Category is not available) ### openCollectionByExternalId This call opens a Collection of Clips and shows Clip with external id. If Clip ID is not supplied, the first Clip in the collection will be opened. ```kotlin fun openCollectionByExternalId( activity: Activity, collectionId: String, externalId: String? = null, titleDrawable: Drawable? = null, onError: (StorytellerError) -> Unit = {} ) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `collectionId` - this is a Clip Collection's ID - `externalId` - this is a Clip's external ID (optional) - `titleDrawable` - this is a Drawable that will be used as the title of the Player (optional) - `onError` - this is called when there is an issue with opening the Collection (e.g. the requested Collection is not available) ## Source: Search.md # Search The `Search` component allows users to search for Storyteller clips and stories. As users type, a list of suggestions will appear, from which users can either select a suggestion or search using their entered term. Results are categorized into two sections: `Stories` and `Clips`, based on their type. Filters allow users to narrow down their search results, enabling them to find specific content that meets their criteria more efficiently. For instance, users can apply filters such as date posted, content type or they can sort it by certain criteria. ## Showcase examples - [Compose — opening Search (`MainScreen`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/main/MainScreen.kt#L75) ## Search Filters `Date Posted` possible values: - `All` - default value - `Past 24 hours` - `Last Week` - `Last Month` - `Last Year` `Content Type` possible values: - `All` - default value - `Stories` - `Clips` `Sort By` values: - `Relevance` - default value - `Like Count` - `Share Count` - `Date Posted` ## How to Use The `Search` feature can be enabled through CMS by setting the `enableSearch` value to `true`. Once enabled, the Search functionality will be available in both Clip and Story players without any additional changes required from the integrating app. Additional `Search` functionality is available through the `Storyteller` class: - `isSearchEnabled` - returns whether search functionality is enabled at the app level. - `openSearch` - opens the `Search`component from anywhere in the app. If Storyteller player is currently displayed, it will be dismissed before presenting the `Search` component. ## Customization Certain UI parts of the `Search` component can be customized. For more information, see [Themes](Themes.md). ## Source: StorytellerBrightcove.md # Storyteller Brightcove Module ## Introduction The Storyteller SDK supports Brightcove analytics as a separate module. This module is designed to work with the Brightcove to provide analytics for videos. ### Setup To setup the module additional dependency needs to be added and the module needs to be initialized and included in `Storyteller.modules` ```groovy maven { url = uri("https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk") } ``` Then add the following reference to your version catalog file: ```toml storyteller-brightcove = { module = "com.storyteller.brightcove:analytics", version.ref = "1.2.0" } ``` And finally reference this in your `build.gradle`: ```groovy implementation(libs.storyteller.brigthcove) ``` To use the module Storyteller SDK version should be set to `10.6.3` or higher. Now initialize the module as follows: ```kotlin val configuration = StorytellerBrightcoveModuleConfiguration( accountId = "your-account-id", playerName = "your-player-name", source = "your-source", destination = "your-destination-url", ) val storytellerBrigthcoveModule = StorytellerBrightCoveModule(configuration) fun initializeStoryteller() { Storyteller.modules = listOf(storytellerBrigthcoveModule) //initialize code } ``` ## Showcase examples - [Compose — Brightcove module config (`StorytellerServiceImpl`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L83) ## Source: Cards.md # Storyteller Cards Storyteller Cards are flexible, themeable components designed to promote content or direct users to key sections within your app. They can display a background image or video, along with an optional title and subtitle. Tapping on a Card can trigger various actions, such as opening a specific Story, a Story Category, a Clip, a Clip Collection or any other action defined in the CMS. The server or personalization engine can choose which Cards to return for a given user. ## Showcase examples - [Compose — rendering Cards (`HomeScreen`)](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/ui/features/home/HomeScreen.kt#L202) ## Usage You can integrate Storyteller Cards into your app using Jetpack Compose. ### Jetpack Compose For Jetpack Compose, use the `StorytellerCard` composable. 1. **Data Model**: Create a `StorytellerCardDataModel` object, specifying the `collectionId` for the Card collection you want to display. You can also provide optional `context` data for analytics. When configured, `context` will be included in all analytics events when users interact with the Card. See [Analytics](Analytics.md#context) for more details. 2. **State**: Initialize a `StorytellerCardState` using `rememberStorytellerCardState()` to manage the card's state and reload functionality. 3. **View**: Create the `StorytellerCard` composable, passing in the `StorytellerCardDataModel` instance and state. 4. **Delegate (Optional)**: Provide an optional `StorytellerCardViewDelegate` to handle events like `onDataLoadComplete`. This allows you to react to data loading success or failure (e.g., by hiding the component). ```kotlin import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.storyteller.ui.compose.components.cards.StorytellerCard import com.storyteller.ui.compose.components.cards.StorytellerCardDataModel import com.storyteller.ui.compose.components.cards.StorytellerCardViewDelegate import com.storyteller.ui.compose.components.cards.rememberStorytellerCardState import com.storyteller.domain.entities.StorytellerError @Composable fun MyCardSection() { val dataModel = StorytellerCardDataModel( collectionId = "card-collection-id", context = hashMapOf("placementId" to "home_card", "location" to "Home") ) val cardState = rememberStorytellerCardState(dataModel.collectionId) val cardViewDelegate = remember { object : StorytellerCardViewDelegate { override fun onDataLoadComplete(success: Boolean, error: StorytellerError?, dataCount: Int) { if (success) { println("Card data loaded successfully!") } else { println("Card data failed to load: ${error?.message}") } } } } StorytellerCard( dataModel = dataModel, state = cardState, delegate = cardViewDelegate, modifier = Modifier ) // Optional: Add a reload button Button(onClick = { cardState.reloadData() }) { Text("Reload Card") } } ``` ## Reloading The `StorytellerCardState` provides a `reloadData()` method. Call this method to manually trigger a refresh of the Card data from the server. ## Viewed/Tapped Ordering In the CMS you can make Card collections be ordered based on viewed or tapped status, so that once a Card is viewed/tapped, the next Card from the collection will be shown to the user. This will enable users to always see fresh content. ## Theming Card appearance and behavior are primarily configured directly within the Storyteller CMS for each Card Collection. The following properties can be configured in the CMS and influence the Card's presentation and behaviour: - `style.textLengthMode` (default: `truncate`): How text that exceeds the available space is handled. +`truncate`: Display text at the specified size; truncate with an ellipsis (...) if it doesn't fit. +`resize`: Start at the specified text size and reduce the font size until the text fits (up to two lines for heading and subheading). - `style.textAlignment` (default: `start`): Horizontal alignment of the heading and subheading. Can be `start`, `center`, or `end`. - `style.padding` (default: `12`): Inner padding around the text content. + For full-bleed cards ( `marginHorizontal = 0`) with text _below_ the image and _all_ cards with text _on_ the image, padding is applied to all sides of the text. + For cards with text _below_ the image where `marginHorizontal > 0`, padding is applied only to the top and bottom of the text. - `style.marginHorizontal` (default: `0`): Horizontal margin around the card. `0` means full-bleed. - `style.cornerRadius` (default: `{theme.primitives.cornerRadius}`): Corner radius of the card. The application depends on `marginHorizontal` and text position. + Not applied for full-bleed cards (`marginHorizontal=0`) with text _below_ the image. + Applied to the _image_ for cards with text _below_ the image and `marginHorizontal > 0`. + Applied to the _whole card_ for cards with text _on_ the image and `marginHorizontal > 0`. - `style.heading.font` (default: `{theme.customFont}`): Font family for the heading. - `style.heading.textSize` (default: `22`): Font size for the heading. - `style.heading.lineHeight` (default: `28`): Line height for the heading. - `style.heading.textCase` (default: `default`): Text case transformation (`upper`, `lower`, `default`). - `style.heading.letterSpacing` (default: `0`): Letter spacing for the heading. - `style.heading.textColor` (default: `{theme.colors.white.primary}`): Text color for the heading. The default applies when text is displayed _on_ the background asset. When text is displayed _below_ the background, the default color is `{theme.colors.black.primary}` in light mode and `{theme.colors.white.primary}` in dark mode. - `style.subHeading.font` (default: `{theme.customFont}`): Font family for the subheading. - `style.subHeading.textSize` (default: `16`): Font size for the subheading. - `style.subHeading.lineHeight` (default: `21`): Line height for the subheading. - `style.subHeading.textCase` (default: `default`): Text case transformation (`upper`, `lower`, `default`). - `style.subHeading.letterSpacing` (default: `0`): Letter spacing for the subheading. - `style.subHeading.textColor` (default: `{theme.colors.white.secondary}`): Text color for the subheading. The default applies when text is displayed _on_ the background asset. When text is displayed _below_ the background, the default color is `{theme.colors.black.secondary}` in light mode and `{theme.colors.white.secondary}` in dark mode. - `behavior.reloading.reloadOnExit` (default: `true`): Whether the Card Collection reloads after returning from tapping a Card (e.g., after dismissing the Story/Clip Player). - `behavior.reloading.reloadOnForeground` (default: `true`): Whether the Card Collection reloads when the app comes to the foreground. ### Button Behavior - **Button positioning**: Buttons are optional visual elements that follow the `textOverContent` property: + When `textOverContent = true`: Button appears on the card (overlaying the content), positioned below the title/subtitle + When `textOverContent = false`: Button appears below the card (below the title/subtitle section) - **Button functionality**: Buttons do not change the tappability of Cards - the entire card remains tappable and executes the same action as the button when tapped - **Button text**: The button text is defined in the Card data, not the theme ### Button Theme Properties - `style.button.title.font` (default: uses heading font): Font family for the button text. If not specified or null, uses the heading font with the button's text size and line height. - `style.button.title.textSize` (default: `16`): Font size for the button text. - `style.button.title.lineHeight` (default: `21`): Line height for the button text. - `style.button.title.textCase` (default: `default`): Text case transformation for the button text (`upper`, `lower`, `default`). - `style.button.title.letterSpacing` (default: `0`): Letter spacing for the button text. - `style.button.title.textColor` (default: `{theme.colors.white.primary}` for text on image, `{theme.colors.black.primary}` for text below image in light mode, `{theme.colors.white.primary}` for text below image in dark mode): Text color for the button. If not specified or null, uses the same color logic as the outline color. - `style.button.backgroundColor` (optional): Background color of the button. If not set, the button will have a transparent background with an outline. - `style.button.outlineColor` (default: `{theme.colors.white.primary}` for text on image, `{theme.colors.black.primary}` for text below image in light mode, `{theme.colors.white.primary}` for text below image in dark mode): Color of the button outline/border. - `style.button.outlineWidth` (default: `1`): Width of the button outline/border in points. - `style.button.cornerRadius` (optional, default: `{theme.primitives.cornerRadius}`): Corner radius of the button. If null or not set, falls back to the theme's default corner radius. - `style.button.textAlignment` (default: uses card `textAlignment`): Text alignment for the button text. If not specified or null, uses the card's text alignment setting. ## Source: OpenSourceLicenses.md # Open Source Licenses The Storyteller SDK uses open source libraries to accomplish some of its functionality. Those libraries are: - [Retrofit](https://github.com/square/retrofit) - [ExoPlayer](https://github.com/google/ExoPlayer) - [Coil](https://coil-kt.github.io/coil/) - [OkHttp](https://square.github.io/okhttp/) These libraries are all subject to the [Apache 2.0](https://github.com/square/retrofit/blob/master/LICENSE.txt) open source license. According to the terms of the license, it may be necessary to include a statement of this license somewhere in the app which integrates the SDK. You should consult your own legal adviser if you are unsure. The text of the license is included below: ```text Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ## Source: AI.md # Using Storyteller SDK Docs with AI Assistants This guide explains how to use Storyteller SDK documentation with AI-powered coding assistants to enhance your development experience. ## Why Use AI Assistants with Documentation? AI coding assistants like GitHub Copilot, Claude, ChatGPT, and others can significantly improve your development workflow when integrating the Storyteller SDK. By providing these AI tools with our documentation, you can: - Get contextually relevant code suggestions - Receive guidance based on Storyteller best practices - Accelerate your implementation process - Troubleshoot issues more effectively ## Adding Documentation to Your Editor AI-enabled editors can index and use Storyteller SDK documentation to provide more accurate suggestions. Here's how you can add our docs to various AI assistants: ### Download `llms.txt` [llms.txt](llms.txt) or you can just copy/paste to your editor below ### Using the Complete Documentation Context (copy/paste to editor of your choice)
Click to expand information about the complete SDK documentation # Storyteller SDK Kotlin ## General Info and Rules to follow > This file contains the complete documentation for the Storyteller SDK for Android (Kotlin). > It should serve our integrators which are using AI to help adding Storyteller SDK to heir code. Rules: 1. Client may use different structure and the codebase should be scanned to check if the right things are added e.g. Client may use build.gradle.kts files instead of build.gradle files when integrating Storyteller 2. When Adding Storyteller methods to the code comment should be created with a link to official www documentation (docs.getstoryteller.com/android) 3. If methods or fields are optional they should not be added to generated code # Quickstart Guide This is a developers' guide for setting up Storyteller for native Android apps. This guide will cover the basic technical steps for initializing the Storyteller SDK, authenticating a user, and adding a `StorytellerStoriesRowView` to your app. ## Resources - [Storyteller Showcase App](https://github.com/getstoryteller/storyteller-showcase-android) ## How to Add the SDK to Your Project Before you can add the Android SDK to your app, you will need to obtain an API Key. This is a secret key used to authenticate the SDK in your app. Throughout this document it will be marked as `[APIKEY]`. ### SDK Dependencies Currently the Android SDK contains the following [dependencies](https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk/Storyteller/sdk/10.12.0/sdk-10.12.0.pom). ### R8 / ProGuard If your app uses R8, the rules are added automatically. ### SDK Installation The Android SDK can be included in your project using [Gradle](https://gradle.org/). It is recommended to use [Android Studio](https://developer.android.com/studio). If you are having problems with configuring your build, check out the [Android Studio guide](https://developer.android.com/studio/build) or [Gradle guides](https://gradle.org/guides/). 1. Add the maven repository for the SDK in the _Project_ `build.gradle` file (`MyAwesomeApp/build.gradle`), under the `allprojects` section > Note: make sure it is added to `allprojects`, and not `buildscript` ```groovy ... allprojects { repositories { google() mavenCentral() maven { url 'https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk' } } } ``` 1. Modify the app _Module_ `build.gradle` file (`MyAwesomeApp/app/build.gradle`) ```groovy ... dependencies { def storyteller_version = "10.12.0" implementation(group: "Storyteller", name: "sdk", version: "$storyteller_version") } ``` 1. Sync your project with Gradle files - Android Studio ## SDK Initialization Before using the Android SDK in your app, you need to initialize it with an API key. ### Adding an API Key Use the `initialize(apiKey: String, userInput: StorytellerUserInput? = null, onSuccess: () -> Unit = {}, onFailure: (StorytellerError) -> Unit = {})` public method to manually initialize the SDK at runtime. This will authenticate the SDK on the Storyteller API and configure it with the corresponding settings. - `apiKey`: (**Required**) the API key you wish to initialize the SDK with - `userInput` : details of the user to be authenticated (this should be a unique user ID. If this is not set, the default value is used) - `onSuccess`: callback for successful completion - `onFailure`: callback for failed completion with error Usage: ```kotlin Storyteller.initialize( apiKey = "[APIKEY]", userInput = StorytellerUserInput("unique-user-id"), onSuccess = { // onSuccess action }, onFailure = { error -> // onFailure action } ) ``` Initialization errors: - `InitializationError`: when the context is `null` - `InvalidAPIKeyError`: when an invalid API key is used - `NetworkError`: when the call to load the settings for the SDK fails (i.e. a non-success HTTP status code is returned) - `NetworkTimeoutError`: when the call to load the settings for the SDK times out - `JSONParseError`: when a malformed settings response is received from the server > Note: Please be aware that this method is asynchronous ## Authenticate a User For more information about Users and External IDs, please see [Working with Users](https://docs.getstoryteller.com/android/Users/) ## Adding a StorytellerStoriesRow Composable 1. At first we will need a nesting composable. You can use any composable layout you want: `Box`, `Column`, `LazyColumn`, etc. ```kotlin @Composable fun MainScreen() { Box() { // ... } } ``` 1. Storyteller Composables Now it's time to add the Storyteller Composables to your app. The Storyteller Composables are the building blocks of the Storyteller SDK. ```kotlin StorytellerStoriesRow( modifier = Modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), // data model with the configuration for your Composables. We will describe how to use StorytellerDataModel below. delegate = listViewDelegate, // delegate for the Composables. We will describe how to use StorytellerListViewDelegate below. state = rememberStorytellerRowState() // state for the Composables. We will describe how to use StorytellerRowState below. ) ``` ## XML Views (Legacy) Prefered way to add Storyteller Lists is to use Composables. Storyteller still supports XML/Views and the guide can be found [here](https://docs.getstoryteller.com/android/StorytellerListViews/), although it may be discontinued in the near future. > **Note:** The following section describes legacy Android Views (`StorytellerListView`, `StorytellerStoriesView`, `StorytellerClipsView`, etc.) for use with XML layouts. The preferred method for implementing Storyteller lists is with Jetpack Compose, as detailed in the **Storyteller Jetpack Compose SDK** section (content from `docs/StorytellerLists.md`). Support for these legacy views may be removed in a future SDK version. # What is StorytellerListView (Legacy) `StorytellerListView` is a base abstract class for views displaying lists of Stories or Clips. Soryteller Views are considered Legacy and the support may be removed in near future. Please prefer using [Composables instead](https://docs.getstoryteller.com/android/StorytellerLists/). The Storyteller SDK offers two implementations of this abstract class: - `StorytellerStoriesView` - `StorytellerClipsView` Each of these classes has their corresponding rows and grid views as the following: For Stories - `StorytellerStoriesRowView` - `StorytellerStoriesGridView` For Clips - `StorytellerClipsRowView` - `StorytellerClipsGridView` this abstract class contains all abstract attributes and methods that are common to all `StorytellerListView` implementations. ## How to improve the performance of RecyclerView? If you use the `StorytellerViews` in a `RecyclerView`, using the `setViewHolderParent` method is not mandatory but can significantly improve the UI performance. Inside the `RecyclerView.Adapter`: ```kotlin ... override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val itemView = LayoutInflater.from(parent.context).inflate(R.layout.my_item, parent, false) return MyViewHolder(itemView, parent) } ... ``` Inside the `RecyclerView.ViewHolder`: ```kotlin class MyViewHolder( private val view: View, private val parent: ViewGroup, ): RecyclerView.ViewHolder(view) { private val binding = ListStoryRowBinding.bind(view) fun onBind(item: Item) { binding.storytellerView.setViewHolderParent(parent) ... } } ``` ## Adding a StorytellerStoriesRowView The `StorytellerStoriesRowView` can be added to your app using XML layout or in code. ### XML Add a `com.storyteller.ui.list.StorytellerStoriesRowView` element to your layout ```xml ``` ### Code 1. Create a `StorytellerStoriesRowView` instance ```kotlin val storyRowView = StorytellerStoriesRowView(context) ``` 1. Add the created view instance to the view hierarchy ```kotlin view.addView(storyRowView) ``` ### Recommendations for `StorytellerListView` Hosting Activity Please keep in mind that Stories will open in the activity that displays a status bar only if there is a cutout notch present on the device. In other cases, Stories will open in the activity without a status bar. Status bar visibility changes can have an impact on shared element transitions between Storyteller List View and Story Viewing Activity. For that reason, we recommend hosting activities to use LAYOUT_STABLE flags as in the code snippet below ```kotlin // Storyteller hosting activity onCreate method override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ~~~~~~~ window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE // ~~~~~~~ } ``` ## Further Reading ### Additional Storyteller Methods `Storyteller` has some helper methods which may be useful, see [AdditionalMethods](https://docs.getstoryteller.com/android/AdditionalMethods/) for more details. ### Implementing Storyteller Callbacks `StorytellerDelegate` has callbacks for events which can be implemented, see [StorytellerDelegate](https://docs.getstoryteller.com/android/StorytellerDelegates/) for more details. ### Implementing StorytellerListView Callbacks `StorytellerListView` has callbacks for events which can be implemented, see [StorytellerListViewDelegate](https://docs.getstoryteller.com/android/StorytellerDelegates/) for more details. ## Configuring a StorytellerStoriesView ### Attributes This sections describes attributes common to `StorytellerStoriesRowView` and `StorytellerStoriesGridView`. Notes are used to mark if there is a difference in property interpretation. #### delegate The `delegate` is the `StorytellerListViewDelegate` instance for `StorytellerListView` callbacks (see [Implementing StorytellerListViewDelegate methods](https://docs.getstoryteller.com/android/StorytellerDelegates/)). #### StorytellerStoriesView.ListConfiguration The `ListConfiguration` class is used to configure the `StorytellerStoriesRowView` and `StorytellerStoriesGridView` classes. It is a data class that inherits from `StorytellerListConfiguration`. Usage: ```kotlin val storytellerStoriesRowView = StorytellerStoriesRowView(context) storytellerStoriesRowView.configuration = StorytellerStoriesView.ListConfiguration( theme = customTheme, uiStyle = StorytellerListViewStyle.AUTO, displayLimit = 10, cellType = StorytellerListViewCellType.SQUARE, categories = listOf("category1", "category2", "categoryX") ) ``` ##### cellType The `cellType` is the style of the cell. This can either be `ROUND(0)` or `SQUARE(1)`. The default value is `SQUARE(1)`. ##### categories The `categories` attribute is a list of Categories that the list view should show to the user. The default value is an empty list. The `categories` property can be used to show specific Stories content in the row or grid by supplying a list of valid Categories as strings. Categories can be defined in the CMS. If no Categories are assigned then the content from the default or "Home" Category is displayed. ##### theme The `theme` parameter is used to set the [Theme](https://docs.getstoryteller.com/android/Themes/) for the `StorytellerListView`. If theme is set to null, then theme set as`Storyteller.theme` global property is used. The theme determines how the items within the List View are presented as well as various features of the player once it is launched. ##### uiStyle The `uiStyle` adjusts whether Storyteller renders in light mode, dark mode or follows the system setting. `uiStyle` takes the following values: - `StorytellerListViewStyle.AUTO` - default value, the StorytellerListView will adjust its color scheme automatically according to the current system UI mode - `StorytellerListViewStyle.LIGHT` - force the StorytellerListView to use the light theme - `StorytellerListViewStyle.DARK` - force the StorytellerListView to use the dark theme ##### displayLimit The `displayLimit` is the maximum amount of tiles that can be shown in the list. #### Dynamic Scaling Item tiles will scale dynamically to fit the row or grid view. The base size of the tiles are: - 100dp width x 150dp height for square tiles - 76dp width x 105dp height for round tiles The base size will be used when rendering the row if the dimensions of the row view cannot determine its constraints. The exact dimensions of the row view will depend on how it is defined in the layout view hierarchy, including its parents. In general, tiles will maintain its base proportion and scale up or down to meet view hierarchy constrains. For the sake of simplicity, examples will be provided using `square` as `100dp/150dp = 2/3` proportions to make it easier to do calculations. ##### Example 1 ```xml ``` The final item tile size will be ≈ `133dp x 200dp`, as `200 dp[h] * 2/3 [w/h] ≈ 133dp` ##### Example 2 ```xml ``` The final item tile size will be ≈ 133dp x 200dp. This is because the `StorytellerStoriesRowView` is wrapped in a parent view with a height of 200dp and has its size attributes set to `match_parent`. The system, therefore, expands the row view to fill the height of its parent and scales the width of the tiles accordingly. ##### Example 3 ```xml ``` In this case, the final item tile size will be the height of the window. This happens because the `StorytellerStoriesRowView` is defined as the top-most view and `android:layout_height="match_parent"` has been set on the view. The system, therefore, expands the row view to fill the window height - and so the resulting item tiles will scale to fit the window height also. ##### Example 4 ```xml ... ... ``` In this case, the final item tile size will be 100dp x 150dp (the base size). Since the `StorytellerStoriesRowView` has been defined as the child of another view, setting `android:layout_height="wrap_content"` makes the item tile size 100dp x 150dp (base size). #### XML Attributes can also be applied in XML. ```xml ``` ### Methods #### reloadData The `reloadData` method starts loading fresh data for all Stories from the API. On completion, it updates the Story data, starts prefetching content and updates the read status of the Stories. The `onDataLoadStarted` and `onDataLoadComplete` methods on the [StorytellerListViewDelegate](https://docs.getstoryteller.com/android/StorytellerDelegates/) are called accordingly (the latter with appropriate data depending on the result of the API requests). ```kotlin val storytellerStoriesRowView = StorytellerStoriesRowView(context) storytellerStoriesRowView.reloadData() ``` ## Configuring a StorytellerClipsView ### Attributes This sections describes attributes common to `StorytellerClipsRowView` and `StorytellerClipsGridView`. Notes are used to mark if there is a difference in property interpretation #### StorytellerListViewDelegate - `delegate`: the `StorytellerListViewDelegate` instance for `StorytellerClipsView` callbacks (see [Implementing StorytellerListViewDelegate methods](https://docs.getstoryteller.com/android/StorytellerDelegates/)) #### StorytellerClipsView.ListConfiguration The `ListConfiguration` class is used to configure the `StorytellerClipsRowView` and `StorytellerClipsGridView` classes. It is a data class that inherits from `StorytellerListConfiguration`. `StorytellerClipsView.ListConfiguration` class attributes: Example: ```kotlin val storytellerClipsRowView = StorytellerClipRowView(context) storytellerClipsRowView.configuration = StorytellerClipsView.ListConfiguration( theme = customTheme, uiStyle = StorytellerListViewStyle.AUTO, displayLimit = 10, cellType = StorytellerListViewCellType.SQUARE, collection = "collectionId" ) ``` - `collection`: the Collection that the list view should show to the user. The default value is `null`.The `collection` property is used to show specific Clips content in the row or grid by supplying a single Collection as string. Collections can be defined in the CMS. If no Collection is assigned, then no Clips content is displayed. - `theme`: This parameter is used to set the [Theme](https://docs.getstoryteller.com/android/Themes/) for the `StorytellerClipsView`. If theme is set to `null`, then theme set as`Storyteller.theme` global property is used. The theme determines how the items within the List View are presented as well as various features of the player once it is launched. - `displayLimit`: only display up to this number of tiles in the list. - `uiStyle`: adjust whether Storyteller renders in light mode, dark mode or follows the system setting. `uiStyle` takes the following values: - `StorytellerListViewStyle.AUTO` - default value, the StorytellerClipsView will adjust its color scheme automatically according to the current system UI mode - `StorytellerListViewStyle.LIGHT` - force the StorytellerClipsView to use the light theme - `StorytellerListViewStyle.DARK` - force the StorytellerClipsView to use the dark theme #### Feed Title The feed title in the Clips player can be configured in the CMS for the Collection. This can be a custom title or image. ### Dynamic Scaling Item tiles will scale dynamically to fit the row or grid view. The base size of the tiles are: - 100dp width x 150dp height for square tiles - 76dp width x 105dp height for round tiles The base size will be used when rendering the row if the dimensions of the row view cannot determine its constraints. The exact dimensions of the row view will depend on how it is defined in the layout view hierarchy, including its parents. In general, tiles will maintain its base proportions and scale up or down to meet view hierarchy constrains. For the sake of simplicity, examples will be provided using `square` as `100dp/150dp = 2/3` proportions to make it easier to do calculations. #### Example 1 ```xml ``` The final item tile size will be ≈ `133dp x 200dp`, as `200 dp[h] * 2/3 [w/h] ≈ 133dp` #### Example 2 ```xml ``` The final item tile size will be ≈ 133dp x 200dp. This is because the `StorytellerRowView` is wrapped in a parent view with a height of 200dp and has its size attributes set to `match_parent`. The system, therefore, expands the row view to fill the height of its parent and scales the width of the tiles accordingly. #### Example 3 ```xml ``` In this case, the final item tile size will be the height of the window. This happens because the `StorytellerRowView` is defined as the top-most view and `android:layout_height="match_parent"` has been set on the view. The system, therefore, expands the row view to fill the window height - and so the resulting item tiles will scale to fit the window height also. #### Example 4 ```xml ... ... ``` In this case, the final item tile size will be `100dp x 150dp` (the base size). Since the `StorytellerRowView` has been defined as the child of another view, setting `android:layout_height="wrap_content"` makes the item tile size `100dp x 150dp` (base size). ### XML Views can also be applied in XML. ```xml ``` ### Methods #### reloadData The `reloadData` method starts loading fresh data for all Clips from the API. On completion, it updates the Clips data, starts prefetching content and updates the read status of the Clips. The `onDataLoadStarted` and `onDataLoadComplete` methods on the [StorytellerListViewDelegate](https://docs.getstoryteller.com/android/StorytellerDelegates/) are called accordingly (the latter with appropriate data depending on the result of the API requests). ```kotlin val storytellerClipsRowView = StorytellerClipsRowView(context) storytellerClipsRowView.reloadData() ``` # Storyteller Jetpack Compose SDK This is a developers' guide for setting up Storyteller Compose SDK for native Android apps. This guide will cover the basic technical steps for integrating Storyteller Compose SDK into your app. *Note: While this guide focuses on the Jetpack Compose implementation, corresponding traditional Android Views (e.g., `StorytellerStoriesRowView`, `StorytellerStoriesGridView`, `StorytellerClipsRowView`, `StorytellerClipsGridView`) also exist and can be used in XML layouts or programmatically. Their configuration attributes and delegate usage are similar to their Compose counterparts.* ## Getting Started ### Resources - [Storyteller Android Compose Sample App](https://github.com/getstoryteller/storyteller-sample-android/tree/main/app/src/main/java/com/example/storytellerSampleAndroid/compose) ### How to Add the SDK to Your Project Before you can add the Android SDK to your app, you will need to obtain an API Key. This is a secret key used to authenticate the SDK in your app. Throughout this document it will be marked as `[APIKEY]`. #### SDK Installation The Compose SDK can be included in your project using [Gradle](https://gradle.org/). It is recommended to use [Android Studio](https://developer.android.com/studio). If you are having problems with configuring your build, check out the [Android Studio guide](https://developer.android.com/studio/build) or [Gradle guides](https://gradle.org/guides/). > Note: Starting from 9.3.0 SDK version Compose is now part of the SDK and there is no need to add separate Compose dependency. The steps to add the SDK are [here](https://docs.getstoryteller.com/android/GettingStarted/) #### Usage 1. At first we will need a nesting composable. You can use any composable layout you want: `Box`, `Column`, `LazyColumn`, etc. ```kotlin @Composable fun MainScreen() { Box() { // ... } } ``` 1. Storyteller Composables Now it's time to add the Storyteller Composables to your app. The Storyteller Composables are the building blocks of the Storyteller SDK. ```kotlin StorytellerStoriesRow( modifier = Modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), // data model with the configuration for your Composables. We will describe how to use StorytellerDataModel below. delegate = listViewDelegate, // delegate for the Composables. We will describe how to use StorytellerListViewDelegate below. state = rememberStorytellerRowState() // state for the Composables. We will describe how to use StorytellerRowState below. ) ``` We have the following **composable types** available: - StorytellerStoriesRow - StorytellerStoriesGrid - StorytellerClipsRow - StorytellerClipsGrid 1. Using the Storyteller Composables You can always modify your Composables during recomposition. Reload Data: Just like normal composable lists, they have a list state, so as Storyteller lists, they have a state, and we will use it to reload the data. There is an extra parameter `state` in the Composables, which is set by default. Let's see how it's used: ```kotlin val state = rememberStorytellerRowState() // or rememberStorytellerGridState() for StorytellerStoriesGrid or StorytellerClipsGrid StorytellerStoriesRow( // ... state = state ) // At any point in your code you can call `reloadData` method, maybe after doing PTR or after some event. LaunchedEffect(Unit) { state.reloadData() } ``` 1. Setting categories and collections, applying themes and styles You can use `StorytellerStoriesDataModel` object for configuring your Stories Composables. It has the following properties: ```kotlin StorytellerStoriesDataModel( categories = listOf("category1", "category2"), // list of stories categories for the Composable. If you want to use all categories, you can pass an empty list. theme = UiTheme(), // theme for the Composable. A global theme will be set if no value will be passed here. uiStyle = StorytellerListViewStyle.LIGHT, // a style of the Composable. It can be AUTO, LIGHT or DARK. AUTO will represent the theme of the app. displayLimit = 6, // a limit of the stories to be displayed in the Composable. Default is Int.MAX_VALUE. cellType = StorytellerListViewCellType.SQUARE, // a cell type of the Composable. It can be SQUARE or ROUND. visibleTiles = null // a number of visible tiles for rows. Default is null and turned off. If the value is set for greater than 0, the visible tiles will be turned on. The height of the row will be calculated based on the number of visible tiles and if set to eg. 3.5 it will always show three and a half tile independently of the screen size. ) ``` > Note: `StorytellerStoriesGrid` supports only `cellType = StorytellerListViewCellType.SQUARE`. You can use `StorytellerClipsDataModel` object for configuring your Clips Composables. It has the following properties: ```kotlin StorytellerClipsDataModel( collection = "collection1", // a string representing your collection of Clips. theme = UiTheme(), // theme for the Composable. A global theme will be set if no value will be passed here. uiStyle = StorytellerListViewStyle.LIGHT, // a style of the Composable. It can be AUTO, LIGHT or DARK. AUTO will represent the theme of the app. displayLimit = 6, // a limit of the clips to be displayed in the Composable. Default is Int.MAX_VALUE. cellType = StorytellerListViewCellType.SQUARE, // a cell type of the Composable. It can be SQUARE or ROUND. visibleTiles = null // a number of visible tiles for rows. Default is null and turned off. If the value is set for greater than 0, the visible tiles will be turned on. The height of the row will be calculated based on the number of visible tiles and if set to eg. 3.5 it will always show three and a half tile independently of the screen size. ) ``` > Note: `StorytellerClipsGrid` supports only `cellType = StorytellerListViewCellType.SQUARE`. ## Storyteller Compose ListView Delegate The `StorytellerListViewDelegate` will provide you necessary callbacks for the state of data inside you Composables. This delegate is used to track when data load starts, when it completes and when player was dismissed by user. We recommend using `remember` for the delegate's object to avoid creating new ones during recomposition. ```kotlin val listViewDelegate by remember("your_item_id") { val value = object : StorytellerListViewDelegate { override fun onPlayerDismissed() { // handle player dismissed event } override fun onDataLoadStarted() { // handle loading started event } override fun onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int) { // handle data load complete event } } mutableStateOf( value ) } StorytellerStoriesRow( delegate = listViewDelegate, // ... ) ``` ## StorytellerStoriesGrid The `StorytellerStoriesGrid` is a Composable with same functionality as the `StorytellerStoriesGridView` view from the Storyteller SDK. ### Usage in Compose ```kotlin //Stories Grid StorytellerStoriesGrid( modifier = modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), delegate = listViewDelegate, state = rememberStorytellerGridState(), isEnabled = true ) //Stories Scrollable Grid StorytellerStoriesGrid( modifier = modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), delegate = listViewDelegate, state = rememberStorytellerGridState(), isScrollable = true, isEnabled = true ) ``` ### Parameters - `modifier` - a `Modifier` object that will be applied to the `StorytellerStoriesGrid` composable. - `delegate` - a `StorytellerListViewDelegate` object that will be used to handle data load events and player dismissed event. - `state` - a `StorytellerGridState` object that can be used to reload data and it has a reference to the actual grid state. + the `rememberStorytellerGridState()` has two parameters: - `lazyGridState` - a `LazyGridState` object that will be used to handle the state of the grid. - `enablePullToRefresh` - a `Boolean` flag that indicates if the pull to refresh should be enabled. it's off if the grid is not scrollable. and it's off by default if the grid is scrollable. - `isScrollable` - a `Boolean` flag that indicates if the grid is scrollable, if `true` the grid can be put in a non scrollable container. if `false` the grid can be put in a scrollable container, but a display limit should be set wisely as it wil layout all tiles in the grid. - `isEnabled` - a `Boolean` flag that indicates if the tile click should be enabled. ## StorytellerStoriesRow The `StorytellerStoriesRow` is a Composable with same functionality as the `StorytellerStoriesRowView` view from the Storyteller SDK. ### Usage in Compose ```kotlin StorytellerStoriesRow( modifier = modifier, dataModel = StorytellerStoriesDataModel(categories = emptyList()), delegate = listViewDelegate, state = rememberStorytellerRowState() ) ``` ## StorytellerClipsGrid The `StorytellerClipsGridView` is a Composable with same functionality as the `StorytellerClipsGridView` view from the Storyteller SDK. ### Usage in Compose ```kotlin //Clips Grid StorytellerClipsGrid( modifier = modifier, dataModel = StorytellerClipsDataModel(collection = "collection"), delegate = listViewDelegate, state = rememberStorytellerGridState(), isScrollable = false, isEnabled = true ) //Clips Scrollable Grid StorytellerClipsGrid( modifier = modifier, dataModel = StorytellerClipsDataModel(collection = "collection"), delegate = listViewDelegate, state = rememberStorytellerGridState(), isScrollable = true, isEnabled = true ) ``` ### Parameters - `modifier` - a `Modifier` object that will be applied to the `StorytellerClipsGrid` composable. - `delegate` - a `StorytellerListViewDelegate` object that will be used to handle data load events and player dismissed event. - `state` - a `StorytellerGridState` object that can be used to reload data and it has a reference to the actual grid state. + the `rememberStorytellerGridState()` has two parameters: - `lazyGridState` - a `LazyGridState` object that will be used to handle the state of the grid. - `enablePullToRefresh` - a `Boolean` flag that indicates if the pull to refresh should be enabled. it's off if the grid is not scrollable. and it's off by default if the grid is scrollable. - `isScrollable` - a `Boolean` flag that indicates if the grid is scrollable, if `true` the grid can be put in a non scrollable container. if `false` the grid can be put in a scrollable container, but a display limit should be set wisely as it will layout all tiles in the grid. - `isEnabled` - a `Boolean` flag that indicates if the tile click should be enabled. - `searchInput` - an optional `SearchInput` object that can be used to filter clips in the grid. - `isVisible` - a `Boolean` flag that indicates if the grid should be visible. Defaults to true. ## StorytellerClipsRow The `StorytellerClipsRow` is a Composable with same functionality as the `StorytellerClipsRowView` view from the Storyteller SDK. ### Usage in Compose ```kotlin StorytellerClipsRow( modifier = modifier, dataModel = StorytellerClipsDataModel(collection = "collection"), delegate = listViewDelegate, state = rememberStorytellerRowState() ) ``` # Additional Methods ## Table of Contents - [Setting the global StorytellerDelegate](#setting-the-global-storytellerdelegate) - [Static Attributes](#static-attributes) - [Static Methods](#static-methods) This page covers additional helper methods and properties. ## Setting the global StorytellerDelegate Please see the dedicated [Storyteller Delegates](https://docs.getstoryteller.com/android/StorytellerDelegates/) page. ```kotlin Storyteller.storytellerDelegate = myCustomStorytellerDelegate ``` ## Static Attributes ### isInitialized `isInitialized` is a boolean static property which is set to `true` if Storyteller was initialized successfully. ```kotlin val initialized = Storyteller.isInitialized ``` ### isSearchEnabled `isSearchEnabled` is a boolean static property which is set to `true` if the Search feature is enabled in the Storyteller configuration. ```kotlin val searchEnabled = Storyteller.isSearchEnabled ``` ### isPlayerVisible `isPlayerVisible` is a boolean property which is set to `true` when a Story or Clip player is opened, `false` when the Story or Clip player is dismissed. ```kotlin val isStoryPlayerVisible = Storyteller.isPlayerVisible ``` ### version The `version` is a static String property which holds the SDK version. ```kotlin val storytellerVersion = Storyteller.version ``` ### user Allows setting custom attributes for audience targeting. Please see [User Customization](https://docs.getstoryteller.com/android/Users/) ```kotlin Storyteller.user.setCustomAttribute('location', 'New York'); ``` ## Static Methods ### openSearch ```kotlin fun openSearch(activity: Activity) ``` This method opens the Search screen. Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Search activity ### openSheet ```kotlin fun openSheet(activity: AppCompatActivity, sheetId: String, onError: (Error) -> Unit = {}) ``` This call opens a Sheet with a given ID. Parameters: - `activity` - this is the Activity that will be used to launch the Sheet - `sheetId` - this is a Sheet's ID, if it is `null` then `onError` will be called - `onError` - this is called when there is an issue with opening a Sheet (e.g. the sheetId is a blank string) ### dismissPlayer `dismissPlayer(Boolean, String?)`: force closes the currently open Story or Player Views. If no Player is open when this is called, it has no effect. `animated`: the flag indicating if close animation should be triggered. Defaults to `true`. `reason`: the reason why the Story Page was force closed. This will be used to populate the `dismissedReason` parameter of the corresponding `onUserActivityOccurred` callback. If this is set to `null` the `onUserActivityOccurred` callback will not be triggered. `onCompletion`: a callback that will be called when the player is dismissed. This is useful when you want to perform an action after the player is dismissed. ```kotlin Storyteller.dismissPlayer(true, "reason") ``` ### resumePlayer `resumePlayer()`: resumes the currently open Story or Player Views. If no Player is open when this is called, it has no effect. ```kotlin Storyteller.resumePlayer() ``` ### getStoriesCount ```kotlin suspend fun getStoriesCount(categoryIds: List): Int ``` This method returns the count of stories available in the specified categories. It can be used to determine if a particular category or set of categories contains any Stories before rendering a UI component. Parameters: - `categoryIds` - a list of category IDs to check for stories Return value: - Returns the total number of stories available in the specified categories, or 0 if Storyteller is not initialized or the categoryIds list is empty ### getClipsCount ```kotlin suspend fun getClipsCount(categoryIds: List): Int ``` This method returns the count of clips available in the specified categories. It can be used to determine if a particular category or set of categories contains any Clips before rendering a UI component. Parameters: - `categoryIds` - a list of category IDs to check for clips Return value: - Returns the total number of clips available in the specified categories, or 0 if Storyteller is not initialized or the categoryIds list is empty ``` # Ads ## Table of Contents + [Introduction](#introduction) + [Storyteller First Party Ads](#storyteller-first-party-ads) + [Storyteller GAM SDK](#storyteller-gam-sdk) ## Introduction The Storyteller SDK supports displaying ads that can be created in the Storyteller CMS (First Party Ads), as well as Ads from Google Ad Manager via an SDK extension developed by Storyteller and Ads from other sources via custom implementation provided by the integrator. Which source of ads is used can be configured on your behalf by a member of the Storyteller Delivery Team. ### Storyteller First Party Ads If your tenant is configured to use Storyteller First Party Ads, which can be managed in the Storyteller CMS, then no changes to the Storyteller integration code are necessary. The Ads code is managed entirely within the Storyteller SDK. ### Storyteller GAM SDK To use Ads from Google Ad Manager in Storyteller, first reach out to your Storyteller contact and they will assist you with setting up Google Ad Manager to traffic ads to Storyteller. You will then need to use the Storyteller Google Ad Manager SDK extension to fetch the ads from Google Ad Manager. To use this extension, first install it using Gradle. Ensure you have the following Maven repository added to your `settings.gradle`: ```groovy maven { url = uri("https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk") } ``` Then add the following reference to your version catalog file: ```toml storyteller-ads = { module = "Storyteller:ads", version.ref = "storyteller" } ``` And finally reference this in your `build.gradle`: ```groovy implementation(libs.storyteller.ads) ``` Now initialize the extension as follows: ```kotlin import com.storyteller.modules.ads.StorytellerGamModule import com.storyteller.domain.ads.entities.CustomNativeTemplateIds val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply { init( adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo -> "/33813572/storyteller" }, templateIds = CustomNativeTemplateIds("12102683", "12269089"), keyValuePairs = { emptyMap() }, ) } fun initializeStoryteller() { Storyteller.modules = listOf(storytellerGamModule) //initialize code } ``` You will need to supply the following parameters: | Parameter Name | Description | |-----------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `adUnit = { storytellerAdRequestInfo -> adUnitId` } | The lambda which returns desired `adUnitId`. This can be used for dynamic `adUnitId` changes depending on `storytellerAdRequestInfo` content. If you do not need dynamic adUnit changes simply put the static ad unit as a return value. | | `templateIds` | If you have worked with the Storyteller Delivery team to setup Custom Native Ads, you will need to supply their IDs here. If you are only using Stories (but not Clips) it is only necessary to supply one property of this struct. | | `keyValuePairs = { mapOfKeyValuePairs }` | The Storyteller GAM SDK passes a default set of KVPs to GAM to allow targeting based on the content of the Stories/Clips the user is viewing. If you have any additional parameters that you need to be able to target by, these should be passed here. Note that the SDK will not inherit any KVPs being set in the rest of your app. | Then pass the newly created instance of the extension to the `modules` property on the `Storyteller` instance: ```kotlin Storyteller.modules = listOf(storytellerGamModule) ``` Our Showcase app uses this module to integrate ads - the relevant code is available [here](https://github.com/getstoryteller/storyteller-showcase-android/blob/main/compose/app/src/main/java/com/getstoryteller/storytellershowcaseapp/data/StorytellerServiceImpl.kt#L92) Example for dynamic Ad Unit changes when we want to use different adUnit for Stories and Clips: ```kotlin import com.storyteller.modules.ads.StorytellerGamModule import com.storyteller.domain.ads.entities.CustomNativeTemplateIds val storytellerGamModule = StorytellerGamModule.getInstance(applicationContext).apply { init( adUnit = { storytellerAdRequestInfo: StorytellerAdRequestInfo -> when (storytellerAdRequestInfo) { is StorytellerAdRequestInfo.StoriesAdRequestInfo -> "/33813572/storyteller/stories" else -> "/33813572/storyteller/clips" } }, templateIds = StorytellerGoogleAdInfo.TEMPLATE_IDCustomNativeTemplateIds("12102683", "12269089"), keyValuePairs = { emptyMap() }, ) } fun initializeStoryteller() { Storyteller.modules = listOf(storytellerGamModule) //initialize code } ``` # Deep Linking The Storyteller SDK makes it possible to handle the deep links. The implementing app should follow the official [Android guideline](https://developer.android.com/training/app-links/deep-linking). In order to enable it separate intent filters need to be added to the implementing app's `AndroidManifest.xml`. ```xml ``` In the above, `[tenant_name]` is the lower case variant of your tenant's name. For example, if the tenant name is "MyTenant", then `[tenant_name]` should be "mytenant". Each link should be added as a separate intent-filter as in the example above. If Android system starting from Android 11 will fail to auto-verify any of the links which should be auto-verified it will disable the verification for all of them. This will result in the app not being able to handle any of the deep links in the Manifest. > Note: Please note that the last intent filter above for "[tenant_name]stories" does not have android:autoVerify="true" attribute. This is because the auto-verification is not supported for custom non https schemes. The deep link can be handled by using `Storyteller.openDeepLink` static method which opens the link. This can be done automatically by using `openDeepLink` or manually using Story or Page ID (`openStory` and `openPage` respectively). Example 1: ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) val data: Uri? = intent?.data if (data != null && Storyteller.isStorytellerDeepLink(data.toString()) { //open link automatically Storyteller.openDeepLink(this, data.toString()) } } ``` > Note: Starting from Android 6.0+, a Digital Asset Links file is required to properly handle `https` deep links. It can be generated inside the Storyteller CMS and > it requires you to add your app's package name and SHA-256 fingerprint of certificate the app was signed with. For more information on the Digital Asset Links file, see the [Android developer documentation](https://developer.android.com/studio/write/app-link-indexing#associatesite) To generate the Digital Asset Links file in the Storyteller CMS: 1. Navigate to the 'Apps' section on the left-hand side 2. Click on the 'New App' button 3. Fill out the 'Package Name' and 'SHA 256 Cert Fingerprint' - Package Name - this is the application ID declared in the app's `build.gradle` file. This value should be the same as the "App Store Identifier" entered above. E.g. `com.example.myapp.` - SHA 256 Cert Fingerprint - this is the SHA256 fingerprint of your app's signing certificate. You can use the following command to generate the fingerprint via the Java keytool: `keytool -list -v -keystore my-release-key.keystore`. Or, if using the Google Play Store Signing, the SHA256 signature can be downloaded using the Google Play Console: + Login to the Play Console + Select the app you want to configure + Go to Setup → App integrity + Copy the SHA-256 value under "App signing certificate" + This value should be entered into your Storyteller App's form without the "SHA-256: " prefix. E.g.`15:8D:E9:83:C5:73...` After saving your app, the Digital Asset Links file for your tenant can be viewed at the following URLs: `https://yourtenantname.ope.nstori.es/assetlinks.json` and `https://yourtenantname.ope.nstori.es/.well-known/assetlinks.json` **Note: the file may take up to 5 minutes to appear after saving your App.** ## Deep Link Handling ### Open Deep Link The `openDeepLink` function in the Storyteller SDK handles different types of deep links. There are 3 different ways in which it does this ```kotlin fun openDeepLink(activity: Activity, url: String, onError: ((Error) -> Unit)) ``` This call makes Storyteller open the provided deeplink (showing the requested Page/Story). The method returns `true` if the URL is a Storyteller deeplink Parameters: - `activity` - Activity. - `url` - this is the Deep Link URL. - `onError` - this is called when there is any issue with opening the deep link (e.g. the requested content is no longer available) #### Story Category - For story category deep links, the openDeepLink method identifies and processes links based on the following structure: - The link contains either `/open/category/` or `/go/category/` - The link includes a category identifier following the `/category/` part of the path - The category ID is extracted from the deep link and the `openCategory` function is called with the extracted category ID - eg: `"https://[tenantname].shar.estori.es/go/category/123456"` or `"https://[tenantname].shar.estori.es/open/category/123456"` #### Clip collection - For clip collection deep links, the openDeepLink method identifies and processes links based on the following structure: - The link contains either `/open/clip`, `/go/clip`, `/open/clips`, or `/go/clips` - The link includes a unique identifier (UUID) in the path - The link must include a `collectionId` query parameter - An optional `categoryId` query parameter can be included to specify a category to open in the Clip Collection as an initial category (`initialCategory` of the openCollection method) - The information extracted will be used to call `openCollection()` - eg: `"https://[tenantname].shar.estori.es/open/clip/((clipId))?collectionId=abcd1234?categoryId=123456"` #### Story Deep link - For story deep links, the openDeepLink method identifies and processes links based on the following structure: - The link is confirmed to be a URI object - It extracts storyId or pageId from the deep link via segment positions. One must be present - It proceeds to call `openPage()` or `openStory()` with either the storyId or pageId, - eg openStory: `https://[tenantname].shar.estori.es/story/((storyId))` or openPage: `https://[tenantname].shar.estori.es/page/((pageId))` #### Sheet - For sheet deep link, the openDeepLink method identifies and processes links based on the following structure: - The link contains either `/open/sheet/` or `/go/sheet/` - It extracts `sheetId` from the deep link via segment positions. One must be present - eg: `"https://[tenantname].shar.estori.es/go/sheet/123456"` or `"https://[tenantname].ope.nstori.es/open/sheet/123456"` ## Manual Deep Link Handling It is generally recommended to author your own deeplink and extract the required data. This is to not upset the current state and deeplink integration of your app and to allow for more control over the deeplink handling then use `openStory()`/`openPage()`/`openCollection()`/`openCategory()`/`openSheet()` to pass the relevant data to the SDK, see the [Open Player](https://docs.getstoryteller.com/android/OpenPlayer/) (or [AdditionalMethods](https://docs.getstoryteller.com/android/AdditionalMethods/)) documentation for more information. ### isStorytellerDeepLink `isStorytellerDeepLink(String)` checks if the string is a valid Storyteller deep link. ```kotlin Storyteller.isStorytellerDeepLink("stories://open/9329bed2-2311-69b8-cbcf-39fcf8d8af21/f6445df7-bd79-71de-cdfb-39fd071568a1") ``` # Navigating to App A `StorytellerDelegate` has method for managing in app navigation. It can be used for custom handling of `deeplink` URLs which can configured per page in CMS. This method will be called when user presses on the action button with the `deeplink` navigation type. ## How to Use To use global `userNavigatedToApp`, implement the `StorytellerDelegate` interface by overriding the required methods and set it in `Storyteller` object. `userNavigatedToApp(url: String)` is the method responsible for handling these `deeplink` URLs. ## Navigation to the Integrating App The callback `userNavigatedToApp` is called when a user taps on an action button on a page which has its link type set to `deeplink`. In this case, your app will be passed a URL which has been entered in the Storyteller CMS and your app is then responsible for parsing this URL and following it to the correct location within your app. Example: ```kotlin override fun userNavigatedToApp(url: String) { // parse the url and navigate to the destination } ``` # Open Player ## Open Stories This call opens a Story with a given ID. The call will only open that individual Story. ```kotlin fun openStory(activity: Activity, storyId: String? = null, onError: (Error) -> Unit = {}) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `storyId` - this is a Story's ID, if it is `null` then `onError` will be called - `onError` - this is called when there is an issue with opening a Story (e.g. the requested content is no longer available) ## Open Pages This call opens a Page with a given ID. The call will only open that individual Story containing that page. ```kotlin fun openPage(activity: Activity, pageId: String? = null, onError: (Error) -> Unit = {}) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `pageId` - this is a Page's ID, if it is `null` then `onError` will be called - `onError` - this is called when there is an issue with opening a Story (e.g. the requested content is no longer available) ## Open Collection ```kotlin fun openCollection( activity: Activity, configuration: StorytellerClipCollectionConfiguration, titleDrawable: Drawable? = null, onError: (Error) -> Unit = {} ) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `configuration` - this is a Clip Collection configuration, see below - `titleDrawable` - this is a Drawable that will be used as the title of the Player (optional) - `onError` - this is called when there is an issue with opening the Collection (e.g. the requested Collection is not available) `StorytellerClipCollectionConfiguration` parameters: - `collectionId` - this is a Clip Collection's ID (required) - `categoryId` - this is a Category ID (optional), if provided, this category will be opened, if the category is not available or not found it will be ignored - `clipId` - this is a Clip ID to open (optional) ## Open Category This call opens a Story category. If Story ID is not supplied, the first Story in the collection will be opened. ```kotlin fun openCategory( activity: Activity, category: String, storyId: String? = null, onError: (Error) -> Unit = {} ) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `category` - this is a Story category - `storyId` - this is a Story ID (optional) - `onError` - this is called when there is an issue with opening the Category (e.g. the requested Category is not available) ### openStoryByExternalId This call opens a Story by external ID. ```kotlin fun openStoryByExternalId( context: Activity, externalId: String? = null, onError: (Error) -> Unit = {} ) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `externalId` - external Id to open a Story - `onError` - this is called when there is an issue with opening the Category (e.g. the requested Category is not available) ### openCollectionByExternalId This call opens a Collection of Clips and shows Clip with external id. If Clip ID is not supplied, the first Clip in the collection will be opened. ```kotlin fun openCollectionByExternalId( activity: Activity, collectionId: String, externalId: String? = null, titleDrawable: Drawable? = null, onError: (Error) -> Unit = {} ) ``` Parameters: - `activity` - this is the Activity that will be used to launch the Storyteller Player - `collectionId` - this is a Clip Collection's ID - `externalId` - this is a Clip's external ID (optional) - `titleDrawable` - this is a Drawable that will be used as the title of the Player (optional) - `onError` - this is called when there is an issue with opening the Collection (e.g. the requested Collection is not available) # Implementing Storyteller Delegate Callbacks ## Table of Contents - [StorytellerDelegate](#storytellerdelegate) - [StorytellerListViewDelegate](#storytellerlistviewdelegate) A `StorytellerDelegate` has methods for managing `Storyteller` events. It is used as global object for handling all events when opening a Story Player with and without the `StorytellerStoriesRowView` or `StorytellerStoriesGridView`. Please see the dedicated `StorytellerListViewDelegate` below for handling events related to rows and grids. ## StorytellerDelegate To use global `StorytellerDelegate`, implement the `StorytellerDelegate` interface by overriding the required methods and set it in `Storyteller` object. Example: ```kotlin Storyteller.storytellerDelegate = myCustomStorytellerDelegate ``` ### onUserActivityOccured The `onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: UserActivityData)` method is is called when an analytics event is triggered. See the dedicated [Analytics](https://docs.getstoryteller.com/android/Analytics/) page for more information on analytic events. ### getAd The `getAd(adRequestInfo: StorytellerAdRequestInfo, onComplete: (StorytellerAd) -> Unit = {}, onError: () -> Unit)` method is called when the tenant is configured to request ads from the containing app and the SDK requires ad data from the containing app. For more information on how to supply ads to the Storyteller SDK, see the dedicated [Ads](https://docs.getstoryteller.com/android/Ads/) page. ### userNavigatedToApp The `userNavigatedToApp(url: String)` method is called when a user taps on an action button on a Page which should direct the user to a specific place within the integrating app. More information on `In App` links [Navigating to App](https://docs.getstoryteller.com/android/NavigatingToApp/). For more information on deep linking, see the dedicated [Deep Linking](https://docs.getstoryteller.com/android/DeepLinking/) page. ## Analytics The callback `onUserActivityOccurred` provides analytics events and corresponding data triggered internally by the SDK. This information can be used in your app. The following parameters are passed to the callback method: - `type` - type of event that occurred, as a `StorytellerUserActivity.EventType` enum - `data` - an object containing data about the event which occurred Example: ```kotlin ... override fun onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: UserActivityData) { if (type == StorytellerUserActivity.EventType.OPENED_STORY) { // Retrieve the story id value val openStoryId = data.storyId // Retrieve the story title value val openStoryTitle = data.storyTitle // Report retrieved values from your app } } ``` For a detailed discussion of all the relevant events and properties please see the dedicated [Analytics](https://docs.getstoryteller.com/android/Analytics/) page. ## Client Ads By implementing `getAd`, you can provide custom ad data for the SDK to render, this is only applicable when the ad configuration is set to `Integrating App` in the CMS. Ad data can be obtained asynchronously, and should be provided using the `onComplete` closure parameter. Example: ```kotlin ... override fun getAd(adRequestInfo: StorytellerAdRequestInfo, onComplete: (StorytellerAd) -> Unit = {}, onError: () -> Unit) { // Action to get some ads val ad = getMyAd() // Convert the ad to a StorytellerAd val storytellerAd = convertToStorytellrAd(ad) // Provide the ad to the SDK onComplete(ad) } ``` For a detailed discussion of all the relevant considerations, please see the dedicated [Ads](https://docs.getstoryteller.com/android/Ads/) page. ### configureWebView This method allows you to configure the WebView with custom settings or actions when the Storyteller SDK, is to display a WebView. It takes three parameters: the WebView to configure, an optional URL string, and an optional favicon Bitmap. This method is called from the `onPageStarted` method of a custom `WebViewClient` when a new page starts loading in the WebView. #### Parameters - `view: WebView` - The WebView instance to be configured. - `url: String?` - (Optional) The URL string associated with the WebView page that is being loaded. - `favicon: Bitmap?` - (Optional) The favicon Bitmap to be displayed for the WebView page. #### Example Usage To intercept and customize WebViews created by the Storyteller SDK, you can implement your own version of the `configureWebView` method. Here is an example of how to do this: ```kotlin override fun configureWebView( view: WebView, url: String?, favicon: Bitmap? ) { // Your custom configuration code goes here // The following line shows a simple alert in the WebView with the message "test webview javascript". view.evaluateJavascript("javascript: alert('test webview javascript');", null) } ``` ## Followable Categories The method `fun categoryFollowActionTaken(category: Category, isFollowing: Boolean)` is invoked when a user adds or removes a category from within SDK's UI. The callback method receives the following parameters: - `category` - An object representing the clip category - `isFollowing` - A boolean value indicating whether the user is following or unfollowing the specified category ### customScreenForCategory The `fun customScreenForCategory(): @Composable (StorytellerFollowableCategoryCustomScreen) -> Unit` method is called when a user navigates to a followable category screen. The callback receives a `StorytellerFollowableCategoryCustomScreen` object. with the following parameters: - `pendingModifier` - a `Modifier` object that is used to apply custom styling to the screen, this must be applied to the root composable of your custom screen - `category` - a `Category` object that is the category that the user is navigating to - `onBackClicked` - a `() -> Unit` a callback that should be called when the user clicks the back button from the custom screen, this is to let Storyteller know that the user has navigated back to the previous screen. If this is not called, the user will be stuck on the custom screen and unable to navigate back to the previous screen. Example: ```kotlin override fun customScreenForCategory(): @Composable() ((StorytellerFollowableCategoryCustomScreen) -> Unit) { return { (pendingModifier, category, onBackClicked) -> Scaffold( modifier = pendingModifier, topBar = { CenterAlignedTopAppBar( title = { Text("${category.displayTitle}") }, navigationIcon = { IconButton(onClick = onBackClicked) { Icon( imageVector = Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back" ) } } ) } ) { paddingValues -> Surface( modifier = Modifier.padding(paddingValues) ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.Center ) { Text("Custom layout for ${category.displayTitle}") } } } } } ``` Alongside the `customScreenForCategory` callback, the `Storyteller` object also has a `useCustomScreenForCategory` property that is a `(Category) -> Boolean` callback. This is to allow the developer to conditionally show the custom screen for a specific category or default to the original implementation of Followable Categories by Storyteller. The callback must return a `Boolean` value, which determines whether the custom screen should be shown for the given category. Example: ```kotlin Storyteller.useCustomScreenForCategory = { if (it.type == "custom") { // return true if you want to show the custom followable category screen true } else { // return false if you want to show the default followable category screen false } } ``` The `customScreenForCategory` will only be called if the `useCustomScreenForCategory` callback returns `true` for the given category. Otherwise, if it's not set or returns `false`, the default implementation of the Followable Categories by Storyteller will be used. ### bottomSheetScreen The `fun bottomSheetScreen(urlProvider: () -> String, onDismiss: () -> Unit): @Composable () -> Unit` method is called when the deep link includes the query parameter `shouldUseModal=true`. - `urlProvider` A callback that provides the URL to be loaded in the WebView or used for other purposes. - `onDismiss` A callback invoked when the user dismisses the bottom sheet. Example: ```kotlin override fun bottomSheetScreen( urlProvider: () -> String, onDismiss: () -> Unit ): @Composable (() -> Unit) { return { DemoBottomSheet(urlProvider, onDismiss) } } ``` ## StorytellerListViewDelegate A `StorytellerListViewDelegate` has methods for managing `StorytellerStoriesRowView` and `StorytellerStoriesGridView` events. ### How to Use To use `StorytellerListViewDelegate`, implement the `StorytellerListViewDelegate` interface and override the required methods: #### onDataLoadStarted The `onDataLoadStarted()` method is called when the network request to load data for all Stories has started. #### onDataLoadComplete The `onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int)` method is called when the data loading network request is complete. | Property | Description | | --------------------- | ------------------------------------------------------- | | `success` | This confirms whether or not the request was successful | | `error` | The HTTP error if the request was not successful | | `dataCount` | The number of Stories loaded | Example: ```kotlin val StorytellerStoriesRowView = StorytellerStoriesRowView() StorytellerStoriesRowView.delegate = myDelegate ``` #### Error Handling By using the callback function `onDataLoadComplete` and the data it provides, you can handle the current state of the `StorytellerStoriesRowView` appropriately in your app. > Note: `dataCount` is the total number of Stories in the existing `StorytellerStoriesRowView` at any given time Example: ```kotlin ... override fun onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int) { if (success) { // stories data has been loaded successfully // dataCount is the current total number of stories, including newly added/removed data } else if (error != null) { // an error has occurred, unwrap the error value for more information // dataCount is the total number of stories before loading began } } ``` Another example: ```kotlin ... override fun onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int) { if (error != null && dataCount == 0) { // stories have failed to load with error and there is no data to show // you may wish to hide the `StorytellerStoriesRowView` instance here StorytellerStoriesRowView.visibility = View.GONE } } ``` # StorytellerClipsFragment `StorytellerClipsFragment` is a fragment that can be used to embed a clip in your own activities. Like a regular Android Fragment, it can be configured from an XML layout or instantiated programmatically and attached to the host's (another fragment or activity) FragmentManager via a fragment transaction. > Clips are delivered in a 9:16 aspect ratio. While `StorytellerClipsFragment` will do content > cropping in a way that all given space is taken by the content (all area is consumed, cropping is > applied to avoid content distortion). If you want to present content without > cropping, `StorytellerClipsFragment` should reside > in a container that maintains a 9:16 aspect ratio. ## Using StorytellerClipsFragment from xml layout `StorytellerClipsFragment`can be used directly from the layout XML files. The usage is identical to typical Android fragments. You need to specify the fully qualified fragment class name in the android:name property and the collection ID using app:storyteller_collection_id_property. Note that in this example, a constraint is applied to the dimensions' proportions so the clip content is not cropped at all. app:storyteller_initial_category is optional and can be used to set the initial category of the collection to be viewed. If this is used. The clip will be first loaded with the collection. The selected category will then be navigated to automatically. ```xml ``` ## Using StorytellerClipsFragment programmatically `StorytellerClipsFragment` can also be used programmatically. To embed a clip fragment programmatically, you need to follow these steps: 1. Create a new fragment instance using the `StorytellerClipsFragment.create(collectionId: String)` method. a. Optionally, you can also set the `initialCategory` property to a string value to automatically navigate to a selected category. 2. Create a fragment transaction that would add this fragment to the fragment container view. 3. Commit the created transaction. See the following snippet illustrating attaching a fragment in the `Activity.onCreate` method: ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // An example using viewBinding feature; see view binding for reference. val binding = ActivityClipFragmentHostBinding.inflate(layoutInflater) setContentView(binding.root) val storytellerClipsFragment = StorytellerClipsFragment.create("yourCollectionId") val transaction = supportFragmentManager.beginTransaction() transaction.add( binding.fragmentHost.id, storytellerClipsFragment ) transaction.commit() } ``` ## Controlling playback in StorytellerClipsFragment `StorytellerClipsFragment` by default starts playback as soon as it is attached to the host and visible. Standard Android Fragment lifecycle events typically handle automatic pausing and resuming (e.g., when the app goes to the background), complementing the manual control provided by the `shouldPlay` property. ```kotlin // finding the fragment by id using the fragment manager val storytellerClipsFragment = supportFragmentManager.findFragmentById(binding.fragmentHost.id) as StorytellerClipsFragment storytellerClipsFragment.shouldPlay = false // stops playback storytellerClipsFragment.shouldPlay = true // starts playback ``` `StorytellerClipsFragment` also contains `canGoBack:Boolean` property which can be used to check if the fragment can go back from the current Category or is it at the top level. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create(collection = "yourCollectionId", topLevelBackEnabled = true) val canGoBack = storytellerClipsFragment.canGoBack ``` `StorytellerClipsFragment` also contains `listener` property which can be used to control playback and handle top level back button press. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create(collection = "yourCollectionId", topLevelBackEnabled = true) storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener { override fun onTopLevelBackPressed(): Boolean { // will be invoked when top level back button is pressed return true // true if fragment needs to be stopped / host should handle back } override fun onDataLoadStarted() { // will be invoked when data load starts } override fun onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int){ // will be invoked when data load completes or fails } } storytellerClipsFragment.reloadData() ``` ## External back button handling `StorytellerClipsFragment` does not show the top level back button by default. You can control the behaviour by setting `topLevelBackEnabled` property and setting `onTopLevelBackPressed` according to your needs. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create(collection = "yourCollectionId", topLevelBackEnabled = true) storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener { override fun onTopLevelBackPressed(): Boolean { // will be invoked when top level back button is pressed return true // true if fragment needs to be stopped } } ``` `StorytellerClipsFragment` also contains `canGoBack:Boolean` property which can be used to check if the fragment can go back from the current Category or is it at the top level. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create(collection = "yourCollectionId", topLevelBackEnabled = true) val canGoBack = storytellerClipsFragment.canGoBack ``` ## Initial Category To set the `StorytellerClipsFragment` to start with a specific category, you can set the `initialCategory` property. Programmatical Usage ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create(collection = "yourCollectionId", initialCategory = "yourCategory") ``` XML Usage ```xml ``` If the category is found in the collection, the clip will be loaded with the collection and the selected category. The category will be navigated to automatically. If the category is not found in the collection or is invalid, it will be ignored. ## Reload Data `StorytellerClipsFragment` in order to reload data you can call `reloadData()` method. This method will make a request to the backend to fetch the latest data. If this method is called when there are category filters applied, then it will go back one level. If there are no category filters applied this method will reload data. Loading state can be observed by setting `listener` property and overriding it's `onDataLoadStart` and `onDataLoadComplete` methods. ```kotlin val storytellerClipsFragment = StorytellerClipsFragment.create(collection = "yourCollectionId", topLevelBackEnabled = true) storytellerClipsFragment.listener = object : StorytellerClipsFragment.Listener { override fun onTopLevelBackPressed(): Boolean { // will be invoked when top level back button is pressed return true // true if fragment needs to be stopped / host should handle back } override fun onDataLoadStarted() { // will be invoked when data load starts } override fun onDataLoadComplete(success: Boolean, error: Error?, dataCount: Int){ // will be invoked when data load completes or fails } } storytellerClipsFragment.reloadData() ``` ## Inset Management One of the usecase of `StorytellerClipsFragment` is to embed it in a view that is laid behind status bar. While that will increase size of the screen available for the content, this arrangement can potentially causing the clip title and other UI elements to be obscured by the status bar. To avoid this problem you can set `StorytellerClipsFragment.topInset` property to the height of the status bar. This will cause the fragment to offset the title and the top buttons by the given amount of pixels. Similarly if the bottom part of the view is obscured by the Navbar, you can set `StorytellerClipsFragment.bottomInset` property to the height of the navigation bar. The recommended approach is to use this property together with `onApplyWindowInsets` callback. ```kotlin ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, winInsets -> val inset = winInsets.getInsets(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars()) storytellerClipsFragment.topInset = inset.top storytellerClipsFragment.bottomInset = inset.bottom WindowInsetsCompat.CONSUMED } ``` ## Compose Integration `StorytellerClipsFragment` can be used in Jetpack Compose using the `StorytellerEmbeddedClips` composable. ```kotlin class DemoComposeEmbeddedClipsActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val collection = intent.getStringExtra("collection") ?: "" val widthPercentage = intent.getIntExtra("width", 100) / 100F val heightPercentage = intent.getIntExtra("height", 80) / 100F val topLevelBack = intent.getBooleanExtra("topLevelBack", false) val initialCategory = intent.getStringExtra("category") ?: "" enableEdgeToEdge() setContent { val state = rememberStorytellerEmbeddedClipsState(collectionId = collection, topLevelBack = topLevelBack, initialCategory = initialCategory) StorytellerEmbeddedClips( modifier = Modifier .fillMaxWidth(widthPercentage) .fillMaxHeight(heightPercentage), state = state, ) } } companion object { fun start(context: Context, collection: String, category: String?, width: Int, height: Int, topLevelBack: Boolean) { // add params to intent Intent(context, DemoComposeEmbeddedClipsActivity::class.java).apply { putExtra("collection", collection) putExtra("category", intialCategory) putExtra("width", width) putExtra("height", height) putExtra("topLevelBack", topLevelBack) context.startActivity(this) } } } } ``` ### StorytellerEmbeddedClipsState `rememberStorytellerEmbeddedClipsState` is a composable function that creates a `StorytellerEmbeddedClipsState` object that holds the state of the `StorytellerEmbeddedClips` composable. ```kotlin val state = rememberStorytellerEmbeddedClipsState(collectionId = "collection", topLevelBack = topLevelBack) StorytellerEmbeddedClips( modifier = Modifier, state = state ) ``` `StorytellerEmbeddedClipsState` contains `canGoBack` property that can be used to check if the fragment can go back from the current Category or is it at the top level. ```kotlin val state = rememberStorytellerEmbeddedClipsState(collectionId = "collection", topLevelBack = topLevelBack) val canGoBack = state.canGoBack // true if user can navigate back ``` `StorytellerEmbeddedClipsState` contains `goBack()` which will move the content to previous Category if the user is not at the top level. ```kotlin val state = rememberStorytellerEmbeddedClipsState(collectionId = collection, topLevelBack = topLevelBack) state.goBack() // navigate to previous category programmatically ``` # Custom Themes Overview The Storyteller SDK's appearance can be customized by setting the `Storyteller.theme` global property or the `StorytellerListView.theme` view-specific property. This is done using a `UiTheme` configuration object, preferably created with the `buildTheme` DSL. ## Defining a Theme with `buildTheme` The `buildTheme` method provides a DSL to define theme properties. It supports `light` and `dark` variants. You can define properties for the `light` theme and then easily apply them to the `dark` theme using `dark from light`. ```kotlin import com.storyteller.domain.theme.builders.buildTheme import com.storyteller.domain.theme.builders.ofHexCode import com.storyteller.domain.theme.builders.fontRes // For font resources import com.storyteller.domain.theme.builders.drawableRes // For drawable resources Storyteller.theme = buildTheme { light { // Configure the light theme colors.primary = ofHexCode("#1A2B3C") // Example: Set primary color font = fontRes(R.font.your_custom_font_family) // Example: Set a custom font // ... other light theme customizations } dark from light // Apply light theme settings to dark, then override if needed // dark.colors.primary = ofHexCode("#D3E4F5") // Example: Override primary for dark } ``` **Key Points for `buildTheme`:** - Set the global theme (`Storyteller.theme`) before any Storyteller list views are inflated. - Theme properties are optional; the SDK provides default values. - Use `light` and `dark` blocks to specify theme variants. - The active theme variant is chosen based on the current system UI mode and the `StorytellerListView.uiStyle` property. ## What Can Be Themed? The `UiTheme` object allows customization of various aspects, broadly categorized into: - **Colors**: Base colors for UI elements (primary, success, alert, white/black shades for text). - Set with `ofHexCode("#RRGGBB")` or Android `@ColorInt`. - **Font**: A global custom font for the UI (requires a font family XML resource). - Set with `fontRes(R.font.your_font_family)`. - **Primitives**: Base values like `cornerRadius`. - **Lists**: Appearance of Story/Clip rows and grids (spacing, insets, background). - **Story and Clip Tiles**: Appearance of individual tiles (chips, titles, indicators, borders for both circular and rectangular tiles). - **Player**: Properties of the Story/Clip player (icons, buttons, timestamps, live indicators). - **Buttons**: General button appearance (background, text color, corner radius). - **Instructions Screen**: Appearance of the initial instructions screen. - **Engagement Units**: Styling for Polls and Quizzes (answer colors, result bars). - **Search**: Customizations for the Search component. - **Home**: Styling for elements within the Storyteller Home feature. Many specific properties exist within these categories to allow fine-grained control. Refer to the detailed `UiTheme` structure or the full `Themes.md` documentation for an exhaustive list if deeper customization is needed. ### Setting Specific Property Types - **Colors**: Use `ofHexCode("#RRGGBB")` or any Android `@ColorInt`. ```kotlin light.colors.primary = ofHexCode("#FF00FF") ``` - **Drawables**: For properties expecting a `StorytellerDrawable`, use `drawableRes(R.drawable.your_drawable)`. ```kotlin light.player.icons.close = drawableRes(R.drawable.custom_close_icon) ``` - **Fonts**: Define a font family XML (e.g., `res/font/custom_font.xml`) and set it using `fontRes(R.font.custom_font)`. ```xml ``` ```kotlin light.font = fontRes(R.font.custom_font) ``` This summary provides the core information needed to start theming the Storyteller SDK. For detailed property names and advanced configurations, consult the full `Themes.md` documentation. # Analytics Overview The Storyteller SDK provides a robust analytics system, triggering various events based on user interactions. These events can be captured by implementing the `StorytellerDelegate`. ## Listening to Analytics Events To receive analytics events, you need to set a `StorytellerDelegate` on the `Storyteller` object. The primary method for receiving these events is `onUserActivityOccurred`. ```kotlin // Example of setting up the delegate (typically in your Application class or where Storyteller is initialized) Storyteller.storytellerDelegate = object : StorytellerDelegate { // ... other delegate methods override fun onUserActivityOccurred(type: StorytellerUserActivity.EventType, data: UserActivityData) { // Handle the analytics event here // 'type' is an enum member of StorytellerUserActivity.EventType // 'data' is a UserActivityData object containing details about the event when (type) { StorytellerUserActivity.EventType.OPENED_STORY -> { val storyId = data.storyId // Process Opened Story event } StorytellerUserActivity.EventType.OPENED_CLIP -> { val clipId = data.clipId // Process Opened Clip event } // ... handle other event types as needed else -> { // Optional: handle other events or log them } } } // ... other delegate methods } ``` ## Event Categories The SDK triggers a wide range of events, which can be broadly categorized as: - **Story Events**: Interactions with Stories, such as `OPENED_STORY`, `OPENED_PAGE`, `DISMISSED_STORY`, `SKIPPED_STORY`, `COMPLETED_PAGE`, `VOTED_POLL`, `TRIVIA_QUIZ_COMPLETED`, etc. - **Clip Events**: Interactions with Clips, including `OPENED_CLIP`, `DISMISSED_CLIP`, `NEXT_CLIP`, `LIKED_CLIP`, `FOLLOW_CATEGORY`, `ENABLE_CAPTIONS`, etc. - **Sheet Events**: Events related to opening and dismissing Sheets, like `OPENED_SHEET`, `DISMISSED_SHEET`. - **Ad Events**: Events specifically for ad playback within Stories or Clips, such as `OPENED_AD`, `DISMISSED_AD`, `SKIPPED_AD`, `AD_ACTION_BUTTON_TAPPED`, and quartile view events (`VIEWED_AD_PAGE_FIRST_QUARTILE`, etc.). - **Playback Events**: Events related to media playback status, primarily for video content, like `READY_TO_PLAY`, `MEDIA_STARTED`, `BUFFERING_STARTED`, `BUFFERING_ENDED`. - **Search Events**: User interactions within the Storyteller search feature, including `OPENED_SEARCH`, `PERFORMED_SEARCH`, `USED_SUGGESTION`. ## Event Data (`UserActivityData`) Each event callback provides a `type` (`StorytellerUserActivity.EventType`) and a `data` object (`UserActivityData`). The `UserActivityData` class contains numerous optional properties that provide context about the event. Common properties include: - `storyId`, `storyTitle`, `storyIndex`, `storyPageCount` - `pageId`, `pageIndex`, `pageType` - `clipId`, `clipTitle`, `clipIndex` - `collection`, `categories`, `currentCategory` - `openedReason`, `dismissedReason` - `durationViewed`, `pagesViewedCount`, `loopsViewed` - Ad-specific data like `adId`, `advertiserName`, `adType`, `adPlacement` - And many more specific to certain events (e.g., `pollAnswerId`, `triviaQuizScore`, `searchTerm`). Refer to the full `Analytics.md` documentation for a complete list of all event types and the specific data properties available for each event if detailed tracking is required.
### Editor-Specific Instructions #### Cursor Cursor includes the ability to add project-specific documentation: 1. Open your Storyteller SDK project in Cursor 2. Use the `/file` command and reference `public-docs/llms.txt` to add it to your chat context: ``` /file public-docs/llms.txt ``` 3. Ask questions about Storyteller SDK implementation #### GitHub Copilot Add the documentation to your project context: 1. Create a `.github/copilot/` directory in your project 2. Copy the content from `public-docs/llms.txt` to a new file named `storyteller-sdk-docs.md` in that directory 3. Ask Copilot questions about implementing Storyteller #### Zed In Zed, you can: 1. Use the `/file` command to add the documentation to your chat: ``` /file public-docs/llms.txt ``` 2. Reference the documentation in your queries #### Claude, ChatGPT, and Other Web-Based AIs For web-based AI tools, you can: 1. Copy the content from `public-docs/llms.txt` 2. Paste it at the beginning of your conversation or chat session 3. Preface your prompts with instructions to consider this documentation ## Example Prompts Here are some effective prompts to use with AI assistants when implementing Storyteller SDK: 1. "Using the Storyteller SDK documentation, help me initialize SDK in my Kotlin Android app." 2. "Based on the Storyteller documentation, what's the best way to implement a StorytellerStoriesRow composable in my app?" 3. "Help me troubleshoot this initialization error with Storyteller SDK: [paste your error]" 4. "Using the Storyteller docs context, show me how to customize the theme of my StorytellerStoriesRow" 5. "With the Storyteller documentation, help me implement analytics tracking for story views." ## Additional Resources - [Storyteller Showcase App](https://github.com/getstoryteller/storyteller-showcase-android) ## Source: Changelog.md # Changelog ## **11.0.4** - 26/11/25 ### Bug Fixes - Fixed Embedded Clips backgrounding the app when reload button is double-tapped in navigated category - Fixed memory leaks - Fixed action buttons not opening Clip Player properly - Fixed tile animations not working after Story and Clip Player is closed in More Screen - Fixed Cards not sorting by taps correctly - Fixed clickable area for image Ads in Native Ad Clips where tapping the title area incorrectly triggered the action button ## **11.0.3** - 21/11/25 ### Bug Fixes - Fixed action buttons on custom native ads not responding to taps or swipe-up gestures - Fixed external links intermittently failing to open in Android WebView - Prevented the screen from dimming or turning off while watching For You/Following content ## **11.0.2** - 13/11/25 ### Bug Fixes - Fixed issue where standard video ads would freeze after navigating between custom image ads on Story pages ### Improvements - Moved mute button to right-hand rail in Clips player for improved UI consistency - Updated action button widths to follow industry standards - buttons no longer span full width. Button width type is now configurable via CMS and received from backend, allowing per-action customization ## **11.0.1** - 29/10/25 ### Bug Fixes - Fix potential crash which could occur when presenting Native Ads ## **11.0.0** - 22/10/25 ### Breaking Changes - **Standardized public API with `Storyteller` prefix**: All public classes now include the `Storyteller` prefix for consistency. Key renamings include: + `UserInput` → `StorytellerUserInput` + `UserActivity` → `StorytellerUserActivity` + `Category` → `StorytellerCategory` + `Error` → `StorytellerError` + `Environment` → `StorytellerEnvironment` + `DisabledFunctionalFeature` → `StorytellerDisabledFunctionalFeature` + `UserAttributes` → `StorytellerUserAttributes` + `CustomNativeTemplateIds` → `StorytellerCustomNativeTemplateIds` + `TileType` → `StorytellerTileType` + `FollowableCategoryCustomScreen` → `StorytellerFollowableCategoryCustomScreen` - `eventTrackingOptions` can now only be set during SDK initialization by passing a `StorytellerEventTrackingOptions` object to the `initialize` method. Runtime modifications are no longer supported. The property remains publicly readable via `Storyteller.eventTrackingOptions`. To change tracking options, you must reinitialize the SDK. See [Privacy and Tracking](PrivacyAndTracking.md) for more information - Removed deprecated `Storyteller.currentUserId` method for VPPA compliance - Removed `showVoteCount` property from `EngagementTheme` for Polls - Removed `show` property from `TitleTheme` - Removed default `isScrollable` parameter from `StorytellerClipsGrid` and `StorytellerStoriesGrid` - Removed all VPPA-related deprecated methods ### New Features - Added support for custom `context` data in analytics events. Context allows integrators to provide additional analytics attribution data for Storyteller rows, grids, cards, home screens, and embedded clips. For details, see [Context](Analytics.md#context). - Added **Progress Bar for Clips Player** with scrubbing/seeking functionality and analytics events (`scrubbedClip`, completion type tracking) - Added **Stories Captions** support with CC button, enabling/disabling events, and global state persistence - Added **Configurable Default Mute State** with persistent mute preferences - Added **Buttons for Cards** with display, theming, and analytics support - Added **Long Engagement Unit Answers** supporting multi-line text display in Polls and Quizzes - Enhanced **Following** behavior with new public API methods and conditional logic based on `followingFrom` setting - Added automatic user attribute management for followed categories where Storyteller is handling all following logic, without the need for storing and syncing followed categories app-side. See [Following Model](Users.md#storyteller-managed-following) for more information - Added **Card Viewed/Tapped Ordering** for improved card content organization - Added `actionLinkId` property to analytics events with page/clip actions - Added story/clip context properties to ad analytics events - Added `adFormat` support for First-Party ads in Stories - Added `completionType` property to `finishedClip` and `completedLoop` events - Added `captionsEnabled` property to all Story analytics events - Added `hasButton` property to all Card analytics events - Added native back button support for webview navigation ### Bug Fixes - Fixed analytics event issues including missing properties and incorrect triggers - Fixed `openedCategory` event not recorded when navigating back from another category - Fixed `finishedClip` event not sent when switching between For You and Following tabs - Fixed Story page eyebrow and timestamp overlap - Fixed action buttons not being interactable on tablets - Fixed captions not displaying correctly for long text - Fixed Story player dismissing automatically when opened from More screen on Compose - Fixed embedded clips top-level back button not working - Fixed follow state not syncing between Clips page and Followable Category page - Fixed `canGoBack` always returning false when navigating subcategories on Following tab - Fixed theme not applying to rows/grids on bottom sheet - Fixed numerous For You/Following tab navigation and reload issues - Fixed clip ads sometimes switching shortly after appearing - Fixed various UI issues with Stories, Clips, and Ads players ### Improvements - Improved accessibility support for large font sizes - Enhanced ad preloading for Clips - Improved theme handling and persistence - Updated public interface standardization across iOS and Android platforms ## **10.13.10** - 02/10/25 ### Breaking Changes - Added new parameter `offset` to `StorytellerClipsDataModel`, `StorytellerStoriesDataModel`, `ListConfiguration` see [StorytellerListViews.md](StorytellerListViews.md) and [StorytellerLists.md](StorytellerLists.md) ### Bug Fixes - Fixed tiles reload behavior - Fixed Clips duplications - Disabled custom params when ad tracking is disabled ## **10.13.9** - 24/09/25 ### Bug Fixes - Fixed tiles not auto-reloading properly - Fixed audio focus not being requested when unmuting - Fixed incorrect grid display limitations - Fixed text font styling issues in Clip player ## **10.13.8** - 09/09/25 ### Bug Fixes - Fixed double calling of `onDataLoadCompleted` callback - Fixed placeholders not hiding when item count is 0 - Fixed `inApp` action not opening properly - Fixed webView opening issues ## **10.13.7** - 27/08/25 ### Bug Fixes - Fixed an issue with `onDataLoadCompleted` returning incorrect data count ## **10.13.6** - 25/08/25 ### Bug Fixes - Fixed crashes when theme is null in Embedded Clips ## **10.13.5** - 18/08/25 ### Improvements - Improved support for privacy flags ### Bug Fixes - Fixed crashes when swiping through stories ## **10.13.3** - 14/08/25 ### Improvements - Improved support for privacy flags ## **10.13.2** - 11/08/25 ### Bug Fixes - Fixed black screen appearing randomly when scrolling fast between Clips - Fixed order of navigation elements for category navigation inside Clips - Fixed Clip looping issue when swiping to next clip during PTR animation - Fixed `OpenedClip` event issues - Fixed `DismissedClip` event issues - Fixed `OpenedCategory` event not being recorded when opening Clips Player via category methods - Fixed `DismissedCategory` event missing properties when returning using back button - Fixed `FinishedClip` event issues - Fixed Banner/Native Ad action triggering when fast skipping pages - Fixed mute state issues with device volume changes after muting from category navigation - Fixed `collectionClipCount` always being 0 for Muted/unmuted events in FY/F collections - Fixed Title and Eyebrow alignment when Mute toggle enabled - Fixed like status/share number not resetting after PTR or Reload if staying on same clip - Fixed duplicate Clip Ad requests for the initial index - Fixed missing `adIndex` property on ad events - Fixed followable category screen when opening it on tablet landscape - Fixed share/like issues when respective features disabled - Fixed crashes when swiping through stories - Fixed `skippedAd` and `finishedAd` events triggered without ad display when skipping clips quickly - Fixed followable category screen appearing on Clip Ads - Fixed Share and Action Buttons shifting downwards when swiping through Stories - Fixed Eyebrow text positioning with different theme settings - Fixed category title ellipsis display on Clip player - Fixed `DismissPlayer` event timing issues - Fixed crashes when opening `StorytellerEmbeddedClipsFragment` (XML) ### New Features - Added `adIndex` KVP support for Stories and Clips when requesting Ads ### Improvements - Updated FollowableCategory Status request with per-category granularity ## **10.13.1** - 07/08/25 ### Bug Fixes - Fixed if search is remotely disabled in the CMS, the search button still appears on Clips ## **10.13.0** - 30/07/25 ### Breaking Changes - Added `onTileTapped` callback to `StorytellerListViewDelegate` for enhanced tile interaction tracking, see [StorytellerDelegates](StorytellerDelegates.md) - Relaxed public `openDeepLink` method activity requirement from `AppCompatActivity` to `Activity` for better compatibility ### New Features - **Clips Player is now rewritten fully in Compose for better performance and user experience** - **Added support for For You/Following tabs for Clip Rows and Grids** - Added Mute Toggle functionality for Stories and Clips with comprehensive analytics see [Analytics](Analytics.md): + Added muted/unmuted events for Stories, Clips, and Ads + Added `isMuted` property to all Story, Clip, and Ad analytics events - Added Followable Category Limits feature with analytics events, see [Analytics](Analytics.md): + Added Followable Category Limit Shown event + Added Followable Category Limit Action Tapped event + Added Followable Category Limit Dismissed event - Added Story Eyebrow and Clip Eyebrow support - Added Independent Configuration of Analytics Features, see [PrivacyAndTracking](PrivacyAndTracking.md) - Enhanced Ad Placement Algorithms for Stories and Clips - Added `metadata` filed to analytic events ### Bug Fixes - Fixed multiple issues related to previous Clip Player implementation based on Android Views - Fixed multiple issues related to For You/Following Tabs navigation and behaviour - Fixed multiple issues related to the Clips playback - Fixed Story/Clips player action buttons to properly listen to `theme.buttons.textCase` configuration ## **10.12.1** - 10/07/25 ### Breaking Changes - Updated Kotlin version from `1.7.20` to `1.9.0` ## **10.12.0** - 30/06/25 ### New Features - Added new option to Event Tracking Options called `enableRemoteViewingStore` for VPPA compliance. See [PrivacyAndTracking](PrivacyAndTracking.md) for details. ### Improvements - Cards are now using `Activity` instead of `AppCompatActivity` ### Bug Fixes - Fixed Cards video background freezing when backgrounding and foregrounding - Fixed issue with Storyteller Lists height - Fixed Sheets being dismissed automatically when rotating the device on tablet - Fixed handling of `intent://` redirects inside the internal WebView - Fixed crash when opening Clips with Secondary Actions - Fixed CardViewed events not triggering for Cards with image backgrounds - Fixed rare crash in GAM module for Custom Native Ads ## **10.11.0** - 16/06/25 ### Breaking Changes - Implemented VPPA compliance measures including SHA256 hashing for User IDs for requests and removal of Content Id from ad requests. See [PrivacyAndTracking](PrivacyAndTracking.md) for details. - Deprecated `Storyteller.currentUserId` method as part of privacy enhancements. See [Users](Users.md) for updated user management methods. - Added `enableFullVideoAnalytics` flag to `eventTrackingOptions` for granular video analytics control. See [Analytics](Analytics.md) for details. ### New Features - Introduced **Cards** feature - a new content type that can be displayed alongside Stories and Clips with full theming support, personalization, targeting, and analytics. See [Cards](Cards.md) for event details. - Added **Secondary Actions** support for both Stories and Clips, allowing multiple action buttons per content item. See [Analytics](Analytics.md) for additional fields added. - Cards support video playback based on visibility and include comprehensive analytics events (`Card Tapped`, `Card Viewed`). See [Analytics](Analytics.md) for event details. - Enhanced analytics with new properties for Secondary Actions and Cards integration. See [Analytics](Analytics.md) for complete event documentation. ### Improvements - Added methods to retrieve count of clips in a collection and number of Stories in categories. See [AdditionalMethods](AdditionalMethods.md) for details. - Enhanced Embedded Clips to support their own custom themes. See [StorytellerClipsFragment](StorytellerClipsFragment.md) and [Themes](Themes.md) for details. - Added analytics event for 'Story Instructions Screen Viewed'. See [Analytics](Analytics.md) for details. - Improved poll vote count display with per-poll configuration support - Added support for custom tab titles in For You/Following tabs. Configurable via CMS - Improved VPPA compliance by removing direct user identifiers from various API requests. See [PrivacyAndTracking](PrivacyAndTracking.md) for details. ### Bug Fixes - Fixed live story progress bar jumping issues on single-page stories - Resolved clip playback getting stuck when navigating between categories - Fixed search functionality issues including keyboard behavior and story navigation. See [Search](Search.md) for details. - Corrected chip positioning for live clips and ads - Fixed deep linking issues for clips and stories. See [DeepLinking](DeepLinking.md) for details. - Improved pull-to-refresh behavior across different screens - Fixed tablet-specific layout issues including timestamp positioning - Resolved various analytics event property issues. See [Analytics](Analytics.md) for updated event documentation. ## **10.9.5** - 29/05/25 ### Bug Fixes - Fixed an issue where external app links were not opened correctly with ST internal WebView - Fixed an issue where edge to edge layouts were not correctly supported on Android 15+ ## **10.9.4** - 24/04/25 ### Bug Fixes - Fixed an issue with `onDataLoadCompleted` returning incorrect data count - Fixed an issue where disabling `theme.tiles.title.show` property could cause Round Storyteller Lists to disappear ## **10.9.3** - 18/04/25 ### Bug Fixes - Fixed an additional issue with opening a deeplink using externalId ## **10.9.2** - 15/04/25 ### Bug Fixes - fixed open a deeplink with externalId ## **10.9.1** - 10/04/25 ### Improvements - added `collectionClipCount` property to Clip Ad analytics events to reflect the total number of Clips in the Collection, see [Analytics](Analytics.md) for more details ### Bug Fixes - updated the Constraint Layout dependencies to avoid NoMethodFoundError crashes ## **10.9.0** - 6/04/25 ### Breaking Changes - Compose BoM dependency updated to `2024.12.01` - The `openDeepLink` method now requires an `AppCompatActivity` instead of an `Activity`. Please update your calls accordingly. See [DeepLinking](DeepLinking.md) for more details. ### New Features - Introduced **Sheets**, a new way to display web content within the SDK experience. Sheets can be triggered via action buttons or opened directly. See [AdditionalMethods](AdditionalMethods.md#opensheet) and [DeepLinking](DeepLinking.md#sheet) for details. - Added analytics events for `OpenedSheet` and `DismissedSheet`. See [Analytics](Analytics.md). ### Improvements - Added support for respecting Ad Tracking Preferences in the Storyteller SDK and GAM Module. See [PrivacyAndTracking](PrivacyAndTracking.md) for details. - Ensured the `clipIndex` property on Clip analytics events correctly reflects the index within the overall Collection when using Paging. - Corrected the calculation and reporting of the `durationViewed` property in analytics events. - Addressed memory leaks related to Sheets and the Story Pager. ### Bug Fixes - Fixed an issue where the progress bar could disappear on native page ads when tapped quickly. - Fixed a bug preventing muting a story via the volume button if it was previously unmuted. - Resolved an issue where old content briefly flashed after unfollowing a team/category from the clip player and refreshing. - Fixed search results not updating to an empty state when modifying a valid search term to an invalid one. - Corrected behavior where unfollowing items in the "For You" tab did not update the "Following" tab. - Fixed video playback resuming from the stopped point instead of restarting when navigating between video and image pages. - Resolved an issue where the clip player could continue playing audio in the background after pressing the device back button. - Fixed deep links with a correct page ID but incorrect story ID failing to open the player. - Fixed story page elements being removed after viewing native image ads. - Addressed an issue where the first clip ad might appear later than the configured frequency. - Made captions enablement a user-specific setting rather than device-wide. - Fixed crashes related to ConstraintLayout dependencies. - Fixed a crash that could occur on the "More" screen within a clip collection. - Resolved a clip mismatch issue after viewing a "Not Viewed" clip via search. - Fixed users being unable to close Sheets with a swipe gesture over the WebView area. - Corrected status bar icon visibility when Sheets are opened in light mode. - Addressed issues with video page requests not completing in specific environments. - Fixed an issue where tapping the first clip thumbnail opened the wrong clip player if all clips on the initial page were already read (Paging). - Fixed the story player freezing when opening a story while a specific video type (Game Recap) was playing. ## **10.8.0** - 11/03/25 ### Breaking changes - Adding `bottomSheetScreen` functionality. [StorytellerDelegates](StorytellerDelegates.md) section Note that if you are not using this functionality then simply return the default value for this method in your Delegate implementation. ### New Features - Added Clips Paging functionality to Clips Player to improve performance and user experience - Adding support for deep-links via external ID URLs ### Improvements - Updated `collectionClipCount` property on Clip analytics events to be the total number of Clips in the Collection - Added `clipIndex` property to all Ad analytics events and `captionsEnabled` to all Clips analytics events - Improved ad loading performance and preloading logic - Improved loading behavior for embedded Clips. - Updated behavior for deep links with Clips Paging functionality ### Bug Fixes - Fixed a crash occurring when fast-swiping between Clips. - Fixed an issue where Clip progress was not saved when the app was backgrounded. - Fixed incorrect event logging for deep-linked Clips. - Fixed an issue where multiple search filter menus appeared when tapping quickly. - Fixed an issue with inconsistent font rendering on certain devices. - Fixed an issue where users could not tap to go to the next page if it was a page ad. - Fixed an issue where the first tap after reopening a paused native ad was not responsive. - Fixed playback getting stuck when navigating and skipping too fast. - Fixed a lifecycle bug where sound plays when the app is swiped to the background. - Fixed an issue where a shared quiz link navigates the user to the end of the quiz results instead of the beginning. - Fixed an issue where Stories tiles could not be set to round on Compose screen. - Fixed incorrect event property for `clipsViewed` event on Clip Close events. - Fixed an issue where dismissing a story with a CTA button does not auto-reload that story. - Fixed an issue where PTR stops working on clips after multiple fast PTR attempts. - Fixed an issue where followable category screens send two identical paging requests. - Fixed an issue where tapping on a clip while swiping to the followable category screen was possible. - Fixed an issue where auto-scroll occurred while the player was open and the app was backgrounded. - Fixed an issue where the store action button was not opening the Play Store. - Fixed playback issues after category navigation. - Fixed issues with search displaying incorrect or incomplete results. - Fixed an issue where playback was stuck when opening a page ad. - Fixed an issue where category navigation opens a black screen with the sound of the previous clip. - Fixed an issue where opening a clip from the followable category screen freezes the page. - Fixed an issue where a grey line appeared on image poll answers. - Fixed an issue where swiping to other embedded clips got stuck with a reload button. - Fixed an issue where sound continued playing after dismissing a clip on the search/category screen. ## **10.7.6** - 27/02/25 ### Bug Fixes - fixed an issue with crash when rotating Embedded Clips on Samsung tablets ## **10.7.5** - 31/01/25 - fixed an issue with a wrong position for Clip captions - fixed some issues with Ads which resulted in content being cut on some devices - fixed issue with insets handling on Android 15 for internal WebView ### Bug Fixes - fixed insets for both normal clips and embedded clips for both xml and compose implementations - fixed issue where Link to open Story without Page Id fails ## **10.7.4** - 16/01/25 ### Bug Fixes - fixed insets for both normal clips and embedded clips for both xml and compose implementations - fixed issue where Link to open Story without Page Id fails ### Improvements - `reloadData()` method on `StorytellerEmbeddedClips` has no effect when embedded clips is on the custom followable category screen - added missing theme handling for gradient when using `from` infix methods for circular tiles ## **10.7.3** - 24/12/24 ### Bug Fixes - fixed a bug with SHARE_SUCCESS event not being sent when sharing a Clip ## **10.7.2** - 20/12/24 ### Improvements - `dismissPlayer` method now has an optional `onCompletion` callback that is called when the player is dismissed. Please, see the [Additional Methods](AdditionalMethods.md) for more details ## **10.7.1** - 19/12/24 ### New Features - Enabled displaying the exact number of tiles per row on various screens. Please, see the `visibleTiles` property in the "Setting categories and collections, applying themes and styles" section of the [StorytellerCompose](./StorytellerListViews.md) for more details. - Enabled changing the text casing for "Navigating Stories" header on the Instruction screen. Please, see `theme.instructions.headingTextCase` [Themes](./Themes.md#instructions) for more details. - Implemented custom fonts for the Instructions screens headers. Please, see `theme.instructions.headingFont` [Themes](./Themes.md#instructions) for more details. - Added support for chip borders. Please, see the updated section [Themes](./Themes.md#story-and-clip-tiles) for more details. ### Bug Fixes - Fixed some issues related to working with themes for Row. - Fixed the `shareSuccess` event not being sent on Android 10. - Fixed a crash on Storyteller rows. - Corrected search visibility in Clips when disabled. - Fixed missing key-value pairs in the request for GAM Ads. - Ensured Clip events are sent correctly when opened from a category or search screen. ## **10.7.0** - 10/12/24 ### Breaking Changes - Added `customScreenForCategory()` to `StorytellerDelegate` for handling custom followable category actions. [StorytellerDelegates.md](StorytellerDelegates.md) - Added `useCustomScreenForCategory()` to `Storyteller` for allowing the developer to conditionally show the custom screen for a specific category or default. [StorytellerDelegates.md](StorytellerDelegates.md) Note that if you are not using this feature you can simply return the default return values for these delegate methods. ### Bug Fixes - Fixed issue where pausing required multiple clicks. - Resolved a bug preventing tablet ads from rendering correctly. - Fixed crash occurring in stories. - Addressed issue where old content briefly displayed after following/unfollowing a team. - Fixed bug where the "Following" tab showed user content after logout. - Resolved issue with custom native and client ads not resetting progress correctly. - Fixed incorrect calculation of Poll & Quiz results. - Analytics events: + Fixed `nextClip` and `previousClip` event issues. + `finishedClip` event now includes correct clip information when dismissing a category. - Fixed issue where tapping the left side of the first story closed it. - Resolved crash when clicking on two clips simultaneously. - Removed live chip from the search page. - Fixed "Not found" message incorrectly appearing on the search page. - Actions for followable categories now work correctly. - Fixed top-level back button functionality. - Addressed issue where the refresh button briefly appeared for a loading clip. - Disabled the search button when the search field is empty. - Collection name inputs are now trimmed correctly. - Resolved issue where banner ads occasionally missed the Google "i" icon. - Fixed clip grid on the search page not scrolling to a dismissed clip. - Fixed GAM analytics inside the Storyteller Ads Module. - Fixed KVPs for Banner Ads inside the Storyteller Ads Module. ### New Features - Added `resumePlayer()` to `Storyteller` for resuming the player after pausing. [AdditionalMethods.md](AdditionalMethods.md) ### Improvements - Live clips now resume from their current live position when viewed again. - Story rows behavior aligned with disabled Read/Unread ordering. - Added fallback to Story ID when a page cannot be found. - Updated captions to include a CC logo with a toggle option. - Set default font sizes for Stories, Clips, and Singletons in the Storyteller Home theme. - Clips now pause automatically on the clip player when an action is triggered. - Improved caching logic for Stories and Clips. ## **10.6.5** - 22/11/24 ### Bug Fixes - fix deprecated `Modifier.animateItemPlacement` and replaced with `Modifier.animateItem` ## **10.6.4** - 1/11/24 ### Bug Fixes - fix for sound playing on double back click on followable categories ## **10.6.3** - 25/10/24 ### New Features - add `isLive` and `collectionClipCount` fields to analytic events, see [Analytics](Analytics.md) for more details ### Bug Fixes - fixed an issue with Native Ad impressions not being reported in StorytellerGamModule - fixed a crash which could potentially happen when using hardware keyboard in Search screen ## **10.6.2** - 22/10/24 ### New Features - new signature for `StorytellerGamModule.init` method, which allows using different Ad Units, see [Ads](Ads.md) for more details ### Bug Fixes - fixed issue with potential crash when resuming CLips player - fixed issue which could cause ANR when launching the App using cold start ## **10.6.1** - 10/10/24 ### Bug Fixes - fixed multiple issues with playback stuck on For You / Following - fixed an action button not working for the empty Following screen ## **10.6.0** - 8/10/24 ### Breaking Changes - Compose Bom dependency updated to `2024.09.03` ### New Features - Added For You / Following feature to our embedded clips - Added auto reloading support to our lists ### Improvements - Some of our theme parameters can be configured from CMS - Updated our theme parameters documentation - Fixed standard banner ads not displaying on Tablets - Fixed a crash due to reorder animation issues - Video playback improvements - Video and Image preloading and caching improvements - Added Swipe gesture to dismiss Category screen ### Bug Fixes - fixed a crash which could occur when doing PTR on Embedded Clips - fixed a UI glitch for Native ads - fixed a bug where the Search screen was reloaded after closing the Player - fixed UI glitches on Category screen - fixed `dismissedAd` being sent for Stories on the Search/Category screen ## **10.5.1** - 10/09/24 ### Improvements - Ads will restart the video if the last action was `play` or `idle` - Introduced new tracking of ads when they are looped/repeated or Action buttons are tapped. - All add type events now include advertiser name ### Bug Fixes - fixed wrong use of googleAdsResponseId in ad tracking - fixed an issue where the incorrect cursor color was used in the search bar - fixed an issue where page ad events are also triggered when swiping down - `pageActionType` is now included for all types of ads and their analytic events - story ads analytics are correctly referred to as `video` instead of `image` - fix incorrectly laid out banner ads ## **10.5.0** - 3/09/24 ### New Features - added support for Standard ads GAM `Google Ads Manager` - introduced Storyteller Gam module `StorytellerGamModule`. It can be used to facilitate the integration of native ads into the Storyteller SDK so that the adverts are injected between the stories and clips. see [Ads](Ads.md) for more details - added `Storyteller.module` list during initialization to enable the native ads feature. see [StorytellerModule](StorytellerModule.md) for more details ### Improvements - added support for keyboard navigation for categories in the Clips player - buttons have been added to expand and shrink both the title and category lists - improved the animation of closing the player so it's less noticeable - improved keyboard focus handling within Search - aligned the embedded Clips play button to match the normal Clips play button - improved text input and keyboard responsiveness in search for Samsung devices - improved accessibility descriptions for the Clips player ### Bug Fixes - fixed a bug where it was possible to trigger requests to the API before the SDK was initialized - fixed a bug where navigation was possible to the same category in the category list - fixed a crash that could occur when opening stories from a story list - fixed a stack overflow crash for tablets when changing orientation - fixed broken layout for large titles and category lists on tablets and phones - fixed a bug where the live chip would be shown as read before it's clicked - fixed a bug where `No results found` would not show when tapping on the search button - fixed an issue where the gradient on an asset had a delay on reload ## **10.4.1** - 9/08/24 ### Bug fixes - fixed a crash that could occur when rendering a Row or Grid with animations enabled ## **10.4.0** - 8/08/24 ### New Features - added support for Accessibility adherence according to [WCAG 2.1](https://www.w3.org/TR/WCAG21/) standards - added support for keyboard navigation in the Stories and Clips player ### Improvements - added talkback announcements when navigating between Stories and Pages - improved all accessibility elements for all elements in Stories - upgraded the `HTTP/1` protocol to `HTTP/2` for better performance and security - enabled the re-order animations by default in themes please see `theme.lists.animateTilesOnReorder` [Themes](Themes.md) for more details - improved the alignment for labels and headings between ios and android - improved accessibility bypass/skip blocks for the Stories and Clips player - added error state when search fails to load - reduced load of dependency graph by deferring some dependencies initialization - improved animations for Stories and Clips - improved preloading of category thumbnails inside Clips ### Bug fixes - fixed analytic discrepancy issues (double clicks being ignored or filtered out) - fixed various mislabeled accessibility elements (action buttons, titles, images, Polls and Quizzes) - fixed preloading of poll videos not cancelling - fixed android back button not interacting correctly - fixed infinite loading or no loading at all in search - fixed a bug for followable categories where you cannot follow or unfollow - fixed a bug where sound is playing in the background - fixed a bug where sound is not playing when you return to the app - fixed a bug where images would not load or preload in wrong order - fixed a crash related to android 8.0.0+ devices - fixed a bug where poll videos would not play after coming back to the app - fixed a bug where Dismissed Reason was missing in Story Closed event - fixed a bug where previous search results were shown after multiple queries - fixed a bug with excessive space between title and categories in the Clips player - fixed a bug where "New" chip on Clip tiles would appear and disappear when opening the Clips player - fixed a bug where Story player would freeze after putting app into background - fixed a bug where sound would not stop playing when going between image and video pages in Stories - fixed a bug where pull-to-refresh on embedded Clips could stop playback - fixed a bug where there would be a flash on search screen during orientation change - fixed a bug where Story row inside search screen would appear after a delay - fixed a bug where search input in dark mode still had light mode style - fixed a bug where followable category screen would miss a "star" icon ## **10.3.0** - 15/07/24 ### New Features - lists can now reload in an animated fashion, controlled by the [animateTilesOnReorder](Themes.md) flag in the Theme. - [Storyteller Home](StorytellerHome.md) can now have a fixed UI Style set ### Improvements - standardizing Clips and Stories Player present/dismiss transitions - improved loading sequence when the tiles are appearing on the screen to feel smoother - improving the Accessibility experience of the Player - made clarifications around how the [User ID](Users.md) is used within the SDK - organized the docs for the [Open Player](OpenPlayer.md) methods - improved loading time of the lists in Compose - improved Search and Categories screens in Compose ### Bug fixes - fixed issue with the Theme not applied when opening Search - fixed issue with Story Playback mode in Analytics - fixed issue with the title gradient not appearing in square tiles - fixed crashes related to opening Quizzes and Polls - fixed View Model crash when accessing from a detached fragment ## **10.2.3** - 5/07/24 ### Bugfixes - fixed a crash which could occur when try to open story from deep link, but story is not available ## **10.2.2** - 3/07/24 ### Bugfixes - fixed a crash which could occur when reloading the row with Quizzes ## **10.2.1** - 27/06/24 ### Improvements - when an empty String is passed for `UserInput` in `Storyteller.initialize(...)` the default user will be used ## **10.2.0** - 24/06/24 ### New Features - added new liveChip properties to `rectangularTile` the theme, see [Themes](Themes.md) for more details - added new fade loading effect for images in rows and grids when transitioning from placeholder to the actual image - added support for linking to a specific clip within the moments tab including correct analytic events surrounding this - exposed the property `canGoBack` from embedded clips to allow querying about the possibility of going back in the embedded clip collection, see [StorytellerClipsFragment](StorytellerClipsFragment.md) for more details - added support for initial Category within the `openCollection` method, see [Additional Methods](AdditionalMethods.md) for more details - added support for initial category for the deep links. Via the query param `categoryId` you can specify the initial category to open, see [DeepLinking](DeepLinking.md) for more details - added support for initial category for embedded clips. see [StorytellerClipsFragment](StorytellerClipsFragment.md) for more details ### Breaking Changes - renamed `theme.player.liveChipImage` to `theme.player.liveChip.*` that contains `textColor`, `image`, `backgroundGradient`, and `backgroundColor` properties for better customization, see [Themes](Themes.md) for more details - removed `theme.tiles.rectangularTile/circularTile.liveChip.textColor` in favor of `theme.tiles.rectangularTile/circularTile.liveChip.readTextColor` and `theme.tiles.rectangularTile/circularTile.liveChip.unreadTextColor`, see [Themes](Themes.md) for more details ### Bugfixes - fixed an issue where backButton was not visible when navigating from a category to home then back to Moments - fixed an issue with multiple filter menus appearing when fast tapping on search filter button - fixed an issue where where action buttons shift incorrectly on Moments screen/tab - fixed various alignment issues & animations & theming issues across the SDK (Followable categories, Moments, etc.) - fixed a bug where the after killing the app and reopening, the theme resets - fixed playback issues while navigating - fixed analytics issues where nextCategory and previousCategory is sent instead of openedCategory and dismissedCategory - fixed bug where stories would close when navigating and trying to show an advert - fixed playback gets stuck when navigating & skipping too fast - fixed a lifecycle bug where sound plays when app is swiped to background - fixed a crash when swiping fast between moments and pressing home immediately afterwards - fixed an issue with search filters on tablets, filters sheet was half visible - fixed an issue with first party ads deep-links were not working - fixed a potential crash with first party ads - fixed stuck playback when opening a page ad - fixed a glitch with embedded clips header shifting on tablet layouts (samsung devices) when opening the share dialog - fixed an issue with square tiles that made title gradient flash when navigating between screens ## **10.1.5** - 10/06/24 ### Bugfixes - fixed an issue where the round tile title can be cut off in Compose ## **10.1.4** - 5/06/24 ### Bugfixes - fixed an issue with NPE crash which could happen when dismissing a Clip before video loads in `StorytellerClipsFragment` ## **10.1.3** - 5/06/24 ### Bugfixes - fixed an issue with NPE crash which could happen when opening the Stories Player - fixed an issue with ANR which could happen when initializing the SDK - fixed an issue with Stories crashing due to `ConcurrentModificationException` ## **10.1.2** - 23/05/24 ### Bugfixes - fixed an issue with Stories crashing due to `ConcurrentModificationException` ## **10.1.1** - 17/05/24 ### Bugfixes - fixed an issue with playback not starting for StorytellerClipsFragment - fixed an issue with the StorytellerClipsFragment playback not starting after reloading the container screen ## **10.1.0** - 16/05/24 ### New Features - added support for First Party Ads - added support for gradients for Circular Tiles, see [Themes](Themes.md) for more details ### Improvements - improved content preloading for faster playback ### Bugfixes - fixed an issue with the Row blinking on Tablets - fixed an issue with `PreviousClip` analytics event - fixed an issue where `CompletedStory` analytics event was not sent for single Paged Stories - fixing an issue with the filters on Search screen - fixing an issue with wrong theme applied when navigating between categories in `StorytellerClipsFragment` - fixing an issue where the long version of the title was used instead of the short one for the Circular tiles - fixing an issue where multiple Page Ads could be added in the same slot - fixing an issue where sometimes No Results Screen was not shown correctly in Search - fixing an issue where Clips could sent an error to delegate while reloading the content ## **10.0.3** - 27/05/24 ### Bugfixes - fixed an issue with Stories crashing due to `ConcurrentModificationException` (backport from **10.1.2**) ## **10.0.2** - 09/05/24 ### New Features - added compose implementation for embedded clips the `StorytellerEmbeddedClips` composable, see [StorytellerClipsFragment](StorytellerClipsFragment.md) for more details ### Bugfixes - fix an issue for stories where the player is dismissed when the user navigates to search from stories - fix an issue where grid columns could be mixed when recycled ## **10.0.1** - 03/05/24 ### Breaking Changes - `reloadData()` for embedded clips will take you back one level if not on the top level of the collection. se [Reload Data](StorytellerClipsFragment.md#reload-data) in [StorytellerClipsFragment](StorytellerClipsFragment.md) for more details - fixed an issue with `tileSpacing` where it was applied twice, so make sure to adjust your theme if you were using 2x `tileSpacing` value ### New Features - introduced `setLocale` method to the `Storyteller.user` object to set the user's locale, see [User Customization](Users.md) for more details ### Bugfixes - fixing an issue with clips subtitles can disappear when tapping the share button - fixing the missing search-specific analytics parameters for Stories - fixing an issue with the tile shrinking animation when the player is closed - fixing an issue where action buttons were not working on subsequent screens - fixing an issue with action button could crash the app when tapped after opening category details screen - fixing an issue where clips playback could be stuck after opening a story from category details screen - fixing an issue with embedded clips where swiping left to open category details might not work while on the first clip - fixing an issue with embedded clips reloading, back arrow can be still visible when it should not ## **10.0.0** - 18/04/24 ### Breaking Changes - deprecated xml view params removed in favour of `StorytellerListConfiguration`, see [StorytellerStoriesView](StorytellerListViews.md) or [StorytellerClipsView](StorytellerListViews.md) for more details - added an additional method `categoryFollowActionTaken` to `StorytellerDelegate`, see [StorytellerDelegate](StorytellerDelegates.md) for more details - `isRefreshing` parameter for Composable Lists has been replaced with `state.reloadData()` method, see [StorytellerCompose](StorytellerListViews.md) for more details ### New Features - introduced **Followable Categories**, enhancing user interaction and engagement by allowing users to follow/unfollow categories. For more details, please see [User Customization](Users.md) - `StorytellerListViews` now make use of ComposableViews which improve performance, see [StorytellerStoriesView](StorytellerListViews.md) or [StorytellerClipsView](StorytellerListViews.md) for more details - introduced Captions support for Clips - various Performance improvements for Lists scrolling and Tile loading ### Bugfixes - fixing some UI issues when navigating categories inside Clips - fixing issue with scrolling to the top of the list after reloading data - increased rounded corners default Theme value to 8 - fixing data reloading and scrolling for Composable Lists - added missing empty state for Search - fixed sound playing before navigation screen opens - fixed issue with previous play card visible when doing PTR on Clips - fixing analytic events and data for Clips, Search, Ads and Stories ## **9.13.4** - 10/04/24 ### Bugfixes - fixing a bug where `Storyteller.dismissPlayer` was ignoring `animate` parameter ## **9.13.3** - 8/04/24 ### Bugfixes - removed unused flag from manifest ## **9.13.2** - 2/04/24 ### Bugfixes - added R8 rule for `StorytellerHomeFragment` to prevent obfuscation ## **9.13.1** - 2/04/24 ### Bugfixes - aligned the `cornerRadius` theme property for the Stories action buttons and Onboarding ## **9.13.0** - 29/03/24 ### New Features - added `StorytellerHome` support, see [StorytellerHome](StorytellerHome.md) for more details - added better support for configuring privacy and analytics events, see [Additional Methods](AdditionalMethods.md) for more details ### Bugfixes - fixed an issue where Clips were started with a small delay - fixed an issue where Stories action buttons were not respecting `cornerRadius` theme property - fixed an issue in Compose where background was not properly resized for Live/Read/Unread chips ## **9.12.1** - 27/03/24 ### Bugfixes - fixed an issue where `openedReason` could be wrong when opening Clips/Stories player via a deep link - fixed an issue where the `shareSuccess` analytics event was not sent when sharing a Clip - fixing an issue where `Storyteller.openStory` was not emitting `openedStory` - fixing a black screen issue when opening not existing Clip collection ## **9.12.0** - 18/03/24 ### Improvements - added `itemTitle` and `actionText` to action analytic events, see [Analytics](Analytics.md) for more details - changed `Storyteller.currentUser` to be `Storyteller.currentUserId` - gradient mask increased on the tiles for better readability ### Bugfixes - fixed a bug where the wrong link was used for email sharing - fixed a bug where it was not possible to correctly alignment for Compose tiles - fixed handling of custom live chip in Compose - fixed an issue where it was possible to get `onFailure` callback when `onSuccess` was already triggered for `Storyteller.initialize` - fixed handling of the custom theme images in Compose ## **9.11.0** - 8/03/24 ### Improvements - added support for handling share on action buttons ## **9.10.0** - 28/02/24 ### Improvements - analytics improvements: added missing fields, fixed some events ### Bugfixes - fixed a bug with the `placement` field was set incorrectly for `AdRequestInfo` - fixed a bug where the search icon was visible when CLip Player loading failed - fixed and improved multiple areas of Analytics - fixed a bug where the image title was visible in Clips player on Ads - fixed a bug where filtering by content type was not working in Search - fixed a bug where the Search button was not working when using keyboard - fixed Compose list views where it was possible to see viewed/not viewed badges on Clips even when they were turned off in the CMS ## **9.9.2** - 21/02/24 ### Bugfixes - fixed a bug with the SHARE_SUCCESS event not having correct data and sometimes not being sent ## **9.9.1** - 02/02/24 ### Improvements - improved accessibility support for the player so that it announces player content without saying page 3x times ### Bugfixes - fixed a bug where Clips player was opening slowly after a delay and sometimes not opening at all - fixed a crash which could occur when opening the Clips player at a very edge case scenario ## **9.9.0** - 31/01/24 ### New Features - added 'Viewed/Not Viewed' status indicators for Clips, improving content tracking for users - removed support for round tiles in xml `StorytellerClipListViews`, streamlining the design - implemented sortOrder handling for Clips, offering better content organization - if `enableReadUnreadOrdering` is set to `false` for stories, New chips are now hidden for both read/unread statuses, ensuring a cleaner UI ### Bugfixes - fixed the order inconsistency between list and embedded Clips, enhancing the user experience - resolved the issue where Clips ordering was incorrect and ensured that Pull-to-Refresh (PTR) maintains the correct order - addressed the issue where the new chip did not disappear if the clip was watched on Search, ensuring consistent UI behavior - fixed the issue where like and share options were removed after reloading on embedded clips, maintaining feature availability - resolved the issue where, after a second PTR, read clips would become unread again, ensuring consistent read status - fixed the issue where the gray background remained static for a few frames for clips, polishing the visual experience ## **9.8.4** - 11/01/24 ### New Features - Embedded Clips has a new property `bottomInset` which can be used to set the bottom padding for the Clips Player, please see `Inset Management` section in [StorytellerClipsFragment](StorytellerClipsFragment.md) for more details - Storyteller has a new property `isSearchEnabled` which can be used to check if the search is enabled for the current tenant, please see [Additional Methods](AdditionalMethods.md) for more details - added accessibility support for Embedded Clips, Search, Instructions and Compose ### Bugfixes - fixed an issue with round tiles layout in Compose - fixed an issue with `StorytellerListViewDelegate` methods are sometimes not triggered - fixed an issue to remember start clip id even if reload reorders clip data in embedded clip ### Breaking Changes - Storyteller Themes are accepting now resource ids for Fonts and Drawables using helper functions, see [Themes](Themes.md) for more details ## **9.8.3** - 13/12/23 ### New Features - added support for `StorytellerClipsViewControllerDelegate` inside `StorytellerClipsFragment`, please see [StorytellerClipsFragment](StorytellerClipsFragment.md) ### Improvements - improved Round tiles in Compose - improved PTR support for `StorytellerClipsFragment` ### Bugfixes - fixing an issue with not working `StorytellerDelegate` when the hosting activity is closed - fixing an issue with `CompletedStory` not being sent when the user is on the last page ## **9.8.2** - 1/12/23 ### Bugfixes - fixing an issue with Compose round tile titles ## **9.8.1** - 24/11/23 ### Bugfixes - fixing read/unread Stories order in Player - the top bar in Clip Player is now fixed - fixing default height constraints for Compose rows - fixing deep link handling for Stores/Clips when ids that are passed are wrong ### Breaking Changes - Storyteller Themes are accepting now resource ids for Fonts and Drawables only, see [Themes](Themes.md) for more details ## **9.8.0** - 9/11/23 ### New Features - rewritten `StorytellerListViews` fully in Jetpack Compose, please see [StorytellerCompose](StorytellerListViews.md) for more details - Clip collections are not looping on `StorytellerClipsFragment` anymore (user cannot advance from last to first Clip), please see [StorytellerClipsFragment](StorytellerClipsFragment.md) ### Breaking Changes - Compose versions of `StorytellerListViews` do not use controllers anymore, please see [StorytellerCompose](StorytellerListViews.md) for more details - updated Compose BoM dependencies to `2023.10.01` ## **9.7.7** - 29/11/23 ### Bugfixes - fixing a crash which could occur when dismissing Clips player ## **9.7.6** - 14/11/23 ### Bugfixes - fixing a crash which could occur when opening Clips or Stories player ## **9.7.5** - 9/11/23 ### Bugfixes - fixing a crash which could occur when using PTR on the Embedded Clips ## **9.7.4** - 8/11/23 ### Bugfixes - fixing an issue with `displayLimit` when same category is used in multiple places - fixing an issue with wrong count format of Clip Likes inside the player - fixing an issue with Stories/Clips Player close animation ### New Features - added support for `reloadData()` and PTR for `StorytellerClipsFragment`, please see [StorytellerClipsFragment](StorytellerClipsFragment.md) for more details ## **9.7.3** - 18/10/23 ### Bugfixes - fixing a crash which could occur when navigating to the Embedded Clips via a deep link ## **9.7.2** - 16/10/23 ### New Features - Storyteller now supports Android 14 - upgraded Android Gradle Plugin from `8.0.2` to `8.1.1` ## **9.7.1** - 9/10/23 ### Bugfixes - fixing an action button issue for Clips ### New Features - added top level back button handling for Embedded Clips, please see [StorytellerClipsFragment](StorytellerClipsFragment.md) for more details ## **9.7.0** - 29/09/23 ### Bugfixes - fixing an issue with the Clip Player header image - fixing an issue where th instructions screen was not shown when changing the orientation - fixing memory leaks in Story Player ### New Features - added `openCategory` method, please see [Additional Methods](AdditionalMethods.md) for more details - added `openStoryByExternalId` and `openClipByExternalId` methods, please see [Additional Methods](AdditionalMethods.md) for more details - added `pageTitle` parameter to analytic events, please see [Analytics](Analytics.md) for more details - added fixed order rows mode for Stories which can be set up via the CMS - added pinned stories support for Stories which can be set up via the CMS ## **9.6.0** - 18/09/23 ### Improvements - updated top and bottom shade gradient for Clips - more/less button displayed below the categories list for Clips - improved sorting of Stories lists ### Bugfixes - fixing a bug where the Story close animation had a low quality - fixing memory leaks in the Clips/Stories player ## **9.5.0** - 30/08/23 ### New Features - added support for Page Ads which appear between Pages ### Breaking Changes - changed signature of ad delegate from `getAdsForList` to `getAd`, please see [Ads](Ads.md) for more details - changed classes inside `StorytellerAdRequestInfo`, `StoryInfo` and `ClipInfo` are now single class `IntemInfo`, please see [Ads](Ads.md) for more details ### Improvements - added `adPlacement` to analytics, please see [Analytics](Analytics.md) for more details ## **Important** - **Please note that the Storyteller SDK is now distributed via CloudRepo. Please see [GettingStarted](GettingStarted.md) for more details. MyGet repository is not working anymore** - The only change required is to add the following maven repository to your project's `build.gradle` file and MyGet can be removed: ```groovy maven { url 'https://storyteller.mycloudrepo.io/public/repositories/storyteller-sdk' } ``` ## **9.4.0** - 15/08/23 ### New Features - added support for Clips Category Navigation to the Clips Player ## **9.3.1** - 1/08/23 ### Improvements - improved performance of loading Story tile images ## **9.3.0** - 24/07/23 ### Breaking Changes - Composable views are part of the main SDK. Please refer to the [StorytellerCompose](StorytellerListViews.md) documentation for more details. - instead of setting params on Android views (which are now marked as deprecated), `StorytellerListViewConfiguration` object was introduced. Please see [StorytellerClipsView](StorytellerListViews.md) and [StorytellerStoriesView](StorytellerListViews.md) for more details. - Composable views are now also using `StorytellerListViewConfiguration` object. Please refer to the [StorytellerCompose](StorytellerListViews.md) documentation for more details. - some class names related to Ads were changed: | Old class | New class | | ---------------------------------------------------- | ------------------------------------------------------------- | | `ClientAd` | `StorytellerAd` | | `TrackingPixelClientAd` | `StorytellerAdTrackingPixel` | | `ClientAction` | `StorytellerAdAction` | ### Improvements - better scroll position handling for the Row views - improvements for Composable view performance ## **9.2.2** - 4/07/23 ### Improvements - added better support for Video and Image pages between Quiz pages ## **9.2.1** - 19/06/23 ### Bug fixes - fixed an issue with sharing Clips ## **9.2.0** - 13/06/23 ### New Features - added support for embedding the Clips Player as a fragment, see [StorytellerClipsFragment](StorytellerClipsFragment.md) ### Improvements - changed underlying image loading library to Coil to improve image loading performance - improved open/close transition animations - removed `StoryCategory` and `ClipCategory` classes in favour of `Category` class ### Bug fixes - fixed a problem with init spikes in the storyteller initialisation process - fixed a problem with a previous story thumbnail being visible when navigating stories quickly - fixed a problem with updating the read status of stories - fixed a problem with closing the instructions screen by tapping anywhere on the screen ## **9.1.7** - 6/06/23 ### Bugfixes - fixed a crash which could appear when inflating the views - fixed a return animation issue from the tiles ## **9.1.6** - 23/05/23 ### Bugfixes - fixed a bug where ads are not cleared when the Player is dismissed ## **9.1.5** - 18/05/23 ### New Features - `Storyteller.user.setCustomAttribute` now accepts only `String` values, see [User Customization](Users.md) ### Bugfixes - fixed a bug where the Page action could be triggered by a swipe up even if it had no action set ## **9.1.4** - 16/05/23 ### New Features - Ad indicator added to the Clips player ## **9.1.3** - 11/05/23 ### New Features - `adType` param added to `UserActivity`, see [Analytics](Analytics.md) - changed definition of `ClipsAdRequestInfo`, see [Ads](Ads.md) ## **9.1.2** - 04/05/23 ### Bugfixes - fixed a bug with dependency injection that might affect unit tests ## **9.1.1** - 02/05/23 ### Bugfixes - fixed a bug with proguard rules for AGP 8.0 ## **9.1.0** - 01/05/23 ### New Features - live clips are here. - added Live indicator to the Clips tile and the Clips player - implemented during live stream behaviour - implemented before/after live stream behaviour ### Bugfixes - fixed pager scrolling glitch on clips when having two clips - fixed gray button appearing on Quiz summary when share button is turned off in theme - fixed a potential crash which could occur when sending Storyteller to the background ## **9.0.1** - 17/04/23 Improvements: - added support for Stories ordering by category Bugfixes: - fixing an issue with unit tests - fixing an issue with the Clips title image not appearing - fixing a crash which could occur when navigating the Stories inside a grid - fixing a a crash which could occur when navigating to a previous story ## **9.0.0** - 6/04/23 Improvements: - added support for Ads between Clips - performance improvements for Clips Player - added support for setting custom attributes for Storyteller requests - improved image preloading performance for Story and Clip tiles - removed deprecated `tileBecameVisible`, `getAdsForList` methods Bugfixes: - fixing a bug where the Story tiles were not loaded immediately when scrolling `StorytellerListView` - fixing a bug where `StorytellerListView` was not scrolled to the beginning after reloading the data Breaking Changes: - removed support for `category` on Clips and `collection` for Stories - changed `getAdsForList` callback to better support Clips and Story Ads, see [Ads](Ads.md) - introduced a better class hierarchy for the StorytellerListView classes: ```text StorytellerListView ├── StorytellerStoriesView │ ├── StorytellerStoriesRowView │ └── StorytellerStoriesGridView └── StorytellerClipsView ├── StorytellerClipsRowView └── StorytellerClipsGridView ``` for more information please check the [StorytellerListView](StorytellerListViews.md) documentation. ## **8.5.11** - 12/12/23 Bug Fixes: - fixing an issue with not working `StorytellerDelegate` when the hosting activity is closed ## **8.5.9** - 3/04/23 Bug Fixes: - fixing a rare crash which could occur when the player is recreated ## **8.5.8** - 27/03/23 Improvements: - introduced different thumbnail sizes for better story and clip previews ## **8.5.7** - 14/03/23 Bug Fixes: - fixed a bug where a Video analytic event was not sent on time ## **8.5.6** - 2/03/23 Bug Fixes: - fixed a bug with gestures which could appear when moving the app to the background and pressing on a Story. - fixed a bug where the video would not start after opening a Story from a deep link - fixed a crash that could occur when there is a large number of tiles in the `StorytellerListView` ## **8.5.5** - 20/02/23 Bug Fixes: - fixed a bug that was causing the onboarding process to appear every time the application was initialized - fixed a bug where the ads created by the GAM were not properly animating upon closing ## **8.5.4** - 15/02/23 Bug Fixes: - fixed a bug where category parameters were not being set for Story, Clip and Ad analytics events - fixed a bug that was causing the ad chip to not be displayed for Client ads ## **8.5.3** - 10/02/23 Bug Fixes: - fixed a bug with setting an empty user id (`null`) during Storyteller initialization - fixed a bug with a video playing under the instructions screen - fixed a bug with the Stories player layout - fixed a bug with the opening transition ## **8.5.2** - 2/02/23 Bug Fixes: - fixed a bug with wrong `openedReason` set for Clips analytics ## **8.5.1** - 30/01/23 Bug Fixes: - fixed a crash which could occur on Android versions lower than 10 Improvements: - improvements to the close transition handling ## **8.5.0** - 19/01/23 Breaking changes: SDK Repackaging was done to organise the code better. Please update Storyteller imports. | Old class | New class | | ---------------------------------------------------- | ------------------------------------------------------------- | | `com.storyteller.domain.StorytellerListViewCellType` | `com.storyteller.domain.entities.StorytellerListViewCellType` | | `com.storyteller.services.Error` | `com.storyteller.domain.entities.Error` | | `com.storyteller.domain.AdResponse` | `com.storyteller.domain.entities.ads.AdResponse` | | `com.storyteller.domain.ClientStory` | `com.storyteller.domain.entities.ads.ClientStory` | | `com.storyteller.domain.ListDescriptor` | `com.storyteller.domain.entities.ads.ListDescriptor` | | `com.storyteller.domain.UserActivity` | `com.storyteller.domain.entities.UserActivity` | | `com.storyteller.domain.UserActivityData` | `com.storyteller.domain.entities.UserActivityData` | | `com.storyteller.domain.UserInput` | `com.storyteller.domain.entities.UserInput` | | `com.storyteller.domain.ClientAd` | `com.storyteller.domain.entity.ads.ClientAd` | Bug Fixes: - fixed a bug with `onPlayerDismissed` callback - fixed a bug with `onDataLoadComplete` callback - fixed a bug with Video from previous story playing in background - fixed bugs with some parameters not set for the analytic events Improvements: - added `topInset` and `bottomInset` to grids - - please see [Themes](Themes.md) for more details - added better support for open/close transitions using `activityReentered` method ## **8.4.0** - 16/12/22 Bug Fixes: - fixed a bug with the loading spinner on Clips - fixed rare bug with Story transitions - fixed a bug that causes Quiz Pages to be answered when tapping back - fixed an animation bug with Story tiles Improvements: - improvements to the prefetching of Clips' playcard image - implemented a more efficient UserActivity reporting system - corrected the use case of thumbnails and playcards - implemented single ExoPlayer to improve video playback ## **8.3.4** - 21/12/22 Bug Fixes: - fixed a bug with `dismissPlayer` on Clips ## **8.3.3** - 21/12/22 Bug Fixes: - added Clip Collection and Clip Categories event data for all Clip analytic events ## **8.3.2** - 21/12/22 Bug Fixes: - Skipped Ad events are now recorded when tapping on an ad and swiping left on an ad Improvements: - added Current Category to the event data for Story and Ad analytic events ## **8.3.1** - 15/12/22 Improvements: - added support for multiple font weights - replaced SHARE_TAPPED event with SHARE_BUTTON_TAPPED for event consistency between Stories and Clips ## **8.3.0** - 06/12/22 Bug Fixes: - fixed bug when a user taps outside a Story border on Quizzes - fixed default value for `answeredMessageTextColor` - fixed bug with presenting rounded corners on Stories - fixed bug with tracking pixels Improvements: - added additional properties to ad events to help identify individual ads - improvements to the positioning of Action buttons - modified the `getAdsForList` callback to provide more ad information - adjusted the timestamp property so timescales are updated for each new Page ## **8.2.9** - 29/11/22 Bug Fixes: - implemented a solution for a shared animation bug on Android 5 to Android 9 when targeting `singleInstance` activity Improvements: - stability improvements ## **8.2.8** - 25/11/22 Bug Fixes: - fixed bug with Live Chip properties - fixed bug with `onCurrentVideoPlayerChanged` event - fixed bug that could cause a `NullPointerException` - fixed bug that could cause a Story to not be marked as read when quickly switching Pages ## **8.2.7** - 22/11/22 Bug Fixes: - fixed bug with Stories not opening in Single Story mode when using deep links - fixed bug with user activity reports with Stories launched via deep link - fixed bug that causes delegates to be disconnected in certain instances - fixed bug with opening Clips whilst reloading data ## **8.2.6** - 15/11/22 Bug Fixes: - fixed rare crash when opening player ## **8.2.5** - 11/11/22 Bug Fixes: - fixed bug with Story playback when tapping quickly through Stories - fixed bug that could cause rare adapter crash - fixed bug with video playback when changing user Deprecations and Changes: - ads are now requested after every Story to align ad call logic with iOS SDK ## **8.2.4** - 04/11/22 Bug Fixes: - fixed bug that could cause the same Story to play after ad insertion - fixed bug that could cause rare crash after opening webpage links - exposed classes needed to support React Native ## **8.2.3** - 01/11/22 Bug Fixes: - fixed bug that could cause crash when changing Page whilst the player is being closed - fixed bug that could cause tracking events to be started incorrectly Improvements: - reduced memory footprint to improve playback - stopped empty or failed items appearing in StorytellerHomeView on rare occasions - implemented improved retry policy Deprecations and Changes: - removed all open methods from StorytellerListViews to just leave static methods ## **8.2.2** - 25/10/22 Bug Fixes: - fixed rare crash when opening player Improvements: - improved logging ## **8.2.1** - 21/10/22 New Features: - implemented preloading for Stories and Clips - added support for selective Story caching for Story rows Bug Fixes: - fixed bug with the opening animations of Trivia Quizzes - fixed bug with fetching ads after using airplane mode - fixed bug with video playback getting stuck - fixed bug with view model that could cause Story crashes Improvements: - improved analytics handling - improvements in handling of DeepLinkManager - improvements in data use cases ## **8.2.0** - 11/10/22 New Features: - added support for Clips and Collections Bug Fixes: - fixed bug that delays dismissing of an opened Story - fixed inconsistencies with `theme.engagementUnits.poll.answeredMessageTextColor` - fixed bug with `theme.player.liveChipImage` and `theme.primitives.cornerRadius` on Poll answers and Story Action buttons - fixed bug that could cause Story to freeze when tapping fast - fixed bugs with fresh tenants swiping up on an ad and closing ads - fixed swipe down animation bug - fixed bug with preview transitions on tablets - fixed bug that could cause user to move to a Story row instead of a Story when closing web Improvements: - added refresh button to Story videos - new and improved mechanism for batch ad handing, audio manager focus handling, events and callbacks - improved handling of media sharing - new and improved mechanisms for analytics and opening Story/Page IDs - performance improvements for Stories and Clips loading times - adding theme `theme.tiles.rectangularTile.unreadIndicator.textSize` - making all tile themes respect UiStyle for all tile types - open/close shared transition handling improvements ## **8.1.15** - 26/10/22 Bug Fixes: - fixed a bug where it wasn't possible to select files inside the Action Web View New Features: - added support for External Action Links ## **8.1.14** - 17/10/22 New Features: - added the ability to open a clip or collection directly using the `openCollection` method ## **8.1.13** - 21/09/22 Bug Fixes: - fixed option to save Story assets from a Story not displaying correctly ## **8.1.12** - 01/09/22 Bug Fixes: - fixed Story sharing issue between iOS and Android - fixed Quiz sharing issues ## **8.1.11** - 24/08/22 Bug Fixes: - fixed occasional crash when closing player - fixed reload button visibility not working Improvements: - renamed `Storyteller.isPresentingStory` to `Storyteller.isPlayerVisible` UI Changes: - improving the Story Player UI on tablet ## **8.1.10** - 19/08/22 Bug Fixes: - fixed issue of duplicate apps when share sheet opens - fixed issue where a Page with a Poll plays audio from the previous Page after sharing - fixed issue of Poll sharing only working if the Poll has been answered and shared from another Android device - fixed issue of video playback not playing on Video Poll after watching a Video Page - fixed issue where the `SHARE_SUCCESS` event was not sent Improvements: - improving the Story Player UI - improving the share sheet UI Changes: - navigation buttons added back into Stories - status bar visible in Stories ## **8.1.9** - 12/08/22 Bug Fixes: - fixed an issue where the share button could be cropped on smaller devices - fixed issue of grid being cropped on navigation to more Stories - fixed an issue where a placeholder could be briefly visible after close animation is finished Improvements: - improved audio focus handling on Android API < 26 (Oreo) New Features: - added `contentLength` parameter for analytics events - please see [Analytics](Analytics.md) for more details UI Changes: - general UI refinements for Story Tiles and Story Player - ensured all grid tiles have corner radii ## **8.1.8** - 11/08/22 Bug Fixes: - fixing some minor issues with the tile layouts Improvements: - improving the audio focus handling when the Story Player is open ## **8.1.7** - 05/08/22 Improvements: - update client ads `getAdsForList` public api - improve polls UI ## **8.1.6** - 02/08/22 New Features: - add support for circular tile border theming - add support for circular tile custom live chip theming ## **8.1.5** - 02/08/22 Bug Fixes: - fix a crash caused by triggering web action button - improve story player UI - fix row/grid `displayLimit` not working for placeholders ## **8.1.4** - 27/07/22 Bug Fixes: - fix row/grid placeholders styling for light/dark modes Improvements: - improve light/dark mode theme compatibility ## **8.1.3** - 25/07/22 New Features: - add support for multiple row/grids with the same categories ## **8.1.2** - 25/07/22 Bug Fixes: - fix bug where sometimes no Stories could be shown when categories are set Improvements: - improve performance or rows/grids inside recycler views ## **8.1.1** - 22/07/22 Bug Fixes: - fixing a rare crash which could occur when tapping on a web action button - fixing an issue where the share button for Polls was misaligned UI Changes: - improving the layout of the round Story tile items Deprecations and Changes: - removing `topInset` and `bottomInset` from grids ## **8.1.0** - 13/07/22 Improvements: - improving the video playback experience ## **8.0.3** - 11/07/22 Bug Fixes: - fix bug where live chip was incorrect for read/unread story tiles - fix inconsistent round tile sizes for varying lines of title text - fix title text alignment in round tiles ## **8.0.2** - 28/06/22 Bug Fixes: - fixing an issue where it was possible for a progress indicator to stay on a Poll page - fixing an issue where the live chip was not aligned correctly on a tile - fixing an issue with `lists.grid.columns` in theme ## **8.0.1** - 24/06/22 Bug Fixes: - fixing an issue with R8 and the SDK package name - fixing an issue where it was possible for video playback to not start when viewing a video Page - fixing an issue where it was possible for Stories not not be marked as Read - fixing an issue where it was possible for video playback to start in the background - fixing an issue where it was possible for video playback to freeze unexpectedly - fixing an issue where it was possible for the instructions screen to layout incorrectly - fixing an issue where it was possible for the share button to not be clickable in certain scenarios Improvements: - improving the performance of the Story Player New Features: - adding the ability to enable/disable Storyteller event tracking via the SDK method `enableEventTracking()` and `disableEventTracking` - please see [Additional Methods](AdditionalMethods.md) for more information - adding action buttons instead of swipe ups for Stories - adding `displayLimit` parameter to `StorytellerListView` - please see [Configuring a StorytellerListView](StorytellerListViews.md) UI Changes: - improving the UI for Polls and Quizzes Deprecations and Changes: - renamed callbacks in `StorytellerListViewDelegate` - please see [Implementing StorytellerListViewDelegate Callbacks](StorytellerDelegates.md) ## **7.1.0** - 29/04/22 Bug Fixes: - fixing an issue with the instructions screen UI New Features: - adding support for Live Stories - adding additional attributes to support Live Stories theming - please see [Custom Themes](Themes.md) for details UI Changes: - improving UI/UX experience in Story Player ## **7.0.3** - 12/04/22 Bug Fixes: - fixing a dependency clash with Koin library ## **7.0.2** - 0/04/22 Bug Fixes: - fixing an issue with ReactNative ## **7.0.1** - 04/04/22 Bug Fixes: - fixing an issues with quizzes and theme colors - fixing an issue where the `UiTheme` had obfuscated name ## **7.0.0** - 01/04/22 Bug Fixes: - fixing an issue where is was possible foe the new indicators to appear on placeholders - improving the category caching for faster load times - fixing a crash which could occur on initialization - fixing an issue where it was possible for the global delegate not to register - fixing an issue where it was possible for the share button to disappear after swiping up on a Story Improvements: - making it easier to define a custom theme - please see [Themes](Themes.md) for details New Features: - adding support for Trivia Quizzes ## **6.2.0** - 25/02/22 Improvements: - improved support for multiple `StoryListViews` inside a RecyclerView ## **6.1.4** - 14/03/22 Bug Fixes: - fixing an issue with the navigation for the video pages on Android 12 ## **6.1.3** - 08/03/22 Bug Fixes: - fixing a crash which could occur on the open/close story animation - fixing an issue with the `primaryTextColor` - fixing a crash which could occur when opening the web link ## **6.1.2** - 23/02/22 Bug Fixes: - fixing obfuscation issues ## **6.1.1** - 23/02/22 Bug Fixes: - fixing an issue with the reloading of the stories list - fixing an issue with the story tiles flickering when reloading Improvements: - improving the story tile resizing Deprecations and Changes: - `pagesViewedCount` is public again ## **6.1.0** - 16/02/22 Bug Fixes: - fixing an issue with navigation which could occur when ads are enabled - fixing an issue with poll vote calculations - fixing a bug where the story list tiles were not updated properly on older Android versions - fixing an issue with the colors from the theme not being applied properly for `StorytellerListViews` Deprecations and Changes: - `uiStyle` parameter moved to `StorytellerListView` (see here for more details [Configuring a StorytellerListView](StorytellerListViews.md) ## **6.0.2** - 07/02/22 Bug Fixes: - fixing obfuscation issues ## **6.0.1** - 03/03/22 Bug Fixes: - fixing an issue which could occur when opening a story when in the landscape orientation - fixing a crash which could occur on the open/close story animation Improvements: - improving the poll UI - improving the UI on the devices with notch ## **6.0.0** - 17/01/22 Improvements: - Improving the scaling behavior of story tiles - Improvements and bugfixes in how themes are handled New Features: - Added `StorytellerGridView` - Adding the ability to override the theme on a per list basis Deprecations and Changes: - Public API breaking changes: + package `com.storyteller.ui.row` renamed to `com.storyteller.ui.list` + `StorytellerRowViewDelegate` renamed to `StorytellerListViewDelegate` + `StorytellerDelegate`'s `getAdsForRow` renamed to `getAdsForList` + `StorytellerRowView`'s `insetRight` and `insetLeft` renamed correspondingly to `insetStart` and `insetEnd` + `Theme` class' property `row` renamed to `list` + `RowViewTheme` class renamed to `ListViewTheme` + `StorytellerRowViewCellType` renamed to `StorytellerListViewCellType` + `StorytellerRowViewStyle` renamed to `StorytellerListViewStyle` + `OpenedReason`'s enum value `STORY_ROW_TAPPED` renamed to `STORY_LIST_TAP`, tracking string `story_row_tapped` renamed to `story_list_tapped` + `StoryPlaybackMode`'s enum value `ROW` renamed to `LIST`, tracking string: `row` renamed to `list` ## **5.0.8** - 08/02/22 Bug Fixes: - fix for race conditions in case of rapid onPause/onResume scenarios ## **5.0.7** - 31/01/22 Bug Fixes: - fixing a potential issue which could occur when running unit tests with Storyteller ## **5.0.6** - 25/01/22 Bug Fixes: - fixing dependency issues ## **5.0.5** - 03/01/22 Bug Fixes: - fixing a concurrency issue ## **5.0.4** - 15/12/21 Bug Fixes: - fixing a crash which could occur when skipping through the pages fast ## **5.0.3** - 14/12/22 Bug Fixes: - fixing a crash which could occur when invoking `Storyteller.initialize` multiple times ## **5.0.2** - 02/12/21 Bug Fixes: - fixing a bug with the user activity data parsing Deprecations and Changes: - removing all `jcenter()` dependencies ## **5.0.1** - 24/11/21 Bug Fixes: - fixing a UI glitch where the initial story page could be shown when switching between the pages - fixing a crash which could happen when using delegates, `configureSwipeUpWebView(...)` is now mandatory to override - fixing a bug which could occur when using Storyteller ads ## **5.0.0** - 18/11/21 Improvements: - improving sending of analytics events New Features: - adding support for categories - adding support for opening the story player in single story mode without the `StorytellerRowView` - please see [AdditionalMethods](AdditionalMethods.md) for details - adding additional param `storyPlaybackMode` to analytics events - please see [Analytics](Analytics.md) for details Deprecations and Changes: - splitting `StorytellerListViewDelegate` into two separate delegates - please see [StorytellerDelegate](StorytellerDelegates.md) and [StorytellerListViewDelegate](StorytellerDelegates.md) for details - removing the `cellScale` parameter from the `StorytellerRowView` in favour of auto adapting to the parent's view height - removing callbacks from the `reloadData()` method ## **4.5.11** - 03/11/21 Bug Fixes: - fixing a crash which could occur when sharing the story on Android 12 ## **4.5.10** - 02/11/21 New Features: - adding additional method `configureSwipeUpWebView(webview: Webview, url : String?, favicon: Bitmap?)` to allow additional configuration of the WebView - please see [StorytellerListViewDelegate](StorytellerDelegates.md) ## **4.5.9** - 20/10/21 Bug Fixes: - fixing a crash which could occur when opening the story and scrolling the row in a container ## **4.5.8** - 13/10/21 Bug Fixes: - fixing a problem with ads and navigation - fixing a UI issue with the rounded corners on tablets ## **4.5.7** - 30/09/21 Bug Fixes: - fixing a potential clash with resource names - fixing a crash which could occur when using a malformed swipe up url ## **4.5.6** - 24/09/21 Bug Fixes: - fixing Windows build issue originating in the kotlinModule compilation artifact containing characters disallowed in windows filesystem ## **4.5.5** - 13/09/21 Bug Fixes: - fixing a bug where the swipe up to `in_app` links was not working New Features: - adding additional method `openDeepLink(String,Boolean,(Error) -> Unit)` to make Storyteller SDK integration easier - please see [AdditionalMethods](AdditionalMethods.md) and [Deep Linking](DeepLinking.md) - adding `secondaryColor` property to themes - please see [Themes](Themes.md) for details ## **4.5.4** - 31/08/21 Bug Fixes: - fixing a bug where the story could not be closed if the delegate was not set - fixing a bug where the poll percentages could show invalid values when the orientation is changed - fixing a rare crash which could occur when opening poll stories Improvements: - deep linking documentation update - please see [Deep Linking](DeepLinking.md) ## **4.5.3** - 11/08/21 Bug Fixes: - fixing a packaging issue ## **4.5.2** - 11/08/21 Bug Fixes: - fixing a bug where the poll answers could be cut on some devices New Features: - adding additional method `isStorytellerDeepLink(String)` to make Storyteller SDK integration easier - please see [AdditionalMethods](AdditionalMethods.md) ## **4.5.1** - 06/08/21 Bug Fixes: - fixing a tablet bug where the story player could be frozen when changing orientation ## **4.5.0** - 05/08/21 Improvements: - minor performance improvements ## **4.4.2** - 03/08/21 Bug Fixes: - fixing a bug where the `COMPLETED_STORY` event could be triggered too early Improvements: - improving the story tile loading states ## **4.4.1** - 28/07/21 Improvements: - improve in app swipe up handling New Features: - add `animated` parameter for `openPage` and `openStory` public methods ## **4.4.0** - 21/07/21 Bug Fixes: - fixing a bug which could occur when updating the story with new pages - fixing a UI issue with the instructions screen which could appear on some smaller tablets - fixing a bug where the story could be opened multiple times Improvements: - resources can be used to set the theme properties - please see [Themes](Themes.md) for details ## **4.3.3** - 06/07/21 Bug Fixes: - fixing a crash which could occur during the preload of images ## **4.3.2** - 02/07/21 Bug Fixes: - added another fix for crash caused by connection timeouts - fixing a bug where the swipe ups were sometimes not working ## **4.3.1** - 02/07/1 Bug Fixes: - added a fix for crash caused by connection timeouts ## **4.3.0** - 01/07/21 Bug Fixes: - fixing a crash which could occur when using custom ads - fixing a bug where the poll answers could start flickering Improvements: - improving the page loading states ## **4.2.0** - 10/06/21 Improvements: - internalizing the Exoplayer `2.14.0` to remove any version conflicts with the client applications New Features: - adding support for themes - please see [Themes](Themes.md) for details - adding additional methods to make Storyteller SDK integration easier - please see [AdditionalMethods](AdditionalMethods.md) ## **4.1.3** - 14/04/21 Bug Fixes: - fixed a bug where the poll video could be played after changing the page - fixed a crash which could occur when changing the orientation on tablets Improvements: - improved poll UI and animations ## **4.1.2** - 02/04/21 Bug Fixes: - fixed an issue with tile animation being clipped UI Changes: - extended the page duration when a user selects poll answer - improved poll UI and animations ## **4.1.1** - 24/03/21 Bug Fixes: - fixing a bug where the navigation on poll pages could stop working Improvements: - improving the network request format ## **4.1.0** - 19/03/21 Bug Fixes: - fixing a crash which could occur when sending analytics - fixing a bug where the instructions modal button could appear too low on tablets - fixing a bug where the story player could be closed when navigating through the stories which were read Improvements: - improving the poll UI ## **4.0.5** - 01/03/21 Bug Fixes: - fixing a crash which could occur when opening a story Improvements: - improving the animation when swiping between stories for Android 11 devices ## **4.0.4** - 15/02/21 Bug Fixes: - fixing a crash which could occur when opening a story from a deep link ## **4.0.3** - 11/02/21 Improvements: - improving the preloading of videos on poll pages - improving sharing to different apps ## **4.0.2** - 04/02/21 Bug Fixes: - fixing a crash which could occur when closing the story view - fixing a bug where changing the `rowScale` property on the Storyteller Row had no effect ## **4.0.1** - 01/12/21 Bug Fixes: - fixing some visual bugs which could occur on the poll pages - fixing a crash which could occur when closing the story view New Features: - adding support for video backgrounds on poll pages ## **4.0.0** - 25/01/21 Bug Fixes: - fixing a bug where it was possible for swipe ups to stop working - fixing a crash which could occur when saving analytics data Improvements: - performance improvements for video and image pages New Features: - adding support for poll pages - adding support for multiple instances of the Story Row in a single app Deprecations and Changes: - changing the method signatures of `openStory`, `openPage` and `reloadData` ## **3.17.5** - 15/12/20 Bug Fixes: - fixing a bug where the in-app browser address bar could be white in night mode ## **3.17.4** - 10/12/20 Bug Fixes: - fixing a bug where the read stories could be shown when tapping on unread story - fixing a bug where the profile image was not shown for ads - fixing a bug where the swipe up UI could be shown even if it was disabled - fixing a bug with the onboarding screen margins ## **3.17.2** - 10/12/20 Bug Fixes: - fixed a bug where an incorrect border color is applied to a story row item after it became read upon closing the story viewer ## **3.17.1** - 30/11/20 Bug Fixes: - fixing a crash which could occur when preloading videos for stories ## **3.17.0** - 27/11/20 Bug Fixes: - fixing a bug which could cause video playback to resume when a user was swiping to change stories - fixing a crash which could occur when preloading videos for ads - fixing a crash for some customers which could occur during swiping on Android 11 ## **3.16.4** - 18/11/20 Bug Fixes: - fixing a bug where the blank page was opened instead of a story ## **3.16.3** - 18/11/20 Bug Fixes: - fixing potential null pointer exceptions - fixing a bug where the Play Store was not opened, when swiping up on a page with redirect - fixing a bug where the `resumedAdPage` event was triggered too often - fixing a bug where the read status of a story was not saved ## **3.16.2** - 06/11/20 Bug Fixes: - fixing a bug introduced in 3.16.0 where it was possible for incorrect tracking pixels to be called for integrating app ads Improvements: - improving video preloading with the new version of Exoplayer Deprecations and Changes: - not sending extra parameters to tracking pixels supplied via integrating app ads ## **3.16.1** - 02/11/20 Bug Fixes: - fixing a crash which could occur when swiping to interact with the Story View ## **3.16.0** - 30/10/20 Bug Fixes: - fixing a crash which could occur when watching stories and changing from an image page to a video page - fixing the alignment of the dismiss icon within the story view relative to the story title - fixing a bug where if the system dark mode was changed when the story view was opened, the current story would persist behind other stories - fixing a bug where the new tag could not be removed from the story row tiles in certain circumstances Improvements: - improving the performance of the story view when quickly transitioning between pages New Features: - adding video playback events - please see [Analytics](Analytics.md) for details ## **3.15.5** - 06/11/20 Bug Fixes: - fixing a bug introduced in 3.15.3 where it was possible for incorrect tracking pixels to be called for integrating app ads ## **3.15.4** - 04/11/20 Bug Fixes: - fixing a crash which could occur when swiping to interact with the Story View ## **3.15.3** - 02/11/20 Bug Fixes: - fixing a crash which could occur when swiping to interact with the Story View ## **3.15.2** - 29/10/20 Bug Fixes: - fixing a crash which could occur when swiping to interact with the Story View - upgrading Exoplayer dependency to version 2.11.3 ## **3.15.1** - 22/10/20 Improvements: - improving the smartphone onboarding layout - improving the animation when story is opened - improving the preloading of videos Deprecations and Changes: - adjusting the initialize method errors - see [Getting Started](GettingStarted.md) for more details ## **3.15.0** - 16/10/20 Bug Fixes: - fixing a bug where rounded corners were not displayed correctly in the story view - fixing a bug on Android 8 where it was possible to see the story image as a background in the story view Improvements: - improving the preloading of stories in the story view - improving the video cache - improving the animation when a story is dismissed and returned to the row view - improving the sensitivity of the swipe up gesture ## **3.14.1** - 09/10/20 Bug Fixes: - fixing a bug where the instructions modal could be dismissed if the app was sent to the background when it was visible - fixing a bug where it was possible for tiles in the row to render incorrectly in certain situations Improvements: - improving the error handling around the `initialize` flow to reduce the likelihood of any errors when this method is called ## **3.14.0** - 02/10/20 Bug Fixes: - fixing a bug where it was possible for a user to be presented with a black screen with no loading indicator when opening the story view and on a slow connection - fixing a bug where it was possible for a loading indicator not to be presented when viewing an image page in certain circumstances over a slow/unreliable network Improvements: - improving the handling of retries when assets in the story view fail to load Deprecations and Changes: - adjusting the behavior of the instructions modal on tablets so that it appears over the story view rather than over the place in the app where it was presented from - cancelling requests to preload content for the story view if the user is on a slow network connection in order to maximize the chance they are able to view the content they have on screen ## **3.13.1** - 29/09/20 Bug Fixes: - fixing a bug where it was possible for tiles to appear blank in the story carousel - removing a memory leak from within the SDK - fixing a bug where if the `rowScale` for a row was changed while the row was being rendered, the story tiles could appear at the wrong size - fixing a bug which could cause the SDK to crash in certain circumstances - fixing a bug where it was possible to get stuck in the story view when skipping through a large number of stories and pages - fixing a bug where it was possible for the story view to get stuck whilst dismissing Improvements: - improving the behavior of the SDK with respect to transitions when a non-translucent status bar is used for the activity where the row view is displayed - improving the animation when a story is dismissed and returned to the row view ## **3.13.0** - 18/09/20 Bug Fixes: - fixing a memory leak within the SDK Improvements: - further improvements to the transitions between tiles in the story row and the story view New Features: - adding full and intentional tablet support to the SDK ## **3.12.0** - 04/09/20 Improvements: - improving email sharing by including the story title in any email sharing copy - improving the reliability of directing users to the Play Store from a swipe up action Deprecations and Changes: - exposing Story Categories on the `getAdsForRow` callback - please see the [Ads](Ads.md) documentation for more details ## **3.11.0** - 24/08/20 Bug Fixes: - fixing a bug where it was possible for an incorrect value to be passed for `openedReason` when a page completed - fixing a bug where it was possible for `completedStory` events not to fire when a user completed a story - fixing a bug where it was possible for an incorrect value to be passed to `dismissedReason` when a user completed the final page of the final story Improvements: - improving the open animation when a user taps a tile in the Story View New Features: - adding support for swiping up to a location within the integrating app or to the App Store from within Client Supplied Ads - see [Ads](Ads.md) for more details Deprecations and Changes: - adjusting the initialization flow of the SDK - see [Getting Started](GettingStarted.md) for more details ## **3.10.1** - 07/08/20 Bug Fixes: - fixing a bug where stories were not marked as read until `reloadData` was called - fixing a bug where it was possible for a story to be marked as unread when a page was removed from the story - fixing a crash which could occur when retrieving or persisting encrypted data - fixing a bug where it was possible for the Story View to open in landscape mode New Features: - adding a method to the [StorytellerListViewDelegate](StorytellerDelegates.md) called `tileBecameVisible` to allow the integrating app to be notified when a tile is visible on screen ## **3.9.0** - 31/07/20 Bug Fixes: - fixing a bug where text was misaligned when using square tiles - fixing a bug where it was possible to swipe twice on the final story and end up on a blank screen Improvements: - UI improvements to the Instructions Screen - improving the reliability of the activity tracking Deprecations and Changes: - adjusting the [StorytellerListViewDelegate](StorytellerDelegates.md) `getAdsForRow` callback to allow it to accept `null` values for `StorytellerAd` ## **3.8.3** - 27/07/20 New Features: - adding `dismissStoryView` method to `StorytellerRowView` to allow dismissing of any currently open Story View ## **3.8.0** - 15/07/20 Improvements: - ensuring the user's device doesn't sleep when watching stories - improving the experience when sharing via email UI Changes: - increasing the size of the tap target for the Swipe Up UI ## **3.7.4** - 14/07/20 Bug Fixes: - fixing a bug where it was possible for a blank screen to be displayed when pushing the device back button on the instructions screen - fixing a bug where it was possible for the story view to render at the size of a story tile - fixing a bug where it was possible for `userSwipedUpToApp` to be triggered when navigating back to the Story View from the deeplink destination ## **3.7.3** - 10/07/20 Improvements: - improving the performance of `getAdsForRow` so that it doesn't wait for the ads to be ready before opening the Story View ## **3.7.2** - 08/07/20 Bug Fixes: - fixing a crash when opening the Story View on Android 8 - fixing a bug where it was challenging to swipe up on stories - fixing a bug where the dismiss animation from the stories row did not happen in some situations - fixing a bug where it was possible for tracking pixels to be called multiple times in certain scenarios ## **3.7.1** - 07/07/20 Bug Fixes: - fixing a bug where if the `StorytellerRowView` was within a `RecyclerView` it was possible for there to be unexpected behavior when the view was recycled Improvements: - improving the performance of `setUserDetails` ## **3.7.0** - 06/07/20 Bug Fixes: - fixing a bug where the `adId` parameter was not present in certain scenarios for ad analytics events Improvements: - ensuring that `FinishedAd` also fires at the same time as `PreviousAd` - UI improvements and bug fixes New Features: - adding the `advertiserName` parameter to ad analytics events Deprecations and Changes: - rewriting the transition from the Story Row to the Story View ## **3.6.0** - 01/07/20 Bug Fixes: - fixing a bug where it was possible for Story events to fire for Ads - fixing a bug where it was possible for the SDK to crash when rendering certain client supplied ads - fixing a bug where it was possible for the SDK to crash when adding or removing pages to already published stories Improvements: - improving fault tolerance around network timeouts - identifying and fixing 2 memory leaks within the SDK Deprecations and Changes: - exposing a new `FinishedAd` event which rolls up the `DismissedAd`, `SkippedAd` and `ViewedAdPageComplete` events so it's easier to tell when an ad is not being presented to the user ## **3.5.2** - 20/06/20 - adding the ability to supply custom tracking pixels as part of the `getAdsForRow` request - improving the interaction between the SDK and the status bar - fixing a UI bug where the gradients in the loading tiles would not render correctly - fixing a crash on Android 8 ## **3.5.0** - 24/06/20 Bug Fixes: - fixing a bug where it was possible for the close button and native back button to work if the Story View was triggered from landscape mode - fixing a crash which could occur when swiping to particular stories - fixing a bug where the Story View did not exit when a user finished watching all of their unread stories - fixing a bug where single page stories were not being marked as read correctly - fixing a crash which could occur when closing the Story View Improvements: - improved the loading performance of ads UI Changes: - UI improvements and bugfixes ## **3.4.0** - 17/06/20 Bug Fixes: - fixing a crash which occurred when opening the Story View on Android 8 New Features: - UI improvements and bugfixes UI Changes: - adding support for swiping up to the App Store or to a location within the integrating app from a page or an ad - see [StorytellerRowViewDelegate](StorytellerDelegates.md) for more details ## **3.3.0** - 11/06/20 Bug Fixes: - fixing some bugs in the page navigation behavior around which pages are opened when stories are read/unread - fixing a bug where it was possible for `durationViewed` to be `0.0` in situations where the user had viewed content Improvements: - improving the transitions when tapping a tile in the story row to open the Story View and when pulling down to dismiss the Story View - improvements to preloading of stories to make swiping and tapping through the Story View feel much smoother - encrypting user access tokens for the Storyteller API at rest - ensuring `openedReason` is included on `OpenedAd` events - ensuring `openedReason` is included on `OpenedPage` events - ensuring that `DismissedAd` events report the `durationViewed` and `pagesViewedCount` properties New Features: - adding a `ShareSuccess` event to the SDK - adding the ability to supply ads to the SDK via the `getAdsForRow` callback on the [StorytellerRowViewDelegate](StorytellerDelegates.md) - adding new events for tracking user progress through ads - see [Analytics](Analytics.md) for specifics Deprecations and Changes: - when calling `openPage` or `openStory`, the `onFailure` callback will be called if the ID passed to `openPage` or `openStory` doesn't match a valid story or page - removing the `onSuccess` callback from `openPage` and `openStory`. If you would like to handle this case then please implement the `onUserActivityOccurred` callback and listen for `openedStory` or `openedPage` events with an `openedReason` of `deeplink` ## **3.2.0** - 03/06/20 Bug Fixes: - fixing a bug where the `openedReason` analytics property could have an incorrect value when the user swiped between stories Improvements: - improving the rendering performance of the StoryRowView when using the square layout ## **3.1.0** - 28/05/20 Bug Fixes: - fixing issue where `UserActivityData` class did not have publicly visible members New Features: - adding support for first-party ads loaded from the Storyteller CMS - emitting events related to ads playback via the `onUserActivityOccurred` callback - adding the ability to disable the story icon in the story view on a per-tenant basis - adding the ability to include a timestamp in the story view on a per-tenant basis - adding the ability to share a link configured in the CMS for each page (rather than the page's media) and the ability to configure this on a per-tenant basis ## **3.0.0** - 20/05/20 New Features: - (beta) adding `onUserActivityOccurred` callback to `StorytellerRowViewDelegate` so the containing app can receive notifications when users take actions within the SDK Deprecations and Changes: - removing `onChannelsDataLoadStarted`, `onChannelsDataLoadComplete` and `onChannelDismissed` callbacks from `StorytellerRowViewDelegate` - adding `onStoriesDataLoadStarted`, `onStoriesDataLoadComplete`, and `onStoryDismissed` callbacks to `StorytellerRowViewDelegate` - renaming public method `openChannel` to `openStory` - moving the store of read/unread pages from the server to the client ## Source: javadoc.md # Storyteller Javadoc Welcome to the Storyteller Javadoc. Please find the links to the relevant documentation below: - [Main SDK Documentation](dokka/sdk/html/index.html) - [Ads Module Documentation](dokka/ads/html/index.html)