2FA codes can now be scanned.
This commit is contained in:
@@ -42,6 +42,10 @@ dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.3.0'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
|
||||
|
||||
/ One-time password /
|
||||
implementation "dev.turingcomplete:kotlin-onetimepassword:2.0.1"
|
||||
|
||||
/ QRCode reader /
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<activity
|
||||
android:name=".CreateActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
||||
@@ -1,25 +1,42 @@
|
||||
package com.github.mondei1.offpass
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import com.google.zxing.integration.android.IntentResult
|
||||
import dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator
|
||||
import kotlinx.android.synthetic.main.activity_create.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class CreateActivity : AppCompatActivity() {
|
||||
private var fragment_title: TextInput? = null
|
||||
private var fragment_username: TextInput? = null
|
||||
private var schema: QRSchema? = null
|
||||
|
||||
private var fa_coroutine: Job? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
fragment_title = TextInput.newInstance("Title", "ENTER TITLE", "30dp")
|
||||
fragment_username = TextInput.newInstance("Username", "ENTER USERNAME", "30dp")
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.title_box, fragment_title!!)
|
||||
.replace(R.id.username, fragment_username!!)
|
||||
.commit()
|
||||
|
||||
//this.schema = QRSchema(this)
|
||||
//this.schema!!.decrypted_raw = "%JtuB4O9M42%Gitea|Nicolas|542superGoOD_pW&|klier.nicolas@protonmail.com|\$ul|(\$vb)\$O4|()What's your favorite series%Rick and morty|(2fa)otpauth://totp/OffPass%20Test?secret=d34gfkki5dkd5knifysrpgndd5xb2c7eddwki7ya4pvoisfa5c3ko5pv&issuer=Nicolas%20Klier"
|
||||
//this.schema!!.parse(this)
|
||||
@@ -34,6 +51,80 @@ class CreateActivity : AppCompatActivity() {
|
||||
Log.i("CREATE", "Back got clicked!")
|
||||
finish()
|
||||
}
|
||||
|
||||
fa_input.isFocusable = false
|
||||
|
||||
fa_input.setOnClickListener {
|
||||
if (fa_coroutine != null) {
|
||||
if (fa_coroutine!!.isActive) {
|
||||
// Copy code if already scanend
|
||||
Toast.makeText(this, "Copied!", Toast.LENGTH_SHORT).show()
|
||||
val clip: ClipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clip.setPrimaryClip(ClipData.newPlainText("2FA code", fa_input.text))
|
||||
return@setOnClickListener
|
||||
}
|
||||
}
|
||||
|
||||
// Open QR-Code scanner
|
||||
IntentIntegrator(this)
|
||||
.setPrompt("Scan 2FA secret")
|
||||
.setBeepEnabled(false)
|
||||
.setBarcodeImageEnabled(false)
|
||||
.initiateScan()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat", "SetTextI18n")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
var result: IntentResult =
|
||||
IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
|
||||
if (result.contents == null) {
|
||||
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
// We scanned a 2FA code
|
||||
if (result.contents.startsWith("otpauth://totp/")) {
|
||||
fa_progress.visibility = View.VISIBLE
|
||||
val fa_uri = Uri.parse(result.contents)
|
||||
val fa_generator = GoogleAuthenticator(base32secret = fa_uri.getQueryParameter("secret").toString())
|
||||
|
||||
/*val objAnim: ObjectAnimator = ObjectAnimator.ofInt(fa_progress, "progress")
|
||||
objAnim.duration = 300
|
||||
objAnim.interpolator = LinearInterpolator()
|
||||
objAnim.start()*/
|
||||
|
||||
// TODO: There is a bug. When the user changes his current app, this coroutine stops.
|
||||
fa_coroutine = GlobalScope.launch(Dispatchers.Main) {
|
||||
while (true) {
|
||||
fa_input.setText(fa_generator.generate())
|
||||
|
||||
var seconds_remaining = CryptoOperations().getRemainingTOTPTime()
|
||||
Log.i("2FA Generator", "Remaining: $seconds_remaining")
|
||||
|
||||
if (seconds_remaining == 0) seconds_remaining = 30
|
||||
|
||||
// Change color
|
||||
if (seconds_remaining >= 15) {
|
||||
fa_progress.progressTintList = ColorStateList.valueOf(Color.GREEN)
|
||||
} else if (seconds_remaining < 15 && seconds_remaining >= 8) {
|
||||
fa_progress.progressTintList = ColorStateList.valueOf(Color.YELLOW)
|
||||
} else {
|
||||
fa_progress.progressTintList = ColorStateList.valueOf(Color.RED)
|
||||
}
|
||||
|
||||
fa_progress.progress = seconds_remaining
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
|
||||
fa_label.text = "${fa_uri.path?.replace("/", "")} (${fa_uri.getQueryParameter("issuer")})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
fa_coroutine?.cancel(CancellationException("Activity is stopping"))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import android.util.Log
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.KeySpec
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.SecretKeyFactory
|
||||
@@ -27,13 +30,25 @@ class CryptoOperations {
|
||||
private val possibleChars: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
private lateinit var chars: CharArray
|
||||
|
||||
fun getRemainingTOTPTime(): Int {
|
||||
/*
|
||||
* If the time (in seconds) is 13, then this function should return 17.
|
||||
* If the time ("") is 46, then this function should return 14
|
||||
*/
|
||||
val format: DateFormat = SimpleDateFormat("ss")
|
||||
val time_seconds = format.format(Calendar.getInstance().time).toInt()
|
||||
|
||||
if (time_seconds <= 30) return 30 - time_seconds
|
||||
else return 60 - time_seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password for storage.
|
||||
* Hashes a any input with
|
||||
*
|
||||
* @return a secure authentication token to be stored for later authentication
|
||||
*/
|
||||
fun hash(password: CharArray, salt: ByteArray): String {
|
||||
val spec: KeySpec = PBEKeySpec(password, salt, 85000, 8*31)
|
||||
val spec: KeySpec = PBEKeySpec(password, salt, 8000, 256)
|
||||
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val hash = factory.generateSecret(spec).encoded
|
||||
|
||||
@@ -41,6 +56,12 @@ class CryptoOperations {
|
||||
return Base64.encodeToString(hash, Base64.DEFAULT)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function encrypts a provided string `plain` with a provided key `key`.
|
||||
* Encryption function is AES-256-CBC
|
||||
*
|
||||
* @return Encrypted plain in Base64 encoded (without wrapping)
|
||||
*/
|
||||
fun encrypt(key: String, plain: String): EncryptionResult {
|
||||
val salt = nextString(10).toByteArray()
|
||||
val iv = nextString(16).toByteArray()
|
||||
@@ -56,10 +77,10 @@ class CryptoOperations {
|
||||
}
|
||||
|
||||
val key: SecretKey = SecretKeySpec(aes_key, "AES")
|
||||
val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||
val cipher: Cipher = Cipher.getInstance("AES/CBC/ISO10126Padding")
|
||||
|
||||
// Performing actual crypto operation
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, GCMParameterSpec(128, iv))
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
|
||||
Log.i("Crypto", "Encrypt using ${String(aes_key)} '$plain' ${String(iv)}")
|
||||
val ciphered = cipher.doFinal(plain.toByteArray(Charsets.UTF_8))
|
||||
|
||||
@@ -67,6 +88,14 @@ class CryptoOperations {
|
||||
return EncryptionResult(Base64.encodeToString(ciphered, Base64.NO_WRAP), salt, iv)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function decrypts a provided string `encrypted` with it's iv `iv`, key `key` and
|
||||
* salt `salt`.
|
||||
*
|
||||
* Required is that the cipher AES-256-CBC was used.
|
||||
*
|
||||
* @return Decrypted cipher text as plain String.
|
||||
*/
|
||||
fun decrypt(encrypted: String, key: String, iv: ByteArray, salt: ByteArray): String {
|
||||
val key_bytes: ByteArray = hash(key.toCharArray(), salt).toByteArray(Charsets.UTF_8)
|
||||
val aes_key: ByteArray = ByteArray(32)
|
||||
@@ -84,10 +113,10 @@ class CryptoOperations {
|
||||
Log.i("Crypto", "Decrypt using ${String(aes_key)} '${Base64.encodeToString(encrypted_raw, Base64.NO_WRAP)} (${encrypted_raw.size})' ${String(iv)}")
|
||||
|
||||
val keySpec: SecretKey = SecretKeySpec(aes_key, "AES")
|
||||
val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||
val cipher: Cipher = Cipher.getInstance("AES/CBC/ISO10126Padding")
|
||||
|
||||
// Performing actual crypto operation
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, GCMParameterSpec(128, iv))
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(iv))
|
||||
val ciphered = cipher.doFinal(encrypted_raw)
|
||||
|
||||
// Concat everything into one byte array
|
||||
@@ -98,6 +127,11 @@ class CryptoOperations {
|
||||
return String(byteBuffer.array())
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random IV used for encryption.
|
||||
*
|
||||
* @return Random IV of a length of 12 bytes
|
||||
*/
|
||||
fun nextIV(): ByteArray {
|
||||
val iv: ByteArray = ByteArray(12)
|
||||
val random: SecureRandom = SecureRandom()
|
||||
@@ -106,6 +140,9 @@ class CryptoOperations {
|
||||
return iv
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces secure random string. <b>This function can be slow, use only if necessary!</b>
|
||||
*/
|
||||
fun nextString(length: Int): String {
|
||||
chars = CharArray(length)
|
||||
for (idx in chars.indices) chars[idx] = possibleChars[random.nextInt(possibleChars.length)]
|
||||
@@ -115,16 +152,3 @@ class CryptoOperations {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CryptoEncryptionRunnable(
|
||||
private val key: String,
|
||||
private val data: String,
|
||||
private val iv: ByteArray
|
||||
) : HandlerThread("Enc_Crypto") {
|
||||
|
||||
override fun run() {
|
||||
Looper.prepare()
|
||||
val crypto = CryptoOperations()
|
||||
crypto.hash(key.toCharArray(), iv)
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,6 @@ class MainActivity : AppCompatActivity() {
|
||||
.setBeepEnabled(false)
|
||||
.setBarcodeImageEnabled(false)
|
||||
.initiateScan()
|
||||
Log.i("Main", "Clicked on text")
|
||||
}
|
||||
|
||||
add_button.setOnClickListener {
|
||||
|
||||
@@ -32,7 +32,9 @@ class QRSchema {
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes a key `db_key` and tries to resolve it.
|
||||
* This function takes a key `key` and tries to resolve it.
|
||||
*
|
||||
* @return The decrypted value associated with the key.
|
||||
*/
|
||||
private suspend fun resolve(key: String, context: Context): String {
|
||||
val to_resolve = key.replace("$", "")
|
||||
@@ -53,6 +55,7 @@ class QRSchema {
|
||||
* This function will take the already set raw content and tries to parse it.
|
||||
*
|
||||
* @return It will return `true` if the operation was successful and `false` if otherwise.
|
||||
* Parsed objects can be read from this class instance.
|
||||
*/
|
||||
fun parse(context: Context): Boolean {
|
||||
if (decrypted_raw == "") {
|
||||
@@ -61,7 +64,7 @@ class QRSchema {
|
||||
}
|
||||
|
||||
// First will be to split the string into it's parts
|
||||
var fields: MutableList<String> = this.decrypted_raw.split("|").toMutableList()
|
||||
val fields: MutableList<String> = this.decrypted_raw.split("|").toMutableList()
|
||||
|
||||
for (i in 0 until fields.size) {
|
||||
// First four items have to exist and are title, username, password, URL in this order.
|
||||
@@ -83,7 +86,7 @@ class QRSchema {
|
||||
|
||||
// Look if website url is compressed
|
||||
if (this.website_url.startsWith("$")) {
|
||||
var that_class = this
|
||||
val that_class = this
|
||||
runBlocking {
|
||||
that_class.website_url = resolve(that_class.website_url, context)
|
||||
}
|
||||
@@ -111,7 +114,7 @@ class QRSchema {
|
||||
|
||||
// We got a security question/awnser
|
||||
if (key == "") {
|
||||
var qa = value.split("%")
|
||||
val qa = value.split("%")
|
||||
question_awnser.put(qa[0], qa[1]);
|
||||
} else {
|
||||
custom.put(key, value)
|
||||
@@ -131,7 +134,7 @@ class QRSchema {
|
||||
/**
|
||||
* This function will take an raw string input and will decrypt it.
|
||||
*
|
||||
* @return It returns the raw decrypted raw value.
|
||||
* @return It returns the decrypted raw value.
|
||||
*/
|
||||
fun decrypt(raw: String, passphrase: String): String {
|
||||
val parts = raw.split(":")
|
||||
|
||||
@@ -44,6 +44,7 @@ class TextInput : Fragment() {
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
head.text = arg_head
|
||||
title_.hint = arg_title
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
BIN
app/src/main/res/drawable/dummy_qr_code.png
Normal file
BIN
app/src/main/res/drawable/dummy_qr_code.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
BIN
app/src/main/res/font/robotobold.ttf
Normal file
BIN
app/src/main/res/font/robotobold.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/robotoregular.ttf
Normal file
BIN
app/src/main/res/font/robotoregular.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/robotothin.ttf
Normal file
BIN
app/src/main/res/font/robotothin.ttf
Normal file
Binary file not shown.
@@ -5,6 +5,7 @@
|
||||
style="@style/AppTheme"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorPrimary"
|
||||
tools:context=".CreateActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
@@ -48,50 +49,255 @@
|
||||
<ImageButton
|
||||
android:id="@+id/settings"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginEnd="60dp"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="31dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="72dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:scaleX="1"
|
||||
android:scaleY="1"
|
||||
android:src="@drawable/baseline_settings_white_36"
|
||||
android:text=""
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/settings2"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="54dp"
|
||||
android:layout_height="37dp"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="116dp"
|
||||
android:layout_marginBottom="3dp"
|
||||
android:src="@drawable/ic_compress"
|
||||
android:text=""
|
||||
android:textColor="#000000"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/title_box"
|
||||
android:name="com.github.mondei1.offpass.TextInput"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="32dp"
|
||||
<ScrollView
|
||||
android:layout_width="416dp"
|
||||
android:layout_height="566dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/username"
|
||||
android:name="com.github.mondei1.offpass.TextInput"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/url_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="Login URL"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title_box" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/email_input" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/email_input"
|
||||
style="@style/Widget.AppCompat.AutoCompleteTextView"
|
||||
android:layout_width="354dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:ems="10"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:hint="ricklactica42@example.com"
|
||||
android:inputType="none|textEmailAddress"
|
||||
android:singleLine="true"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/email_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="Username"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title_input" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password_input"
|
||||
android:layout_width="288dp"
|
||||
android:layout_height="68dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:ems="10"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:hint="You shouldn't see that"
|
||||
android:inputType="textPassword"
|
||||
android:singleLine="true"
|
||||
android:textSize="28sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/password_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username_input"
|
||||
style="@style/Widget.AppCompat.AutoCompleteTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:ems="10"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:hint="Ricklactica42"
|
||||
android:inputType="textPersonName"
|
||||
android:singleLine="true"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/username_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/password_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="Password"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/username_input" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/url_input"
|
||||
style="@style/Widget.AppCompat.AutoCompleteTextView"
|
||||
android:layout_width="354dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:ems="10"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:hint="https://example.com/login"
|
||||
android:inputType="none|textEmailAddress"
|
||||
android:singleLine="true"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/url_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/title_input"
|
||||
style="@style/Widget.AppCompat.AutoCompleteTextView"
|
||||
android:layout_width="283dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:ems="10"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:hint="Google Account"
|
||||
android:inputType="textPersonName"
|
||||
android:singleLine="true"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/email_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="E-Mail"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/password_input" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="Title"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/password_hide"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@color/colorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/password_input"
|
||||
app:layout_constraintStart_toEndOf="@+id/password_input"
|
||||
app:layout_constraintTop_toTopOf="@+id/password_input"
|
||||
app:layout_constraintVertical_bias="0.48000002"
|
||||
app:srcCompat="@drawable/baseline_visibility_black_36" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/password_random"
|
||||
android:layout_width="31dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:tint="#000000"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/password_input"
|
||||
app:layout_constraintStart_toEndOf="@+id/password_hide"
|
||||
app:layout_constraintTop_toTopOf="@+id/password_input"
|
||||
app:srcCompat="@drawable/baseline_sd_storage_24" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="110dp"
|
||||
android:background="@color/colorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fa_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Two factor authentication"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/fa_input"
|
||||
style="@style/Widget.AppCompat.AutoCompleteTextView"
|
||||
android:layout_width="354dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:editable="false"
|
||||
android:ems="10"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:hint="TAP TO SCAN"
|
||||
android:inputType="none|textEmailAddress"
|
||||
android:singleLine="true"
|
||||
android:textSize="24sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/fa_label" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/fa_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="379dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:max="30"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/fa_input" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -17,7 +17,7 @@
|
||||
android:textSize="20sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/title_box"
|
||||
android:id="@+id/title_"
|
||||
style="@style/Widget.AppCompat.EditText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
79
app/src/main/res/print.html
Normal file
79
app/src/main/res/print.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Head is not important since
|
||||
we will only print the body-->
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(font/robotolight.ttf);
|
||||
font-weight: lighter;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(font/robotoregular.ttf);
|
||||
font-weight: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
src: url(font/robotobold.ttf);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
* {
|
||||
text-align: center;
|
||||
font-family: Roboto;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 8rem;
|
||||
font-size: 36pt;
|
||||
}
|
||||
p {
|
||||
font-size: 24pt;
|
||||
}
|
||||
#hint_title {
|
||||
margin-top: 10rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
img {
|
||||
margin-top: 5rem;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/* Background stripes */
|
||||
#red {
|
||||
position: absolute;
|
||||
bottom: -350px;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 340px;
|
||||
height: 815px;
|
||||
background-color: #FF2A2A;
|
||||
transform: rotate(130deg);
|
||||
}
|
||||
#black {
|
||||
position: absolute;
|
||||
bottom: -320px;
|
||||
right: 0;
|
||||
z-index: -1;
|
||||
width: 380px;
|
||||
height: 815px;
|
||||
background-color: #1C1C1C;
|
||||
transform: rotate(-130deg);
|
||||
}
|
||||
</style>
|
||||
<div id="content">
|
||||
<h1>OffPass</h1>
|
||||
<p>Created on $DATE</p>
|
||||
|
||||
<p id="hint_title">Password hint</p>
|
||||
<p>$HINT</p>
|
||||
|
||||
<img src="drawable/dummy_qr_code.png">
|
||||
<div id="red"></div>
|
||||
<div id="black"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user