1 Commits

Author SHA1 Message Date
Renovate Bot
09b93b3758 Update dependency joda-time:joda-time to v2.14.0 2025-10-09 02:01:34 +00:00
22 changed files with 123 additions and 214 deletions

View File

@@ -23,14 +23,9 @@ jobs:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
with: with:
submodules: true submodules: true
fetch-depth: 0
- name: Make script executable
run: chmod +x ./patch_submodule.sh
- name: Run patch submodule script
run: ./patch_submodule.sh
- name: set up secrets - name: set up secrets
run: | run: |
echo "${{ secrets.RELEASE_KEYSTORE }}" > keystore.asc echo "${{ secrets.RELEASE_KEYSTORE }}" > keystore.asc
@@ -38,6 +33,8 @@ jobs:
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch keystore.asc > app/keystore.properties gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch keystore.asc > app/keystore.properties
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch key.asc > app/key.jks gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch key.asc > app/key.jks
- uses: gradle/wrapper-validation-action@v3
- name: create and checkout branch - name: create and checkout branch
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
env: env:
@@ -47,16 +44,12 @@ jobs:
- name: set up JDK - name: set up JDK
uses: actions/setup-java@v5 uses: actions/setup-java@v5
with: with:
java-version: 21 java-version: 17
distribution: "temurin" distribution: "temurin"
cache: 'gradle' cache: 'gradle'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Build APK - name: Build APK
run: | run: ./gradlew assemble
VERSION_CODE=$(git rev-list --count HEAD)
./gradlew assemble -PVERSION_CODE=$VERSION_CODE
# - name: Upload APK # - name: Upload APK
# uses: actions/upload-artifact@v4 # uses: actions/upload-artifact@v4
@@ -65,7 +58,7 @@ jobs:
# path: app/build/outputs/apk/release/app-release.apk # path: app/build/outputs/apk/release/app-release.apk
- name: Release - name: Release
uses: softprops/action-gh-release@v3 uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
files: | files: |

View File

@@ -3,13 +3,13 @@
<h1>Keepass Fidelity</h1> <h1>Keepass Fidelity</h1>
<img width="100px" src="./metadata/en-US/images/icon.png" alt="Logo"> <img width="100px" src="./metadata/en-US/images/icon.png" alt="Logo">
<p>A minimalist fidelity/loyalty card app with Keepass Database storage</p> <p>A minimalist fidelity/loyalty card plugin</p>
<img src="https://forthebadge.com/images/badges/built-for-android.svg" alt="Built for Android"> <img src="https://forthebadge.com/images/badges/built-for-android.svg" alt="Built for Android">
<img src="https://forthebadge.com/images/badges/built-with-love.svg" alt="Built with love"> <img src="https://forthebadge.com/images/badges/built-with-love.svg" alt="Built with love">
<br> <br>
<a href="https://github.com/helcel-net/keepass-fidelity/actions/workflows/build.yml"> <a href="https://github.com/choelzl/keepass-fidelity/actions/workflows/build.yml">
<img src="https://github.com/helcel-net/keepass-fidelity/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status"> <img src="https://github.com/choelzl/keepass-fidelity/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status">
</a> </a>
</div> </div>
@@ -27,7 +27,7 @@
## ⭐ Features ## ⭐ Features
- Search entries in Keepass Database - Search entries in [Keepass2Android](https://github.com/PhilippC/keepass2android/)
- Scan & Create entries - Scan & Create entries
- Recently used history for fast access - Recently used history for fast access
- Protect entries from caching - Protect entries from caching

View File

@@ -1,38 +1,27 @@
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 { plugins {
id 'com.android.application' id 'com.android.application'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.21' id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.compose' version '2.3.21' id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.20'
id 'org.jetbrains.kotlin.plugin.compose' version '2.2.20'
} }
android { android {
namespace 'net.helcel.fidelity' namespace 'net.helcel.fidelity'
compileSdk = 37 compileSdk 36
defaultConfig { defaultConfig {
applicationId 'net.helcel.fidelity' applicationId 'net.helcel.fidelity'
versionName "1.3b" versionName "1.0d"
versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1
buildConfigField("String", "APP_NAME", "\"Keepass Fidelity\"") buildConfigField("String", "APP_NAME", "\"Keepass Fidelity\"")
manifestPlaceholders["APP_NAME"] = "Keepass Fidelity" manifestPlaceholders["APP_NAME"] = "Keepass Fidelity"
minSdk = 28 minSdk 28
targetSdk = 37 targetSdk 36
} }
signingConfigs { signingConfigs {
register("release") { create("release") {
try { try {
def keystorePropertiesFile = rootProject.file("app/keystore.properties") def keystorePropertiesFile = rootProject.file("app/keystore.properties")
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
@@ -52,12 +41,10 @@ android {
buildTypes { buildTypes {
debug { debug {
debuggable true debuggable true
initWith(buildTypes.release)
signingConfig signingConfigs.debug
} }
release { release {
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
signedRelease { signedRelease {
@@ -99,41 +86,43 @@ android {
disable 'UsingMaterialAndMaterial3Libraries' disable 'UsingMaterialAndMaterial3Libraries'
disable 'PreviewAnnotationInFunctionWithParameters' disable 'PreviewAnnotationInFunctionWithParameters'
} }
} }
dependencies { dependencies {
implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.material3:material3:1.4.0' implementation 'androidx.compose.material3:material3:1.4.0'
implementation 'androidx.compose.material:material:1.11.2' implementation 'androidx.compose.material:material:1.9.2'
implementation 'androidx.compose.material:material-icons-extended:1.7.8' implementation 'androidx.compose.material:material-icons-extended:1.7.8'
implementation 'androidx.navigation:navigation-compose:2.9.8' implementation 'androidx.navigation:navigation-compose:2.9.5'
implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation "androidx.biometric:biometric:1.2.0-alpha05" implementation "androidx.biometric:biometric:1.2.0-alpha05"
implementation "androidx.security:security-crypto:1.1.0" implementation "androidx.security:security-crypto:1.1.0"
implementation "androidx.datastore:datastore-preferences:1.2.1" implementation "androidx.datastore:datastore-preferences:1.1.7"
implementation "androidx.security:security-crypto:1.1.0" implementation "androidx.security:security-crypto:1.1.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.5'
implementation 'androidx.camera:camera-lifecycle:1.6.1' implementation 'androidx.camera:camera-lifecycle:1.5.0'
implementation 'androidx.camera:camera-view:1.6.1' implementation 'androidx.camera:camera-view:1.5.0'
runtimeOnly 'androidx.camera:camera-camera2:1.6.1' runtimeOnly 'androidx.camera:camera-camera2:1.5.0'
implementation 'com.google.android.material:material:1.14.0' implementation 'com.google.android.material:material:1.13.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0'
implementation 'com.google.zxing:core:3.5.4' implementation 'com.google.zxing:core:3.5.3'
implementation project(":database") implementation project(":database")
implementation project(":crypto") implementation project(":crypto")
implementation platform('androidx.compose:compose-bom:2026.05.01') implementation platform('androidx.compose:compose-bom:2025.09.01')
implementation 'androidx.compose.ui:ui-tooling:1.11.2' implementation 'androidx.compose.ui:ui-tooling:1.9.2'
implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.ui:ui-tooling-preview'
//Submodule //Submodule
//noinspection NewerVersionAvailable //noinspection NewerVersionAvailable
implementation 'joda-time:joda-time:2.14.2' implementation 'joda-time:joda-time:2.14.0'
implementation 'org.joda:joda-convert:3.0.1' implementation 'org.joda:joda-convert:2.2.4'
} }

View File

@@ -3,6 +3,5 @@
-keepattributes Signature -keepattributes Signature
-keep class org.joda.convert.** { *; } -keep class org.joda.convert.** { *; }
-dontwarn org.threeten.bp.**
# Optional. For using GSON @Expose annotation # Optional. For using GSON @Expose annotation
-keepattributes AnnotationDefault,RuntimeVisibleAnnotations -keepattributes AnnotationDefault,RuntimeVisibleAnnotations

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.joda_time_version = '2.14.1' // ext.kotlin_version = '1.8.20'
ext.commons_io_version = '2.21.0' // ext.android_core_version = '1.10.1'
ext.android_test_version = '1.7.0' // ext.android_appcompat_version = '1.6.1'
// ext.android_material_version = '1.9.0'
ext.android_test_version = '1.5.2'
} }
plugins { plugins {
id 'com.android.application' version '9.2.1' apply false id 'com.android.application' version '8.13.0' apply false
id 'com.android.library' version '9.2.1' apply false id 'com.android.library' version '8.13.0' apply false
id 'org.jetbrains.kotlin.android' version '2.3.21' apply false id 'org.jetbrains.kotlin.android' version '2.2.20' apply false
id 'com.autonomousapps.dependency-analysis' version '3.13.0' apply true id 'com.autonomousapps.dependency-analysis' version '3.0.4' apply true
} }

View File

@@ -22,7 +22,3 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.onlyEnableUnitTestForTheTestedBuildType=false
android.uniquePackageNames=false
android.r8.strictFullModeForKeepRules=false
android.dependency.useConstraints=false

Binary file not shown.

View File

@@ -1,9 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

2
gradlew vendored
View File

@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.

31
gradlew.bat vendored
View File

@@ -23,8 +23,8 @@
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled @rem Set local scope for the variables with windows NT shell
setlocal EnableExtensions if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1 goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,7 +65,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1 goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
@@ -73,10 +73,21 @@ echo location of your Java installation. 1>&2
@rem Execute Gradle @rem Execute Gradle
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:exitWithErrorLevel :end
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts @rem End local scope for the variables with windows NT shell
"%COMSPEC%" /c exit %ERRORLEVEL% if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1 +1 @@
<p><i>Keepass-Fidelity</i> adds an interface to view&save barcodes (QR included) with a Keepass Database.</p><p><br></p><ul><li><b>Launcher:</b> view and launch recent entries (a per entry flag can disable this behaviour)</li><li><b>View:</b> view entries from loaded from the database</li><li><b>Create:</b> add entries from the camera, an image of by filling out a form. The entry is then created in the database</li><li><b>Data:</b> the app uses the following data Title (entry name), barcode type (QR, UPC, ...), barcode content (number/text content) and a "secure" flag (enable/disable caching the entry).</li></ul> <p><i>Keepass-Fidelity</i> adds an interface to view/save barcodes (QR included) to Keepass through the plugin interface of the Keepass2Android app.</p><p><br></p><ul><li><b>Launcher:</b> view and launch recent entries (a per entry flag can disable this behaviour)</li><li><b>View:</b> view entries from the history or queried from Keepass2Android</li><li><b>Create:</b> add entries from the camera, an image of by filling out a form. The entry is then created in the Keepass2Android app</li><li><b>Data:</b> the app uses the following data Title (entry name), barcode type (QR, UPC, ...), barcode content (number/text content) and a "secure" flag (enable/disable caching the entry).</li></ul>

View File

@@ -1 +1 @@
Fidelity (Membership/Loyalty) Card app with Keepass Database Storage Fidelity (Membership/Loyalty) Card plugin for Keepass2Android

View File

@@ -1,9 +0,0 @@
#!/bin/bash
for file in external/KeePassDX/{crypto,database}/build.gradle; do
if [ -f "$file" ]; then
sed -i "/id 'kotlin-android'/d" "$file"
sed -i "/apply plugin: 'kotlin-android'/d" "$file"
sed -i '/kotlinOptions {/,/}/d' "$file"
fi
done