Skip to content

Commit f60a658

Browse files
authored
Merge pull request #3 from SoftwareLegends/AL-189-improve-fingerprint-library-scanning-functionality
[AL-189] - Improve Fingerprint Library Scanning Functionality
2 parents 68bb6ef + a55bd3b commit f60a658

File tree

12 files changed

+334
-86
lines changed

12 files changed

+334
-86
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,14 @@ dependencies {
5454
implementation(projects.fingerprint)
5555
implementation(libs.androidx.core.ktx)
5656
implementation(libs.androidx.lifecycle.runtime.ktx)
57+
implementation(libs.androidx.lifecycle.runtime)
5758
implementation(libs.androidx.activity.compose)
5859
implementation(platform(libs.androidx.compose.bom))
5960
implementation(libs.androidx.ui)
6061
implementation(libs.androidx.ui.graphics)
6162
implementation(libs.androidx.ui.tooling.preview)
6263
implementation(libs.androidx.material3)
64+
implementation(libs.androidx.material.icons)
6365
implementation(libs.kotlinx.serialization.json)
6466
testImplementation(libs.junit)
6567
androidTestImplementation(libs.androidx.junit)

app/src/main/java/com/fingerprint/app/MainActivity.kt

Lines changed: 113 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.fingerprint.app
22

3+
import android.content.ContentValues
4+
import android.content.Context
5+
import android.graphics.Bitmap
36
import android.os.Bundle
7+
import android.os.Environment
8+
import android.provider.MediaStore
49
import android.widget.Toast
510
import androidx.activity.ComponentActivity
611
import androidx.activity.compose.setContent
@@ -13,49 +18,59 @@ import androidx.compose.foundation.layout.Arrangement
1318
import androidx.compose.foundation.layout.Box
1419
import androidx.compose.foundation.layout.Column
1520
import androidx.compose.foundation.layout.ColumnScope
21+
import androidx.compose.foundation.layout.Row
1622
import androidx.compose.foundation.layout.fillMaxSize
1723
import androidx.compose.foundation.layout.fillMaxWidth
24+
import androidx.compose.foundation.layout.height
1825
import androidx.compose.foundation.layout.padding
19-
import androidx.compose.foundation.layout.size
2026
import androidx.compose.foundation.lazy.grid.GridCells
2127
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
2228
import androidx.compose.foundation.lazy.grid.itemsIndexed
2329
import androidx.compose.foundation.shape.CircleShape
2430
import androidx.compose.foundation.text.KeyboardOptions
2531
import androidx.compose.material.icons.Icons
32+
import androidx.compose.material.icons.rounded.Save
2633
import androidx.compose.material.icons.rounded.Star
2734
import androidx.compose.material3.AlertDialog
2835
import androidx.compose.material3.Button
36+
import androidx.compose.material3.Checkbox
2937
import androidx.compose.material3.HorizontalDivider
3038
import androidx.compose.material3.Icon
39+
import androidx.compose.material3.IconButton
3140
import androidx.compose.material3.Scaffold
3241
import androidx.compose.material3.Text
3342
import androidx.compose.material3.TextButton
3443
import androidx.compose.material3.TextField
3544
import androidx.compose.material3.TextFieldDefaults
45+
import androidx.compose.material3.VerticalDivider
3646
import androidx.compose.runtime.Composable
3747
import androidx.compose.runtime.LaunchedEffect
38-
import androidx.compose.runtime.collectAsState
3948
import androidx.compose.runtime.getValue
4049
import androidx.compose.runtime.mutableStateOf
4150
import androidx.compose.runtime.remember
4251
import androidx.compose.runtime.setValue
4352
import androidx.compose.ui.Alignment
4453
import androidx.compose.ui.Modifier
4554
import androidx.compose.ui.graphics.Color
55+
import androidx.compose.ui.graphics.ImageBitmap
56+
import androidx.compose.ui.graphics.asAndroidBitmap
4657
import androidx.compose.ui.input.pointer.pointerInput
4758
import androidx.compose.ui.platform.LocalClipboardManager
4859
import androidx.compose.ui.platform.LocalContext
4960
import androidx.compose.ui.platform.LocalFocusManager
5061
import androidx.compose.ui.text.AnnotatedString
5162
import androidx.compose.ui.text.input.KeyboardType
5263
import androidx.compose.ui.unit.dp
64+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
5365
import com.fingerprint.FingerprintInitializer
54-
import com.fingerprint.app.ui.theme.FingerprintHF4000Theme
66+
import com.fingerprint.app.ui.theme.FingerprintTheme
5567
import com.fingerprint.manager.FingerprintEvent
5668
import com.fingerprint.manager.FingerprintManager
5769
import kotlinx.coroutines.CoroutineScope
5870
import kotlinx.coroutines.Dispatchers
71+
import kotlinx.coroutines.launch
72+
import java.io.OutputStream
73+
import java.util.UUID
5974

6075

6176
class MainActivity : ComponentActivity() {
@@ -68,7 +83,7 @@ class MainActivity : ComponentActivity() {
6883
initializeFingerprintManager()
6984

7085
setContent {
71-
FingerprintHF4000Theme {
86+
FingerprintTheme {
7287
App(fingerprintManager)
7388
}
7489
}
@@ -86,18 +101,23 @@ class MainActivity : ComponentActivity() {
86101
@Composable
87102
fun App(fingerprintManager: FingerprintManager) {
88103
val clipboardManager = LocalClipboardManager.current
104+
val scope = remember { CoroutineScope(Dispatchers.IO) }
89105
var status by remember { mutableStateOf("") }
90106
var deviceInfo by remember { mutableStateOf("") }
91107
var scanCount by remember { mutableStateOf("5") }
92108
var finished by remember { mutableStateOf(true) }
93109
var showInfo by remember { mutableStateOf(false) }
94-
val events by fingerprintManager.eventsFlow.collectAsState()
110+
var isBlue by remember { mutableStateOf(false) }
111+
var isFilter by remember { mutableStateOf(false) }
112+
val events by fingerprintManager.eventsFlow.collectAsStateWithLifecycle()
95113
val focusManager = LocalFocusManager.current
96114

97115
LaunchedEffect(key1 = events) {
98116
println("DEBUGGING -> Progress: ${fingerprintManager.progress}")
99117
when (events) {
118+
is FingerprintEvent.Connected,
100119
is FingerprintEvent.CapturedSuccessfully,
120+
is FingerprintEvent.Disconnected,
101121
is FingerprintEvent.ProcessCanceledTheFingerLifted -> {
102122
finished = true
103123
status = events.message
@@ -147,6 +167,40 @@ fun App(fingerprintManager: FingerprintManager) {
147167
text = { Text(text = deviceInfo) }
148168
)
149169

170+
Row(
171+
verticalAlignment = Alignment.CenterVertically
172+
) {
173+
Checkbox(checked = isFilter, onCheckedChange = {
174+
isFilter = it
175+
scope.launch {
176+
fingerprintManager.improveTheBestCapture(
177+
isApplyFilters = isFilter,
178+
isBlue = isBlue
179+
)
180+
}
181+
})
182+
Text(text = "Apply Filter")
183+
VerticalDivider(
184+
modifier = Modifier
185+
.height(20.dp)
186+
.padding(start = 14.dp)
187+
)
188+
Checkbox(
189+
checked = isBlue,
190+
onCheckedChange = {
191+
isBlue = it
192+
scope.launch {
193+
fingerprintManager.improveTheBestCapture(
194+
isApplyFilters = isFilter,
195+
isBlue = isBlue
196+
)
197+
}
198+
},
199+
enabled = isFilter
200+
)
201+
Text(text = "Blue Pixels")
202+
}
203+
150204
Button(onClick = {
151205
deviceInfo = fingerprintManager.deviceInfo.toString()
152206
showInfo = !showInfo
@@ -184,16 +238,37 @@ fun ColumnScope.CaptureGrid(fingerprintManager: FingerprintManager) {
184238
horizontalArrangement = Arrangement.SpaceBetween
185239
) {
186240
itemsIndexed(items = fingerprintManager.captures) { index, bitmap ->
187-
Box(contentAlignment = Alignment.TopStart) {
188-
Image(bitmap = bitmap, contentDescription = null)
189-
if (index == fingerprintManager.bestCaptureIndex) {
190-
Icon(
191-
imageVector = Icons.Rounded.Star,
192-
contentDescription = null,
193-
tint = Color.Yellow.copy(green = 0.75f)
194-
)
195-
}
196-
}
241+
FingerprintImage(
242+
bitmap = bitmap,
243+
index = index,
244+
bestCaptureIndex = fingerprintManager.bestCaptureIndex
245+
)
246+
}
247+
}
248+
}
249+
250+
@Composable
251+
fun FingerprintImage(bitmap: ImageBitmap, index: Int = -1, bestCaptureIndex: Int = -2) {
252+
val context = LocalContext.current
253+
Box(contentAlignment = Alignment.TopStart) {
254+
Image(bitmap = bitmap, contentDescription = null)
255+
if (index == bestCaptureIndex) {
256+
Icon(
257+
imageVector = Icons.Rounded.Star,
258+
contentDescription = null,
259+
tint = Color.Yellow.copy(green = 0.75f)
260+
)
261+
}
262+
263+
IconButton(
264+
modifier = Modifier.align(Alignment.BottomEnd),
265+
onClick = { bitmap.saveBitmapToGallery(context) }
266+
) {
267+
Icon(
268+
imageVector = Icons.Rounded.Save,
269+
contentDescription = null,
270+
tint = Color.Yellow.copy(green = 0.75f)
271+
)
197272
}
198273
}
199274
}
@@ -206,14 +281,10 @@ fun BestCaptureSection(fingerprintManager: FingerprintManager, scanCount: String
206281
verticalArrangement = Arrangement.spacedBy(16.dp)
207282
) {
208283
HorizontalDivider()
209-
Text(text = "Best Capture")
284+
Text(text = "Improved Best Capture")
210285
HorizontalDivider()
211-
fingerprintManager.bestCapture?.let {
212-
Image(
213-
bitmap = it,
214-
contentDescription = null,
215-
modifier = Modifier.size(172.dp)
216-
)
286+
fingerprintManager.bestCapture?.let { bitmap ->
287+
FingerprintImage(bitmap = bitmap)
217288
}
218289
HorizontalDivider()
219290
}
@@ -240,3 +311,23 @@ fun ScanButton(
240311
}
241312
) { Text(text = "Scan") }
242313
}
314+
315+
fun ImageBitmap.saveBitmapToGallery(context: Context, title: String? = null) = runCatching {
316+
val filename = "${title ?: UUID.randomUUID().leastSignificantBits.toString()}.png"
317+
val resolver = context.contentResolver
318+
val contentValues = ContentValues().apply {
319+
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
320+
put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
321+
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
322+
}
323+
324+
val imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
325+
val fos: OutputStream? = imageUri?.let { resolver.openOutputStream(it) }
326+
327+
fos?.use {
328+
asAndroidBitmap().compress(Bitmap.CompressFormat.PNG, 100, it)
329+
Toast.makeText(context, "Image saved to gallery", Toast.LENGTH_SHORT).show()
330+
} ?: run {
331+
Toast.makeText(context, "Failed to save image", Toast.LENGTH_SHORT).show()
332+
}
333+
}

app/src/main/java/com/fingerprint/app/ui/theme/Theme.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ private val LightColorScheme = lightColorScheme(
3333
)
3434

3535
@Composable
36-
fun FingerprintHF4000Theme(
36+
fun FingerprintTheme(
3737
darkTheme: Boolean = isSystemInDarkTheme(),
3838
// Dynamic color is available on Android 12+
3939
dynamicColor: Boolean = true,

fingerprint/src/main/java/com/fingerprint/manager/FingerprintEvent.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ sealed class FingerprintEvent(val message: String) {
1414
data object KeepFinger : FingerprintEvent("Keep Finger")
1515
data object CapturedSuccessfully : FingerprintEvent("Captured Successfully")
1616
data object CapturingFailed : FingerprintEvent("Capturing Failed")
17+
data object CleanTheFingerprint : FingerprintEvent("Clean The Fingerprint")
1718
class NewImage(val bitmapArray: ByteArray) : FingerprintEvent("New Image")
1819
}

fingerprint/src/main/java/com/fingerprint/manager/FingerprintManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface FingerprintManager : DefaultLifecycleObserver {
1717
fun connect()
1818
fun disconnect()
1919
fun scan(count: Int): Boolean
20+
fun improveTheBestCapture(isApplyFilters: Boolean = false, isBlue: Boolean = false)
2021

2122
override fun onResume(owner: LifecycleOwner) = connect()
2223

0 commit comments

Comments
 (0)