diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 2911dd7960..dd9f6cfce9 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -120,13 +120,13 @@ jobs: ./gradlew :stream-video-android-core:testDebugUnitTest --scan --stacktrace - name: Unit tests core results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: unit-tests-core-results path: stream-video-android-core/build/reports/tests/testDebugUnitTest/index.html - name: Unit tests compose results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: unit-tests-compose-results path: stream-video-android-ui-compose/build/reports/tests/testDebugUnitTest/index.html diff --git a/.github/workflows/app-distribute.yml b/.github/workflows/app-distribute.yml index f67dcc418e..ccc307da8b 100644 --- a/.github/workflows/app-distribute.yml +++ b/.github/workflows/app-distribute.yml @@ -27,7 +27,7 @@ jobs: - name: Assemble run: bash ./gradlew :demo-app:assembleRelease --stacktrace - name: Upload APK - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: demo-app-release path: demo-app/build/outputs/apk/demo-app/release/ diff --git a/.github/workflows/artifact-upload.yaml b/.github/workflows/artifact-upload.yaml index e0979be1e4..70a0e54121 100644 --- a/.github/workflows/artifact-upload.yaml +++ b/.github/workflows/artifact-upload.yaml @@ -35,7 +35,7 @@ jobs: run: ./gradlew bundleRelease --stacktrace - name: Upload AAB as artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: app-bundle path: demo-app/build/outputs/bundle/productionRelease/demo-app-production-release.aab diff --git a/build-logic/convention/src/main/kotlin/io/getstream/video/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/io/getstream/video/AndroidCompose.kt index 659a289666..a14f1234f2 100644 --- a/build-logic/convention/src/main/kotlin/io/getstream/video/AndroidCompose.kt +++ b/build-logic/convention/src/main/kotlin/io/getstream/video/AndroidCompose.kt @@ -8,6 +8,7 @@ import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.getByType import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension +import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag /** * Configure Compose-specific options @@ -31,7 +32,7 @@ internal fun Project.configureAndroidCompose( } extensions.configure { - enableStrongSkippingMode = true + featureFlags.addAll(ComposeFeatureFlag.StrongSkipping, ComposeFeatureFlag.IntrinsicRemember) reportsDestination = layout.buildDirectory.dir("compose_compiler") stabilityConfigurationFile = rootProject.layout.projectDirectory.file("compose_compiler_config.conf") } diff --git a/buildSrc/src/main/kotlin/io/getstream/video/android/Configuration.kt b/buildSrc/src/main/kotlin/io/getstream/video/android/Configuration.kt index 7d2bbfd85c..fba92c7b00 100644 --- a/buildSrc/src/main/kotlin/io/getstream/video/android/Configuration.kt +++ b/buildSrc/src/main/kotlin/io/getstream/video/android/Configuration.kt @@ -6,11 +6,11 @@ object Configuration { const val minSdk = 24 const val majorVersion = 1 const val minorVersion = 0 - const val patchVersion = 13 + const val patchVersion = 14 const val versionName = "$majorVersion.$minorVersion.$patchVersion" - const val versionCode = 37 + const val versionCode = 38 const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion + 1}-SNAPSHOT" const val artifactGroup = "io.getstream" - const val streamVideoCallGooglePlayVersion = "1.1.6" + const val streamVideoCallGooglePlayVersion = "1.1.7" const val streamWebRtcVersionName = "1.1.1" } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/LandscapeControls.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/LandscapeControls.kt index 67379e3d52..2fdf65f63f 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/LandscapeControls.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/LandscapeControls.kt @@ -29,7 +29,6 @@ import androidx.compose.material.icons.filled.CallEnd import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview @@ -48,7 +47,6 @@ import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall import io.getstream.video.android.tooling.extensions.toPx -@OptIn(ExperimentalComposeUiApi::class) @Composable fun LandscapeControls(call: Call, onDismiss: () -> Unit) { val isCameraEnabled by call.camera.isEnabled.collectAsStateWithLifecycle() diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/LayoutChooser.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/LayoutChooser.kt index b4e135cd73..d6c9539fec 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/LayoutChooser.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/LayoutChooser.kt @@ -14,13 +14,10 @@ * limitations under the License. */ -@file:OptIn(ExperimentalLayoutApi::class) - package io.getstream.video.android.ui.call import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AutoAwesome diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/ParticipantsDialog.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/ParticipantsDialog.kt index ae39e7b058..be87934629 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/ParticipantsDialog.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/ParticipantsDialog.kt @@ -18,7 +18,6 @@ package io.getstream.video.android.ui.call import android.content.ClipboardManager import android.content.Context -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -100,7 +99,6 @@ fun ParticipantsList(call: Call) { ParticipantsListContent(call, clipboardManager, participants) } -@OptIn(ExperimentalFoundationApi::class) @Composable fun ParticipantsListContent( call: Call, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginScreen.kt index 0730d5b048..bf37897108 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/login/LoginScreen.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -48,7 +47,7 @@ import androidx.compose.material.icons.filled.Email import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.outlined.Adb import androidx.compose.material.icons.outlined.GroupAdd -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -408,7 +407,7 @@ private fun BuiltInUsersLoginDialog( .fillMaxWidth() .clickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(bounded = true), + indication = ripple(bounded = true), onClick = { login(true, LoginEvent.SignIn(user)) onDismissRequest() @@ -439,7 +438,6 @@ private fun BuiltInUsersLoginDialog( ) } -@OptIn(ExperimentalLayoutApi::class) @Composable fun SelectableDialog( items: List, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt index c3fa19bb3e..7447eff3ce 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt @@ -39,7 +39,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview @@ -63,7 +62,7 @@ import io.getstream.video.android.util.filters.SampleAudioFilter import kotlinx.coroutines.launch import java.nio.ByteBuffer -@OptIn(ExperimentalComposeUiApi::class, ExperimentalPermissionsApi::class) +@OptIn(ExperimentalPermissionsApi::class) @Composable internal fun SettingsMenu( call: Call, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index 786c3fcf6e..7d11d48e0e 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -206,6 +206,7 @@ object StreamVideoInitHelper { ) authData.token }, + appName = "Stream Video Demo App", ).build() } } diff --git a/docusaurus/docs/Android/03-guides/07-querying-calls.mdx b/docusaurus/docs/Android/03-guides/07-querying-calls.mdx index c9c12fc39e..07a279560b 100644 --- a/docusaurus/docs/Android/03-guides/07-querying-calls.mdx +++ b/docusaurus/docs/Android/03-guides/07-querying-calls.mdx @@ -25,6 +25,16 @@ val sort = listOf(SortField.Asc("starts_at")) val result = client.queryCalls(filters=filters, sort=sort, limit=10, watch=true) ``` +**Calls that are ongoing / currently have participants** + +```kotlin +client.queryCalls(mapOf("ongoing" to true)).let { result -> + result + .onSuccess { calls: QueriedCalls -> Log.d(TAG, "Query success: $calls") } + .onError { error: Error -> Log.e(TAG, "Query failure: ${error.message}") } +} +``` + **Calls filters on a custom property** ```kotlin @@ -36,6 +46,7 @@ val result = client.queryCalls(filters=filters, sort=sort, limit=10, watch=true) ``` **Pagination** + The query response is paginated and the maximum count of items is defined by the `limit` parameter. Use the `prev` and `next` parameters from the last response as parameters for requesting the next page. @@ -54,12 +65,6 @@ val resultPage2 = client.queryCalls( ) ``` -**Calls that live/ currently have participants** - -```kotlin -TODO -``` - ### Fields for Query Calls You can filter on the following fields @@ -78,15 +83,15 @@ You can filter on the following fields | `members` | Check if you are a member of this call | | `custom` | You can query custom data using the "custom.myfield" syntax | -Sorting is supported on these fields below: +Sorting is supported on the fields below: -* starts_at -* created_at -* updated_at -* ended_at -* type -* id -* cid +* `starts_at` +* `created_at` +* `updated_at` +* `ended_at` +* `type` +* `id` +* `cid` If you specify `watch` the SDK will automatically keep the data about these calls updated. This allows you to show a live preview of who's in the call. \ No newline at end of file diff --git a/docusaurus/docs/Android/06-advanced/08-events.mdx b/docusaurus/docs/Android/06-advanced/08-events.mdx index 09fb99a9bb..4ca216ce87 100644 --- a/docusaurus/docs/Android/06-advanced/08-events.mdx +++ b/docusaurus/docs/Android/06-advanced/08-events.mdx @@ -3,7 +3,7 @@ title: Events description: How to listen to events --- -In most cases you can simply use the stateflow objects Stream exposes. +In most cases you can simply use the `Stateflow` objects Stream exposes. However for some customizations you'll want to listen to the underlying events that power these state objects. ### Listening to events @@ -19,7 +19,7 @@ val sub = client.subscribe { event: VideoEvent -> sub.dispose() ``` -You can also subscribe for a specific call +You can also subscribe to call events. ```kotlin val call = client.call("default", "123") @@ -30,11 +30,11 @@ val sub = call.subscribe { event: VideoEvent -> sub.dispose() ``` -Or listen to a specific event +Or listen to a specific event. ```kotlin -val sub = client.subscribeFor { newMessageEvent -> - logger.d { newMessageEvent.toString() } +val sub = client.subscribeFor { event -> + logger.d { event.toString() } } // stop listening sub.dispose() @@ -42,6 +42,45 @@ sub.dispose() ### Events -The following events are triggered by the client: +The following events are emitted by the client: -TODO \ No newline at end of file +| Event Name | Description | +|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `BlockedUserEvent` | This event is sent to call participants to notify when a user is blocked on a call. Clients can use this event to show a notification. If the user is the current user, the client should leave the call screen as well. | +| `CallAcceptedEvent` | This event is sent when a user accepts a notification to join a call. | +| `CallCreatedEvent` | This event is sent when a call is created. Clients receiving this event should check if the ringing field is set to true and if so, show the call screen. | +| `CallEndedEvent` | This event is sent when a call is marked as ended for all its participants. Clients receiving this event should leave the call screen. | +| `CallHLSBroadcastingStartedEvent` | This event is sent when call HLS broadcasting has started. | +| `CallHLSBroadcastingStoppedEvent` | This event is sent when call HLS broadcasting has stopped. | +| `CallHLSBroadcastingFailedEvent` | This event indicates that call HLS broadcasting has failed. | +| `CallLiveStartedEvent` | This event is sent when a livestream has started. | +| `CallMemberAddedEvent` | This event is sent when one or more members are added to a call. | +| `CallMemberRemovedEvent` | This event is sent when one or more members are removed from a call. | +| `CallMemberUpdatedEvent` | This event is sent when one or more members are updated. | +| `CallMemberUpdatedPermissionEvent` | This event is sent when one or more members get their role capabilities updated. | +| `CallReactionEvent` | This event is sent when a reaction is sent in a call, clients should use this to show the reaction in the call screen | +| `CallRecordingStartedEvent` | This event is sent when call recording has started. | +| `CallRecordingStoppedEvent` | This event is sent when call recording has stopped. | +| `CallRecordingReadyEvent` | Indicates that a call recording is ready. | +| `CallRecordingFailedEvent` | Indicates that recording a call failed. | +| `CallRejectedEvent` | This event is sent when a user rejects a notification to join a call. | +| `CallRingEvent` | This event is sent to all call members to notify they are getting called. | +| `CallSessionStartedEvent` | This event is sent when a call session starts. | +| `CallSessionEndedEvent` | This event is sent when a call session ends. | +| `CallSessionParticipantJoinedEvent` | This event is sent when a participant joins a call session. | +| `CallSessionParticipantLeftEvent` | This event is sent when a participant leaves a call session. | +| `CallTranscriptionStartedEvent` | This event indicates that call transcribing has started. | +| `CallTranscriptionStoppedEvent` | Indicates that call transcribing has stopped. | +| `CallTranscriptionReadyEvent` | This event is sent when call transcriptions are ready. | +| `CallTranscriptionFailedEvent` | Indicates that call transcribing failed. | +| `CallUpdatedEvent` | This event is sent when a call is updated. Clients should use this update the local state of the call. This event also contains the capabilities by role for the call, clients should update the own_capability for the current. | +| `ConnectedEvent` | This event is sent when the WS connection is established and authenticated. This event contains the full user object as it is stored on the server. | +| `ConnectionErrorEvent` | This event is sent when the WS connection attempt fails. | +| `HealthCheckEvent` | Periodic event used to check the connection health. | +| `PermissionRequestEvent` | This event is sent when a user requests access to a feature on a call, clients receiving this event should display a permission request to the user. | +| `UnblockedUserEvent` | This event is sent when a user is unblocked on a call. This can be useful to notify the user that they can now join the call again. | +| `UpdatedCallPermissionsEvent` | This event is sent to notify about permission changes for a user. Clients receiving this event should update their UI accordingly. | +| `VideoEvent` | The discriminator object for all websocket events, you should use this to map event payloads to the correct type. | +| `WSCallEvent` | Placeholder for all call events. | +| `WSClientEvent` | Placeholder for all client events. | +| `CustomVideoEvent` | A custom event. This event is used to send custom events to other participants in the call. | diff --git a/docusaurus/docs/Android/06-advanced/09-recording.mdx b/docusaurus/docs/Android/06-advanced/09-recording.mdx index 9c92a4a4c3..eba79edfc3 100644 --- a/docusaurus/docs/Android/06-advanced/09-recording.mdx +++ b/docusaurus/docs/Android/06-advanced/09-recording.mdx @@ -1,59 +1,62 @@ --- -title: Recording & Broadcasting -description: Recording & Broadcasting +title: Recording +description: Recording Calls --- -### Recording +In certain situations, you may need to record a call and share the recording with the participants. The Stream Video SDK supports this functionality via the `Call` recording API. -The example below shows how to start and stop recording a call +### Start and Stop Recording + +To start recording, we simply invoke `call.startRecording()`. To stop recording, we use `call.stopRecording()`. ```kotlin call.startRecording() call.stopRecording() ``` -You can retrieve recordings: +The `call.state.recording` property of type `StateFlow` will be updated when call recording starts/stops. ```kotlin -val result = call.listRecordings() +val isRecording by call.state.recording.collectAsStateWithLifecycle() // Use to update the UI ``` -There are several layout options... TODO - -### Broadcasting to HLS - -You can broadcast your call to HLS - -```kotlin -call.startBroadcasting() -call.stopBroadcasting() -``` +### Get a List of Recordings -The HLS url is available in the call state +You can retrieve recordings by using `call.listRecordings()`. If the query is successful, `result` will contain a list of recordings, each containing information about the filename, URL and the start and end times. You can use the URL to show the recording in a video player. ```kotlin +val result = call.listRecordings() +result + .onSuccess { response: ListRecordingsResponse -> + response.recordings.forEach { recording: CallRecording -> + Log.d(TAG, recording.filename) + Log.d(TAG, recording.url) + Log.d(TAG, recording.startTime.toString()) + Log.d(TAG, recording.endTime.toString()) + } + } + .onError { error: Error -> + Log.e(TAG, "Failure: ${error.message}") + } ``` -### RTMP-in - -You can also add RTMPs stream's to your call. - -```kotlin -val url = call.state.ingress.value?.rtmp?.address -// TODO: streaming key -``` - -We plan to add support for other livestreaming protocols in the future. If something is missing be sure to let us know. - -### Displaying HLS +### Listening to Recording Events -On android you can display HLS using ExoPlayer +You can listen to recording-related events and change to UI accordingly. ```kotlin -implementation "androidx.media3:media3-exoplayer:1.0.2" -implementation "androidx.media3:media3-ui:1.0.2" -implementation "androidx.media3:media3-exoplayer-hls:1.0.2" +val sub = call.subscribeFor( + CallRecordingStartedEvent::class.java, + CallRecordingStoppedEvent::class.java, + CallRecordingReadyEvent::class.java, + CallRecordingFailedEvent::class.java +) { + Log.e(TAG, "Event type: ${it.getEventType()}") +} + +// stop listening +sub.dispose() ``` -This article explains how to use (ExoPlayer with compose)[https://proandroiddev.com/learn-with-code-jetpack-compose-playing-media-part-3-3792bdfbe1ea]. +Read more about subscribing to events on the [events](08-events.mdx) page. \ No newline at end of file diff --git a/docusaurus/docs/Android/06-advanced/10-broadcasting.mdx b/docusaurus/docs/Android/06-advanced/10-broadcasting.mdx new file mode 100644 index 0000000000..b4a3b7a0ae --- /dev/null +++ b/docusaurus/docs/Android/06-advanced/10-broadcasting.mdx @@ -0,0 +1,73 @@ +--- +title: Broadcasting +description: Broadcasting Calls +--- + +The Stream Video SDK has support for HLS broadcasting. + +### Start and Stop HLS broadcasting + +```kotlin +call.startHLS() +call.stopHLS() +``` + +After few seconds of setup, broadcasting will start and the state of the call will be updated: the `call.state.broadcasting` boolean flag will become `true`. + +### Listening to Broadcasting Events + +You can listen to broadcasting-related events and change to UI accordingly. + +```kotlin +val sub = subscribeFor( + CallHLSBroadcastingStartedEvent::class.java, + CallHLSBroadcastingStoppedEvent::class.java, + CallHLSBroadcastingFailedEvent::class.java, +) { + Log.e(TAG, "Event type: ${it.getEventType()}") +} + +// stop listening +sub.dispose() +``` + +See more about subscribing to events on the [events](08-events.mdx) page. + +### Retrieving the Broadcast URL + +The URL for the broadcast can be retrieved from the `CallHLSBroadcastingStartedEvent` event. It can be used by others to watch the broadcast. + +```kotlin +call.subscribe { event -> + when (event) { + is CallHLSBroadcastingStartedEvent -> { + Log.d(TAG, event.hlsPlaylistUrl) + } + } +} +``` + +### Displaying HLS + +On Android you can play a HLS broadcast by using ExoPlayer. + +```kotlin +implementation "androidx.media3:media3-exoplayer:1.0.2" +implementation "androidx.media3:media3-ui:1.0.2" +implementation "androidx.media3:media3-exoplayer-hls:1.0.2" +``` + +[This](https://proandroiddev.com/learn-with-code-jetpack-compose-playing-media-part-3-3792bdfbe1ea) article explains how to use ExoPlayer with Compose. + +### RTMP-In + +You can also use RTMP streams as input for a call. + +```kotlin +val url = call.state.ingress.value?.rtmp?.address +val streamingKey = call.state.ingress.value?.rtmp?.streamKey +``` + +You can read more about RTMP-In in our [livestreaming tutorial](https://getstream.io/video/sdk/android/tutorial/livestreaming). + +We plan to add support for other livestreaming protocols in the future. If something is missing be sure to let us know. \ No newline at end of file diff --git a/docusaurus/docs/Android/06-advanced/10-custom-data.mdx b/docusaurus/docs/Android/06-advanced/11-custom-data.mdx similarity index 100% rename from docusaurus/docs/Android/06-advanced/10-custom-data.mdx rename to docusaurus/docs/Android/06-advanced/11-custom-data.mdx diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e2cfc4aa40..3420408de4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,11 +3,11 @@ androidGradlePlugin = "8.4.2" cameraCamera2 = "1.3.0" spotless = "6.21.0" nexusPlugin = "1.3.0" -kotlin = "2.0.0" -ksp = "2.0.0-1.0.22" -kotlinSerialization = "1.6.3" +kotlin = "2.0.20" +ksp = "2.0.20-1.0.25" +kotlinSerialization = "1.7.1" kotlinSerializationConverter = "1.0.0" -kotlinxCoroutines = "1.8.0" +kotlinxCoroutines = "1.8.1" kotlinDokka = "1.9.20" jvmTarget = "11" @@ -16,21 +16,21 @@ androidxMaterial = "1.11.0" androidxAppCompat = "1.6.1" androidxCore = "1.12.0" androidxAnnotation = "1.7.1" -androidxLifecycle = "2.7.0" +androidxLifecycle = "2.8.5" androidxStartup = "1.1.1" -androidxActivity = "1.9.0" +androidxActivity = "1.9.2" androidxDataStore = "1.0.0" googleService = "4.3.14" -androidxComposeBom = "2024.06.00" +androidxComposeBom = "2024.09.00" androidxComposeTracing = "1.0.0-beta01" androidxHiltNavigation = "1.2.0" -androidxComposeNavigation = "2.7.7" +androidxComposeNavigation = "2.8.0" composeStableMarker = "1.0.5" coil = "2.6.0" -landscapist = "2.3.5" -accompanist = "0.32.0" +landscapist = "2.3.6" +accompanist = "0.34.0" telephoto = "0.3.0" audioswitch = "1.1.8" libyuv = "0.30.0" @@ -44,7 +44,7 @@ tink = "1.9.0" turbine = "0.13.0" streamWebRTC = "1.1.2" -streamResult = "1.1.0" +streamResult = "1.2.0" streamChat = "6.0.13" streamLog = "1.1.4" streamPush = "1.1.7" @@ -72,10 +72,10 @@ playAppUpdate = "2.1.0" hilt = "2.51.1" leakCanary = "2.13" -binaryCompatabilityValidator = "0.14.0" +binaryCompatabilityValidator = "0.16.3" playPublisher = "3.8.4" -googleMlKitSelfieSegmentation = "16.0.0-beta4" +googleMlKitSelfieSegmentation = "16.0.0-beta6" [libraries] androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCamera2" } diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 1d6bde92f1..78171e6a4a 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -823,7 +823,8 @@ public final class io/getstream/video/android/core/StreamVideoBuilder { public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Z)V public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;)V public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;I)V - public synthetic fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;)V + public synthetic fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lio/getstream/video/android/core/StreamVideo; } @@ -4238,6 +4239,7 @@ public final class io/getstream/video/android/core/notifications/internal/servic public final class io/getstream/video/android/core/notifications/internal/service/CallServiceConfigKt { public static final fun callServiceConfig ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; + public static final fun livestreamAudioCallServiceConfig ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; public static final fun livestreamCallServiceConfig ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; public static final fun livestreamGuestCallServiceConfig ()Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfig; } diff --git a/stream-video-android-core/src/main/AndroidManifest.xml b/stream-video-android-core/src/main/AndroidManifest.xml index 701e3aadb5..7360e764c4 100644 --- a/stream-video-android-core/src/main/AndroidManifest.xml +++ b/stream-video-android-core/src/main/AndroidManifest.xml @@ -103,6 +103,11 @@ android:foregroundServiceType="camera|microphone" android:exported="false" /> + + CameraDirection.Front } val device = devices.firstOrNull { it.direction == newDirection } - device?.let { select(it.id, false) } + device?.let { select(it.id, triggeredByFlip = true) } videoCapturer.switchCamera(null) } } - fun select(deviceId: String, startCapture: Boolean = false) { + fun select(deviceId: String, triggeredByFlip: Boolean = false) { + if (!triggeredByFlip) { + stopCapture() + if (!::devices.isInitialized) initDeviceList() + } + val selectedDevice = devices.firstOrNull { it.id == deviceId } if (selectedDevice != null) { _direction.value = selectedDevice.direction ?: CameraDirection.Back _selectedDevice.value = selectedDevice - _availableResolutions.value = - selectedDevice.supportedFormats?.toList() ?: emptyList() + _availableResolutions.value = selectedDevice.supportedFormats?.toList() ?: emptyList() _resolution.value = selectDesiredResolution( selectedDevice.supportedFormats, mediaManager.call.state.settings.value?.video, ) - if (startCapture) { + if (!triggeredByFlip) { + setup(force = true) startCapture() } } @@ -681,14 +686,13 @@ public class CameraManager( * Handle the setup of the camera manager and enumerator * You should only call this once the permissions have been granted */ - internal fun setup() { - if (setupCompleted) { + internal fun setup(force: Boolean = false) { + if (setupCompleted && !force) { return } - cameraManager = mediaManager.context.getSystemService() - enumerator = Camera2Enumerator(mediaManager.context) - val cameraIds = cameraManager?.cameraIdList ?: emptyArray() - devices = sortDevices(cameraIds, cameraManager, enumerator) + + initDeviceList() + val devicesMatchingDirection = devices.filter { it.direction == _direction.value } val selectedDevice = devicesMatchingDirection.firstOrNull() if (selectedDevice != null) { @@ -707,6 +711,13 @@ public class CameraManager( } } + private fun initDeviceList() { + cameraManager = mediaManager.context.getSystemService() + enumerator = Camera2Enumerator(mediaManager.context) + val cameraIds = cameraManager?.cameraIdList ?: emptyArray() + devices = sortDevices(cameraIds, cameraManager, enumerator) + } + /** * Creates a sorted list of camera devices * diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideo.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideo.kt index c2f1a928f0..c244b9610e 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideo.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideo.kt @@ -203,26 +203,26 @@ public interface StreamVideo : NotificationHandler { } /** - * Builds a detailed header of information we track around the SDK, Android OS, API Level, device name and - * vendor and more. + * Builds the client information header (X-Stream-Client) that will be added to requests. * - * @return String formatted header that contains all the information. + * @return Header value as a string. */ internal fun buildSdkTrackingHeaders(): String { - val clientInformation = "stream-video-android-${BuildConfig.STREAM_VIDEO_VERSION}" - - val buildModel = Build.MODEL - val deviceManufacturer = Build.MANUFACTURER - val apiLevel = Build.VERSION.SDK_INT - val osName = "Android ${Build.VERSION.RELEASE}" - - return clientInformation + - "|os=$osName" + - "|api_version=$apiLevel" + - "|device_vendor=$deviceManufacturer" + - "|device_model=$buildModel" + val streamVideoVersion = "stream-video-android-${BuildConfig.STREAM_VIDEO_VERSION}" + val os = "|os=Android ${Build.VERSION.RELEASE}" + val apiVersion = "|api_version=${Build.VERSION.SDK_INT}" + val deviceVendor = "|device_vendor=${Build.MANUFACTURER}" + val deviceModel = "|device_model=${Build.MODEL}" + val appName = buildAppName() + + return streamVideoVersion + os + apiVersion + deviceVendor + deviceModel + appName } + private fun buildAppName(): String = + (internalStreamVideo as? StreamVideoImpl)?.let { streamVideoImpl -> + "|app_name=" + (streamVideoImpl.appName ?: streamVideoImpl.context.packageName) + } ?: "" + /** * Uninstall a previous [StreamVideo] instance. */ diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index 1d0b579d4b..180dc5ccb0 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -77,6 +77,7 @@ import java.net.ConnectException * @property permissionCheck Used to check for system permission based on call capabilities. See [StreamPermissionCheck]. * @property crashOnMissingPermission Throw an exception or just log an error if [permissionCheck] fails. * @property audioUsage Used to signal to the system how to treat the audio tracks (voip or media). + * @property appName Optional name for the application that is using the Stream Video SDK. Used for logging and debugging purposes. * * @see build * @see ClientState.connection @@ -102,6 +103,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( private val crashOnMissingPermission: Boolean = false, private val permissionCheck: StreamPermissionCheck = DefaultStreamPermissionCheck(), private val audioUsage: Int = defaultAudioUsage, + private val appName: String? = null, ) { private val context: Context = context.applicationContext private val scope = CoroutineScope(DispatcherProvider.IO) @@ -199,6 +201,7 @@ public class StreamVideoBuilder @JvmOverloads constructor( permissionCheck = permissionCheck, crashOnMissingPermission = crashOnMissingPermission, audioUsage = audioUsage, + appName = appName, ) if (user.type == UserType.Guest) { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt index d4b95fe578..8b4163ab6b 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt @@ -156,6 +156,7 @@ internal class StreamVideoImpl internal constructor( internal val permissionCheck: StreamPermissionCheck = DefaultStreamPermissionCheck(), internal val crashOnMissingPermission: Boolean = false, internal val audioUsage: Int = defaultAudioUsage, + internal val appName: String? = null, ) : StreamVideo, NotificationHandler by streamNotificationManager { private var locationJob: Deferred>? = null diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt index cc7899c635..0226507d86 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfig.kt @@ -62,6 +62,19 @@ public fun livestreamCallServiceConfig(): CallServiceConfig { ) } +/** + * Return a default configuration for the call service configuration for livestream which has no camera + */ +public fun livestreamAudioCallServiceConfig(): CallServiceConfig { + return CallServiceConfig( + runCallServiceInForeground = true, + callServicePerType = mapOf( + Pair(ANY_MARKER, CallService::class.java), + Pair("livestream", LivestreamAudioCallService::class.java), + ), + ) +} + /** * Return a default configuration for the call service configuration. */ diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/LivestreamCallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/LivestreamCallService.kt index 608bf5bdcc..b59b211479 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/LivestreamCallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/LivestreamCallService.kt @@ -28,6 +28,14 @@ internal open class LivestreamCallService : CallService() { override val serviceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE } +/** + * Due to the nature of the livestream calls, the service that is used is of different type. + */ +internal open class LivestreamAudioCallService : CallService() { + override val logger: TaggedLogger by taggedLogger("LivestreamHostCallService") + override val serviceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE +} + /** * Due to the nature of the livestream calls, the service that is used is of different type. */ diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/PersistentSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/PersistentSocket.kt index 0fd5ad5418..21a3cfeab4 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/PersistentSocket.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/PersistentSocket.kt @@ -17,7 +17,6 @@ package io.getstream.video.android.core.socket import io.getstream.log.taggedLogger -import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.dispatchers.DispatcherProvider import io.getstream.video.android.core.errors.VideoErrorCode import io.getstream.video.android.core.internal.network.NetworkStateProvider @@ -277,7 +276,6 @@ public open class PersistentSocket( .url(url) .addHeader("Connection", "Upgrade") .addHeader("Upgrade", "websocket") - .addHeader("X-Stream-Client", StreamVideo.buildSdkTrackingHeaders()) .build() return httpClient.newWebSocket(request, this) diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigTest.kt index 6962fd80fa..7331131f8e 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/CallServiceConfigTest.kt @@ -124,4 +124,24 @@ class CallServiceConfigTest { assertEquals(LivestreamViewerService::class.java, livestreamServiceClass) assertEquals(AudioAttributes.USAGE_MEDIA, audioUsage) } + + @Test + fun `livestreamAudioCallServiceConfig should return correct default configuration`() { + // Given + val config = livestreamAudioCallServiceConfig() + + // When + val runInForeground = config.runCallServiceInForeground + val servicePerTypeSize = config.callServicePerType.size + val hostServiceClass = config.callServicePerType[ANY_MARKER] + val livestreamServiceClass = config.callServicePerType["livestream"] + val audioUsage = config.audioUsage + + // Then + assertEquals(true, runInForeground) + assertEquals(2, servicePerTypeSize) + assertEquals(CallService::class.java, hostServiceClass) + assertEquals(LivestreamAudioCallService::class.java, livestreamServiceClass) + assertEquals(AudioAttributes.USAGE_VOICE_COMMUNICATION, audioUsage) + } } diff --git a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api index 6daa63645b..cf9a49199b 100644 --- a/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api +++ b/stream-video-android-ui-compose/api/stream-video-android-ui-compose.api @@ -260,6 +260,12 @@ public final class io/getstream/video/android/compose/theme/StreamDimens$Compani public final fun defaultDimens (Landroidx/compose/runtime/Composer;I)Lio/getstream/video/android/compose/theme/StreamDimens; } +public final class io/getstream/video/android/compose/theme/StreamRippleConfiguration { + public static final field $stable I + public static final field INSTANCE Lio/getstream/video/android/compose/theme/StreamRippleConfiguration; + public final fun default (Landroidx/compose/runtime/Composer;I)Landroidx/compose/material/RippleConfiguration; +} + public final class io/getstream/video/android/compose/theme/StreamShapes { public static final field $stable I public static final field Companion Lio/getstream/video/android/compose/theme/StreamShapes$Companion; @@ -295,7 +301,7 @@ public abstract interface class io/getstream/video/android/compose/theme/StreamT public fun getColors (Landroidx/compose/runtime/Composer;I)Lio/getstream/video/android/compose/theme/StreamColors; public fun getDimens (Landroidx/compose/runtime/Composer;I)Lio/getstream/video/android/compose/theme/StreamDimens; public fun getReactionMapper (Landroidx/compose/runtime/Composer;I)Lio/getstream/video/android/core/mapper/ReactionMapper; - public fun getRippleTheme (Landroidx/compose/runtime/Composer;I)Landroidx/compose/material/ripple/RippleTheme; + public fun getRippleConfiguration (Landroidx/compose/runtime/Composer;I)Landroidx/compose/material/RippleConfiguration; public fun getShapes (Landroidx/compose/runtime/Composer;I)Lio/getstream/video/android/compose/theme/StreamShapes; public fun getStyles (Landroidx/compose/runtime/Composer;I)Lio/getstream/video/android/compose/ui/components/base/styling/CompositeStyleProvider; public fun getTypography (Landroidx/compose/runtime/Composer;I)Lio/getstream/video/android/compose/theme/StreamTypography; @@ -350,7 +356,7 @@ public final class io/getstream/video/android/compose/theme/VideoTheme : io/gets } public final class io/getstream/video/android/compose/theme/VideoThemeKt { - public static final fun VideoTheme (ZLio/getstream/video/android/compose/theme/StreamColors;Lio/getstream/video/android/compose/theme/StreamDimens;Lio/getstream/video/android/compose/theme/StreamTypography;Lio/getstream/video/android/compose/theme/StreamShapes;Landroidx/compose/material/ripple/RippleTheme;Lio/getstream/video/android/core/mapper/ReactionMapper;ZLio/getstream/video/android/compose/ui/components/base/styling/CompositeStyleProvider;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun VideoTheme (ZLio/getstream/video/android/compose/theme/StreamColors;Lio/getstream/video/android/compose/theme/StreamDimens;Lio/getstream/video/android/compose/theme/StreamTypography;Lio/getstream/video/android/compose/theme/StreamShapes;Lio/getstream/video/android/compose/theme/StreamRippleConfiguration;Lio/getstream/video/android/core/mapper/ReactionMapper;ZLio/getstream/video/android/compose/ui/components/base/styling/CompositeStyleProvider;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V } public final class io/getstream/video/android/compose/ui/ComposableSingletons$StreamCallActivityComposeDelegateKt { diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/StreamRippleTheme.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/StreamRippleConfiguration.kt similarity index 52% rename from stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/StreamRippleTheme.kt rename to stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/StreamRippleConfiguration.kt index 55427cd27c..9231b22ae1 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/StreamRippleTheme.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/StreamRippleConfiguration.kt @@ -14,36 +14,31 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterialApi::class) + package io.getstream.video.android.compose.theme -import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.LocalContentColor +import androidx.compose.material.LocalRippleConfiguration import androidx.compose.material.MaterialTheme -import androidx.compose.material.ripple.RippleAlpha -import androidx.compose.material.ripple.RippleTheme +import androidx.compose.material.RippleConfiguration import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.ui.graphics.Color +import androidx.compose.runtime.ReadOnlyComposable /** - * A modified version of the default [RippleTheme] from [MaterialTheme] which + * A modified version of the default [RippleConfiguration] from [MaterialTheme] which * works in case the [MaterialTheme] is not initialized. */ -@Immutable -internal object StreamRippleTheme : RippleTheme { - @Composable - override fun defaultColor(): Color { - return RippleTheme.defaultRippleColor( - contentColor = LocalContentColor.current, - lightTheme = !isSystemInDarkTheme(), - ) - } +public object StreamRippleConfiguration { @Composable - override fun rippleAlpha(): RippleAlpha { - return RippleTheme.defaultRippleAlpha( - contentColor = LocalContentColor.current, - lightTheme = !isSystemInDarkTheme(), - ) + @ReadOnlyComposable + public fun default(): RippleConfiguration { + val rippleConfiguration = LocalRippleConfiguration.current + if (rippleConfiguration != null) return rippleConfiguration + + val contentColor = LocalContentColor.current + return RippleConfiguration(color = contentColor) } } diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/VideoTheme.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/VideoTheme.kt index 62c3d79156..58eb881d05 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/VideoTheme.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/theme/VideoTheme.kt @@ -20,8 +20,9 @@ package io.getstream.video.android.compose.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box -import androidx.compose.material.ripple.LocalRippleTheme -import androidx.compose.material.ripple.RippleTheme +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.LocalRippleConfiguration +import androidx.compose.material.RippleConfiguration import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable @@ -72,18 +73,19 @@ private val LocalStyles = compositionLocalOf { * @param dimens The set of dimens we provide, wrapped in [StreamDimens]. * @param typography The set of typography styles we provide, wrapped in [StreamTypography]. * @param shapes The set of shapes we provide, wrapped in [StreamShapes]. - * @param rippleTheme Defines the appearance for ripples. + * @param rippleConfiguration Defines the appearance for ripples. * @param reactionMapper Defines a mapper of the emoji code from the reaction events. * @param content The content shown within the theme wrapper. */ @Composable +@OptIn(ExperimentalMaterialApi::class) public fun VideoTheme( isInDarkMode: Boolean = isSystemInDarkTheme(), colors: StreamColors = StreamColors.defaultColors(), dimens: StreamDimens = StreamDimens.defaultDimens(), typography: StreamTypography = StreamTypography.defaultTypography(colors, dimens), shapes: StreamShapes = StreamShapes.defaultShapes(dimens), - rippleTheme: RippleTheme = StreamRippleTheme, + rippleConfiguration: StreamRippleConfiguration = StreamRippleConfiguration, reactionMapper: ReactionMapper = ReactionMapper.defaultReactionMapper(), allowUIAutomationTest: Boolean = true, styles: CompositeStyleProvider = CompositeStyleProvider(), @@ -94,7 +96,7 @@ public fun VideoTheme( LocalDimens provides dimens, LocalTypography provides typography, LocalShapes provides shapes, - LocalRippleTheme provides rippleTheme, + LocalRippleConfiguration provides rippleConfiguration.default(), LocalReactionMapper provides reactionMapper, LocalStyles provides styles, ) { @@ -108,6 +110,7 @@ public fun VideoTheme( } } +@OptIn(ExperimentalMaterialApi::class) public interface StreamTheme { /** * Retrieves the current [StreamColors] at the call site's position in the hierarchy. @@ -138,11 +141,11 @@ public interface StreamTheme { get() = LocalShapes.current /** - * Retrieves the current [RippleTheme] at the call site's position in the hierarchy. + * Retrieves the current [RippleConfiguration] at the call site's position in the hierarchy. */ - public val rippleTheme: RippleTheme + public val rippleConfiguration: RippleConfiguration? @Composable @ReadOnlyComposable - get() = LocalRippleTheme.current + get() = StreamRippleConfiguration.default() /** * Retrieves the current [ReactionMapper] at the call site's position in the hierarchy. diff --git a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/avatar/Avatar.kt b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/avatar/Avatar.kt index 7362f9fa86..58041a0ff4 100644 --- a/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/avatar/Avatar.kt +++ b/stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/avatar/Avatar.kt @@ -23,7 +23,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -107,7 +107,7 @@ internal fun Avatar( val clickableModifier: Modifier = if (onClick != null) { modifier.clickable( onClick = onClick, - indication = rememberRipple(bounded = false), + indication = ripple(bounded = false), interactionSource = remember { MutableInteractionSource() }, ) } else {