Skip to content

Commit

Permalink
Merge pull request #105 from GetStream/feature/stream-video
Browse files Browse the repository at this point in the history
Implement a video call feature
  • Loading branch information
skydoves authored Oct 23, 2023
2 parents 63b7902 + 9ef795c commit 706fee1
Show file tree
Hide file tree
Showing 33 changed files with 559 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ build/

# Local configuration file (sdk path, etc)
local.properties
secrets.properties

# Proguard folder generated by Eclipse
proguard/
Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ plugins {

android {
namespace = "io.getstream.whatsappclone"
compileSdk = Configurations.compileSdk

defaultConfig {
applicationId = "io.getstream.whatsappclone"
Expand Down Expand Up @@ -60,6 +59,7 @@ dependencies {
implementation(project(":features:chats"))
implementation(project(":features:status"))
implementation(project(":features:calls"))
implementation(project(":features:video"))

// material
implementation(libs.androidx.appcompat)
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="io.getstream.whatsappclone.androidx-startup"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="io.getstream.whatsappclone.chats.initializer.StreamChatInitializer"
android:name="io.getstream.whatsappclone.initializer.MainInitializer"
android:value="androidx.startup" />
</provider>
</application>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2023 Stream.IO, Inc. All Rights Reserved.
*
* 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.
*/

package io.getstream.whatsappclone.initializer

import android.content.Context
import androidx.startup.Initializer
import io.getstream.whatsappclone.chats.initializer.StreamChatInitializer
import io.getstream.whatsappclone.chats.initializer.StreamLogInitializer
import io.getstream.whatsappclone.video.initializer.StreamVideoInitializer

class MainInitializer : Initializer<Unit> {

override fun create(context: Context) {
}

override fun dependencies(): List<Class<out Initializer<*>>> = listOf(
StreamLogInitializer::class.java,
StreamChatInitializer::class.java,
StreamVideoInitializer::class.java
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import io.getstream.whatsappclone.chats.messages.WhatsAppMessages
import io.getstream.whatsappclone.model.WhatsAppUser
import io.getstream.whatsappclone.ui.WhatsAppTabPager
import io.getstream.whatsappclone.ui.WhatsAppTopBar
import io.getstream.whatsappclone.video.WhatsAppVideoCall

fun NavGraphBuilder.whatsAppHomeNavigation() {
composable(route = WhatsAppScreens.Home.name) {
Expand Down Expand Up @@ -58,4 +59,13 @@ fun NavGraphBuilder.whatsAppHomeNavigation() {
whatsAppUser = whatsAppUser
)
}

composable(
route = WhatsAppScreens.VideoCall.name,
arguments = WhatsAppScreens.VideoCall.navArguments
) {
val callId = it.arguments?.getString(WhatsAppScreens.VideoCall.KEY_CALL_ID) ?: return@composable

WhatsAppVideoCall(id = callId)
}
}
2 changes: 1 addition & 1 deletion benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ plugins {

android {
namespace = "io.getstream.whatsappclone.benchmark"
compileSdk = Configurations.compileSdk
compileSdk = 34

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal fun Project.configureKotlinAndroid(
commonExtension: CommonExtension<*, *, *, *, *>,
) {
commonExtension.apply {
compileSdk = 33
compileSdk = 34

defaultConfig {
minSdk = 21
Expand Down
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ buildscript {

plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.google.secrets) apply false
alias(libs.plugins.spotless) apply false
}
3 changes: 1 addition & 2 deletions buildSrc/src/main/kotlin/Configurations.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
object Configurations {
const val compileSdk = 34
const val targetSdk = 34
const val minSdk = 21
const val minSdk = 24
const val majorVersion = 1
const val minorVersion = 0
const val patchVersion = 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ package io.getstream.whatsappclone.designsystem.component

import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.getstream.whatsappclone.designsystem.theme.GREEN450

@Composable
fun WhatsAppLoadingIndicator() {
CircularProgressIndicator(color = GREEN450)
fun WhatsAppLoadingIndicator(modifier: Modifier = Modifier) {
CircularProgressIndicator(
modifier = modifier,
color = GREEN450
)
}
4 changes: 3 additions & 1 deletion core/model/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ android {
}

dependencies {
api(libs.stream.client)
api(libs.stream.chat.client)
api(libs.stream.video.core)

api(libs.retrofit.kotlin.serialization)
api(libs.kotlinx.serialization.json)
compileOnly(libs.compose.stable.marker)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023 Stream.IO, Inc. All Rights Reserved.
*
* 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.
*/

package io.getstream.whatsappclone.model

import kotlinx.serialization.Serializable

@Serializable
data class TokenResponse(val userId: String, val token: String)
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,15 @@ import io.getstream.whatsappclone.navigation.navtypes.WhatsAppUserType

sealed class WhatsAppScreens(
val route: String,
val index: Int? = null,
val navArguments: List<NamedNavArgument> = emptyList()
) {
val name: String = route.appendArguments(navArguments)

// home screen
object Home : WhatsAppScreens("home")
data object Home : WhatsAppScreens("home")

// message screen
object Messages : WhatsAppScreens(
data object Messages : WhatsAppScreens(
route = "messages",
navArguments = listOf(navArgument("channelId") { type = NavType.StringType })
) {
Expand All @@ -42,7 +41,7 @@ sealed class WhatsAppScreens(
}

// call info screen
object CallInfo : WhatsAppScreens(
data object CallInfo : WhatsAppScreens(
route = "call_info",
navArguments = listOf(
navArgument("user") {
Expand All @@ -55,6 +54,22 @@ sealed class WhatsAppScreens(
fun createRoute(whatsAppUser: WhatsAppUser) =
name.replace("{${navArguments.first().name}}", WhatsAppUserType.encodeToString(whatsAppUser))
}

// video call screen
data object VideoCall : WhatsAppScreens(
route = "video_call",
navArguments = listOf(
navArgument("call_id") {
type = NavType.StringType
nullable = false
}
)
) {
const val KEY_CALL_ID = "call_id"

fun createRoute(callId: String) =
name.replace("{${navArguments.first().name}}", callId)
}
}

private fun String.appendArguments(navArguments: List<NamedNavArgument>): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import io.getstream.whatsappclone.network.service.StreamVideoTokenService
import io.getstream.whatsappclone.network.service.WhatsAppUserService
import javax.inject.Singleton
import kotlinx.serialization.ExperimentalSerializationApi
Expand All @@ -44,20 +45,39 @@ internal object NetworkModule {

@Provides
@Singleton
fun provideRetrofit(networkJson: Json): Retrofit {
@RetrofitService(BaseService.WhatsApp)
fun provideWhatsAppRetrofit(networkJson: Json): Retrofit {
return Retrofit.Builder()
.baseUrl(
"https://gist.githubusercontent.com/skydoves/44140b10c3b1057b8ac00e2a59eaaa86/raw/" +
"0ca2cdbb34c7eaf365130c75969a29d4e33bd2fc/"
)
.baseUrl(BaseService.WhatsApp.baseUrl)
.addConverterFactory(networkJson.asConverterFactory("application/json".toMediaType()))
.addCallAdapterFactory(ResultCallAdapterFactory.create())
.build()
}

@Provides
@Singleton
fun provideWhatsAppUerService(retrofit: Retrofit): WhatsAppUserService {
fun provideWhatsAppUerService(
@RetrofitService(BaseService.WhatsApp) retrofit: Retrofit
): WhatsAppUserService {
return retrofit.create()
}

@Provides
@Singleton
@RetrofitService(BaseService.StreamVideo)
fun provideStreamVideoRetrofit(networkJson: Json): Retrofit {
return Retrofit.Builder()
.baseUrl(BaseService.StreamVideo.baseUrl)
.addConverterFactory(networkJson.asConverterFactory("application/json".toMediaType()))
.addCallAdapterFactory(ResultCallAdapterFactory.create())
.build()
}

@Provides
@Singleton
fun provideStreamVideoTokenService(
@RetrofitService(BaseService.StreamVideo) retrofit: Retrofit
): StreamVideoTokenService {
return retrofit.create()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2023 Stream.IO, Inc. All Rights Reserved.
*
* 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.
*/

package io.getstream.whatsappclone.network.di

import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class RetrofitService(val baseService: BaseService)

enum class BaseService(val baseUrl: String) {
WhatsApp(
baseUrl = "https://gist.githubusercontent.com/skydoves/44140b10c3b1057b8ac00e2a59eaaa86/raw/" +
"0ca2cdbb34c7eaf365130c75969a29d4e33bd2fc/"
),
StreamVideo(
baseUrl = "https://stream-calls-dogfood.vercel.app/"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2023 Stream.IO, Inc. All Rights Reserved.
*
* 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.
*/

package io.getstream.whatsappclone.network.service

import io.getstream.whatsappclone.model.TokenResponse
import retrofit2.http.GET
import retrofit2.http.Query

interface StreamVideoTokenService {

@GET("api/auth/create-token")
suspend fun fetchToken(
@Query("user_id") userId: String?,
@Query("api_key") apiKey: String
): Result<TokenResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.skydoves.sealedx.core.Extensive
import com.skydoves.sealedx.core.annotations.ExtensiveModel
import com.skydoves.sealedx.core.annotations.ExtensiveSealed
import io.getstream.chat.android.client.models.Channel
import io.getstream.video.android.core.Call

/**
* Generates restartable and skippable UI states based on KSP and extensive models.
Expand All @@ -29,11 +30,13 @@ import io.getstream.chat.android.client.models.Channel
@ExtensiveSealed(
models = [
ExtensiveModel(type = Channel::class, name = "WhatsAppMessage"),
ExtensiveModel(type = WhatsAppUserExtensive::class, name = "WhatsAppUser")
ExtensiveModel(type = WhatsAppUserExtensive::class, name = "WhatsAppUser"),
ExtensiveModel(type = Call::class, name = "WhatsAppVideo")
]
)
@Immutable
sealed interface UiState {

data class Success(val data: Extensive) : UiState
data object Loading : UiState
data object Error : UiState
Expand Down
Loading

0 comments on commit 706fee1

Please sign in to comment.