diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/App.kt b/demo-app/src/main/kotlin/io/getstream/video/android/App.kt index c97551de29d..25bb70f165c 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/App.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/App.kt @@ -20,7 +20,7 @@ import android.app.Application import android.content.Context import dagger.hilt.android.HiltAndroidApp import io.getstream.video.android.datastore.delegate.StreamUserDataStore -import io.getstream.video.android.tooling.util.StreamFlavors +import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.util.StreamVideoInitHelper import kotlinx.coroutines.runBlocking @@ -57,7 +57,7 @@ class App : Application() { val Context.app get() = applicationContext as App -val defaultCallId get() = if (BuildConfig.FLAVOR == StreamFlavors.development) { +val defaultCallId get() = if (StreamBuildFlavorUtil.isDevelopment) { "default:79cYh3J5JgGk" } else { "" diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt b/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt index f2561228725..a013a8324e8 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/MainActivity.kt @@ -37,7 +37,7 @@ import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.notifications.NotificationHandler import io.getstream.video.android.datastore.delegate.StreamUserDataStore import io.getstream.video.android.model.StreamCallId -import io.getstream.video.android.tooling.util.StreamFlavors +import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.ui.AppNavHost import io.getstream.video.android.ui.AppScreens import io.getstream.video.android.ui.common.StreamCallActivity @@ -66,7 +66,7 @@ class MainActivity : ComponentActivity() { // Try to read the Google Play install referrer value. We use it to deliver // the Call ID from the QR code link. @Suppress("KotlinConstantConditions") - if (BuildConfig.FLAVOR == StreamFlavors.production) { + if (StreamBuildFlavorUtil.isProduction) { InstallReferrer(this).extractInstallReferrer { callId: String -> Log.d("MainActivity", "Call ID: $callId") firebaseAnalytics.logEvent(FirebaseEvents.INSTALL_FROM_QR_CODE, null) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/InMemoryStore.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/InMemoryStore.kt new file mode 100644 index 00000000000..0a0751d61d7 --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/InMemoryStore.kt @@ -0,0 +1,26 @@ +/* + * 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.data.datasource.local + +import io.getstream.video.android.model.User + +interface InMemoryStore { + + fun saveUser(user: User) + + fun getUser(): User? +} diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/InMemoryStoreImpl.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/InMemoryStoreImpl.kt new file mode 100644 index 00000000000..7e8bb74daa4 --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/InMemoryStoreImpl.kt @@ -0,0 +1,34 @@ +/* + * 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.data.datasource.local + +import io.getstream.video.android.model.User + +/** + * Only used in E2E Testing builds + */ +class InMemoryStoreImpl : InMemoryStore { + private var temporaryUser: User? = null + + override fun saveUser(user: User) { + temporaryUser = user + } + + override fun getUser(): User? { + return temporaryUser + } +} diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/NoopMemoryStoreImpl.kt b/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/NoopMemoryStoreImpl.kt new file mode 100644 index 00000000000..9d955475a33 --- /dev/null +++ b/demo-app/src/main/kotlin/io/getstream/video/android/data/datasource/local/NoopMemoryStoreImpl.kt @@ -0,0 +1,26 @@ +/* + * 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.data.datasource.local + +import io.getstream.video.android.model.User + +class NoopMemoryStoreImpl : InMemoryStore { + + override fun saveUser(user: User) {} + + override fun getUser(): User? = null +} diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/di/AppModule.kt b/demo-app/src/main/kotlin/io/getstream/video/android/di/AppModule.kt index 1cec7a156bb..1922ca305b8 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/di/AppModule.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/di/AppModule.kt @@ -26,8 +26,13 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import io.getstream.video.android.R +import io.getstream.video.android.data.datasource.local.InMemoryStore +import io.getstream.video.android.data.datasource.local.InMemoryStoreImpl +import io.getstream.video.android.data.datasource.local.NoopMemoryStoreImpl import io.getstream.video.android.data.repositories.GoogleAccountRepository import io.getstream.video.android.datastore.delegate.StreamUserDataStore +import io.getstream.video.android.tooling.util.StreamBuildFlavor +import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.util.NetworkMonitor import javax.inject.Singleton @@ -62,4 +67,14 @@ object AppModule { @Singleton fun provideNetworkMonitor(@ApplicationContext context: Context) = NetworkMonitor(context) + + @Provides + @Singleton + fun provideInMemoryStore(): InMemoryStore { + val buildFlavour = StreamBuildFlavorUtil.current + return when (buildFlavour) { + StreamBuildFlavor.E2eTesting -> InMemoryStoreImpl() + else -> NoopMemoryStoreImpl() + } + } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/tooling/util/StreamFlavors.kt b/demo-app/src/main/kotlin/io/getstream/video/android/tooling/util/StreamFlavors.kt index 78795aa67e0..95a8ff0d1f5 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/tooling/util/StreamFlavors.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/tooling/util/StreamFlavors.kt @@ -16,12 +16,35 @@ package io.getstream.video.android.tooling.util +import io.getstream.video.android.BuildConfig +import io.getstream.video.android.tooling.util.StreamBuildFlavor.Development +import io.getstream.video.android.tooling.util.StreamBuildFlavor.E2eTesting +import io.getstream.video.android.tooling.util.StreamBuildFlavor.Production + /** * Defined flavors. Used in code logic. */ -internal object StreamFlavors { - const val development = "development" - const val production = "production" + +object StreamBuildFlavorUtil { + val current: StreamBuildFlavor + get() = StreamBuildFlavor.from(BuildConfig.FLAVOR) + + val isDevelopment: Boolean get() = current == Development + val isProduction: Boolean get() = current == Production + val isE2eTesting: Boolean get() = current == E2eTesting +} + +enum class StreamBuildFlavor(val type: String) { + Development("development"), + Production("production"), + E2eTesting("e2etesting"), + ; + + companion object { + fun from(type: String?): StreamBuildFlavor { + return entries.find { it.type == type } ?: Development + } + } } public object StreamEnvironments { diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt index b3e2125b7b6..a6a803d0b10 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt @@ -114,7 +114,7 @@ import io.getstream.video.android.filters.video.VirtualBackgroundVideoFilter import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewCall import io.getstream.video.android.tooling.extensions.toPx -import io.getstream.video.android.tooling.util.StreamFlavors +import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.ui.closedcaptions.ClosedCaptionUiState import io.getstream.video.android.ui.closedcaptions.ClosedCaptionUiState.Available.toClosedCaptionUiState import io.getstream.video.android.ui.closedcaptions.ClosedCaptionsContainer @@ -287,7 +287,7 @@ fun CallScreen( call = call, layout = layout, enableInPictureInPicture = true, - enableDiagnostics = BuildConfig.DEBUG || BuildConfig.FLAVOR == StreamFlavors.development, + enableDiagnostics = BuildConfig.DEBUG || StreamBuildFlavorUtil.isDevelopment, onCallAction = { when (it) { ChooseLayout -> isShowingLayoutChooseMenu = true diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt index 9227448285b..15e980460e3 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt @@ -89,7 +89,6 @@ import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupPositionProvider import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import io.getstream.video.android.BuildConfig import io.getstream.video.android.R import io.getstream.video.android.compose.theme.VideoTheme import io.getstream.video.android.compose.ui.components.avatar.UserAvatar @@ -101,7 +100,7 @@ import io.getstream.video.android.defaultCallId import io.getstream.video.android.mock.StreamPreviewDataUtils import io.getstream.video.android.mock.previewUsers import io.getstream.video.android.model.User -import io.getstream.video.android.tooling.util.StreamFlavors +import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.util.config.AppConfig import io.getstream.video.android.util.config.types.StreamEnvironment @@ -136,7 +135,7 @@ fun CallJoinScreen( ) { CallJoinHeader( user = user, - showDirectCall = BuildConfig.FLAVOR == StreamFlavors.development, + showDirectCall = StreamBuildFlavorUtil.isDevelopment || StreamBuildFlavorUtil.isE2eTesting, onAvatarLongClick = { if (isNetworkAvailable) isSignOutDialogVisible = true }, onDirectCallClick = navigateToDirectCallJoin, onSignOutClick = { @@ -197,7 +196,7 @@ private fun HandleCallJoinUiState( @Composable private fun CallJoinHeader( user: User?, - isProduction: Boolean = BuildConfig.FLAVOR == StreamFlavors.production, + isProduction: Boolean = StreamBuildFlavorUtil.isProduction, showDirectCall: Boolean = user?.custom?.get("email")?.contains("getstreamio") == true, onAvatarLongClick: () -> Unit, onDirectCallClick: () -> Unit, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinViewModel.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinViewModel.kt index 12388cff98e..27e57c0d39d 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinViewModel.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinViewModel.kt @@ -24,11 +24,13 @@ import io.getstream.android.push.PushProvider import io.getstream.chat.android.client.ChatClient import io.getstream.video.android.core.Call import io.getstream.video.android.core.StreamVideo +import io.getstream.video.android.data.datasource.local.InMemoryStore import io.getstream.video.android.datastore.delegate.StreamUserDataStore import io.getstream.video.android.model.Device import io.getstream.video.android.model.User import io.getstream.video.android.model.mapper.isValidCallCid import io.getstream.video.android.model.mapper.toTypeAndId +import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.util.NetworkMonitor import io.getstream.video.android.util.StreamVideoInitHelper import io.getstream.video.android.util.fcmToken @@ -37,6 +39,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.shareIn @@ -48,6 +52,7 @@ import javax.inject.Inject class CallJoinViewModel @Inject constructor( private val dataStore: StreamUserDataStore, private val googleSignInClient: GoogleSignInClient, + private val inMemoryStore: InMemoryStore, networkMonitor: NetworkMonitor, ) : ViewModel() { val user: Flow = dataStore.user @@ -84,6 +89,21 @@ class CallJoinViewModel @Inject constructor( } } } + + /** + * For E2E Testing Only + */ + if (StreamBuildFlavorUtil.isE2eTesting) { + viewModelScope.launch { + user + .filterNotNull() + .collectLatest { newUser -> + if (inMemoryStore.getUser() == null) { + inMemoryStore.saveUser(newUser) + } + } + } + } } fun handleUiEvent(event: CallJoinEvent) { 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 a722f6406c7..525ad6996e4 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 @@ -101,7 +101,7 @@ import io.getstream.video.android.compose.ui.components.base.styling.StreamDialo import io.getstream.video.android.model.User import io.getstream.video.android.models.builtInUsers import io.getstream.video.android.tooling.extensions.toPx -import io.getstream.video.android.tooling.util.StreamFlavors +import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import io.getstream.video.android.util.UserHelper import io.getstream.video.android.util.config.AppConfig import io.getstream.video.android.util.config.types.StreamEnvironment @@ -127,7 +127,7 @@ fun LoginScreen( } val selectedEnv by AppConfig.currentEnvironment.collectAsStateWithLifecycle() val availableEnvs by remember { mutableStateOf(AppConfig.availableEnvironments) } - val availableLogins = when (BuildConfig.FLAVOR == StreamFlavors.development) { + val availableLogins = when (StreamBuildFlavorUtil.isDevelopment) { true -> listOf("built-in", "google", "email", "guest") else -> listOf("google", "email", "guest") } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/outgoing/DirectCallJoinViewModel.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/outgoing/DirectCallJoinViewModel.kt index 627e9f7f170..6bef56d36a1 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/outgoing/DirectCallJoinViewModel.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/outgoing/DirectCallJoinViewModel.kt @@ -19,13 +19,14 @@ package io.getstream.video.android.ui.outgoing import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import io.getstream.video.android.BuildConfig +import io.getstream.video.android.data.datasource.local.InMemoryStore import io.getstream.video.android.data.repositories.GoogleAccountRepository import io.getstream.video.android.datastore.delegate.StreamUserDataStore import io.getstream.video.android.model.User import io.getstream.video.android.models.builtInUsers import io.getstream.video.android.models.toUsers -import io.getstream.video.android.tooling.util.StreamFlavors +import io.getstream.video.android.tooling.util.StreamBuildFlavor +import io.getstream.video.android.tooling.util.StreamBuildFlavorUtil import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.firstOrNull @@ -37,6 +38,7 @@ import javax.inject.Inject class DirectCallJoinViewModel @Inject constructor( private val userDataStore: StreamUserDataStore, private val googleAccountRepository: GoogleAccountRepository, + private val inMemoryStore: InMemoryStore, ) : ViewModel() { private val _uiState = MutableStateFlow(DirectCallUiState()) val uiState = _uiState.asStateFlow() @@ -50,8 +52,15 @@ class DirectCallJoinViewModel @Inject constructor( private suspend fun getCombinedUsers(): List { val currentUser = userDataStore.user.firstOrNull() val googleUsers = suspend { googleAccountRepository.getAllAccounts().orEmpty().toUsers() } - return when (BuildConfig.FLAVOR == StreamFlavors.development) { - true -> User.builtInUsers().filterNot { it.id == currentUser?.id } + googleUsers() + val buildFlavour = StreamBuildFlavorUtil.current + return when (buildFlavour) { + StreamBuildFlavor.Development -> User.builtInUsers() + .filterNot { it.id == currentUser?.id } + googleUsers() + + StreamBuildFlavor.E2eTesting -> { + inMemoryStore.getUser()?.let { arrayListOf(it) } ?: emptyList() + } + else -> googleUsers() } } diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt index 34c650c1a52..b6cdf4d0d30 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/config/AppConfig.kt @@ -27,7 +27,7 @@ import com.squareup.moshi.adapter import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import io.getstream.log.taggedLogger import io.getstream.video.android.BuildConfig -import io.getstream.video.android.tooling.util.StreamFlavors +import io.getstream.video.android.tooling.util.StreamBuildFlavor import io.getstream.video.android.util.config.types.StreamEnvironment import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -136,7 +136,7 @@ object AppConfig { } private fun List.default(currentFlavor: String): StreamEnvironment { - return if (currentFlavor == StreamFlavors.development) { + return if (currentFlavor == StreamBuildFlavor.Development.type) { first { it.env == "pronto" } } else { first { it.env == "demo" }