From 90792c601cee6850bfa73f854f6f09a864921b1b Mon Sep 17 00:00:00 2001 From: beetzung Date: Sun, 1 Mar 2026 19:34:29 +0100 Subject: [PATCH] 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 --- .../db3/airmq/features/login/LoginScreen.kt | 372 +++++++++++++++++- .../features/login/LoginScreenContract.kt | 26 ++ .../airmq/features/login/LoginViewModel.kt | 53 +++ .../main/res/drawable-hdpi/ic_facebook.png | Bin 0 -> 595 bytes .../main/res/drawable-mdpi/ic_facebook.png | Bin 0 -> 411 bytes .../main/res/drawable-xhdpi/ic_facebook.png | Bin 0 -> 703 bytes .../main/res/drawable-xxhdpi/ic_facebook.png | Bin 0 -> 1234 bytes .../main/res/drawable-xxxhdpi/ic_facebook.png | Bin 0 -> 1292 bytes app/src/main/res/drawable/ic_facebook.png | Bin 0 -> 595 bytes app/src/main/res/drawable/ic_google.xml | 18 + 10 files changed, 462 insertions(+), 7 deletions(-) create mode 100644 app/src/main/kotlin/org/db3/airmq/features/login/LoginScreenContract.kt create mode 100644 app/src/main/kotlin/org/db3/airmq/features/login/LoginViewModel.kt create mode 100644 app/src/main/res/drawable-hdpi/ic_facebook.png create mode 100644 app/src/main/res/drawable-mdpi/ic_facebook.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_facebook.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_facebook.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_facebook.png create mode 100644 app/src/main/res/drawable/ic_facebook.png create mode 100644 app/src/main/res/drawable/ic_google.xml diff --git a/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreen.kt b/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreen.kt index 50e3e13..ccbbc81 100644 --- a/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreen.kt +++ b/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreen.kt @@ -1,16 +1,374 @@ 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.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.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.features.common.MockScreenScaffold -import org.db3.airmq.features.common.ScreenAction +import org.db3.airmq.features.login.LoginScreenContract.Action +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 -fun LoginScreen(onLogInToManage: () -> Unit) { - MockScreenScaffold( - title = stringResource(id = R.string.button_sign_in), - subtitle = stringResource(id = R.string.coming_soon), - actions = listOf(ScreenAction(stringResource(id = R.string.screen_log_in_to_manage), onLogInToManage)) +fun LoginScreen( + onLogInToManage: () -> Unit, + viewModel: LoginViewModel = hiltViewModel() +) { + 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 = {} + ) + } +} diff --git a/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreenContract.kt b/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreenContract.kt new file mode 100644 index 0000000..cb25111 --- /dev/null +++ b/app/src/main/kotlin/org/db3/airmq/features/login/LoginScreenContract.kt @@ -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 + } +} diff --git a/app/src/main/kotlin/org/db3/airmq/features/login/LoginViewModel.kt b/app/src/main/kotlin/org/db3/airmq/features/login/LoginViewModel.kt new file mode 100644 index 0000000..3703347 --- /dev/null +++ b/app/src/main/kotlin/org/db3/airmq/features/login/LoginViewModel.kt @@ -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 = _uiState.asStateFlow() + + private val _actions = MutableSharedFlow(extraBufferCapacity = 1) + val actions: SharedFlow = _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) + } + } + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_facebook.png b/app/src/main/res/drawable-hdpi/ic_facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..bf1b6c03dd03e6e942d9febb608f594f5accb93f GIT binary patch literal 595 zcmV-Z0<8UsP)ilW_Gtu~(B^;2b|(P*tyDm@{D)U(0}Awy%#U8U6au`t(l?`W+bQ%aYz z!T`9&m2oap~CZ+Urt}p=Kj4_*@=WX?Ry^pTz#!4xx)oQ)w zoL|We27nl2wt^sNmr5mPCKCV}$MFv5JZ-XkWCY+vQFOc0>AdQ8yJ)xD7cI-WMJYWc zgji*aoyik+5XbSll=2M#6$HVvLZPspR+a`KgysfIlH?#slABV>9RLO2_qT1^zHzjJ zGsBEAhX6hRI5fsQ(pv8Uu*T!@J*CtoLWmSXoKNM<4D)?|&vBf!$z%e~xzlVm!&JKc z{r#osbgHG4=NgU1rVyf;8RmK3d#&}#?*`s+oRvzY@`6&joEa8|Vb2)z&=@n7Qohq# ze*wUU!{KGywi}GG3reXC##kjYY*x`i8stALRp1}~v%lTc>HdnMXv4Pcx2ct=*Xz%f zQrCZ(zhM}L_lw2i%VU@44-c{m;Ow6^HDfGDl4Q49t$s@9Y%myH6+$c@&6(>#vRZ+8 h`6p-P=DjTI`FENS89L4l*U10?002ovPDHLkV1gTX8JqwB literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_facebook.png b/app/src/main/res/drawable-mdpi/ic_facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..4a671b4d4585011761445dfb954700dbb5482d2e GIT binary patch literal 411 zcmV;M0c8G(P)qJ!Y#;-sLXgCMuK2sa8k=)n!n3OcwrIEjMz z6*@X}Y>{$9Ii=*9q|)IIPd&*U2WEcx@$!;?fH9#3Cgd1>CV#OVirC{@i#mh&3Wgkv}Sg zh$NrSU)ObAG%{dj91-%KCaj6w|zd}r>y`X;yD0( zM?{VgLfHZdAxnF5D?o^NrfJ%%!C>&ZR;zs#MG>acsl(}X+9Huifl_*Hu88G80B|-O z4)40%?$4_s%w#frI-RalN|mhvjmP6>k|d=$j>CTr1OQs2(YWRJ`@hU#R8>`ES$-tT z@)cdzsnh8^rj%aYib9BZN(eb(IfgmRX0uW%mHHsd@}0k(Kuh-!0Gv`v{d4D)O64dL ziJaJMHtGKbLPP|BS+-{(69i#Np-?FFOCc6Yp68b;!=;C;-EJ2(HTP^5O^U;(o;);0N@-EzcD8OP!~n< z=G6Uz`71ImwZe2keN2fG9a^! zA_KD9?Mu}@7!00JO4sf~8#RhQuM(tKEFO+VqYHP7tUXR9lZ}OHPjb23Apoq|*r=5@ lI!#uO?|;V_DZFzE{Q&e42}2N}TKWJ0002ovPDHLkV1m^KKi~iW literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_facebook.png b/app/src/main/res/drawable-xxhdpi/ic_facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc8034c8f143388a6757eb928b7f8dd3a9df24d GIT binary patch literal 1234 zcmV;@1TFiCP)^PAuNW`J-`FK|w|gaGWUc{cFax$_uf49w5Z%Yq;vpTHgDayc6Sz>c%$SSc0? z1+Blo|FS5GBd+U4AcS3Iqm&95<9{K956b28{h^_uQY(X6f-%NmsZ@Hcr>AEYA#@P{ zAa<4N|HdgHWWzMgiKV5bAIHYV4w~835^Q;SSsff4oQy;wpTlmu>Zs++G|g`;%bJSC zVvkM*wz9GkP9~F6x~_i$fd7JLrBb;=2>Ddk^@ryQ%(ARIuIu_9j4{S2|5baRO9XJG zcz=R$Bx?B@jDxtY`wyjbnNqq&2-)Regb)=%h!8>$WBeRK=p_IEu9uu|!6>DBrfL36 z2>IT&?Y|7eD4C{dRgK4vg#u#&L+rGQMzyDc09^XE0Zah<9l+wQ~%ep!}J^f2QpRWarrhHYf zs~F=ieeE$G`W(mktx~DHAB)A->uGqVv$nRDP*wGUEXx`p#EXoIqP!tV(t8lXke{XK zx~_X`b93|KY&Kg96rQ~s8ygoQk;q4qB#i<9yzx{`czPj((Q4R#%;vYKXgiKGE6eh= z`rcn$T*TRI_FY}qZz6>HPLh-Q5o|`5eF@!{G@C;UOtj^Ti{I>>3!Q^q^cWFWR=fL@D(asQ>_% z5OOb-O5JN1V^TC49YYAc&KTom2=5X_5rxCyOBmzleG?T4A!e~y{F+jF!!QibYw>uT zZEbD2BO@bjqYD^gXnT8Gsy)l^M zzW!`qU*DXfD6cdwcF#1}?(Xg-MNw``lJr6&_*n)6#bWV_rfJ{Fviyv%z)GdkY@ty2 zqTZ-nE=QUzQ}X#d9335fyi!gi5-^=kzp1L~ErgKn8!*Ngu`KIP#@HVXG) z+k3a((ACw|kSxn@Ynt|!D2iSL<Hii4q0=tTg)kz#Gvq}U>0c=K4Zlq$$Psy!BX ztWi2osbCsNvE8H|NHLINJB=KFKNw@}`Y#w`!5C|&5x^pur%9XM=DKBBybk#T0N`|+ z>->Yf!GVE+%Yq=hOb7{g3dR^aAcXv$PN#pZ{}!(VCzHunA%s_W0jE_vwc`+su?IUl zJNJi&hfjYTqV{pVWUz;(L;wH)07*qoM6N<$f{(9Bp#T5? literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_facebook.png b/app/src/main/res/drawable-xxxhdpi/ic_facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..41359ab8c5094e4abb89125ecb7db681da0ce2ff GIT binary patch literal 1292 zcmV+n1@roeP)JCp$^X8k)v*^A}$dtuM5;2>vO(RD>dWF5*L_3f>j;oTFa66cI0;1R>(F zDpV*QT58=?eI>|4N=hU#O;VdbZFV==-I38(d9O{fn{Hd1boP+T%+Aj5lkfcIH^1E< zu#u}L0YLclCO`lgD1-nafUG$Yq9CFmq9AJoh=Q!~>&pirguuqehF4J(!P;a0pSzt- z=Rq!)`*U$|Q8POO(||-#9P@ZQU%B1xXIYkYAcV|5<2v$iGlO3DK8;4BTrQX6(P*@0G*?rAwzjqsQ&UrSIgX=@1{{?3{r&yt z#>dBhG@7p|KB-4Sk!4w<@dl>7SS-FG2*QfdgbARF*yf3X3@8#n0|E$#7U)6KM@@+000i^h2Z3Sg%wn zRkz!HuU@ayM@L6~nxtr&i6F{d3$g;drDwST3L?Va%z`=coUO~F9>(y%YgVE8^j{yK( z{SHpQq|<4}$0Ggc)OeT}m($doVf*>SWmWBN;LEL|!14UI; z^9;j80RT!T<2N@qeThWkyOEKR>GQxDctEXIE1%2dqVagVBcMzsGaZRUzIVA?9s;NX z0Ro69sQ1DkOU%G=5J33UiI7cI)fNu+aQ@~&W~!?GQ79Ci?B=9*c6O#hq0l!Dhr@T8 zH6E9yX`QBNdyjO`_21^3%_hp{^Y7MbwM@I+ekc-lI2?aXPfv^8qUcN}V{f%uw9(JJq-cBEM!^M@SB5c z?}K{%yX2@r&SKVLW1=9UAfg~^1c-vH@gqKnD2OP?8UdmpYy5~0A_^i3vPOU?$QnQ5 zgN98(S6vQuMGAUB5QH@&ts7?E@BsLu$Y?aW9twr7q@eDy0pWtW-QC^CXJ%%q7XkilW_Gtu~(B^;2b|(P*tyDm@{D)U(0}Awy%#U8U6au`t(l?`W+bQ%aYz z!T`9&m2oap~CZ+Urt}p=Kj4_*@=WX?Ry^pTz#!4xx)oQ)w zoL|We27nl2wt^sNmr5mPCKCV}$MFv5JZ-XkWCY+vQFOc0>AdQ8yJ)xD7cI-WMJYWc zgji*aoyik+5XbSll=2M#6$HVvLZPspR+a`KgysfIlH?#slABV>9RLO2_qT1^zHzjJ zGsBEAhX6hRI5fsQ(pv8Uu*T!@J*CtoLWmSXoKNM<4D)?|&vBf!$z%e~xzlVm!&JKc z{r#osbgHG4=NgU1rVyf;8RmK3d#&}#?*`s+oRvzY@`6&joEa8|Vb2)z&=@n7Qohq# ze*wUU!{KGywi}GG3reXC##kjYY*x`i8stALRp1}~v%lTc>HdnMXv4Pcx2ct=*Xz%f zQrCZ(zhM}L_lw2i%VU@44-c{m;Ow6^HDfGDl4Q49t$s@9Y%myH6+$c@&6(>#vRZ+8 h`6p-P=DjTI`FENS89L4l*U10?002ovPDHLkV1gTX8JqwB literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_google.xml b/app/src/main/res/drawable/ic_google.xml new file mode 100644 index 0000000..2e6398b --- /dev/null +++ b/app/src/main/res/drawable/ic_google.xml @@ -0,0 +1,18 @@ + + + + + +