Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google Sign In and Direct calls on demo app prod #891

Merged
merged 22 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f02c37a
Remove LoginViewModel init
liviu-timar Oct 26, 2023
4226cf9
Implement auto-login flag in Login screen and navigation
liviu-timar Oct 27, 2023
c2c3a02
Navigate to Login on Avatar interaction and make Direct Call button v…
liviu-timar Oct 27, 2023
78c376d
Move autoLogin flag to LoginViewModel and use in user object setup
Oct 27, 2023
e914f0e
Fix spotless violations
liviu-timar Oct 27, 2023
c9189b0
Clean-up
liviu-timar Oct 27, 2023
598bba0
Improve naming consistency
liviu-timar Oct 27, 2023
583d61e
Replace avatar click handler with long-click
liviu-timar Oct 27, 2023
cf53da0
Add user helper method that gets full name from email
liviu-timar Oct 27, 2023
00c6f0f
Show user full name and sort user list on Direct Call screen
liviu-timar Oct 27, 2023
4398e13
Rename DirectCallViewModel
liviu-timar Oct 31, 2023
01bae0f
Improve DirectCallJoinScreen UI
liviu-timar Oct 31, 2023
796265a
Show full name for current Google user
liviu-timar Oct 31, 2023
981ed14
Improve naming of parameter
liviu-timar Oct 31, 2023
cc59e20
Fix bug that set user name incorrectly after log out
liviu-timar Nov 1, 2023
27efab0
Improve naming of auto log-in param
liviu-timar Nov 1, 2023
4dd54c5
Improve nav graph
liviu-timar Nov 1, 2023
58c50e3
LoginScreen UI improvements
liviu-timar Nov 1, 2023
d5c18a9
Apply spotless
liviu-timar Nov 1, 2023
78251de
Add sign out dialog and reorder methods in CallJoinScreen
liviu-timar Nov 1, 2023
b66c955
Apply spotless
liviu-timar Nov 1, 2023
f5975f0
Add comment to explain purpose of autoLogIn parameter
liviu-timar Nov 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ class MainActivity : ComponentActivity() {
VideoTheme {
AppNavHost(
startDestination = if (!isLoggedIn) {
AppScreens.Login.destination
AppScreens.Login.routeWithArg(true) // Pass true for autoLogIn
} else {
AppScreens.CallJoin.destination
AppScreens.CallJoin.route
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
package io.getstream.video.android.data.dto

import io.getstream.video.android.models.GoogleAccount
import io.getstream.video.android.util.UserIdHelper
import java.util.Locale
import io.getstream.video.android.util.UserHelper

data class GetGoogleAccountsResponseDto(
val people: List<GoogleAccountDto>,
Expand All @@ -42,13 +41,8 @@ fun GoogleAccountDto.asDomainModel(): GoogleAccount {

return GoogleAccount(
email = email,
id = email?.let { UserIdHelper.getUserIdFromEmail(it) },
name = email
?.split("@")
?.firstOrNull()
?.split(".")
?.firstOrNull()
?.capitalize(Locale.ROOT) ?: email,
id = email?.let { UserHelper.getUserIdFromEmail(it) },
name = email?.let { UserHelper.getFullNameFromEmail(it) },
photoUrl = photos?.firstOrNull()?.url,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ class GoogleAccountRepository @Inject constructor(
okHttpClient.newCall(request).execute().let { response ->
if (response.isSuccessful) {
responseBody = response.body?.string()
responseBody?.let { parseUserListJson(it) }
responseBody?.let { body ->
parseUserListJson(body)?.sortedBy { user -> user.name }
}
} else {
null
}
Expand Down Expand Up @@ -133,7 +135,7 @@ class GoogleAccountRepository @Inject constructor(
return GoogleAccount(
email = currentUser?.email ?: "",
id = currentUser?.id ?: "",
name = currentUser?.givenName ?: "",
name = currentUser?.displayName ?: "",
photoUrl = currentUser?.photoUrl?.toString(),
isFavorite = false,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,50 +36,51 @@ import io.getstream.video.android.ui.outgoing.DirectCallJoinScreen
fun AppNavHost(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
startDestination: String = AppScreens.Login.destination,
startDestination: String = AppScreens.Login.route,
) {
NavHost(
modifier = modifier.fillMaxSize(),
navController = navController,
startDestination = startDestination,
) {
composable(AppScreens.Login.destination) {
composable(AppScreens.Login.route) { backStackEntry ->
LoginScreen(
autoLogIn = backStackEntry.arguments?.getString("auto_log_in")?.let { it.toBoolean() } ?: true,
navigateToCallJoin = {
navController.navigate(AppScreens.CallJoin.destination) {
popUpTo(AppScreens.Login.destination) { inclusive = true }
navController.navigate(AppScreens.CallJoin.route) {
popUpTo(AppScreens.Login.route) { inclusive = true }
}
},
)
}
composable(AppScreens.CallJoin.destination) {
composable(AppScreens.CallJoin.route) {
CallJoinScreen(
navigateToCallLobby = { cid ->
navController.navigate("${AppScreens.CallLobby.destination}/$cid")
navController.navigate(AppScreens.CallLobby.routeWithArg(cid))
},
navigateUpToLogin = {
navController.navigate(AppScreens.Login.destination) {
popUpTo(AppScreens.CallJoin.destination) { inclusive = true }
navigateUpToLogin = { autoLogIn ->
navController.navigate(AppScreens.Login.routeWithArg(autoLogIn)) {
popUpTo(AppScreens.CallJoin.route) { inclusive = true }
}
},
navigateToDirectCallJoin = {
navController.navigate(AppScreens.DirectCallJoin.destination)
navController.navigate(AppScreens.DirectCallJoin.route)
},
)
}
composable(
"${AppScreens.CallLobby.destination}/{cid}",
AppScreens.CallLobby.route,
arguments = listOf(navArgument("cid") { type = NavType.StringType }),
) {
CallLobbyScreen(
navigateUpToLogin = {
navController.navigate(AppScreens.Login.destination) {
popUpTo(AppScreens.CallJoin.destination) { inclusive = true }
navController.navigate(AppScreens.Login.route) {
popUpTo(AppScreens.CallJoin.route) { inclusive = true }
}
},
)
}
composable(AppScreens.DirectCallJoin.destination) {
composable(AppScreens.DirectCallJoin.route) {
val context = LocalContext.current
DirectCallJoinScreen(
navigateToDirectCall = { members ->
Expand All @@ -95,9 +96,16 @@ fun AppNavHost(
}
}

enum class AppScreens(val destination: String) {
Login("login"),
enum class AppScreens(val route: String) {
Login("login/{auto_log_in}"),
CallJoin("call_join"),
CallLobby("call_preview"),
CallLobby("call_lobby/{cid}"),
DirectCallJoin("direct_call_join"),
;

fun routeWithArg(argValue: Any): String = when (this) {
Login -> this.route.replace("{auto_log_in}", argValue.toString())
CallLobby -> this.route.replace("{cid}", argValue.toString())
else -> this.route
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ package io.getstream.video.android.ui.join
import android.net.Uri
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
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.Row
import androidx.compose.foundation.layout.Spacer
Expand All @@ -41,6 +45,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.AlertDialog
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
Expand Down Expand Up @@ -93,18 +98,19 @@ import io.getstream.video.android.ui.theme.StreamButton
fun CallJoinScreen(
callJoinViewModel: CallJoinViewModel = hiltViewModel(),
navigateToCallLobby: (callId: String) -> Unit,
navigateUpToLogin: () -> Unit,
navigateUpToLogin: (autoLogIn: Boolean) -> Unit,
navigateToDirectCallJoin: () -> Unit,
) {
val uiState by callJoinViewModel.uiState.collectAsState(CallJoinUiState.Nothing)
val isLoggedOut by callJoinViewModel.isLoggedOut.collectAsState(initial = false)
val qrCodeCallback = rememberQrCodeCallback()
val context = LocalContext.current
val qrCodeCallback = rememberQrCodeCallback()
var isSignOutDialogVisible by remember { mutableStateOf(false) }
val isLoggedOut by callJoinViewModel.isLoggedOut.collectAsState(initial = false)

HandleCallJoinUiState(
callJoinUiState = uiState,
navigateToCallLobby = navigateToCallLobby,
navigateUpToLogin = navigateUpToLogin,
navigateUpToLogin = { navigateUpToLogin(true) },
)

Column(
Expand All @@ -114,8 +120,13 @@ fun CallJoinScreen(
horizontalAlignment = Alignment.CenterHorizontally,
) {
CallJoinHeader(
onAvatarLongClick = { isSignOutDialogVisible = true },
callJoinViewModel = callJoinViewModel,
onDirectCallClick = navigateToDirectCallJoin,
onSignOutClick = {
callJoinViewModel.autoLogInAfterLogOut = false
callJoinViewModel.logOut()
},
)

CallJoinBody(
Expand All @@ -134,17 +145,50 @@ fun CallJoinScreen(
)
}

if (isSignOutDialogVisible) {
SignOutDialog(
onConfirmation = {
isSignOutDialogVisible = false
callJoinViewModel.autoLogInAfterLogOut = false
callJoinViewModel.logOut()
},
onDismissRequest = { isSignOutDialogVisible = false },
)
}

LaunchedEffect(key1 = isLoggedOut) {
if (isLoggedOut) {
navigateUpToLogin.invoke()
navigateUpToLogin.invoke(callJoinViewModel.autoLogInAfterLogOut)
}
}
}

@Composable
private fun HandleCallJoinUiState(
callJoinUiState: CallJoinUiState,
navigateToCallLobby: (callId: String) -> Unit,
navigateUpToLogin: () -> Unit,
) {
LaunchedEffect(key1 = callJoinUiState) {
when (callJoinUiState) {
is CallJoinUiState.JoinCompleted ->
navigateToCallLobby.invoke(callJoinUiState.callId)

is CallJoinUiState.GoBackToLogin ->
navigateUpToLogin.invoke()

else -> Unit
}
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun CallJoinHeader(
callJoinViewModel: CallJoinViewModel = hiltViewModel(),
onAvatarLongClick: () -> Unit,
onDirectCallClick: () -> Unit,
onSignOutClick: () -> Unit,
) {
val user by callJoinViewModel.user.collectAsState(initial = null)

Expand All @@ -155,11 +199,24 @@ private fun CallJoinHeader(
verticalAlignment = Alignment.CenterVertically,
) {
user?.let {
UserAvatar(
modifier = Modifier.size(24.dp),
userName = it.userNameOrId,
userImage = it.image,
)
Box(
modifier = if (BuildConfig.FLAVOR == "production") {
Modifier.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = {},
onLongClick = onAvatarLongClick,
)
} else {
Modifier
},
) {
UserAvatar(
modifier = Modifier.size(24.dp),
userName = it.userNameOrId,
userImage = it.image,
)
}

Spacer(modifier = Modifier.width(8.dp))
}
Expand All @@ -172,21 +229,21 @@ private fun CallJoinHeader(
fontSize = 16.sp,
)

if (BuildConfig.FLAVOR == "dogfooding") {
if (user?.custom?.get("email")?.contains("getstreamio") == true) {
TextButton(
colors = ButtonDefaults.textButtonColors(contentColor = Color.White),
content = { Text(text = stringResource(R.string.direct_call)) },
onClick = { onDirectCallClick.invoke() },
)
if (user?.custom?.get("email")?.contains("getstreamio") == true) {
TextButton(
colors = ButtonDefaults.textButtonColors(contentColor = Color.White),
content = { Text(text = stringResource(R.string.direct_call)) },
onClick = { onDirectCallClick.invoke() },
)
}

Spacer(modifier = Modifier.width(5.dp))
}
if (BuildConfig.FLAVOR == "dogfooding") {
Spacer(modifier = Modifier.width(5.dp))

StreamButton(
modifier = Modifier.widthIn(125.dp),
text = stringResource(id = R.string.sign_out),
onClick = { callJoinViewModel.signOut() },
onClick = onSignOutClick,
)
}
}
Expand All @@ -213,9 +270,6 @@ private fun CallJoinBody(
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (user != null) {
val name =
user?.name?.ifBlank { user?.id }?.ifBlank { user!!.custom["email"] }.orEmpty()

Image(
modifier = Modifier.size(102.dp),
painter = painterResource(id = R.drawable.ic_stream_video_meeting_logo),
Expand Down Expand Up @@ -359,22 +413,32 @@ private fun CallJoinBody(
}

@Composable
private fun HandleCallJoinUiState(
callJoinUiState: CallJoinUiState,
navigateToCallLobby: (callId: String) -> Unit,
navigateUpToLogin: () -> Unit,
private fun SignOutDialog(
onConfirmation: () -> Unit,
onDismissRequest: () -> Unit,
) {
LaunchedEffect(key1 = callJoinUiState) {
when (callJoinUiState) {
is CallJoinUiState.JoinCompleted ->
navigateToCallLobby.invoke(callJoinUiState.callId)

is CallJoinUiState.GoBackToLogin ->
navigateUpToLogin.invoke()

else -> Unit
}
}
AlertDialog(
modifier = Modifier.border(
BorderStroke(1.dp, Colors.background),
RoundedCornerShape(6.dp),
),
title = { Text(text = "Sign Out") },
text = { Text(text = "Are you sure you want to sign out?") },
confirmButton = {
TextButton(onClick = { onConfirmation() }) {
Text(text = "Sign Out", color = VideoTheme.colors.primaryAccent)
}
},
dismissButton = {
TextButton(onClick = { onDismissRequest() }) {
Text(text = "Cancel", color = VideoTheme.colors.primaryAccent)
}
},
onDismissRequest = { onDismissRequest },
shape = RoundedCornerShape(6.dp),
backgroundColor = Colors.secondBackground,
contentColor = Color.White,
)
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class CallJoinViewModel @Inject constructor(
) : ViewModel() {
val user: Flow<User?> = dataStore.user
val isLoggedOut = dataStore.user.map { it == null }
var autoLogInAfterLogOut = true

private val event: MutableSharedFlow<CallJoinEvent> = MutableSharedFlow()
internal val uiState: SharedFlow<CallJoinUiState> = event
Expand Down Expand Up @@ -94,7 +95,7 @@ class CallJoinViewModel @Inject constructor(
return streamVideo.call(type = type, id = id)
}

fun signOut() {
fun logOut() {
viewModelScope.launch {
FirebaseAuth.getInstance().signOut()
dataStore.clear()
Expand Down
Loading
Loading