Skip to content

Commit

Permalink
Out of the box StreamCallActivity (StreamCallActivity, ComposeStrea…
Browse files Browse the repository at this point in the history
…mCallActivity) added (#1060)
  • Loading branch information
aleksandar-apostolov authored Apr 10, 2024
1 parent c074e00 commit 30b6a6d
Show file tree
Hide file tree
Showing 9 changed files with 1,253 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,35 @@ 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 final class io/getstream/video/android/compose/ui/ComposableSingletons$StreamCallActivityComposeDelegateKt {
public static final field INSTANCE Lio/getstream/video/android/compose/ui/ComposableSingletons$StreamCallActivityComposeDelegateKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
public fun <init> ()V
public final fun getLambda-1$stream_video_android_ui_compose_release ()Lkotlin/jvm/functions/Function2;
}

public class io/getstream/video/android/compose/ui/ComposeStreamCallActivity : io/getstream/video/android/ui/common/StreamCallActivity {
public static final field $stable I
public fun <init> ()V
public fun uiDelegate ()Lio/getstream/video/android/ui/common/StreamActivityUiDelegate;
}

public class io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate : io/getstream/video/android/ui/common/StreamActivityUiDelegate {
public static final field $stable I
public static final field Companion Lio/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate$Companion;
public fun <init> ()V
public fun AudioCallContent (Lio/getstream/video/android/ui/common/StreamCallActivity;Lio/getstream/video/android/core/Call;Landroidx/compose/runtime/Composer;I)V
public fun DefaultCallContent (Lio/getstream/video/android/ui/common/StreamCallActivity;Lio/getstream/video/android/core/Call;Landroidx/compose/runtime/Composer;I)V
public fun NoAnswerContent (Lio/getstream/video/android/ui/common/StreamCallActivity;Lio/getstream/video/android/core/Call;Landroidx/compose/runtime/Composer;I)V
public fun NoPermissions (Lio/getstream/video/android/ui/common/StreamCallActivity;Ljava/util/List;Ljava/util/List;ZLandroidx/compose/runtime/Composer;II)V
public fun RejectedContent (Lio/getstream/video/android/ui/common/StreamCallActivity;Lio/getstream/video/android/core/Call;Landroidx/compose/runtime/Composer;I)V
public fun RootContent (Lio/getstream/video/android/ui/common/StreamCallActivity;Lio/getstream/video/android/core/Call;Landroidx/compose/runtime/Composer;I)V
public fun setContent (Lio/getstream/video/android/ui/common/StreamCallActivity;Lio/getstream/video/android/core/Call;)V
}

public final class io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate$Companion {
}

public final class io/getstream/video/android/compose/ui/components/audio/AudioAppBarKt {
public static final fun AudioAppBar (Landroidx/compose/ui/Modifier;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)V
}
Expand Down
28 changes: 27 additions & 1 deletion stream-video-android-ui-compose/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,30 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest />
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>


<application>
<activity
android:name=".ui.ComposeStreamCallActivity"
android:supportsPictureInPicture="true"
android:showOnLockScreen="true"
android:showWhenLocked="true"
android:launchMode="singleTop"
android:exported="false">
<intent-filter android:priority="1">
<action android:name="io.getstream.video.android.action.INCOMING_CALL" />
<action android:name="io.getstream.video.android.action.NOTIFICATION" />
<action android:name="io.getstream.video.android.action.LIVE_CALL" />
<action android:name="io.getstream.video.android.action.ONGOING_CALL" />
<action android:name="io.getstream.video.android.action.ACCEPT_CALL" />
<action android:name="io.getstream.video.android.action.OUTGOING_CALL" />
</intent-filter>
</activity>

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* 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.
*/

package io.getstream.video.android.compose.ui

import io.getstream.video.android.ui.common.StreamActivityUiDelegate
import io.getstream.video.android.ui.common.StreamCallActivity

/**
* Default [StreamCallActivity] for use with compose.
* Extend this activity if you are using compose and want default call behavior.
*/
public open class ComposeStreamCallActivity : StreamCallActivity() {

@Suppress("UNCHECKED_CAST")
override fun <T : StreamCallActivity> uiDelegate(): StreamActivityUiDelegate<T> {
return StreamCallActivityComposeDelegate() as StreamActivityUiDelegate<T>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
/*
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* 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.
*/

package io.getstream.video.android.compose.ui

import android.Manifest
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.getstream.log.taggedLogger
import io.getstream.video.android.compose.R
import io.getstream.video.android.compose.permission.LaunchPermissionRequest
import io.getstream.video.android.compose.theme.VideoTheme
import io.getstream.video.android.compose.ui.components.base.StreamDialogPositiveNegative
import io.getstream.video.android.compose.ui.components.base.styling.ButtonStyles
import io.getstream.video.android.compose.ui.components.base.styling.StreamDialogStyles
import io.getstream.video.android.compose.ui.components.base.styling.StyleSize
import io.getstream.video.android.compose.ui.components.call.activecall.CallContent
import io.getstream.video.android.compose.ui.components.call.ringing.RingingCallContent
import io.getstream.video.android.core.Call
import io.getstream.video.android.core.RealtimeConnection
import io.getstream.video.android.core.call.state.DeclineCall
import io.getstream.video.android.core.call.state.LeaveCall
import io.getstream.video.android.ui.common.StreamActivityUiDelegate
import io.getstream.video.android.ui.common.StreamCallActivity

/**
* A default implementation of the compose delegate for the call activity.
* Can be extended.
* Provides functions with the context of the activity.
*/
// We suppress build fails since we do not have default parameters in our abstract @Composable
// Remove the @Suppress line once the listed issue is fixed.
// https://issuetracker.google.com/issues/322121224
@Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE")
public open class StreamCallActivityComposeDelegate : StreamActivityUiDelegate<StreamCallActivity> {

public companion object {
private val logger by taggedLogger("StreamCallActivityUiDelegate")
}

/**
* Create the delegate.
*
* @param activity the activity
* @param call the call
*/
override fun setContent(activity: StreamCallActivity, call: Call) {
logger.d { "[onCreate(activity, call)] invoked from compose delegate." }
activity.setContent {
logger.d { "[setContent] with RootContent" }
activity.RootContent(call = call)
}
}

/**
* Root content of the screen.
*
* @param call the call object.
*/
@Composable
public open fun StreamCallActivity.RootContent(call: Call) {
VideoTheme {
LaunchPermissionRequest(listOf(Manifest.permission.RECORD_AUDIO)) {
AllPermissionsGranted {
// All permissions granted
val connection by call.state.connection.collectAsStateWithLifecycle()
LaunchedEffect(key1 = connection) {
if (connection == RealtimeConnection.Disconnected) {
logger.w { "Call disconnected." }
finish()
} else if (connection is RealtimeConnection.Failed) {
// Safely cast, no need to crash if the error message is missing
val conn = connection as? RealtimeConnection.Failed
logger.e(Exception("${conn?.error}")) { "Call connection failed." }
finish()
}
}

RingingCallContent(
isVideoType = isVideoCall(call),
call = call,
modifier = Modifier.background(color = VideoTheme.colors.baseSheetPrimary),
onBackPressed = {
onBackPressed(call)
},
onAcceptedContent = {
if (isVideoCall(call)) {
DefaultCallContent(call = call)
} else {
AudioCallContent(call = call)
}
},
onNoAnswerContent = {
NoAnswerContent(call)
},
onRejectedContent = {
RejectedContent(call)
},
onCallAction = {
onCallAction(call, it)
},
)
}

SomeGranted { granted, notGranted, showRationale ->
// Some of the permissions were granted, you can check which ones.
if (showRationale) {
NoPermissions(granted, notGranted, true)
} else {
logger.w { "No permission, closing activity without rationale! [notGranted: [$notGranted]" }
finish()
}
}
NoneGranted {
// None of the permissions were granted.
if (it) {
NoPermissions(showRationale = true)
} else {
logger.w { "No permission, closing activity without rationale!" }
finish()
}
}
}
}
}

/**
* Content when the call is not answered.
*
* @param call the call.
*/
@Composable
public open fun StreamCallActivity.NoAnswerContent(call: Call) {
onCallAction(call, LeaveCall)
}

/**
* Content when the call is rejected.
*
* @param call the call.
*/
@Composable
public open fun StreamCallActivity.RejectedContent(call: Call) {
onCallAction(call, DeclineCall)
}

/**
* Content for audio calls.
* Call type must be "audio_call"
*
* @param call the call.
*/
@Composable
public open fun StreamCallActivity.AudioCallContent(call: Call) {
val micEnabled by call.microphone.isEnabled.collectAsStateWithLifecycle()
val duration by call.state.durationInDateFormat.collectAsStateWithLifecycle()
io.getstream.video.android.compose.ui.components.call.activecall.AudioCallContent(
onBackPressed = {
onBackPressed(call)
},
call = call,
isMicrophoneEnabled = micEnabled,
onCallAction = {
onCallAction(call, it)
},
durationPlaceholder = duration ?: stringResource(id = R.string.stream_audio_call_ui_calling),
)
}

/**
* Content for all other calls.
*
* @param call the call.
*/
@Composable
public open fun StreamCallActivity.DefaultCallContent(call: Call) {
CallContent(call = call, onCallAction = {
onCallAction(call, it)
}, onBackPressed = {
onBackPressed(call)
})
}

/**
* Content when permissions are missing.
*/
@Composable
public open fun StreamCallActivity.NoPermissions(
granted: List<String> = emptyList(),
notGranted: List<String> = emptyList(),
showRationale: Boolean,
) {
StreamDialogPositiveNegative(
content = {
Text(
text = stringResource(
id = R.string.stream_default_call_ui_permissions_rationale_title,
),
style = TextStyle(
fontSize = 24.sp,
lineHeight = 28.sp,
fontWeight = FontWeight(500),
color = VideoTheme.colors.basePrimary,
textAlign = TextAlign.Center,
),
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = stringResource(
id = R.string.stream_default_call_ui_microphone_rationale,
),
style = TextStyle(
fontSize = 16.sp,
lineHeight = 18.5.sp,
fontWeight = FontWeight(400),
color = VideoTheme.colors.baseSecondary,
textAlign = TextAlign.Center,
),
)
},
style = StreamDialogStyles.defaultDialogStyle(),
positiveButton = Triple(
stringResource(id = R.string.stream_default_call_ui_settings_button),
ButtonStyles.secondaryButtonStyle(StyleSize.S),
) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
},
negativeButton = Triple(
stringResource(id = R.string.stream_default_call_ui_not_now_button),
ButtonStyles.tertiaryButtonStyle(StyleSize.S),
) {
finish()
},
)
}
}
23 changes: 23 additions & 0 deletions stream-video-android-ui-compose/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2024 Stream.io Inc. All rights reserved.
Licensed under the Stream License;
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/GetStream/stream-video-android/blob/main/LICENSE
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.
-->
<resources>
<string name="stream_audio_call_ui_calling">Calling...</string>
<string name="stream_default_call_ui_permissions_rationale_title">Some permissions are required</string>
<string name="stream_default_call_ui_microphone_rationale">The app needs access to your microphone.</string>
<string name="stream_default_call_ui_settings_button">Settings</string>
<string name="stream_default_call_ui_not_now_button">Not now</string>
</resources>
Loading

0 comments on commit 30b6a6d

Please sign in to comment.