Skip to content

Commit 401d96e

Browse files
committed
QR code generation
1 parent e0c7473 commit 401d96e

File tree

5 files changed

+160
-11
lines changed

5 files changed

+160
-11
lines changed

composeapp/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ dependencies {
5959
implementation(Config.Libs.Androidx.Navigation.lifecycleViewmodelNav3)
6060
implementation(Config.Libs.Androidx.kotlinxSerialization)
6161

62+
// QR Code generation for TOTP
63+
implementation("com.google.zxing:core:3.5.3")
64+
6265
testImplementation(Config.Libs.Test.junit)
6366
androidTestImplementation(Config.Libs.Test.junitExt)
6467
androidTestImplementation(platform(Config.Libs.Androidx.Compose.bom))

composeapp/src/main/java/com/firebase/composeapp/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class MainActivity : ComponentActivity() {
5555

5656
FirebaseApp.initializeApp(applicationContext)
5757
val authUI = FirebaseAuthUI.getInstance()
58-
authUI.auth.useEmulator("10.0.2.2", 9099)
58+
// authUI.auth.useEmulator("10.0.2.2", 9099)
5959

6060
val configuration = authUIConfiguration {
6161
context = applicationContext
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2025 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.firebase.composeapp.ui.components
16+
17+
import android.graphics.Bitmap
18+
import androidx.compose.foundation.Image
19+
import androidx.compose.foundation.background
20+
import androidx.compose.foundation.layout.Box
21+
import androidx.compose.foundation.layout.size
22+
import androidx.compose.material3.MaterialTheme
23+
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.remember
25+
import androidx.compose.ui.Alignment
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.graphics.Color
28+
import androidx.compose.ui.graphics.asImageBitmap
29+
import androidx.compose.ui.unit.Dp
30+
import androidx.compose.ui.unit.dp
31+
import com.google.zxing.BarcodeFormat
32+
import com.google.zxing.EncodeHintType
33+
import com.google.zxing.WriterException
34+
import com.google.zxing.qrcode.QRCodeWriter
35+
36+
/**
37+
* Composable that displays a QR code image generated from the provided content.
38+
*
39+
* @param content The string content to encode in the QR code (e.g., TOTP URI)
40+
* @param modifier Modifier to be applied to the QR code image
41+
* @param size The size (width and height) of the QR code image
42+
* @param foregroundColor The color of the QR code pixels (default: black)
43+
* @param backgroundColor The background color of the QR code (default: white)
44+
*/
45+
@Composable
46+
fun QrCodeImage(
47+
content: String,
48+
modifier: Modifier = Modifier,
49+
size: Dp = 250.dp,
50+
foregroundColor: Color = Color.Black,
51+
backgroundColor: Color = Color.White
52+
) {
53+
val bitmap = remember(content, size, foregroundColor, backgroundColor) {
54+
generateQrCodeBitmap(
55+
content = content,
56+
sizePx = (size.value * 2).toInt(), // 2x for better resolution
57+
foregroundColor = foregroundColor,
58+
backgroundColor = backgroundColor
59+
)
60+
}
61+
62+
Box(
63+
modifier = modifier
64+
.size(size)
65+
.background(backgroundColor),
66+
contentAlignment = Alignment.Center
67+
) {
68+
bitmap?.let {
69+
Image(
70+
bitmap = it.asImageBitmap(),
71+
contentDescription = "QR Code for $content",
72+
modifier = Modifier.size(size)
73+
)
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Generates a QR code bitmap from the provided content.
80+
*
81+
* @param content The string to encode
82+
* @param sizePx The size of the bitmap in pixels
83+
* @param foregroundColor The color for the QR code pixels
84+
* @param backgroundColor The background color
85+
* @return A Bitmap containing the QR code, or null if generation fails
86+
*/
87+
private fun generateQrCodeBitmap(
88+
content: String,
89+
sizePx: Int,
90+
foregroundColor: Color,
91+
backgroundColor: Color
92+
): Bitmap? {
93+
return try {
94+
val qrCodeWriter = QRCodeWriter()
95+
val hints = mapOf(
96+
EncodeHintType.MARGIN to 1 // Minimal margin
97+
)
98+
99+
val bitMatrix = qrCodeWriter.encode(
100+
content,
101+
BarcodeFormat.QR_CODE,
102+
sizePx,
103+
sizePx,
104+
hints
105+
)
106+
107+
val bitmap = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888)
108+
109+
val foregroundArgb = android.graphics.Color.argb(
110+
(foregroundColor.alpha * 255).toInt(),
111+
(foregroundColor.red * 255).toInt(),
112+
(foregroundColor.green * 255).toInt(),
113+
(foregroundColor.blue * 255).toInt()
114+
)
115+
116+
val backgroundArgb = android.graphics.Color.argb(
117+
(backgroundColor.alpha * 255).toInt(),
118+
(backgroundColor.red * 255).toInt(),
119+
(backgroundColor.green * 255).toInt(),
120+
(backgroundColor.blue * 255).toInt()
121+
)
122+
123+
for (x in 0 until sizePx) {
124+
for (y in 0 until sizePx) {
125+
bitmap.setPixel(
126+
x,
127+
y,
128+
if (bitMatrix[x, y]) foregroundArgb else backgroundArgb
129+
)
130+
}
131+
}
132+
133+
bitmap
134+
} catch (e: WriterException) {
135+
e.printStackTrace()
136+
null
137+
}
138+
}

composeapp/src/main/java/com/firebase/composeapp/ui/screens/EmailAuthMain.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,20 @@ fun EmailAuthMain(
114114
coroutineScope.launch {
115115
try {
116116
// Reload the user to refresh the authentication token
117-
verificationState.user.reload().addOnCompleteListener { task ->
118-
if (task.isSuccessful) {
119-
android.util.Log.d("EmailAuthMain", "User reloaded. isEmailVerified: ${verificationState.user.isEmailVerified}")
120-
// The auth state listener will automatically update the state
117+
verificationState.user.reload().addOnCompleteListener { reloadTask ->
118+
if (reloadTask.isSuccessful) {
119+
// Force a token refresh to trigger the AuthStateListener
120+
verificationState.user.getIdToken(true).addOnCompleteListener { tokenTask ->
121+
if (tokenTask.isSuccessful) {
122+
val currentUser = authUI.getCurrentUser()
123+
android.util.Log.d("EmailAuthMain", "User reloaded. isEmailVerified: ${currentUser?.isEmailVerified}")
124+
// The AuthStateListener should fire automatically after token refresh
125+
} else {
126+
android.util.Log.e("EmailAuthMain", "Failed to refresh token", tokenTask.exception)
127+
}
128+
}
121129
} else {
122-
android.util.Log.e("EmailAuthMain", "Failed to reload user", task.exception)
130+
android.util.Log.e("EmailAuthMain", "Failed to reload user", reloadTask.exception)
123131
}
124132
}
125133
} catch (e: Exception) {

composeapp/src/main/java/com/firebase/composeapp/ui/screens/MfaEnrollmentMain.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -496,11 +496,11 @@ private fun ConfigureTotpUI(
496496
style = MaterialTheme.typography.bodySmall,
497497
textAlign = TextAlign.Center
498498
)
499-
Text(
500-
text = "(QR code would be displayed here)",
501-
style = MaterialTheme.typography.bodySmall,
502-
color = MaterialTheme.colorScheme.onSurfaceVariant,
503-
textAlign = TextAlign.Center
499+
Spacer(modifier = Modifier.height(8.dp))
500+
com.firebase.composeapp.ui.components.QrCodeImage(
501+
content = url,
502+
modifier = Modifier.padding(16.dp),
503+
size = 250.dp
504504
)
505505
}
506506

0 commit comments

Comments
 (0)