Recreate legacy login screen in Compose.
Port the old sign-in UI and behavior (gradient, branded social buttons, policy footer, and continue-anonymous dialog) and add Login contract/ViewModel stubs while keeping unauthorized Manage-to-Login navigation intact. Made-with: Cursor
This commit is contained in:
@@ -1,16 +1,374 @@
|
|||||||
package org.db3.airmq.features.login
|
package org.db3.airmq.features.login
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.text.ClickableText
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import org.db3.airmq.R
|
import org.db3.airmq.R
|
||||||
import org.db3.airmq.features.common.MockScreenScaffold
|
import org.db3.airmq.features.login.LoginScreenContract.Action
|
||||||
import org.db3.airmq.features.common.ScreenAction
|
import org.db3.airmq.features.login.LoginScreenContract.Event
|
||||||
|
import org.db3.airmq.features.login.LoginScreenContract.State
|
||||||
|
import org.db3.airmq.ui.theme.AirMQTheme
|
||||||
|
|
||||||
|
private val LegacyLoginGradientStart = Color(0xFF449CF5)
|
||||||
|
private val LegacyLoginGradientEnd = Color(0xFF5CE4BB)
|
||||||
|
private val LegacyFacebookBlue = Color(0xFF3B5998)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(onLogInToManage: () -> Unit) {
|
fun LoginScreen(
|
||||||
MockScreenScaffold(
|
onLogInToManage: () -> Unit,
|
||||||
title = stringResource(id = R.string.button_sign_in),
|
viewModel: LoginViewModel = hiltViewModel()
|
||||||
subtitle = stringResource(id = R.string.coming_soon),
|
) {
|
||||||
actions = listOf(ScreenAction(stringResource(id = R.string.screen_log_in_to_manage), onLogInToManage))
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
val context = LocalContext.current
|
||||||
|
var showContinueAnonymousDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
viewModel.actions.collectLatest { action ->
|
||||||
|
when (action) {
|
||||||
|
Action.OpenManage -> onLogInToManage()
|
||||||
|
Action.ShowContinueAnonymousDialog -> showContinueAnonymousDialog = true
|
||||||
|
Action.OpenPrivacyPolicy -> {
|
||||||
|
Toast.makeText(context, context.getString(R.string.coming_soon), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
Action.OpenTermsAndConditions -> {
|
||||||
|
Toast.makeText(context, context.getString(R.string.coming_soon), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
is Action.ShowMessage -> {
|
||||||
|
Toast.makeText(context, action.message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoginScreenContent(
|
||||||
|
uiState = uiState,
|
||||||
|
onEvent = viewModel::onEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
if (showContinueAnonymousDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
showContinueAnonymousDialog = false
|
||||||
|
viewModel.onEvent(Event.ContinueAnonymousDismissed)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.dialog_anonym_title),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.dialog_anonym_mesage),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showContinueAnonymousDialog = false
|
||||||
|
viewModel.onEvent(Event.ContinueAnonymousDismissed)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.button_sign_in),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showContinueAnonymousDialog = false
|
||||||
|
viewModel.onEvent(Event.ContinueAnonymousConfirmed)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.button_continue),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LoginScreenContent(
|
||||||
|
uiState: State,
|
||||||
|
onEvent: (Event) -> Unit
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colorStops = arrayOf(
|
||||||
|
0.0f to LegacyLoginGradientStart,
|
||||||
|
0.35f to LegacyLoginGradientStart,
|
||||||
|
1.0f to LegacyLoginGradientEnd
|
||||||
|
),
|
||||||
|
start = Offset(0f, 0f),
|
||||||
|
end = Offset(1200f, 1200f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.statusBarsPadding()
|
||||||
|
.padding(horizontal = 40.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(56.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.text_sign_in),
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 36.sp,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.airmq_logo),
|
||||||
|
contentDescription = stringResource(id = R.string.content_airmq_logo),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(168.dp)
|
||||||
|
.alpha(0.54f)
|
||||||
|
)
|
||||||
|
if (uiState.isLoading) {
|
||||||
|
CircularProgressIndicator(color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
SocialSignInButton(
|
||||||
|
text = stringResource(id = R.string.button_sign_in_google),
|
||||||
|
iconRes = R.drawable.ic_google,
|
||||||
|
iconTint = Color.Unspecified,
|
||||||
|
backgroundColor = Color.White,
|
||||||
|
textColor = Color(0xFF202124),
|
||||||
|
onClick = { onEvent(Event.GoogleClicked) }
|
||||||
|
)
|
||||||
|
|
||||||
|
SocialSignInButton(
|
||||||
|
text = stringResource(id = R.string.button_sign_in_facebook),
|
||||||
|
iconRes = R.drawable.ic_facebook,
|
||||||
|
iconTint = Color.White,
|
||||||
|
backgroundColor = LegacyFacebookBlue,
|
||||||
|
textColor = Color.White,
|
||||||
|
onClick = { onEvent(Event.FacebookClicked) }
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { onEvent(Event.ContinueAnonymousClicked) },
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = Color.White
|
||||||
|
),
|
||||||
|
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.55f)),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(40.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.button_continue_anonym).uppercase(),
|
||||||
|
style = MaterialTheme.typography.labelLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
PrivacyAndTermsFooter(onEvent = onEvent)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SocialSignInButton(
|
||||||
|
text: String,
|
||||||
|
iconRes: Int,
|
||||||
|
iconTint: Color,
|
||||||
|
backgroundColor: Color,
|
||||||
|
textColor: Color,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
shape = RoundedCornerShape(18.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = backgroundColor,
|
||||||
|
contentColor = textColor
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(40.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxHeight()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = iconRes),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = iconTint,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.size(8.dp))
|
||||||
|
Text(
|
||||||
|
text = text.uppercase(),
|
||||||
|
style = MaterialTheme.typography.labelLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PrivacyAndTermsFooter(onEvent: (Event) -> Unit) {
|
||||||
|
val privacyPolicy = "Privacy Policy"
|
||||||
|
val termsAndConditions = "Terms & conditions"
|
||||||
|
val fullText = stringResource(
|
||||||
|
id = R.string.text_policy_label,
|
||||||
|
privacyPolicy,
|
||||||
|
termsAndConditions
|
||||||
|
)
|
||||||
|
|
||||||
|
val annotatedText = remember(fullText, privacyPolicy, termsAndConditions) {
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(fullText)
|
||||||
|
val privacyStart = fullText.indexOf(privacyPolicy)
|
||||||
|
val termsStart = fullText.indexOf(termsAndConditions)
|
||||||
|
|
||||||
|
if (privacyStart >= 0) {
|
||||||
|
val privacyEnd = privacyStart + privacyPolicy.length
|
||||||
|
addStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White.copy(alpha = 0.85f),
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
),
|
||||||
|
start = privacyStart,
|
||||||
|
end = privacyEnd
|
||||||
|
)
|
||||||
|
addStringAnnotation(
|
||||||
|
tag = "privacy",
|
||||||
|
annotation = "privacy",
|
||||||
|
start = privacyStart,
|
||||||
|
end = privacyEnd
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (termsStart >= 0) {
|
||||||
|
val termsEnd = termsStart + termsAndConditions.length
|
||||||
|
addStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
color = Color.White.copy(alpha = 0.85f),
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
),
|
||||||
|
start = termsStart,
|
||||||
|
end = termsEnd
|
||||||
|
)
|
||||||
|
addStringAnnotation(
|
||||||
|
tag = "terms",
|
||||||
|
annotation = "terms",
|
||||||
|
start = termsStart,
|
||||||
|
end = termsEnd
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClickableText(
|
||||||
|
text = annotatedText,
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = Color.White.copy(alpha = 0.63f),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = { offset ->
|
||||||
|
annotatedText.getStringAnnotations(tag = "privacy", start = offset, end = offset)
|
||||||
|
.firstOrNull()?.let {
|
||||||
|
onEvent(Event.PrivacyPolicyClicked)
|
||||||
|
}
|
||||||
|
annotatedText.getStringAnnotations(tag = "terms", start = offset, end = offset)
|
||||||
|
.firstOrNull()?.let {
|
||||||
|
onEvent(Event.TermsAndConditionsClicked)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun LoginScreenPreview() {
|
||||||
|
AirMQTheme {
|
||||||
|
LoginScreenContent(
|
||||||
|
uiState = State(),
|
||||||
|
onEvent = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.db3.airmq.features.login
|
||||||
|
|
||||||
|
object LoginScreenContract {
|
||||||
|
|
||||||
|
data class State(
|
||||||
|
val isLoading: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface Action {
|
||||||
|
data object OpenManage : Action
|
||||||
|
data object ShowContinueAnonymousDialog : Action
|
||||||
|
data object OpenPrivacyPolicy : Action
|
||||||
|
data object OpenTermsAndConditions : Action
|
||||||
|
data class ShowMessage(val message: String) : Action
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Event {
|
||||||
|
data object GoogleClicked : Event
|
||||||
|
data object FacebookClicked : Event
|
||||||
|
data object ContinueAnonymousClicked : Event
|
||||||
|
data object ContinueAnonymousConfirmed : Event
|
||||||
|
data object ContinueAnonymousDismissed : Event
|
||||||
|
data object PrivacyPolicyClicked : Event
|
||||||
|
data object TermsAndConditionsClicked : Event
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.db3.airmq.features.login
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import org.db3.airmq.R
|
||||||
|
import org.db3.airmq.features.login.LoginScreenContract.Action
|
||||||
|
import org.db3.airmq.features.login.LoginScreenContract.Event
|
||||||
|
import org.db3.airmq.features.login.LoginScreenContract.State
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class LoginViewModel @Inject constructor(
|
||||||
|
@ApplicationContext private val appContext: Context
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow(State())
|
||||||
|
val uiState: StateFlow<State> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _actions = MutableSharedFlow<Action>(extraBufferCapacity = 1)
|
||||||
|
val actions: SharedFlow<Action> = _actions.asSharedFlow()
|
||||||
|
|
||||||
|
fun onEvent(event: Event) {
|
||||||
|
when (event) {
|
||||||
|
Event.GoogleClicked -> {
|
||||||
|
_actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon)))
|
||||||
|
}
|
||||||
|
Event.FacebookClicked -> {
|
||||||
|
_actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon)))
|
||||||
|
}
|
||||||
|
Event.ContinueAnonymousClicked -> {
|
||||||
|
_actions.tryEmit(Action.ShowContinueAnonymousDialog)
|
||||||
|
}
|
||||||
|
Event.ContinueAnonymousConfirmed -> {
|
||||||
|
_actions.tryEmit(Action.OpenManage)
|
||||||
|
}
|
||||||
|
Event.ContinueAnonymousDismissed -> Unit
|
||||||
|
Event.PrivacyPolicyClicked -> {
|
||||||
|
_actions.tryEmit(Action.OpenPrivacyPolicy)
|
||||||
|
}
|
||||||
|
Event.TermsAndConditionsClicked -> {
|
||||||
|
_actions.tryEmit(Action.OpenTermsAndConditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-hdpi/ic_facebook.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_facebook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 595 B |
BIN
app/src/main/res/drawable-mdpi/ic_facebook.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_facebook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 411 B |
BIN
app/src/main/res/drawable-xhdpi/ic_facebook.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_facebook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 703 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_facebook.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_facebook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_facebook.png
Normal file
BIN
app/src/main/res/drawable-xxxhdpi/ic_facebook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable/ic_facebook.png
Normal file
BIN
app/src/main/res/drawable/ic_facebook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 595 B |
18
app/src/main/res/drawable/ic_google.xml
Normal file
18
app/src/main/res/drawable/ic_google.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="18dp"
|
||||||
|
android:height="18dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFC107"
|
||||||
|
android:pathData="M43.61,20.08H42V20H24v8h11.3C33.65,32.66 29.19,36 24,36c-6.63,0 -12,-5.37 -12,-12s5.37,-12 12,-12c3.06,0 5.84,1.15 7.96,3.04l5.66,-5.66C34.01,6.05 29.27,4 24,4 12.95,4 4,12.95 4,24s8.95,20 20,20 20,-8.95 20,-20c0,-1.34 -0.14,-2.65 -0.39,-3.92z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF3D00"
|
||||||
|
android:pathData="M6.31,14.69l6.57,4.82C14.66,15.1 18.94,12 24,12c3.06,0 5.84,1.15 7.96,3.04l5.66,-5.66C34.01,6.05 29.27,4 24,4 16.32,4 9.65,8.34 6.31,14.69z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#4CAF50"
|
||||||
|
android:pathData="M24,44c5.17,0 9.86,-1.98 13.41,-5.2l-6.19,-5.24C29.17,35.1 26.7,36 24,36c-5.17,0 -9.61,-3.32 -11.26,-7.93l-6.52,5.02C9.52,39.56 16.23,44 24,44z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#1976D2"
|
||||||
|
android:pathData="M43.61,20.08H42V20H24v8h11.3c-0.79,2.25 -2.23,4.2 -4.08,5.56l0,0 6.19,5.24C37.01,39.16 44,34 44,24c0,-1.34 -0.14,-2.65 -0.39,-3.92z" />
|
||||||
|
</vector>
|
||||||
Reference in New Issue
Block a user