Skip to content

Commit 055ad37

Browse files
authored
Merge pull request #23 from LookUpGroup27/feature/sign-in-screen
Feature/sign in screen
2 parents b166837 + bf5e0df commit 055ad37

File tree

6 files changed

+237
-4
lines changed

6 files changed

+237
-4
lines changed

.github/workflows/CI.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ jobs:
2626
submodules: recursive
2727
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of Sonar analysis (if we use Sonar Later)
2828

29+
# This step removes the current gradle cache to avoid any caching issues
30+
- name: Remove current gradle cache
31+
run: rm -rf ~/.gradle
2932

3033
# Kernel-based Virtual Machine (KVM) is an open source virtualization technology built into Linux. Enabling it allows the Android emulator to run faster.
3134
- name: Enable KVM group perms
@@ -44,8 +47,13 @@ jobs:
4447
# this means that one would need to re-download and re-process gradle files for every run. Which is very time consuming.
4548
#
4649
# To avoid that, we cache the the gradle folder to reuse it later.
47-
- name: Gradle cache
48-
uses: gradle/actions/setup-gradle@v3
50+
- name: Retrieve gradle cache
51+
uses: actions/cache@v3
52+
with:
53+
path: |
54+
~/.gradle/caches
55+
~/.gradle/wrapper
56+
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
4957

5058
# Cache the Emulator, if the cache does not hit, create the emulator
5159
- name: AVD cache
@@ -69,6 +77,13 @@ jobs:
6977
disable-animations: false
7078
script: echo "Generated AVD snapshot for caching."
7179

80+
# Load google-services.json and local.properties from the secrets
81+
- name: Decode secrets
82+
env:
83+
GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}
84+
run: |
85+
echo "$GOOGLE_SERVICES" | base64 --decode > ./app/google-services.json
86+
7287
- name: Grant execute permission for gradlew
7388
run: |
7489
chmod +x ./gradlew

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
alias(libs.plugins.jetbrainsKotlinAndroid)
44
alias(libs.plugins.ktfmt)
55
alias(libs.plugins.sonar)
6+
alias(libs.plugins.gms)
67
id("jacoco")
78
}
89

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.github.lookupgroup27.lookup.ui.authentication
2+
3+
import androidx.compose.ui.test.assertHasClickAction
4+
import androidx.compose.ui.test.assertIsDisplayed
5+
import androidx.compose.ui.test.assertTextEquals
6+
import androidx.compose.ui.test.junit4.createComposeRule
7+
import androidx.compose.ui.test.onNodeWithTag
8+
import androidx.compose.ui.test.performClick
9+
import androidx.navigation.compose.rememberNavController
10+
import androidx.test.espresso.intent.Intents
11+
import androidx.test.espresso.intent.Intents.intended
12+
import androidx.test.espresso.intent.matcher.IntentMatchers.toPackage
13+
import androidx.test.ext.junit.runners.AndroidJUnit4
14+
import com.github.lookupgroup27.lookup.ui.navigation.NavigationActions
15+
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
16+
import org.junit.After
17+
import org.junit.Before
18+
import org.junit.Rule
19+
import org.junit.Test
20+
import org.junit.runner.RunWith
21+
22+
@RunWith(AndroidJUnit4::class)
23+
class LoginTest : TestCase() {
24+
25+
@get:Rule val composeTestRule = createComposeRule()
26+
27+
@Before
28+
fun setUp() {
29+
Intents.init()
30+
composeTestRule.setContent {
31+
val navController = rememberNavController()
32+
val navigationActions = NavigationActions(navController)
33+
SignInScreen(navigationActions)
34+
}
35+
}
36+
37+
@After
38+
fun tearDown() {
39+
Intents.release()
40+
}
41+
42+
@Test
43+
fun titleAndButtonAreCorrectlyDisplayed() {
44+
// Assert that the title "Welcome" and the Google Sign-In button are displayed correctly
45+
composeTestRule.onNodeWithTag("loginTitle").assertIsDisplayed()
46+
composeTestRule.onNodeWithTag("loginTitle").assertTextEquals("Welcome")
47+
48+
composeTestRule.onNodeWithTag("loginButton").assertIsDisplayed()
49+
composeTestRule.onNodeWithTag("loginButton").assertHasClickAction()
50+
}
51+
52+
@Test
53+
fun googleSignInReturnsValidActivityResult() {
54+
// Perform click on the Google Sign-In button
55+
composeTestRule.onNodeWithTag("loginButton").performClick()
56+
composeTestRule.waitForIdle()
57+
58+
// Assert that an Intent to Google Mobile Services was sent
59+
intended(toPackage("com.google.android.gms"))
60+
}
61+
}
Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,160 @@
11
package com.github.lookupgroup27.lookup.ui.authentication
22

3+
import android.content.Intent
4+
import android.util.Log
5+
import android.widget.Toast
6+
import androidx.activity.compose.ManagedActivityResultLauncher
7+
import androidx.activity.compose.rememberLauncherForActivityResult
8+
import androidx.activity.result.ActivityResult
9+
import androidx.activity.result.contract.ActivityResultContracts
10+
import androidx.compose.foundation.BorderStroke
11+
import androidx.compose.foundation.Image
12+
import androidx.compose.foundation.layout.*
13+
import androidx.compose.foundation.shape.RoundedCornerShape
14+
import androidx.compose.material3.Button
15+
import androidx.compose.material3.ButtonDefaults
16+
import androidx.compose.material3.MaterialTheme
17+
import androidx.compose.material3.Scaffold
18+
import androidx.compose.material3.Text
319
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.rememberCoroutineScope
21+
import androidx.compose.ui.Alignment
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.graphics.Color
24+
import androidx.compose.ui.platform.LocalContext
25+
import androidx.compose.ui.platform.testTag
26+
import androidx.compose.ui.res.painterResource
27+
import androidx.compose.ui.res.stringResource
28+
import androidx.compose.ui.text.font.FontWeight
29+
import androidx.compose.ui.text.style.TextAlign
30+
import androidx.compose.ui.unit.dp
31+
import androidx.compose.ui.unit.sp
32+
import com.github.lookupgroup27.lookup.R
33+
import com.github.lookupgroup27.lookup.ui.navigation.NavigationActions
34+
import com.google.android.gms.auth.api.signin.GoogleSignIn
35+
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
36+
import com.google.android.gms.common.api.ApiException
37+
import com.google.firebase.Firebase
38+
import com.google.firebase.auth.AuthResult
39+
import com.google.firebase.auth.GoogleAuthProvider
40+
import com.google.firebase.auth.auth
41+
import kotlinx.coroutines.launch
42+
import kotlinx.coroutines.tasks.await
443

5-
@Composable fun SignIn() {}
44+
@Composable
45+
fun SignInScreen(navigationActions: NavigationActions) {
46+
val context = LocalContext.current
47+
48+
val launcher =
49+
rememberFirebaseAuthLauncher(
50+
onAuthComplete = { result ->
51+
Log.d("SignInScreen", "User signed in: ${result.user?.displayName}")
52+
Toast.makeText(context, "Login successful!", Toast.LENGTH_LONG).show()
53+
// TODO: navigate to the next screen
54+
},
55+
onAuthError = {
56+
Log.e("SignInScreen", "Failed to sign in: ${it.statusCode}")
57+
Toast.makeText(context, "Login Failed!", Toast.LENGTH_LONG).show()
58+
})
59+
val token = stringResource(R.string.default_web_client_id)
60+
61+
Scaffold(
62+
modifier = Modifier.fillMaxSize(),
63+
containerColor = Color.White,
64+
content = { padding ->
65+
Column(
66+
modifier = Modifier.fillMaxSize().padding(padding),
67+
horizontalAlignment = Alignment.CenterHorizontally,
68+
verticalArrangement = Arrangement.Center,
69+
) {
70+
// App Logo Image
71+
Image(
72+
painter = painterResource(id = R.drawable.app_logo),
73+
contentDescription = "App Logo",
74+
modifier = Modifier.size(250.dp))
75+
76+
Spacer(modifier = Modifier.height(16.dp))
77+
78+
// Welcome Text
79+
Text(
80+
modifier = Modifier.testTag("loginTitle"),
81+
text = "Welcome",
82+
style =
83+
MaterialTheme.typography.headlineLarge.copy(fontSize = 57.sp, lineHeight = 64.sp),
84+
fontWeight = FontWeight.Bold,
85+
// center the text
86+
87+
textAlign = TextAlign.Center,
88+
color = Color.White)
89+
90+
Spacer(modifier = Modifier.height(48.dp))
91+
92+
// Authenticate With Google Button
93+
GoogleSignInButton(
94+
onSignInClick = {
95+
val gso =
96+
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
97+
.requestIdToken(token)
98+
.requestEmail()
99+
.build()
100+
val googleSignInClient = GoogleSignIn.getClient(context, gso)
101+
launcher.launch(googleSignInClient.signInIntent)
102+
})
103+
}
104+
})
105+
}
106+
107+
@Composable
108+
fun GoogleSignInButton(onSignInClick: () -> Unit) {
109+
Button(
110+
onClick = onSignInClick,
111+
colors = ButtonDefaults.buttonColors(containerColor = Color.White), // Button color
112+
shape = RoundedCornerShape(50), // Circular edges for the button
113+
border = BorderStroke(1.dp, Color.LightGray),
114+
modifier =
115+
Modifier.padding(8.dp)
116+
.height(48.dp) // Adjust height as needed
117+
.testTag("loginButton")) {
118+
Row(
119+
verticalAlignment = Alignment.CenterVertically,
120+
horizontalArrangement = Arrangement.Center,
121+
modifier = Modifier.fillMaxWidth()) {
122+
// Load the Google logo from resources
123+
Image(
124+
painter = painterResource(id = R.drawable.google_logo),
125+
contentDescription = "Google Logo",
126+
modifier =
127+
Modifier.size(30.dp) // Size of the Google logo
128+
.padding(end = 8.dp))
129+
130+
// Text for the button
131+
Text(
132+
text = "Sign in with Google",
133+
color = Color.Gray, // Text color
134+
fontSize = 16.sp, // Font size
135+
fontWeight = FontWeight.Medium)
136+
}
137+
}
138+
}
139+
140+
@Composable
141+
fun rememberFirebaseAuthLauncher(
142+
onAuthComplete: (AuthResult) -> Unit,
143+
onAuthError: (ApiException) -> Unit
144+
): ManagedActivityResultLauncher<Intent, ActivityResult> {
145+
val scope = rememberCoroutineScope()
146+
return rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
147+
result ->
148+
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
149+
try {
150+
val account = task.getResult(ApiException::class.java)!!
151+
val credential = GoogleAuthProvider.getCredential(account.idToken!!, null)
152+
scope.launch {
153+
val authResult = Firebase.auth.signInWithCredential(credential).await()
154+
onAuthComplete(authResult)
155+
}
156+
} catch (e: ApiException) {
157+
onAuthError(e)
158+
}
159+
}
160+
}
35.8 KB
Loading

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<resources>
2-
<string name="app_name">SampleApp</string>
2+
<string name="app_name">LookUp</string>
33
<string name="title_activity_main">MainActivity</string>
44
<string name="title_activity_second">SecondActivity</string>
5+
<string name="default_web_client_id">547744760272-ai2a9ujavpfaod29tk7bohs254ngsu3u.apps.googleusercontent.com</string>
56
</resources>

0 commit comments

Comments
 (0)