Fixes and updates

This commit is contained in:
soraefir
2026-05-23 12:52:36 +02:00
parent 82ab401862
commit 249b281ae1
18 changed files with 179 additions and 92 deletions

View File

@@ -1,27 +1,38 @@
def getCommitCount() {
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', '--count', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim().toInteger()
} catch (ignored) {
return 1
}
}
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.21'
id 'org.jetbrains.kotlin.plugin.compose' version '2.3.21'
}
android {
namespace 'net.helcel.fidelity'
compileSdk 36
compileSdk = 37
defaultConfig {
applicationId 'net.helcel.fidelity'
versionName "1.0d"
versionName "1.3"
versionCode getCommitCount()
buildConfigField("String", "APP_NAME", "\"Keepass Fidelity\"")
manifestPlaceholders["APP_NAME"] = "Keepass Fidelity"
minSdk 28
targetSdk 36
minSdk = 28
targetSdk = 37
}
signingConfigs {
create("release") {
register("release") {
try {
def keystorePropertiesFile = rootProject.file("app/keystore.properties")
def keystoreProperties = new Properties()
@@ -41,10 +52,12 @@ android {
buildTypes {
debug {
debuggable true
initWith(buildTypes.release)
signingConfig signingConfigs.debug
}
release {
minifyEnabled true
shrinkResources false
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
signedRelease {
@@ -86,10 +99,8 @@ android {
disable 'UsingMaterialAndMaterial3Libraries'
disable 'PreviewAnnotationInFunctionWithParameters'
}
}
dependencies {
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.material3:material3:1.4.0'

View File

@@ -62,6 +62,7 @@ import net.helcel.fidelity.tools.FidelityEntry
import net.helcel.fidelity.tools.FidelityRepository
import net.helcel.fidelity.tools.FidelityRepository.activeEntry
import net.helcel.fidelity.tools.FidelityRepository.addEntry
import kotlin.time.Duration.Companion.milliseconds
@Preview
@@ -81,7 +82,7 @@ fun CreateEntryScreen(navController: NavHostController?) {
LaunchedEffect(entry) {
isValidBarcode = false
delay(500)
delay(500.milliseconds)
if (entry.code.isEmpty()) return@LaunchedEffect
try {
val bmp = generateBarcode(entry.code, entry.format, 600)
@@ -158,8 +159,9 @@ fun CreateEntryScreen(navController: NavHostController?) {
),
label = { Text("Code") },
isError = errorCode.isNotEmpty(),
modifier = Modifier.fillMaxWidth(),
singleLine = true
maxLines = 5,
singleLine = false,
modifier = Modifier.fillMaxWidth()
)
if (errorCode.isNotEmpty()) {
Text(errorCode, color = MaterialTheme.colors.error)
@@ -358,7 +360,13 @@ private fun onSubmitIfValid(
object CreateEntryEventHandler {
fun onSubmit(navController: NavHostController){
navController.popBackStack()
activeEntry.value = activeEntry.value.copy(null,"","","",false)
activeEntry.value = activeEntry.value.copy(
uid = null,
title = "",
code = "",
format = "",
protected = false
)
}
fun onFileScan(navController: NavHostController){

View File

@@ -1,12 +1,14 @@
package net.helcel.fidelity.activity.fragment
import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -24,9 +26,12 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.HideSource
import androidx.compose.material.icons.filled.PushPin
@@ -36,6 +41,7 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -44,6 +50,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -51,6 +59,8 @@ import androidx.navigation.NavHostController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.helcel.fidelity.activity.ToastHelper
import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.isSearchVisible
import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.onAdd
import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.onEdit
import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.onHide
@@ -58,6 +68,7 @@ import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.onPin
import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.onQuery
import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.onRefresh
import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.onView
import net.helcel.fidelity.activity.fragment.LauncherEventHandlers.searchQuery
import net.helcel.fidelity.tools.CredentialResult
import net.helcel.fidelity.tools.FidelityEntry
import net.helcel.fidelity.tools.FidelityRepository.activeEntry
@@ -79,9 +90,18 @@ fun LauncherScreen(
var showHidden by remember { mutableStateOf(false) }
val context = LocalContext.current
val scope = rememberCoroutineScope()
val sortedEntries = remember(entries) {
val focusRequester = remember { FocusRequester() }
BackHandler(enabled = isSearchVisible) {
onQuery()
}
val sortedEntries = remember(entries, showHidden, searchQuery) {
derivedStateOf {
entries.filter{showHidden || !it.hidden}.sortedWith(
entries.filter {
(showHidden || !it.hidden) &&
(searchQuery.isEmpty() || it.title.contains(searchQuery, ignoreCase = true))
}.sortedWith(
compareByDescending<FidelityEntry> { it.pinned }
.thenBy { it.hidden }
.thenByDescending { it.lastUse }
@@ -105,17 +125,47 @@ fun LauncherScreen(
isRefreshing = isRefreshingState,
modifier = Modifier.fillMaxSize()
) {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier
.fillMaxSize()
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(sortedEntries.value) { entry ->
FidelityRow(navController, entry)
Column(modifier = Modifier.fillMaxSize()) {
if (isSearchVisible) {
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
OutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
colors = TextFieldDefaults.textFieldColors(
textColor = MaterialTheme.colors.onBackground
),
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.focusRequester(focusRequester),
label = { Text("Search") },
singleLine = true,
trailingIcon = {
Icon(
Icons.Default.Close,
contentDescription = "Clear",
modifier = Modifier.clickable {
searchQuery = ""
onQuery()
}
)
}
)
}
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier
.fillMaxSize()
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(sortedEntries.value) { entry ->
FidelityRow(navController, entry)
}
}
}
FloatingActionButton(
@@ -262,21 +312,24 @@ fun FidelityRow(
object LauncherEventHandlers {
var isSearchVisible by mutableStateOf(false)
var searchQuery by mutableStateOf("")
var CRED: CredentialResult.Success? = null
fun onAdd(navController: NavHostController) {
navController.navigate("edit")
}
fun onQuery() {
//TODO
isSearchVisible = !isSearchVisible
if (!isSearchVisible) searchQuery = ""
}
var CRED: CredentialResult.Success? = null
suspend fun onSave(context: Context, navController: NavHostController){
try {
if (CRED == null) {
val res = loadCredentials(context)
when (res) {
CredentialResult.AuthFailed, CredentialResult.NoData -> null
when (val res = loadCredentials(context)) {
CredentialResult.AuthFailed, CredentialResult.NoData -> ToastHelper.show(context, "Unable to Load Credentials")
is CredentialResult.Success -> CRED = res
}
}
@@ -297,11 +350,9 @@ object LauncherEventHandlers {
suspend fun onRefresh(context: Context, navController: NavHostController) {
try {
if (CRED == null) {
val res = loadCredentials(context)
when (res) {
CredentialResult.AuthFailed, CredentialResult.NoData -> null
when (val res = loadCredentials(context)) {
CredentialResult.AuthFailed, CredentialResult.NoData -> ToastHelper.show(context, "Unable to Load Credentials")
is CredentialResult.Success -> CRED = res
}
}
CRED!!
@@ -309,8 +360,8 @@ object LauncherEventHandlers {
genCredentials(context, CRED!!)
}
if (withContext(Dispatchers.IO) {
start(context, CRED!!.db, cred)
})
start(context, CRED!!.db, cred)
})
importDB(context)
} catch (e: Exception) {
println(e.toString())

View File

@@ -53,6 +53,7 @@ import net.helcel.fidelity.activity.ToastHelper
import net.helcel.fidelity.activity.fragment.SetupEventHandlers.onOpen
import net.helcel.fidelity.tools.CredentialResult
import net.helcel.fidelity.tools.FidelityRepository.genCredentials
import net.helcel.fidelity.tools.FidelityRepository.importDB
import net.helcel.fidelity.tools.FidelityRepository.start
import net.helcel.fidelity.tools.KeePassStore.loadCredentials
import net.helcel.fidelity.tools.KeePassStore.packCredentials
@@ -116,8 +117,8 @@ fun InitialScreen(
LaunchedEffect(Unit) {
scope.launch(Dispatchers.Main) {
when(val res = loadCredentials(context)) {
CredentialResult.AuthFailed -> null
CredentialResult.NoData -> null
CredentialResult.AuthFailed -> ToastHelper.show(context, "Unable to Load Credentials")
CredentialResult.NoData -> ToastHelper.show(context, "Unable to Load Credentials")
is CredentialResult.Success -> {
if (res.db != null) dbFile = res.db
if (res.key != null) keyFile = res.key
@@ -219,13 +220,17 @@ fun InitialScreen(
onClick = {
loading = true
scope.launch {
if(onOpen(context, dbFile!!, password, keyFile)){
val res = onOpen(context, dbFile!!, password, keyFile)
if(res != null){
ToastHelper.show(context, "Successful... Importing")
withContext(Dispatchers.IO) {
start(context, dbFile!!,genCredentials(context, res))
}
importDB(context)
navController!!.popBackStack()
navController.navigate("init")
navController.navigate("launcher")
}else{
ToastHelper.show(context, "Auth failed...")
navController!!.popBackStack()
navController.navigate("exit")
ToastHelper.show(context, "Failed... Retry")
}
}
},
@@ -249,7 +254,7 @@ fun InitialScreen(
}
object SetupEventHandlers {
suspend fun onOpen(context: Context, db: Uri, p: String, key: Uri?): Boolean {
suspend fun onOpen(context: Context, db: Uri, p: String, key: Uri?): CredentialResult.Success? {
try {
val packCred = packCredentials(db, p, key)
withContext(Dispatchers.IO) {
@@ -261,14 +266,14 @@ object SetupEventHandlers {
saveCredentials(context, packCred)
}
return when (res) {
CredentialResult.AuthFailed, CredentialResult.NoData -> false
is CredentialResult.Success -> true
CredentialResult.AuthFailed, CredentialResult.NoData -> null
is CredentialResult.Success -> res
}
} catch (e: Exception) {
ToastHelper.show(context, e.message.toString())
println("Err${e.toString()}")
println(e.message)
return false
return null
}
}
}

View File

@@ -13,7 +13,6 @@ import com.google.zxing.common.HybridBinarizer
import net.helcel.fidelity.tools.BarcodeFormatConverter.formatToString
import java.util.concurrent.Executors
@OptIn(ExperimentalGetImage::class)
object BarcodeScanner {
@@ -56,6 +55,4 @@ object BarcodeScanner {
fun bitmapUseCase(bitmap: Bitmap, cb: (String?, String?) -> Unit) {
processImage(bitmap, cb)
}
}

View File

@@ -113,13 +113,21 @@ suspend fun showBiometricPrompt(activity: FragmentActivity, enc: Boolean): Ciphe
activity,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { cont.resume(result.cryptoObject?.cipher) {} }
override fun onAuthenticationError(code: Int, msg: CharSequence) { cont.resume(null) {} }
override fun onAuthenticationFailed() { cont.resume(null) {} }
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
cont.resume(result.cryptoObject?.cipher) { _, _, _ -> }
}
override fun onAuthenticationError(code: Int, msg: CharSequence) {
cont.resume(null) { _, _, _ -> }
}
override fun onAuthenticationFailed() {
cont.resume(null) { _, _, _ -> }
}
}
)
val iv = if(enc) null else prefs[KeePassKeys.IV]?.let { Base64.decode(it, Base64.DEFAULT) }
if (!enc && iv == null) { cont.resume(null) {} }
if (!enc && iv == null) {
cont.resume(null) { _, _, _ -> }
}
val cipher = getCipherForDecryption(getOrCreateBiometricKey(), iv)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Unlock KeePass")

View File

@@ -59,7 +59,7 @@ object FidelityRepository {
db.loadData(
bitStream, c,
{ hardwareKey, seed -> retrieveResponseFromChallenge(hardwareKey, seed) },
false, binaryDir!!,
readOnly=false, allowUserVerification = false,binaryDir!!,
{ BinaryData.canMemoryBeAllocatedInRAM(ctx, it) },
false, null
)
@@ -84,7 +84,7 @@ object FidelityRepository {
hardwareKey: HardwareKey? = null
): MasterCredential {
return MasterCredential(
cred.password,
cred.password.toCharArray(),
cred.key?.let { ctx.contentResolver.openInputStream(cred.key)?.readBytes() },
hardwareKey
)
@@ -103,8 +103,8 @@ object FidelityRepository {
val newEntry = FidelityEntry(
uid=it.nodeId.id.toString(),
title=it.title,
code=code.protectedValue.stringValue,
format=format.protectedValue.stringValue,
code=code.protectedValue.toString(),
format=format.protectedValue.toString(),
protected=code.protectedValue.isProtected,
)
val idx = entries.indexOfFirst { e -> e.uid == newEntry.uid }
@@ -172,7 +172,7 @@ object FidelityRepository {
putExtraField(
Field(
FidelityKeepassFields.FIDELITYFORMAT,
ProtectedString(string= entry.format)
ProtectedString(true, entry.format.toCharArray())
)
)
if(dbParent!=null) title = entry.title

View File

@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>