Compare commits
24 Commits
baee52c69b
...
1.1-rc1
Author | SHA1 | Date | |
---|---|---|---|
1d41671fb6
|
|||
84ebdcc9a8
|
|||
8181da421e
|
|||
af97381bdb
|
|||
ba94beab37
|
|||
19b0f91852 | |||
3b0bf2097e
|
|||
455c95021b | |||
78ad7f983f
|
|||
0a0b55294f
|
|||
040b9c3af4
|
|||
796c76f36c
|
|||
517f0240e3
|
|||
b02920ca41
|
|||
b289648260
|
|||
b6de7ca409
|
|||
12fb04974e
|
|||
cdd4d6db1b
|
|||
a403bdea61
|
|||
657489e255 | |||
95f609bc53 | |||
f14929cdf6
|
|||
ab5c32f2db | |||
cd8a84531e |
BIN
.github/images/apk.png
vendored
Normal file
BIN
.github/images/apk.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
BIN
.github/images/edit.jpg
vendored
Normal file
BIN
.github/images/edit.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
.github/images/izzy.png
vendored
Normal file
BIN
.github/images/izzy.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
.github/images/launcher.jpg
vendored
Normal file
BIN
.github/images/launcher.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
.github/images/view.jpg
vendored
Normal file
BIN
.github/images/view.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
64
.github/workflows/build.yml
vendored
Normal file
64
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#file: noinspection SpellCheckingInspection
|
||||||
|
|
||||||
|
name: CI-Android APK
|
||||||
|
|
||||||
|
env:
|
||||||
|
main_project_module: app
|
||||||
|
playstore_name: KeepassFidelity
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
tags:
|
||||||
|
- '**'
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: set up secrets
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.RELEASE_KEYSTORE }}" > keystore.asc
|
||||||
|
echo "${{ secrets.RELEASE_KEY}}" > key.asc
|
||||||
|
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
|
||||||
|
|
||||||
|
- uses: gradle/wrapper-validation-action@v2
|
||||||
|
|
||||||
|
- name: create and checkout branch
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
env:
|
||||||
|
BRANCH: ${{ github.head_ref }}
|
||||||
|
run: git checkout -B "$BRANCH"
|
||||||
|
|
||||||
|
- name: set up JDK
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
distribution: "temurin"
|
||||||
|
cache: 'gradle'
|
||||||
|
|
||||||
|
- name: Build APK
|
||||||
|
run: ./gradlew assemble
|
||||||
|
|
||||||
|
# - name: Upload APK
|
||||||
|
# uses: actions/upload-artifact@v4
|
||||||
|
# with:
|
||||||
|
# name: app.apk
|
||||||
|
# path: app/build/outputs/apk/release/app-release.apk
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
app/build/outputs/apk/release/app-release.apk
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -7,7 +7,11 @@ local.properties/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
build/
|
build/
|
||||||
app/build/
|
app/build/
|
||||||
|
app/debug/
|
||||||
|
app/release/
|
||||||
captures/
|
captures/
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
keystore.properties
|
||||||
|
key.jks
|
||||||
|
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <https://unlicense.org>
|
69
README.md
69
README.md
@ -0,0 +1,69 @@
|
|||||||
|
<!--suppress ALL -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<h1>Keepass Fidelity</h1>
|
||||||
|
<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-with-love.svg" alt="Built with love">
|
||||||
|
<br>
|
||||||
|
<a href="https://github.com/choelzl/keepass-fidelity/actions/workflows/build.yml">
|
||||||
|
<img src="https://github.com/choelzl/keepass-fidelity/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## 🌄 Screenshots
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 33%; height: 100px;"><img src=".github/images/launcher.jpg" alt="Launcher" style="width: 100%; height: 100%;"></td>
|
||||||
|
<td style="width: 33%; height: 100px;"><img src=".github/images/view.jpg" alt="View" style="width: 100%; height: 100%;"></td>
|
||||||
|
<td style="width: 33%; height: 100px;"><img src=".github/images/edit.jpg" alt="Edit" style="width: 100%; height: 100%;"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## ⭐ Features
|
||||||
|
|
||||||
|
- Search entries in [Keepass2Android](https://github.com/PhilippC/keepass2android/)
|
||||||
|
- Scan & Create entries
|
||||||
|
- Recently used history for fast access
|
||||||
|
- Protect entries from caching
|
||||||
|
- Minimalist design and features
|
||||||
|
- Supported Formats: CODE_39, CODE_93, CODE_128, EAN_8, EAN_13, UPC_A, UPC_E, UPC_EAN, CODE_QR, PDF_417, AZTEC, CODABAR, MAXICODE, DATA_MATRIX, ITF, RSS_14, RSS_EXPANDED
|
||||||
|
|
||||||
|
## 📳 Installation
|
||||||
|
|
||||||
|
<div style="display: flex; justify-content: center; align-items: center; flex-direction: row;">
|
||||||
|
<a href="https://github.com/choelzl/keepass-fidelity/releases/latest">
|
||||||
|
<img width="200" height="84" alt="APK Download" src=".github/images/apk.png">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## ⚙️ Permissions
|
||||||
|
|
||||||
|
- `CAMERA`: necessary for the scanning of barcodes
|
||||||
|
|
||||||
|
## 📝 Contribute
|
||||||
|
|
||||||
|
Keepass-Fidelity is a user-driven project. We welcome any contribution, big or small.
|
||||||
|
|
||||||
|
- **🖥️ Development:** Fix bugs, implement features, or research issues. Open a PR for review.
|
||||||
|
- **🍥 Design:** Improve interfaces, including accessibility and usability.
|
||||||
|
- **📂 Issue Reporting:** Report bugs and edge cases with relevant info.
|
||||||
|
- **🌍 Localization:** Translate if it doesn't support your language.
|
||||||
|
|
||||||
|
## ✏️ Acknowledgements
|
||||||
|
|
||||||
|
Thanks to all contributors, the developers of our dependencies, and our users.
|
||||||
|
|
||||||
|
## 📝 License
|
||||||
|
|
||||||
|
```
|
||||||
|
Copyright (C) 2024 Helcel
|
||||||
|
|
||||||
|
Licensed under the Unlicense
|
||||||
|
For more information, please refer to <https://unlicense.org>
|
||||||
|
```
|
@ -5,31 +5,59 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def keystorePropertiesFile = rootProject.file("app/keystore.properties")
|
||||||
|
def keystoreProperties = new Properties()
|
||||||
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'net.helcel.fidelity'
|
namespace 'net.helcel.fidelity'
|
||||||
compileSdk 34
|
compileSdk 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'net.helcel.fidelity'
|
applicationId 'net.helcel.fidelity'
|
||||||
|
resValue "string", "app_name", "Keepass Fidelity"
|
||||||
minSdk 28
|
minSdk 28
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
signingConfigs {
|
||||||
minifyEnabled false
|
create("release") {
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
keyAlias keystoreProperties['keyAlias']
|
||||||
|
keyPassword keystoreProperties['keyPassword']
|
||||||
|
storeFile file(keystoreProperties['storeFile'])
|
||||||
|
storePassword keystoreProperties['storePassword']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
debuggable true
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
coreLibraryDesugaringEnabled true
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
encoding 'utf-8'
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
}
|
}
|
||||||
@ -37,18 +65,13 @@ android {
|
|||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4'
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
|
||||||
implementation 'androidx.camera:camera-camera2:1.3.2'
|
|
||||||
implementation 'androidx.camera:camera-lifecycle:1.3.2'
|
implementation 'androidx.camera:camera-lifecycle:1.3.2'
|
||||||
implementation 'androidx.camera:camera-view:1.3.2'
|
implementation 'androidx.camera:camera-view:1.3.2'
|
||||||
|
runtimeOnly 'androidx.camera:camera-camera2:1.3.2'
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
implementation 'com.google.android.material:material:1.11.0'
|
implementation 'com.google.android.material:material:1.11.0'
|
||||||
implementation 'com.google.zxing:core:3.5.3'
|
implementation 'com.google.zxing:core:3.5.3'
|
||||||
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
|
|
||||||
|
|
||||||
}
|
}
|
13
app/proguard-rules.pro
vendored
Normal file
13
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Gson uses generic type information stored in a class file when working with
|
||||||
|
# fields. Proguard removes such information by default, keep it.
|
||||||
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# This is also needed for R8 in compat mode since multiple
|
||||||
|
# optimizations will remove the generic signature such as class
|
||||||
|
# merging and argument removal. See:
|
||||||
|
# https://r8.googlesource.com/r8/+/refs/heads/main/compatibility-faq.md#troubleshooting-gson-gson
|
||||||
|
-keep class com.google.gson.reflect.TypeToken { *; }
|
||||||
|
-keep class * extends com.google.gson.reflect.TypeToken
|
||||||
|
|
||||||
|
# Optional. For using GSON @Expose annotation
|
||||||
|
-keepattributes AnnotationDefault,RuntimeVisibleAnnotations
|
@ -1,7 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:versionCode="1"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:versionName="1.0">
|
android:versionCode="2"
|
||||||
|
android:versionName="1.1">
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" />
|
<uses-feature android:name="android.hardware.camera" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
@ -21,27 +22,13 @@
|
|||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".pluginSDK.PluginAccessBroadcastReceiver"
|
android:name=".pluginSDK.PluginAccessBroadcastReceiver"
|
||||||
android:exported="true">
|
android:exported="true"
|
||||||
|
tools:ignore="ExportedReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
|
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
|
||||||
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
|
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
|
||||||
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
|
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".pluginSDK.PluginActionBroadcastReceiver"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="keepass2android.ACTION_OPEN_ENTRY" />
|
|
||||||
<action android:name="keepass2android.ACTION_ENTRY_OUTPUT_MODIFIED" />
|
|
||||||
<action android:name="keepass2android.ACTION_CLOSE_ENTRY_VIEW" />
|
|
||||||
<action android:name="keepass2android.ACTION_ADD_ENTRY_ACTION" />
|
|
||||||
|
|
||||||
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
@ -9,8 +9,12 @@ import androidx.activity.addCallback
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import net.helcel.fidelity.R
|
import net.helcel.fidelity.R
|
||||||
import net.helcel.fidelity.activity.fragment.Launcher
|
import net.helcel.fidelity.activity.fragment.Launcher
|
||||||
|
import net.helcel.fidelity.activity.fragment.ViewEntry
|
||||||
import net.helcel.fidelity.databinding.ActMainBinding
|
import net.helcel.fidelity.databinding.ActMainBinding
|
||||||
|
import net.helcel.fidelity.pluginSDK.Kp2aControl.getEntryFieldsFromIntent
|
||||||
import net.helcel.fidelity.tools.CacheManager
|
import net.helcel.fidelity.tools.CacheManager
|
||||||
|
import net.helcel.fidelity.tools.KeepassWrapper.bundleCreate
|
||||||
|
import net.helcel.fidelity.tools.KeepassWrapper.entryExtract
|
||||||
|
|
||||||
@SuppressLint("SourceLockedOrientationActivity")
|
@SuppressLint("SourceLockedOrientationActivity")
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
@ -38,7 +42,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedInstanceState == null)
|
if (intent.extras != null)
|
||||||
|
loadViewEntry()
|
||||||
|
else if (savedInstanceState == null)
|
||||||
loadLauncher()
|
loadLauncher()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,5 +53,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
.replace(R.id.container, Launcher())
|
.replace(R.id.container, Launcher())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadViewEntry() {
|
||||||
|
val viewEntry = ViewEntry()
|
||||||
|
val data = getEntryFieldsFromIntent(intent)
|
||||||
|
viewEntry.arguments = bundleCreate(entryExtract(data))
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.container, viewEntry)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ class FidelityListAdapter(
|
|||||||
|
|
||||||
inner class FidelityViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
inner class FidelityViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
|
|
||||||
fun bind(triple: Triple<String?, String?, String?>) {
|
fun bind(triple: Triple<String?, String?, String?>) {
|
||||||
val text = "${triple.first}"
|
val text = "${triple.first}"
|
||||||
binding.textView.text = text
|
binding.textView.text = text
|
||||||
|
@ -7,9 +7,11 @@ import android.os.Looper
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.google.zxing.FormatException
|
import com.google.zxing.FormatException
|
||||||
import net.helcel.fidelity.R
|
import net.helcel.fidelity.R
|
||||||
import net.helcel.fidelity.databinding.FragCreateEntryBinding
|
import net.helcel.fidelity.databinding.FragCreateEntryBinding
|
||||||
@ -34,7 +36,7 @@ class CreateEntry : Fragment() {
|
|||||||
startViewEntry(r.first, r.second, r.third)
|
startViewEntry(r.first, r.second, r.third)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isValid: Boolean = false
|
private var isValidBarcode: Boolean = false
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@ -51,41 +53,14 @@ class CreateEntry : Fragment() {
|
|||||||
binding.editTextCode.setText(res.second)
|
binding.editTextCode.setText(res.second)
|
||||||
binding.editTextFormat.setText(res.third, false)
|
binding.editTextFormat.setText(res.third, false)
|
||||||
|
|
||||||
val changeListener = {
|
|
||||||
isValid = false
|
|
||||||
handler.removeCallbacksAndMessages(null)
|
|
||||||
handler.postDelayed({
|
|
||||||
updatePreview()
|
|
||||||
}, DEBOUNCE_DELAY)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.editTextCode.addTextChangedListener { changeListener() }
|
binding.editTextCode.addTextChangedListener { changeListener() }
|
||||||
binding.editTextFormat.addTextChangedListener { changeListener() }
|
binding.editTextFormat.addTextChangedListener { changeListener() }
|
||||||
binding.editTextFormat.addTextChangedListener { binding.editTextFormat.error = null }
|
binding.editTextFormat.addTextChangedListener { binding.editTextFormat.error = null }
|
||||||
binding.btnSave.setOnClickListener {
|
binding.btnSave.setOnClickListener { submit() }
|
||||||
if (!isValid() || !isValid) {
|
|
||||||
ErrorToaster.formIncomplete(requireActivity())
|
binding.editTextTitle.onDone { submit() }
|
||||||
|
binding.editTextCode.onDone { submit() }
|
||||||
|
|
||||||
} else {
|
|
||||||
val kpEntry = KeepassWrapper.entryCreate(
|
|
||||||
this,
|
|
||||||
binding.editTextTitle.text.toString(),
|
|
||||||
binding.editTextCode.text.toString(),
|
|
||||||
binding.editTextFormat.text.toString(),
|
|
||||||
binding.checkboxProtected.isChecked,
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
resultLauncherAdd.launch(
|
|
||||||
Kp2aControl.getAddEntryIntent(
|
|
||||||
kpEntry.first,
|
|
||||||
kpEntry.second
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
ErrorToaster.noKP2AFound(requireActivity())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePreview()
|
updatePreview()
|
||||||
return binding.root
|
return binding.root
|
||||||
@ -99,7 +74,7 @@ class CreateEntry : Fragment() {
|
|||||||
600
|
600
|
||||||
)
|
)
|
||||||
binding.imageViewPreview.setImageBitmap(barcodeBitmap)
|
binding.imageViewPreview.setImageBitmap(barcodeBitmap)
|
||||||
isValid = true
|
isValidBarcode = true
|
||||||
} catch (e: FormatException) {
|
} catch (e: FormatException) {
|
||||||
binding.imageViewPreview.setImageBitmap(null)
|
binding.imageViewPreview.setImageBitmap(null)
|
||||||
binding.editTextCode.error = "Invalid format"
|
binding.editTextCode.error = "Invalid format"
|
||||||
@ -112,19 +87,19 @@ class CreateEntry : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isValid(): Boolean {
|
private fun isValidForm(): Boolean {
|
||||||
var valid = true
|
var valid = true
|
||||||
if (binding.editTextTitle.text.isNullOrEmpty()) {
|
if (binding.editTextFormat.text.isNullOrEmpty()) {
|
||||||
valid = false
|
valid = false
|
||||||
binding.editTextTitle.error = "Title cannot be empty"
|
binding.editTextFormat.error = "Format cannot be empty"
|
||||||
}
|
}
|
||||||
if (binding.editTextCode.text.isNullOrEmpty()) {
|
if (binding.editTextCode.text.isNullOrEmpty()) {
|
||||||
valid = false
|
valid = false
|
||||||
binding.editTextCode.error = "Code cannot be empty"
|
binding.editTextCode.error = "Code cannot be empty"
|
||||||
}
|
}
|
||||||
if (binding.editTextFormat.text.isNullOrEmpty()) {
|
if (binding.editTextTitle.text.isNullOrEmpty()) {
|
||||||
valid = false
|
valid = false
|
||||||
binding.editTextFormat.error = "Format cannot be empty"
|
binding.editTextTitle.error = "Title cannot be empty"
|
||||||
}
|
}
|
||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
@ -138,4 +113,50 @@ class CreateEntry : Fragment() {
|
|||||||
.replace(R.id.container, viewEntryFragment).commit()
|
.replace(R.id.container, viewEntryFragment).commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun changeListener() {
|
||||||
|
isValidBarcode = false
|
||||||
|
handler.removeCallbacksAndMessages(null)
|
||||||
|
handler.postDelayed({
|
||||||
|
updatePreview()
|
||||||
|
}, DEBOUNCE_DELAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun TextInputEditText.onDone(callback: () -> Unit) {
|
||||||
|
setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
callback.invoke()
|
||||||
|
return@setOnEditorActionListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submit() {
|
||||||
|
if (!isValidForm() || !isValidBarcode) {
|
||||||
|
ErrorToaster.formIncomplete(context)
|
||||||
|
} else {
|
||||||
|
val kpEntry = KeepassWrapper.entryCreate(
|
||||||
|
this,
|
||||||
|
binding.editTextTitle.text.toString(),
|
||||||
|
binding.editTextCode.text.toString(),
|
||||||
|
binding.editTextFormat.text.toString(),
|
||||||
|
binding.checkboxProtected.isChecked,
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
resultLauncherAdd.launch(
|
||||||
|
Kp2aControl.getAddEntryIntent(
|
||||||
|
kpEntry.first,
|
||||||
|
kpEntry.second
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
ErrorToaster.noKP2AFound(context)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -45,6 +45,7 @@ class Scanner : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun startCreateEntry() {
|
private fun startCreateEntry() {
|
||||||
val createEntryFragment = CreateEntry()
|
val createEntryFragment = CreateEntry()
|
||||||
createEntryFragment.arguments =
|
createEntryFragment.arguments =
|
||||||
@ -89,13 +90,14 @@ class Scanner : Fragment() {
|
|||||||
}
|
}
|
||||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
val analysisUseCase = getAnalysisUseCase { code, format ->
|
val analysisUseCase = getAnalysisUseCase { code, format ->
|
||||||
if (code != null && format != null) {
|
if (!code.isNullOrEmpty() && !format.isNullOrEmpty()) {
|
||||||
this.code = code
|
this.code = code
|
||||||
this.fmt = format
|
this.fmt = format
|
||||||
binding.btnScanDone.isEnabled = true
|
}
|
||||||
|
val isDone = this.code.isNotEmpty() && this.fmt.isNotEmpty()
|
||||||
} else {
|
requireActivity().runOnUiThread {
|
||||||
binding.btnScanDone.isEnabled = false
|
binding.btnScanDone.isEnabled = isDone
|
||||||
|
binding.ScanActive.isEnabled = !isDone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -6,9 +6,6 @@ import org.json.JSONArray
|
|||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
|
|
||||||
|
|
||||||
class PluginAccessException(msg: String) : Exception(msg)
|
|
||||||
|
|
||||||
|
|
||||||
object AccessManager {
|
object AccessManager {
|
||||||
private const val PREF_KEY_SCOPE = "scope"
|
private const val PREF_KEY_SCOPE = "scope"
|
||||||
private const val PREF_KEY_TOKEN = "token"
|
private const val PREF_KEY_TOKEN = "token"
|
||||||
@ -94,12 +91,4 @@ object AccessManager {
|
|||||||
hostPrefs.edit().remove(hostPackage).apply()
|
hostPrefs.edit().remove(hostPackage).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAccessToken(
|
|
||||||
context: Context, hostPackage: String?,
|
|
||||||
scopes: ArrayList<String?>
|
|
||||||
): String {
|
|
||||||
return tryGetAccessToken(context, hostPackage, scopes)
|
|
||||||
?: throw PluginAccessException(hostPackage + scopes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package net.helcel.fidelity.pluginSDK
|
package net.helcel.fidelity.pluginSDK
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
object KeepassDef {
|
object KeepassDef {
|
||||||
var TitleField: String = "Title"
|
var TitleField: String = "Title"
|
||||||
var UserNameField: String = "UserName"
|
var UserNameField: String = "UserName"
|
||||||
var PasswordField: String = "Password"
|
var PasswordField: String = "Password"
|
||||||
var UrlField: String = "URL"
|
var UrlField: String = "URL"
|
||||||
var NotesField: String = "Notes"
|
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,10 @@ object Kp2aControl {
|
|||||||
fun getEntryFieldsFromIntent(intent: Intent?): HashMap<String, String> {
|
fun getEntryFieldsFromIntent(intent: Intent?): HashMap<String, String> {
|
||||||
val res = HashMap<String, String>()
|
val res = HashMap<String, String>()
|
||||||
try {
|
try {
|
||||||
val json = JSONObject(intent?.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA)!!)
|
val json = JSONObject(intent?.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA) ?: "")
|
||||||
val iter = json.keys()
|
val itr = json.keys()
|
||||||
while (iter.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
val key = iter.next()
|
val key = itr.next()
|
||||||
val value = json[key].toString()
|
val value = json[key].toString()
|
||||||
res[key] = value
|
res[key] = value
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class PluginAccessBroadcastReceiver : BroadcastReceiver() {
|
|||||||
Strings.ACTION_TRIGGER_REQUEST_ACCESS -> requestAccess(ctx, intent)
|
Strings.ACTION_TRIGGER_REQUEST_ACCESS -> requestAccess(ctx, intent)
|
||||||
Strings.ACTION_RECEIVE_ACCESS -> receiveAccess(ctx, intent)
|
Strings.ACTION_RECEIVE_ACCESS -> receiveAccess(ctx, intent)
|
||||||
Strings.ACTION_REVOKE_ACCESS -> revokeAccess(ctx, intent)
|
Strings.ACTION_REVOKE_ACCESS -> revokeAccess(ctx, intent)
|
||||||
else -> println(action)
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,8 +46,6 @@ class PluginAccessBroadcastReceiver : BroadcastReceiver() {
|
|||||||
private val scopes: ArrayList<String?> = ArrayList(
|
private val scopes: ArrayList<String?> = ArrayList(
|
||||||
listOf(
|
listOf(
|
||||||
Strings.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE,
|
Strings.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE,
|
||||||
Strings.SCOPE_DATABASE_ACTIONS,
|
|
||||||
Strings.SCOPE_CURRENT_ENTRY,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,226 +0,0 @@
|
|||||||
package net.helcel.fidelity.pluginSDK
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONException
|
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
class PluginActionBroadcastReceiver : BroadcastReceiver() {
|
|
||||||
open class PluginActionBase
|
|
||||||
(var context: Context, protected var _intent: Intent) {
|
|
||||||
val hostPackage: String?
|
|
||||||
get() = _intent.getStringExtra(Strings.EXTRA_SENDER)
|
|
||||||
}
|
|
||||||
|
|
||||||
open class PluginEntryActionBase(context: Context, intent: Intent) :
|
|
||||||
PluginActionBase(context, intent) {
|
|
||||||
protected val entryFieldsFromIntent: HashMap<String, String>
|
|
||||||
get() {
|
|
||||||
val res = HashMap<String, String>()
|
|
||||||
try {
|
|
||||||
val json =
|
|
||||||
JSONObject(_intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA) ?: "")
|
|
||||||
val iter = json.keys()
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
val key = iter.next()
|
|
||||||
val value = json[key].toString()
|
|
||||||
res[key] = value
|
|
||||||
}
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
protected val protectedFieldsListFromIntent: Array<String?>?
|
|
||||||
get() {
|
|
||||||
try {
|
|
||||||
val json =
|
|
||||||
JSONArray(_intent.getStringExtra(Strings.EXTRA_PROTECTED_FIELDS_LIST))
|
|
||||||
val res = arrayOfNulls<String>(json.length())
|
|
||||||
for (i in 0 until json.length()) res[i] = json.getString(i)
|
|
||||||
return res
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
open val entryId: String?
|
|
||||||
get() = _intent.getStringExtra(Strings.EXTRA_ENTRY_ID)
|
|
||||||
|
|
||||||
|
|
||||||
fun setEntryField(fieldId: String?, fieldValue: String?, isProtected: Boolean) {
|
|
||||||
val i = Intent(Strings.ACTION_SET_ENTRY_FIELD)
|
|
||||||
val scope = ArrayList<String?>()
|
|
||||||
scope.add(Strings.SCOPE_CURRENT_ENTRY)
|
|
||||||
i.putExtra(
|
|
||||||
Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken(
|
|
||||||
context, hostPackage, scope
|
|
||||||
)
|
|
||||||
)
|
|
||||||
i.setPackage(hostPackage)
|
|
||||||
i.putExtra(Strings.EXTRA_SENDER, context.packageName)
|
|
||||||
i.putExtra(Strings.EXTRA_FIELD_VALUE, fieldValue)
|
|
||||||
i.putExtra(Strings.EXTRA_ENTRY_ID, entryId)
|
|
||||||
i.putExtra(Strings.EXTRA_FIELD_ID, fieldId)
|
|
||||||
i.putExtra(Strings.EXTRA_FIELD_PROTECTED, isProtected)
|
|
||||||
|
|
||||||
context.sendBroadcast(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class ActionSelectedAction(ctx: Context, intent: Intent) :
|
|
||||||
PluginEntryActionBase(ctx, intent) {
|
|
||||||
val actionData: Bundle?
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return the Bundle associated with the action. This bundle can be set in OpenEntry.add(Entry)FieldAction
|
|
||||||
*/
|
|
||||||
get() = _intent.getBundleExtra(Strings.EXTRA_ACTION_DATA)
|
|
||||||
|
|
||||||
private val fieldId: String?
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return the field id which was selected. null if an entry action (in the options menu) was selected.
|
|
||||||
*/
|
|
||||||
get() = _intent.getStringExtra(Strings.EXTRA_FIELD_ID)
|
|
||||||
|
|
||||||
val isEntryAction: Boolean
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return true if an entry action, i.e. an option from the options menu, was selected. False if an option
|
|
||||||
* in a popup menu for a certain field was selected.
|
|
||||||
*/
|
|
||||||
get() = fieldId == null
|
|
||||||
|
|
||||||
val entryFields: HashMap<String, String>
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return a hashmap containing the entry fields in key/value form
|
|
||||||
*/
|
|
||||||
get() = entryFieldsFromIntent
|
|
||||||
|
|
||||||
val protectedFieldsList: Array<String?>?
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return an array with the keys of all protected fields in the entry
|
|
||||||
*/
|
|
||||||
get() = protectedFieldsListFromIntent
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class CloseEntryViewAction(context: Context, intent: Intent) :
|
|
||||||
PluginEntryActionBase(context, intent) {
|
|
||||||
override val entryId: String?
|
|
||||||
get() = _intent.getStringExtra(Strings.EXTRA_ENTRY_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
private open inner class OpenEntryAction(context: Context, intent: Intent) :
|
|
||||||
PluginEntryActionBase(context, intent) {
|
|
||||||
val entryFields: HashMap<String, String>
|
|
||||||
get() = entryFieldsFromIntent
|
|
||||||
|
|
||||||
val protectedFieldsList: Array<String?>?
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return an array with the keys of all protected fields in the entry
|
|
||||||
*/
|
|
||||||
get() = protectedFieldsListFromIntent
|
|
||||||
|
|
||||||
fun addEntryAction(
|
|
||||||
actionDisplayText: String?,
|
|
||||||
actionIconResourceId: Int,
|
|
||||||
actionData: Bundle?
|
|
||||||
) {
|
|
||||||
addEntryFieldAction(null, null, actionDisplayText, actionIconResourceId, actionData)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addEntryFieldAction(
|
|
||||||
actionId: String?,
|
|
||||||
fieldId: String?,
|
|
||||||
actionDisplayText: String?,
|
|
||||||
actionIconResourceId: Int,
|
|
||||||
actionData: Bundle?
|
|
||||||
) {
|
|
||||||
val i = Intent(Strings.ACTION_ADD_ENTRY_ACTION)
|
|
||||||
val scope = ArrayList<String?>()
|
|
||||||
scope.add(Strings.SCOPE_CURRENT_ENTRY)
|
|
||||||
i.putExtra(
|
|
||||||
Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken(
|
|
||||||
context, hostPackage!!, scope
|
|
||||||
)
|
|
||||||
)
|
|
||||||
i.setPackage(hostPackage)
|
|
||||||
i.putExtra(Strings.EXTRA_SENDER, context.packageName)
|
|
||||||
i.putExtra(Strings.EXTRA_ACTION_DATA, actionData)
|
|
||||||
i.putExtra(Strings.EXTRA_ACTION_DISPLAY_TEXT, actionDisplayText)
|
|
||||||
i.putExtra(Strings.EXTRA_ACTION_ICON_RES_ID, actionIconResourceId)
|
|
||||||
i.putExtra(Strings.EXTRA_ENTRY_ID, entryId)
|
|
||||||
i.putExtra(Strings.EXTRA_FIELD_ID, fieldId)
|
|
||||||
i.putExtra(Strings.EXTRA_ACTION_ID, actionId)
|
|
||||||
|
|
||||||
context.sendBroadcast(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class DatabaseAction(context: Context, intent: Intent) :
|
|
||||||
PluginActionBase(context, intent) {
|
|
||||||
val fileDisplayName: String?
|
|
||||||
get() = _intent.getStringExtra(Strings.EXTRA_DATABASE_FILE_DISPLAYNAME)
|
|
||||||
|
|
||||||
val filePath: String?
|
|
||||||
get() = _intent.getStringExtra(Strings.EXTRA_DATABASE_FILEPATH)
|
|
||||||
|
|
||||||
val action: String?
|
|
||||||
get() = _intent.action
|
|
||||||
}
|
|
||||||
|
|
||||||
//EntryOutputModified is very similar to OpenEntry because it receives the same
|
|
||||||
//data (+ the field id which was modified)
|
|
||||||
private inner class EntryOutputModifiedAction(context: Context, intent: Intent) :
|
|
||||||
OpenEntryAction(context, intent) {
|
|
||||||
val modifiedFieldId: String?
|
|
||||||
get() = _intent.getStringExtra(Strings.EXTRA_FIELD_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReceive(ctx: Context, intent: Intent) {
|
|
||||||
val action = intent.action ?: return
|
|
||||||
Log.d(
|
|
||||||
"KP2A.pluginsdk",
|
|
||||||
"received broadcast in PluginActionBroadcastReceiver with action=$action"
|
|
||||||
)
|
|
||||||
println(action)
|
|
||||||
|
|
||||||
when (action) {
|
|
||||||
Strings.ACTION_OPEN_ENTRY -> openEntry(OpenEntryAction(ctx, intent))
|
|
||||||
Strings.ACTION_CLOSE_ENTRY_VIEW -> closeEntryView(CloseEntryViewAction(ctx, intent))
|
|
||||||
Strings.ACTION_ENTRY_ACTION_SELECTED ->
|
|
||||||
actionSelected(ActionSelectedAction(ctx, intent))
|
|
||||||
|
|
||||||
Strings.ACTION_ENTRY_OUTPUT_MODIFIED ->
|
|
||||||
entryOutputModified(EntryOutputModifiedAction(ctx, intent))
|
|
||||||
|
|
||||||
Strings.ACTION_LOCK_DATABASE -> dbAction(DatabaseAction(ctx, intent))
|
|
||||||
Strings.ACTION_UNLOCK_DATABASE -> dbAction(DatabaseAction(ctx, intent))
|
|
||||||
Strings.ACTION_OPEN_DATABASE -> dbAction(DatabaseAction(ctx, intent))
|
|
||||||
Strings.ACTION_CLOSE_DATABASE -> dbAction(DatabaseAction(ctx, intent))
|
|
||||||
|
|
||||||
else -> println(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun closeEntryView(closeEntryView: CloseEntryViewAction?) {}
|
|
||||||
|
|
||||||
private fun actionSelected(actionSelected: ActionSelectedAction?) {}
|
|
||||||
|
|
||||||
private fun openEntry(oe: OpenEntryAction?) {}
|
|
||||||
|
|
||||||
private fun entryOutputModified(eom: EntryOutputModifiedAction?) {}
|
|
||||||
|
|
||||||
private fun dbAction(db: DatabaseAction?) {}
|
|
||||||
}
|
|
@ -1,176 +1,30 @@
|
|||||||
package net.helcel.fidelity.pluginSDK
|
package net.helcel.fidelity.pluginSDK
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
object Strings {
|
object Strings {
|
||||||
/**
|
|
||||||
* Plugin is notified about actions like open/close/update a database.
|
|
||||||
*/
|
|
||||||
const val SCOPE_DATABASE_ACTIONS = "keepass2android.SCOPE_DATABASE_ACTIONS"
|
const val SCOPE_DATABASE_ACTIONS = "keepass2android.SCOPE_DATABASE_ACTIONS"
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin is notified when an entry is opened.
|
|
||||||
*/
|
|
||||||
const val SCOPE_CURRENT_ENTRY = "keepass2android.SCOPE_CURRENT_ENTRY"
|
const val SCOPE_CURRENT_ENTRY = "keepass2android.SCOPE_CURRENT_ENTRY"
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin may query credentials for its own package
|
|
||||||
*/
|
|
||||||
const val SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
|
const val SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
|
||||||
"keepass2android.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
|
"keepass2android.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra key to transfer a (json serialized) list of scopes
|
|
||||||
*/
|
|
||||||
const val EXTRA_SCOPES = "keepass2android.EXTRA_SCOPES"
|
const val EXTRA_SCOPES = "keepass2android.EXTRA_SCOPES"
|
||||||
|
|
||||||
|
|
||||||
const val EXTRA_PLUGIN_PACKAGE = "keepass2android.EXTRA_PLUGIN_PACKAGE"
|
const val EXTRA_PLUGIN_PACKAGE = "keepass2android.EXTRA_PLUGIN_PACKAGE"
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra key for sending the package name of the sender of a broadcast.
|
|
||||||
* Should be set in every broadcast.
|
|
||||||
*/
|
|
||||||
const val EXTRA_SENDER = "keepass2android.EXTRA_SENDER"
|
const val EXTRA_SENDER = "keepass2android.EXTRA_SENDER"
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra key for sending a request token. The request token is passed from
|
|
||||||
* KP2A to the plugin. It's used in the authorization process.
|
|
||||||
*/
|
|
||||||
const val EXTRA_REQUEST_TOKEN = "keepass2android.EXTRA_REQUEST_TOKEN"
|
const val EXTRA_REQUEST_TOKEN = "keepass2android.EXTRA_REQUEST_TOKEN"
|
||||||
|
|
||||||
/**
|
|
||||||
* Action to start KP2A with an AppTask
|
|
||||||
*/
|
|
||||||
const val ACTION_START_WITH_TASK = "keepass2android.ACTION_START_WITH_TASK"
|
const val ACTION_START_WITH_TASK = "keepass2android.ACTION_START_WITH_TASK"
|
||||||
|
|
||||||
/**
|
|
||||||
* Action sent from KP2A to the plugin to indicate that the plugin should request
|
|
||||||
* access (sending it's scopes)
|
|
||||||
*/
|
|
||||||
const val ACTION_TRIGGER_REQUEST_ACCESS = "keepass2android.ACTION_TRIGGER_REQUEST_ACCESS"
|
const val ACTION_TRIGGER_REQUEST_ACCESS = "keepass2android.ACTION_TRIGGER_REQUEST_ACCESS"
|
||||||
|
|
||||||
/**
|
|
||||||
* Action sent from the plugin to KP2A including the scopes.
|
|
||||||
*/
|
|
||||||
const val ACTION_REQUEST_ACCESS = "keepass2android.ACTION_REQUEST_ACCESS"
|
const val ACTION_REQUEST_ACCESS = "keepass2android.ACTION_REQUEST_ACCESS"
|
||||||
|
|
||||||
/**
|
|
||||||
* Action sent from the KP2A to the plugin when the user grants access.
|
|
||||||
* Will contain an access token.
|
|
||||||
*/
|
|
||||||
const val ACTION_RECEIVE_ACCESS = "keepass2android.ACTION_RECEIVE_ACCESS"
|
const val ACTION_RECEIVE_ACCESS = "keepass2android.ACTION_RECEIVE_ACCESS"
|
||||||
|
|
||||||
/**
|
|
||||||
* Action sent from KP2A to the plugin to indicate that access is not or no longer valid.
|
|
||||||
*/
|
|
||||||
const val ACTION_REVOKE_ACCESS = "keepass2android.ACTION_REVOKE_ACCESS"
|
const val ACTION_REVOKE_ACCESS = "keepass2android.ACTION_REVOKE_ACCESS"
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action for startActivity(). Opens an activity in the Plugin Host to edit the plugin settings (i.e. enable it)
|
|
||||||
*/
|
|
||||||
const val ACTION_EDIT_PLUGIN_SETTINGS = "keepass2android.ACTION_EDIT_PLUGIN_SETTINGS"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action sent from KP2A to the plugin to indicate that an entry was opened.
|
|
||||||
* The Intent contains the full entry data.
|
|
||||||
*/
|
|
||||||
const val ACTION_OPEN_ENTRY = "keepass2android.ACTION_OPEN_ENTRY"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action sent from KP2A to the plugin to indicate that an entry output field was modified/added.
|
|
||||||
* The Intent contains the full new entry data.
|
|
||||||
*/
|
|
||||||
const val ACTION_ENTRY_OUTPUT_MODIFIED = "keepass2android.ACTION_ENTRY_OUTPUT_MODIFIED"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action sent from KP2A to the plugin to indicate that an entry activity was closed.
|
|
||||||
*/
|
|
||||||
const val ACTION_CLOSE_ENTRY_VIEW = "keepass2android.ACTION_CLOSE_ENTRY_VIEW"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra key for a string containing the GUID of the entry.
|
|
||||||
*/
|
|
||||||
const val EXTRA_ENTRY_ID = "keepass2android.EXTRA_ENTRY_DATA"
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Json serialized list of fields, transformed using the database context (i.e. placeholders are replaced already)
|
|
||||||
*/
|
|
||||||
const val EXTRA_ENTRY_OUTPUT_DATA = "keepass2android.EXTRA_ENTRY_OUTPUT_DATA"
|
const val EXTRA_ENTRY_OUTPUT_DATA = "keepass2android.EXTRA_ENTRY_OUTPUT_DATA"
|
||||||
|
|
||||||
/**
|
|
||||||
* Json serialized lisf of field keys, specifying which field of the EXTRA_ENTRY_OUTPUT_DATA is protected.
|
|
||||||
*/
|
|
||||||
const val EXTRA_PROTECTED_FIELDS_LIST = "keepass2android.EXTRA_PROTECTED_FIELDS_LIST"
|
const val EXTRA_PROTECTED_FIELDS_LIST = "keepass2android.EXTRA_PROTECTED_FIELDS_LIST"
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra key for passing the access token (both ways)
|
|
||||||
*/
|
|
||||||
const val EXTRA_ACCESS_TOKEN = "keepass2android.EXTRA_ACCESS_TOKEN"
|
const val EXTRA_ACCESS_TOKEN = "keepass2android.EXTRA_ACCESS_TOKEN"
|
||||||
|
|
||||||
/**
|
|
||||||
* Action for an intent from the plugin to KP2A to add menu options regarding the currently open entry.
|
|
||||||
* Requires SCOPE_CURRENT_ENTRY.
|
|
||||||
*/
|
|
||||||
const val ACTION_ADD_ENTRY_ACTION = "keepass2android.ACTION_ADD_ENTRY_ACTION"
|
|
||||||
|
|
||||||
const val EXTRA_ACTION_DISPLAY_TEXT = "keepass2android.EXTRA_ACTION_DISPLAY_TEXT"
|
|
||||||
const val EXTRA_ACTION_ICON_RES_ID = "keepass2android.EXTRA_ACTION_ICON_RES_ID"
|
|
||||||
|
|
||||||
const val EXTRA_FIELD_ID = "keepass2android.EXTRA_FIELD_ID"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to pass an id for the action. Each actionId may occur only once per field, otherwise the previous
|
|
||||||
* action with same id is replaced by the new action.
|
|
||||||
*/
|
|
||||||
const val EXTRA_ACTION_ID = "keepass2android.EXTRA_ACTION_ID"
|
|
||||||
|
|
||||||
/** Extra for ACTION_ADD_ENTRY_ACTION and ACTION_ENTRY_ACTION_SELECTED to pass data specifying the action parameters.*/
|
|
||||||
const val EXTRA_ACTION_DATA = "keepass2android.EXTRA_ACTION_DATA"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action for an intent from KP2A to the plugin when an action added with ACTION_ADD_ENTRY_ACTION was selected by the user.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const val ACTION_ENTRY_ACTION_SELECTED = "keepass2android.ACTION_ENTRY_ACTION_SELECTED"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra key for the string which is used to query the credentials. This should be either a URL for
|
|
||||||
* a web login (google.com or a full URI) or something in the form "androidapp://com.my.package"
|
|
||||||
*/
|
|
||||||
const val EXTRA_QUERY_STRING = "keepass2android.EXTRA_QUERY_STRING"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action when plugin wants to query credentials for its own package
|
|
||||||
*/
|
|
||||||
const val ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
|
const val ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
|
||||||
"keepass2android.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
|
"keepass2android.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
|
||||||
|
|
||||||
/**
|
|
||||||
* Action for an intent from the plugin to KP2A to set (i.e. add or update) a field in the entry.
|
|
||||||
* May be used to update existing or add new fields at any time while the entry is opened.
|
|
||||||
*/
|
|
||||||
const val ACTION_SET_ENTRY_FIELD = "keepass2android.ACTION_SET_ENTRY_FIELD"
|
|
||||||
|
|
||||||
/** Actions for an intent from KP2A to the plugin to inform that a database was opened, closed, quicklocked or quickunlocked.*/
|
|
||||||
const val ACTION_OPEN_DATABASE = "keepass2android.ACTION_OPEN_DATABASE"
|
|
||||||
const val ACTION_CLOSE_DATABASE = "keepass2android.ACTION_CLOSE_DATABASE"
|
|
||||||
const val ACTION_LOCK_DATABASE = "keepass2android.ACTION_LOCK_DATABASE"
|
|
||||||
const val ACTION_UNLOCK_DATABASE = "keepass2android.ACTION_UNLOCK_DATABASE"
|
|
||||||
|
|
||||||
/** Extra for ACTION_OPEN_DATABASE and ACTION_CLOSE_DATABASE containing a filepath which is used
|
|
||||||
* by KP2A internally to identify the file. Use only where necessary, might contain credentials
|
|
||||||
* for accessing the file (on remote storage).*/
|
|
||||||
const val EXTRA_DATABASE_FILEPATH = "keepass2android.EXTRA_DATABASE_FILEPATH"
|
|
||||||
|
|
||||||
/** Extra for ACTION_OPEN_DATABASE and ACTION_CLOSE_DATABASE containing a filepath which can be
|
|
||||||
* displayed to the user.*/
|
|
||||||
const val EXTRA_DATABASE_FILE_DISPLAYNAME = "keepass2android.EXTRA_DATABASE_FILE_DISPLAYNAME"
|
|
||||||
|
|
||||||
|
|
||||||
const val EXTRA_FIELD_VALUE = "keepass2android.EXTRA_FIELD_VALUE"
|
|
||||||
const val EXTRA_FIELD_PROTECTED = "keepass2android.EXTRA_FIELD_PROTECTED"
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.helcel.fidelity.tools
|
package net.helcel.fidelity.tools
|
||||||
|
|
||||||
import com.google.mlkit.vision.barcode.common.Barcode
|
|
||||||
import com.google.zxing.BarcodeFormat
|
import com.google.zxing.BarcodeFormat
|
||||||
|
|
||||||
object BarcodeFormatConverter {
|
object BarcodeFormatConverter {
|
||||||
@ -16,22 +15,40 @@ object BarcodeFormatConverter {
|
|||||||
"UPC_A" -> BarcodeFormat.UPC_A
|
"UPC_A" -> BarcodeFormat.UPC_A
|
||||||
"UPC_E" -> BarcodeFormat.UPC_E
|
"UPC_E" -> BarcodeFormat.UPC_E
|
||||||
"PDF_417" -> BarcodeFormat.PDF_417
|
"PDF_417" -> BarcodeFormat.PDF_417
|
||||||
|
"AZTEC" -> BarcodeFormat.AZTEC
|
||||||
|
"CODABAR" -> BarcodeFormat.CODABAR
|
||||||
|
"MAXICODE" -> BarcodeFormat.MAXICODE
|
||||||
|
"DATA_MATRIX" -> BarcodeFormat.DATA_MATRIX
|
||||||
|
"ITF" -> BarcodeFormat.ITF
|
||||||
|
"RSS_14" -> BarcodeFormat.RSS_14
|
||||||
|
"RSS_EXPANDED" -> BarcodeFormat.RSS_EXPANDED
|
||||||
|
"UPC_EAN" -> BarcodeFormat.UPC_EAN_EXTENSION
|
||||||
else -> throw Exception("Unsupported Format: $f")
|
else -> throw Exception("Unsupported Format: $f")
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formatToString(f: Int): String {
|
fun formatToString(f: BarcodeFormat): String {
|
||||||
return when (f) {
|
return when (f) {
|
||||||
Barcode.FORMAT_CODE_128 -> "CODE_128"
|
BarcodeFormat.CODE_39 -> "CODE_39"
|
||||||
Barcode.FORMAT_CODE_39 -> "CODE_39"
|
BarcodeFormat.CODE_93 -> "CODE_93"
|
||||||
Barcode.FORMAT_CODE_93 -> "CODE_93"
|
BarcodeFormat.CODE_128 -> "CODE_128"
|
||||||
Barcode.FORMAT_EAN_8 -> "EAN_8"
|
BarcodeFormat.EAN_8 -> "EAN_8"
|
||||||
Barcode.FORMAT_EAN_13 -> "EAN_13"
|
BarcodeFormat.EAN_13 -> "EAN_13"
|
||||||
Barcode.FORMAT_QR_CODE -> "CODE_QR"
|
BarcodeFormat.QR_CODE -> "CODE_QR"
|
||||||
Barcode.FORMAT_UPC_A -> "UPC_A"
|
BarcodeFormat.UPC_A -> "UPC_A"
|
||||||
Barcode.FORMAT_UPC_E -> "UPC_E"
|
BarcodeFormat.UPC_E -> "UPC_E"
|
||||||
Barcode.FORMAT_PDF417 -> "PDF_417"
|
BarcodeFormat.PDF_417 -> "PDF_417"
|
||||||
|
BarcodeFormat.AZTEC -> "AZTEC"
|
||||||
|
BarcodeFormat.CODABAR -> "CODABAR"
|
||||||
|
BarcodeFormat.MAXICODE -> "MAXICODE"
|
||||||
|
BarcodeFormat.DATA_MATRIX -> "DATA_MATRIX"
|
||||||
|
BarcodeFormat.ITF -> "ITF"
|
||||||
|
BarcodeFormat.RSS_14 -> "RSS_14"
|
||||||
|
BarcodeFormat.RSS_EXPANDED -> "RSS_EXPANDED"
|
||||||
|
BarcodeFormat.UPC_EAN_EXTENSION -> "UPC_EAN"
|
||||||
else -> throw Exception("Unsupported Format: $f")
|
else -> throw Exception("Unsupported Format: $f")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
package net.helcel.fidelity.tools
|
package net.helcel.fidelity.tools
|
||||||
|
|
||||||
import android.content.ContentValues
|
import android.graphics.Bitmap
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.camera.core.ExperimentalGetImage
|
import androidx.camera.core.ExperimentalGetImage
|
||||||
import androidx.camera.core.ImageAnalysis
|
import androidx.camera.core.ImageAnalysis
|
||||||
import androidx.camera.core.ImageProxy
|
import androidx.camera.core.ImageProxy
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanner
|
import com.google.zxing.BinaryBitmap
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
import com.google.zxing.MultiFormatReader
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
import com.google.zxing.NotFoundException
|
||||||
import com.google.mlkit.vision.barcode.common.Barcode
|
import com.google.zxing.RGBLuminanceSource
|
||||||
import com.google.mlkit.vision.common.InputImage
|
import com.google.zxing.ReaderException
|
||||||
|
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
|
||||||
|
|
||||||
@ -19,57 +19,39 @@ object BarcodeScanner {
|
|||||||
|
|
||||||
@OptIn(ExperimentalGetImage::class)
|
@OptIn(ExperimentalGetImage::class)
|
||||||
private fun processImageProxy(
|
private fun processImageProxy(
|
||||||
barcodeScanner: BarcodeScanner,
|
|
||||||
imageProxy: ImageProxy,
|
imageProxy: ImageProxy,
|
||||||
cb: (String?, String?) -> Unit
|
cb: (String?, String?) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val bitmap = imageProxy.toBitmap() // Convert ImageProxy to Bitmap
|
||||||
imageProxy.image?.let { image ->
|
val binaryBitmap = createBinaryBitmap(bitmap)
|
||||||
val inputImage =
|
val reader = MultiFormatReader()
|
||||||
InputImage.fromMediaImage(
|
try {
|
||||||
image,
|
val result = reader.decode(binaryBitmap)
|
||||||
imageProxy.imageInfo.rotationDegrees
|
cb(result.text, formatToString(result.barcodeFormat))
|
||||||
)
|
} catch (e: NotFoundException) {
|
||||||
|
cb(null, null)
|
||||||
barcodeScanner.process(inputImage)
|
} catch (e: ReaderException) {
|
||||||
.addOnSuccessListener { barcodeList ->
|
cb(null, null)
|
||||||
val barcode =
|
} finally {
|
||||||
barcodeList.getOrNull(0)
|
imageProxy.close()
|
||||||
if (barcode != null)
|
|
||||||
cb(barcode.displayValue, formatToString(barcode.format))
|
|
||||||
}
|
|
||||||
.addOnFailureListener {
|
|
||||||
Log.e(ContentValues.TAG, it.message.orEmpty())
|
|
||||||
}.addOnCompleteListener {
|
|
||||||
imageProxy.image?.close()
|
|
||||||
imageProxy.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createBinaryBitmap(bitmap: Bitmap): BinaryBitmap {
|
||||||
|
val pixels = IntArray(bitmap.width * bitmap.height)
|
||||||
|
bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
|
||||||
|
val source =
|
||||||
|
RGBLuminanceSource(bitmap.width, bitmap.height, pixels)
|
||||||
|
return BinaryBitmap(HybridBinarizer(source))
|
||||||
|
}
|
||||||
|
|
||||||
fun getAnalysisUseCase(cb: (String?, String?) -> Unit): ImageAnalysis {
|
fun getAnalysisUseCase(cb: (String?, String?) -> Unit): ImageAnalysis {
|
||||||
val options = BarcodeScannerOptions.Builder().setBarcodeFormats(
|
val analysisUseCase = ImageAnalysis.Builder().build()
|
||||||
Barcode.FORMAT_CODE_128,
|
|
||||||
Barcode.FORMAT_CODE_39,
|
|
||||||
Barcode.FORMAT_CODE_93,
|
|
||||||
Barcode.FORMAT_EAN_8,
|
|
||||||
Barcode.FORMAT_EAN_13,
|
|
||||||
Barcode.FORMAT_QR_CODE,
|
|
||||||
Barcode.FORMAT_UPC_A,
|
|
||||||
Barcode.FORMAT_UPC_E,
|
|
||||||
Barcode.FORMAT_PDF417
|
|
||||||
).build()
|
|
||||||
val scanner = BarcodeScanning.getClient(options)
|
|
||||||
val analysisUseCase = ImageAnalysis.Builder()
|
|
||||||
.build()
|
|
||||||
|
|
||||||
analysisUseCase.setAnalyzer(
|
analysisUseCase.setAnalyzer(
|
||||||
Executors.newSingleThreadExecutor()
|
Executors.newSingleThreadExecutor()
|
||||||
) { imageProxy ->
|
) { imageProxy ->
|
||||||
processImageProxy(scanner, imageProxy, cb)
|
processImageProxy(imageProxy, cb)
|
||||||
}
|
}
|
||||||
return analysisUseCase
|
return analysisUseCase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,22 +1,23 @@
|
|||||||
package net.helcel.fidelity.tools
|
package net.helcel.fidelity.tools
|
||||||
|
|
||||||
import android.app.Activity
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
|
||||||
object ErrorToaster {
|
object ErrorToaster {
|
||||||
private fun helper(activity: Activity, message: String, length: Int) {
|
private fun helper(activity: Context?, message: String, length: Int) {
|
||||||
Toast.makeText(activity, message, length).show()
|
if (activity != null)
|
||||||
|
Toast.makeText(activity, message, length).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun noKP2AFound(activity: Activity) {
|
fun noKP2AFound(activity: Context?) {
|
||||||
helper(activity, "KeePass2Android Not Installed", Toast.LENGTH_LONG)
|
helper(activity, "KeePass2Android Not Installed", Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formIncomplete(activity: Activity) {
|
fun formIncomplete(activity: Context?) {
|
||||||
helper(activity, "Form Incomplete", Toast.LENGTH_SHORT)
|
helper(activity, "Form Incomplete", Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invalidFormat(activity: Activity) {
|
fun invalidFormat(activity: Context?) {
|
||||||
helper(activity, "Invalid Format", Toast.LENGTH_SHORT)
|
helper(activity, "Invalid Format", Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,12 +42,8 @@ object KeepassWrapper {
|
|||||||
callback: (HashMap<String, String>) -> Unit
|
callback: (HashMap<String, String>) -> Unit
|
||||||
): ActivityResultLauncher<Intent> {
|
): ActivityResultLauncher<Intent> {
|
||||||
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
|
||||||
println(result.resultCode)
|
|
||||||
println(result.data.toString())
|
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
val credentials = Kp2aControl.getEntryFieldsFromIntent(result.data)
|
val credentials = Kp2aControl.getEntryFieldsFromIntent(result.data)
|
||||||
println(credentials.toList().toString())
|
|
||||||
callback(credentials)
|
callback(credentials)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,6 +65,10 @@ object KeepassWrapper {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun bundleCreate(triple: Triple<String?, String?, String?>): Bundle {
|
||||||
|
return bundleCreate(triple.first, triple.second, triple.third)
|
||||||
|
}
|
||||||
|
|
||||||
fun bundleExtract(data: Bundle?): Triple<String?, String?, String?> {
|
fun bundleExtract(data: Bundle?): Triple<String?, String?, String?> {
|
||||||
return Triple(
|
return Triple(
|
||||||
data?.getString("title"),
|
data?.getString("title"),
|
||||||
|
@ -22,7 +22,11 @@
|
|||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/editTextTitle"
|
android:id="@+id/editTextTitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:imeOptions="actionNext"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minLines="1" />
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
@ -45,7 +49,11 @@
|
|||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/editTextCode"
|
android:id="@+id/editTextCode"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minLines="1" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
|
||||||
@ -75,6 +83,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:focusable="false"
|
||||||
android:inputType="none" />
|
android:inputType="none" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
android:contentDescription="@string/manual" />
|
android:contentDescription="@string/manual" />
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/ScanActive"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
|
@ -16,14 +16,22 @@
|
|||||||
<string name="format">Format</string>
|
<string name="format">Format</string>
|
||||||
<string name="save">Save</string>
|
<string name="save">Save</string>
|
||||||
<string-array name="format_array">
|
<string-array name="format_array">
|
||||||
<item>CODE_128</item>
|
|
||||||
<item>CODE_39</item>
|
<item>CODE_39</item>
|
||||||
<item>CODE_93</item>
|
<item>CODE_93</item>
|
||||||
|
<item>CODE_128</item>
|
||||||
<item>EAN_8</item>
|
<item>EAN_8</item>
|
||||||
<item>EAN_13</item>
|
<item>EAN_13</item>
|
||||||
<item>CODE_QR</item>
|
<item>CODE_QR</item>
|
||||||
<item>UPC_A</item>
|
<item>UPC_A</item>
|
||||||
<item>UPC_E</item>
|
<item>UPC_E</item>
|
||||||
<item>PDF_417</item>
|
<item>PDF_417</item>
|
||||||
|
<item>AZTEC</item>
|
||||||
|
<item>CODABAR</item>
|
||||||
|
<item>MAXICODE</item>
|
||||||
|
<item>DATA_MATRIX</item>
|
||||||
|
<item>ITF</item>
|
||||||
|
<item>RSS_14</item>
|
||||||
|
<item>RSS_EXPANDED</item>
|
||||||
|
<item>UPC_EAN</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.3.1' apply false
|
id 'com.android.application' version '8.3.1' apply false
|
||||||
id 'com.android.library' version '8.3.0' apply false
|
id 'com.android.library' version '8.3.1' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
|
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
|
||||||
|
id 'com.autonomousapps.dependency-analysis' version '1.30.0' apply true
|
||||||
}
|
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@ -14,5 +14,6 @@ dependencyResolutionManagement {
|
|||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootProject.name = "BeenDroid"
|
|
||||||
|
rootProject.name = "Fidelity"
|
||||||
include ':app'
|
include ':app'
|
||||||
|
Reference in New Issue
Block a user