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

@W-17411362: LoginActivity Fit and Finish #2668

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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 @@ -42,7 +42,9 @@ import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.os.Build.MODEL
import android.os.Build.VERSION.RELEASE
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION.SECURITY_PATCH
import android.os.Build.VERSION_CODES.R
import android.os.Bundle
import android.os.Handler
import android.os.Looper.getMainLooper
Expand All @@ -52,6 +54,7 @@ import android.text.TextUtils.isEmpty
import android.text.TextUtils.join
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
import android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.webkit.CookieManager
import android.webkit.URLUtil.isHttpsUrl
import androidx.compose.material3.ColorScheme
Expand Down Expand Up @@ -1498,18 +1501,29 @@ open class SalesforceSDKManager protected constructor(
}

/**
* Sets the system status and navigation bars as visible regardless of style
* and OS dark theme states.
* Sets the system status and navigation bars to the light theme without
* regard to the system theme. This is useful when the background is light
* even when dark theme is enabled.
*
* @param activity The activity used to set style attributes
* @param activity The activity
*/
open fun setViewNavigationVisibility(activity: Activity) {
if (!isDarkTheme || activity.javaClass.name == loginActivityClass.name) {
/*
* This covers the case where OS dark theme is true, but app has
* disabled.
*/
activity.window.decorView.systemUiVisibility = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
if (SDK_INT > R) {
JohnsonEricAtSalesforce marked this conversation as resolved.
Show resolved Hide resolved
runCatching {
activity.window?.insetsController?.setSystemBarsAppearance(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brandonpage - Here's a modern way to accomplish what this once did with a dandy to-do so we can clean up once API 30 is our minimum. I did some reading, and though the names have changed the overall approach is almost identical.

APPEARANCE_LIGHT_STATUS_BARS,
APPEARANCE_LIGHT_STATUS_BARS
)
}
} else {
// TODO: Remove with minimum API >= 30
activity.window?.decorView?.systemUiVisibility = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ import com.salesforce.androidsdk.accounts.UserAccountManager.USER_SWITCH_TYPE_LO
import com.salesforce.androidsdk.analytics.SalesforceAnalyticsManager
import com.salesforce.androidsdk.app.Features.FEATURE_QR_CODE_LOGIN
import com.salesforce.androidsdk.app.SalesforceSDKManager
import com.salesforce.androidsdk.app.SalesforceSDKManager.Theme.DARK
import com.salesforce.androidsdk.auth.HttpAccess
import com.salesforce.androidsdk.auth.OAuth2.OAuthFailedException
import com.salesforce.androidsdk.auth.OAuth2.TokenEndpointResponse
Expand Down Expand Up @@ -157,7 +158,7 @@ import java.security.cert.X509Certificate
* refresh tokens to create an account via the account manager which stores
* them.
*/
open class LoginActivity: FragmentActivity() {
open class LoginActivity : FragmentActivity() {
// View Model
protected open val viewModel: LoginViewModel
by viewModels { SalesforceSDKManager.getInstance().loginViewModelFactory }
Expand Down Expand Up @@ -194,6 +195,9 @@ open class LoginActivity: FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
if (viewModel.dynamicBackgroundTheme.value == DARK) {
SalesforceSDKManager.getInstance().setViewNavigationVisibility(this)
}

/*
* For Salesforce Identity API UI Bridge support, the overriding
Expand Down Expand Up @@ -754,8 +758,6 @@ open class LoginActivity: FragmentActivity() {

/**
* Called when the IDP login button is clicked.
*
* @param v IDP login button
*/
open fun onIDPLoginClick() {
SalesforceSDKManager.getInstance().spManager?.kickOffSPInitiatedLoginFlow(
Expand Down Expand Up @@ -858,7 +860,7 @@ open class LoginActivity: FragmentActivity() {
* AuthWebViewClient is an inner class of LoginActivity because it makes extensive use of the LoginViewModel,
* which is only available to Activity classes (and composable functions).
*/
open inner class AuthWebViewClient: WebViewClient() {
open inner class AuthWebViewClient : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
// Check if user entered a custom domain
val customDomainPatternMatch = SalesforceSDKManager.getInstance()
Expand All @@ -874,6 +876,7 @@ open class LoginActivity: FragmentActivity() {
null ->
// Add also sets as selected
serverManager.addCustomLoginServer("Custom Domain", baseUrl)

else ->
serverManager.selectedLoginServer = loginServer
}
Expand Down Expand Up @@ -986,6 +989,7 @@ open class LoginActivity: FragmentActivity() {
private const val SETUP_REQUEST_CODE = 72
private const val TAG = "LoginActivity"
private const val PROMPT_LOGIN = "&prompt=login"

// This parses the expected "rgb(x, x, x)" string.
private val rgbTextPattern = "rgb\\((\\d{1,3}), (\\d{1,3}), (\\d{1,3})\\)".toRegex()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Color.Companion.Black
import androidx.compose.ui.graphics.Color.Companion.White
import androidx.compose.ui.graphics.luminance
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.ViewModel
Expand All @@ -44,6 +46,8 @@ import androidx.lifecycle.viewmodel.CreationExtras
import com.salesforce.androidsdk.R.string.oauth_display_type
import com.salesforce.androidsdk.accounts.UserAccount
import com.salesforce.androidsdk.app.SalesforceSDKManager
import com.salesforce.androidsdk.app.SalesforceSDKManager.Theme.DARK
import com.salesforce.androidsdk.app.SalesforceSDKManager.Theme.LIGHT
import com.salesforce.androidsdk.auth.HttpAccess
import com.salesforce.androidsdk.auth.OAuth2
import com.salesforce.androidsdk.auth.OAuth2.TokenEndpointResponse
Expand All @@ -69,8 +73,9 @@ open class LoginViewModel(val bootConfig: BootConfig) : ViewModel() {
val selectedServer = MediatorLiveData<String>()
val loginUrl = MediatorLiveData<String>()
internal var isIDPLoginFlowEnabled = derivedStateOf { SalesforceSDKManager.getInstance().isIDPLoginFlowEnabled }
internal var dynamicBackgroundColor = mutableStateOf(Color.White)
internal var dynamicHeaderTextColor = derivedStateOf { if (dynamicBackgroundColor.value.luminance() > 0.5) Color.Black else Color.White }
internal var dynamicBackgroundColor = mutableStateOf(White)
internal var dynamicBackgroundTheme = derivedStateOf { if (dynamicBackgroundColor.value.luminance() > 0.5) DARK else LIGHT }
internal var dynamicHeaderTextColor = derivedStateOf { if (dynamicBackgroundColor.value.luminance() > 0.5) Black else White }
internal var showServerPicker = mutableStateOf(false)
internal val defaultTitleText: String
get() = if (loginUrl.value == ABOUT_BLANK) "" else selectedServer.value ?: ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ fun LoginView() {
val viewModel: LoginViewModel = viewModel(factory = SalesforceSDKManager.getInstance().loginViewModelFactory)
val loginUrl: String = viewModel.loginUrl.observeAsState().value ?: ""
var showMenu by remember { mutableStateOf(false) }
val dynamicBackgroundColor = viewModel.dynamicBackgroundColor.value
val dynamicHeaderTextColor = viewModel.dynamicHeaderTextColor.value
val topBarColor: Color = viewModel.topBarColor ?: viewModel.dynamicBackgroundColor.value
val activity: LoginActivity = LocalContext.current.getActivity() as LoginActivity
val titleText = if (viewModel.isUsingFrontDoorBridge) {
Expand All @@ -104,7 +106,7 @@ fun LoginView() {
title = viewModel.titleComposable ?: {
Text(
text = titleText,
color = viewModel.dynamicHeaderTextColor.value,
color = dynamicHeaderTextColor,
fontWeight = FontWeight.Bold,
)
},
Expand All @@ -113,7 +115,7 @@ fun LoginView() {
onClick = { showMenu = !showMenu },
colors = IconButtonColors(
containerColor = Color.Transparent,
contentColor = viewModel.dynamicHeaderTextColor.value,
contentColor = dynamicHeaderTextColor,
disabledContainerColor = Color.Transparent,
disabledContentColor = Color.Transparent,
),
Expand All @@ -123,10 +125,9 @@ fun LoginView() {
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
containerColor = Color.White,
) {
DropdownMenuItem(
text = { Text("Change Server", color = Color.Gray) },
text = { Text("Change Server") },
JohnsonEricAtSalesforce marked this conversation as resolved.
Show resolved Hide resolved
onClick = {
viewModel.showServerPicker.value = true
showMenu = false
Expand All @@ -137,11 +138,11 @@ fun LoginView() {
viewModel.clearCookies()
viewModel.reloadWebview()
},
text = { Text("Clear Cookies", color = Color.Gray) },
text = { Text("Clear Cookies") },
)
DropdownMenuItem(
onClick = { viewModel.reloadWebview() },
text = { Text("Reload", color = Color.Gray) },
text = { Text("Reload") },
)
}
},
Expand All @@ -151,7 +152,7 @@ fun LoginView() {
onClick = { activity.finish() },
colors = IconButtonColors(
containerColor = Color.Transparent,
contentColor = Color.Black, // TODO: fix color
contentColor = dynamicHeaderTextColor,
disabledContainerColor = Color.Transparent,
disabledContentColor = Color.Transparent,
),
Expand All @@ -166,7 +167,7 @@ fun LoginView() {
)
},
bottomBar = {
BottomAppBar(containerColor = viewModel.dynamicBackgroundColor.value) {
BottomAppBar(containerColor = dynamicBackgroundColor) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
Expand Down Expand Up @@ -208,7 +209,6 @@ fun LoginView() {
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = Color.Black,
modifier = Modifier
.size(50.dp)
.fillMaxSize(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ fun sfLightColors(): ColorScheme {
secondary = Color(SFColors.secondaryColor(context)),
background = Color(SFColors.background(context)),
surface = Color(SFColors.layoutBackground(context)),
surfaceContainer = Color(SFColors.layoutBackground(context)),
JohnsonEricAtSalesforce marked this conversation as resolved.
Show resolved Hide resolved
onPrimary = Color(SFColors.onPrimaryColor(context)),
onSecondary = Color(SFColors.textColor(context)),
onBackground = Color(SFColors.textColor(context)),
Expand All @@ -156,6 +157,7 @@ fun sfDarkColors(): ColorScheme {
secondary = Color(SFColors.secondaryColorDark(context)),
background = Color(SFColors.backgroundDark(context)),
surface = Color(SFColors.layoutBackgroundDark(context)),
surfaceContainer = Color(SFColors.layoutBackgroundDark(context)),
onPrimary = Color(SFColors.onPrimaryColorDark(context)),
onSecondary = Color(SFColors.textColorDark(context)),
onBackground = Color(SFColors.textColorDark(context)),
Expand All @@ -171,24 +173,6 @@ fun sfDarkColors(): ColorScheme {
)
}

@Composable
fun sfDarkLoginColors(): ColorScheme {
val context = LocalContext.current
return darkColorScheme(
primary = Color(SFColors.primaryColor(context)),
primaryContainer = Color(SFColors.primaryColorDark(context)),
secondary = Color(SFColors.secondaryColorDark(context)),
background = Color(SFColors.background(context)), // Overriding with light mode background
surface = Color(SFColors.layoutBackground(context)), // Overriding with light mode layout
onPrimary = Color(SFColors.secondaryColorDark(context)),
onSecondary = Color(SFColors.textColorDark(context)),
onBackground = Color(SFColors.textColorDark(context)),
onSurface = Color(SFColors.textColorDark(context)),
inverseSurface = Color(SFColors.background(context)), // Login-specific background
inverseOnSurface = Color(SFColors.background(context)) // Login-specific navigation bar
)
}

val ColorScheme.hintTextColor: Color
@Composable
get() = Color(SFColors.hintColor(LocalContext.current))
get() = Color(SFColors.hintColor(LocalContext.current))