shows qr code with app, adds black design

- qr code is generated from text code
- password is shown as well, when clicking
  on the 'view' icon
- hides top view
- removes floating button
- display status is now in an own class
This commit is contained in:
Raphael Maenle 2022-01-06 17:49:26 +01:00
parent e1f3e468cf
commit a72cc71cd9
12 changed files with 250 additions and 89 deletions

View File

@ -54,6 +54,7 @@ dependencies {
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'androidmads.library.qrgenearator:QRGenearator:1.0.3'
}
apply plugin: 'com.google.gms.google-services' // Google Play services Gradle plugin

View File

@ -3,9 +3,7 @@ package com.maenle.bump.ui
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.View
@ -24,21 +22,18 @@ import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.common.InputImage
import java.util.concurrent.Executors
import kotlin.IllegalStateException
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import android.view.WindowInsets
import android.graphics.Insets
import androidx.navigation.fragment.findNavController
import com.maenle.bump.R
import com.maenle.bump.databinding.FragmentCameraBinding
import com.maenle.bump.util.BumpProcessor
import com.maenle.bump.util.CameraXViewModel
import com.maenle.bump.util.Display
class CameraFragment: Fragment() {
private lateinit var display: Display
private var previewView: PreviewView? = null
private var cameraProvider: ProcessCameraProvider? = null
private var cameraSelector: CameraSelector? = null
@ -53,17 +48,7 @@ class CameraFragment: Fragment() {
private val screenAspectRatio: Int
get() {
val activity = requireActivity()
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = activity.windowManager.currentWindowMetrics
val insets: Insets = windowMetrics.windowInsets
.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
windowMetrics.bounds.width() - insets.left - insets.right
} else {
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
return aspectRatio(displayMetrics.widthPixels, displayMetrics.heightPixels)
}
return display.aspectRatio
}
override fun onCreateView(
@ -72,6 +57,7 @@ class CameraFragment: Fragment() {
): View {
_binding = FragmentCameraBinding.inflate(inflater, container, false)
display = Display(requireActivity())
setupCamera()
return binding.root
}
@ -212,25 +198,6 @@ class CameraFragment: Fragment() {
}
}
/**
* [androidx.camera.core.ImageAnalysis], [androidx.camera.core.Preview] requires enum value of
* [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
*
* Detecting the most suitable ratio for dimensions provided in @params by counting absolute
* of preview ratio to one of the provided values.
*
* @param width - preview width
* @param height - preview height
* @return suitable aspect ratio
*/
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = max(width, height).toDouble() / min(width, height)
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,

View File

@ -2,23 +2,25 @@ package com.maenle.bump.ui
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.navigation.fragment.findNavController
import com.google.firebase.messaging.FirebaseMessaging
import com.maenle.bump.util.BumpProcessor
import com.maenle.bump.util.RestSingleton
import com.maenle.bump.util.sendNotification
import com.maenle.bump.R
import com.maenle.bump.databinding.FragmentFirstBinding
import com.maenle.bump.util.LocalData
import org.json.JSONArray
import com.maenle.bump.util.*
import com.google.zxing.WriterException
import android.graphics.Bitmap
import android.util.Log
import android.view.*
import androidmads.library.qrgenearator.QRGContents
import androidmads.library.qrgenearator.QRGEncoder
import com.maenle.bump.util.Display
/**
* A simple [Fragment] subclass as the default destination in the navigation.
@ -31,9 +33,8 @@ class FirstFragment : Fragment() {
// onDestroyView.
private val binding get() = _binding!!
private lateinit var rest: RestSingleton
private lateinit var log: JSONArray
private lateinit var local: LocalData
private var message: MessageProcessor? = null
private lateinit var bump: BumpProcessor
override fun onCreateView(
@ -41,6 +42,8 @@ class FirstFragment : Fragment() {
savedInstanceState: Bundle?
): View {
local = LocalData(requireContext())
message = local.code?.let { MessageProcessor(it) }
_binding = FragmentFirstBinding.inflate(inflater, container, false)
bump = BumpProcessor(requireContext())
@ -65,11 +68,25 @@ class FirstFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonFirst.setOnClickListener {
binding.buttonScan.setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_CameraFragment)
}
val local = LocalData(requireContext())
local.code?.let { binding.textviewFirst.text = it}
binding.buttonView.setOnClickListener {
if(binding.textviewEncryption.visibility == View.GONE){
binding.textviewEncryption.visibility = View.VISIBLE
binding.idIVQrcode.visibility = View.VISIBLE
binding.buttonView.text = getString(R.string.hide_sender)
message?.let { binding.textviewScan.text = it.sender.replace("-", "-\u200b")+"-"}
} else {
binding.textviewEncryption.visibility = View.GONE
binding.idIVQrcode.visibility = View.GONE
binding.buttonView.text = getString(R.string.view_sender)
message?.let { binding.textviewScan.text = it.sender.replace("-", "-\u200b")}
}
}
message?.let { binding.textviewScan.text = it.sender.replace("-", "-\u200b")}
message?.let { binding.textviewEncryption.text = it.password.replace("-", "-\u200b")}
message?.let {binding.idIVQrcode.setImageBitmap(generateQrCode(it.code))}
}
private fun createChannel(channelId: String, channelName: String) {
@ -93,22 +110,65 @@ class FirstFragment : Fragment() {
notificationManager.createNotificationChannel(notificationChannel)
}
private fun generateQrCode(code: String): Bitmap? {
val nightModeFlags = requireContext().resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK
val display = Display(requireActivity())
// generating dimension from width and height.
var dimen = if (display.width < display.height) display.width else display.height
dimen = dimen * 3 / 4
val qrgEncoder = QRGEncoder(code, null, QRGContents.Type.TEXT, dimen)
var bitmap = Bitmap.createBitmap(display.width, display.height, Bitmap.Config.ARGB_8888)
try {
bitmap = qrgEncoder.encodeAsBitmap()
} catch (e: WriterException) {
Log.d("Tag", e.toString())
}
if(nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
bitmap = ChangeColor(bitmap)
}
return bitmap
}
private fun ChangeColor(scrBitmap: Bitmap): Bitmap {
// make an empty bitmap the same size as scrBitmap
val newBitmap = Bitmap.createBitmap(scrBitmap.width, scrBitmap.height, Bitmap.Config.ARGB_8888)
for (i in 0 until newBitmap.width) {
for (j in 0 until newBitmap.height) {
// get the pixel from the scrBitmap image
val pixel = scrBitmap.getPixel(i, j)
if(pixel == Color.WHITE) {
newBitmap.setPixel(i, j, Color.BLACK)
} else {
newBitmap.setPixel(i, j, Color.WHITE)
}
}
}
return newBitmap
}
private fun subscribeTopic() {
// [START subscribe_topic]
FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
.addOnCompleteListener { task ->
var message = getString(R.string.message_subscribed)
var curmessage = getString(R.string.message_subscribed)
if (!task.isSuccessful) {
message = getString(R.string.message_subscribe_failed)
curmessage = getString(R.string.message_subscribe_failed)
}
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
Log.d(TAG, curmessage.toString())
}
// [END subscribe_topics]
}
companion object {
private val TAG = MainActivity::class.java.simpleName
private const val TOPIC = "Bump"
fun newInstance() = FirstFragment()
private val TOPIC = "Bump"
}

View File

@ -29,10 +29,6 @@ class MainActivity : AppCompatActivity() {
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
binding.fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -110,7 +110,7 @@ class BumpProcessor constructor(context: Context) {
val tokenTask = FirebaseMessaging.getInstance().token
val secret = LocalData(context).code
val messenger = secret?.let { MessageProcessor(it) }
tokenTask.addOnSuccessListener { token -> ( messenger?.sender?.let { rest.firebase(it, token) { result -> Log.d("result", result.toString())} } ) }
tokenTask.addOnSuccessListener { token -> ( messenger?.let { rest.firebase(messenger.sender, token) { result -> Log.d("result", result.toString())} } ) }
}
}

View File

@ -1,4 +1,13 @@
package com.maenle.bump.util
class CodeProcessor {
abstract class Code() {
abstract var sender: String
abstract var password: String
init {
}
private fun codeValid(code: String): Boolean {
return code.split("-").size >= MessageProcessor.KEY_LENGTH + MessageProcessor.SENDER_LENGTH
}
}

View File

@ -0,0 +1,80 @@
package com.maenle.bump.util
import android.app.Activity
import android.graphics.Insets
import android.graphics.Point
import android.os.Build
import android.util.DisplayMetrics
import android.view.WindowInsets
import androidx.camera.core.AspectRatio
import com.maenle.bump.ui.CameraFragment
import com.maenle.bump.ui.MainActivity
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class Display(activity: Activity) {
private var pwidth: Int = 0
private var pheight: Int = 0
private var pAspectRatio: Int = 0
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = activity.windowManager.currentWindowMetrics
val insets = windowMetrics.windowInsets
.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
pwidth = windowMetrics.bounds.width()
pheight = windowMetrics.bounds.height()
pAspectRatio = width - insets.left - insets.right
} else {
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
pwidth = displayMetrics.widthPixels
pheight = displayMetrics.heightPixels
pAspectRatio = aspectRatio(displayMetrics.widthPixels, displayMetrics.heightPixels)
}
}
val width: Int
get() {
return pwidth
}
val height: Int
get() {
return pheight
}
val aspectRatio: Int
get() {
return pAspectRatio
}
/**
* [androidx.camera.core.ImageAnalysis], [androidx.camera.core.Preview] requires enum value of
* [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
*
* Detecting the most suitable ratio for dimensions provided in @params by counting absolute
* of preview ratio to one of the provided values.
*
* @param width - preview width
* @param height - preview height
* @return suitable aspect ratio
*/
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = max(width, height).toDouble() / min(width, height)
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
companion object {
private val TAG = MainActivity::class.java.simpleName
private const val PERMISSION_CAMERA_REQUEST = 1
private const val RATIO_4_3_VALUE = 4.0 / 3.0
private const val RATIO_16_9_VALUE = 16.0 / 9.0
}
}

View File

@ -14,17 +14,32 @@ import java.time.Duration
import java.time.temporal.TemporalAmount
class MessageProcessor(code: String, private val salt: ByteArray? = null) {
var sender: String
private var password: String
private var pSender: String
private var pPassword: String
init {
codeValid(code).let {
val codeSplit: List<String> = code.split("-")
sender = codeSplit.subList(0, SENDER_LENGTH).joinToString("-")
password = codeSplit.subList(SENDER_LENGTH, codeSplit.size).joinToString("-")
pSender = codeSplit.subList(0, SENDER_LENGTH).joinToString("-")
pPassword = codeSplit.subList(SENDER_LENGTH, codeSplit.size).joinToString("-")
}
}
val password: String
get() {
return pPassword
}
val sender: String
get() {
return pSender
}
val code: String
get() {
return "$pSender-$pPassword"
}
private fun codeValid(code: String): Boolean {
return code.split("-").size >= KEY_LENGTH + SENDER_LENGTH
}
@ -32,12 +47,12 @@ class MessageProcessor(code: String, private val salt: ByteArray? = null) {
fun encrypt(decrypted: String): String {
val message = MessageEncrypt(decrypted)
salt?.let { message.updateSalt(salt) }
return message.encryptWith(password)
return message.encryptWith(pPassword)
}
fun decrypt(messageRaw: String): String {
val message = MessageDecrypt(messageRaw)
return message.validateAndDecryptWith(password)
return message.validateAndDecryptWith(pPassword)
}

View File

@ -22,13 +22,4 @@
<include layout="@layout/content_main" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -7,22 +7,61 @@
tools:context="com.maenle.bump.ui.FirstFragment">
<TextView
android:id="@+id/textview_first"
android:id="@+id/textview_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/current_sender"
app:layout_constraintBottom_toTopOf="@id/button_first"
android:layout_marginTop="40dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_first"
<TextView
android:id="@+id/textview_encryption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/scan_sender"
app:layout_constraintBottom_toBottomOf="parent"
android:text="@string/current_sender"
android:gravity="center"
android:breakStrategy="balanced"
android:maxEms="16"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textview_first" />
app:layout_constraintTop_toBottomOf="@id/textview_scan"/>
<ImageView
android:id="@+id/idIVQrcode"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_centerHorizontal="true"
android:contentDescription="@string/qr_code"
android:layout_marginTop="20dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/textview_encryption"
app:layout_constraintStart_toStartOf="parent"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/idIVQrcode"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/button_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:text="@string/scan_sender" />
<Button
android:id="@+id/button_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/view_sender" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,7 +8,7 @@
<fragment
android:id="@+id/FirstFragment"
android:name="com.maenle.bump.ui.FirstFragment"
android:label="@string/first_fragment_label"
android:label="@string/main_fragment_label"
tools:layout="@layout/fragment_first">
<action

View File

@ -2,7 +2,7 @@
<string name="app_name">Bump</string>
<string name="action_settings">Settings</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="main_fragment_label">Bump</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
@ -11,7 +11,7 @@
<string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
<string name="current_sender">No Sender Added</string>
<string name="scan_sender">Scan</string>
<string name="camera_fragment_label">Camera Fragment</string>
<string name="camera_fragment_label">Scan new Fragment</string>
<string name="preference_file_key">code_file_key</string>
<string name="code_key">code_key</string>
<string name="notification_title">Bump</string>
@ -21,4 +21,7 @@
<string name="notification">This is a notification</string>
<string name="message_subscribed">Subscribed to Channel</string>
<string name="message_subscribe_failed">Could not subscribe</string>
<string name="view_sender">View</string>
<string name="qr_code">none</string>
<string name="hide_sender">Hide</string>
</resources>