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.+' testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 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 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.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -24,21 +22,18 @@ import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.common.InputImage import com.google.mlkit.vision.common.InputImage
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.IllegalStateException 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 androidx.navigation.fragment.findNavController
import com.maenle.bump.R
import com.maenle.bump.databinding.FragmentCameraBinding import com.maenle.bump.databinding.FragmentCameraBinding
import com.maenle.bump.util.BumpProcessor import com.maenle.bump.util.BumpProcessor
import com.maenle.bump.util.CameraXViewModel import com.maenle.bump.util.CameraXViewModel
import com.maenle.bump.util.Display
class CameraFragment: Fragment() { class CameraFragment: Fragment() {
private lateinit var display: Display
private var previewView: PreviewView? = null private var previewView: PreviewView? = null
private var cameraProvider: ProcessCameraProvider? = null private var cameraProvider: ProcessCameraProvider? = null
private var cameraSelector: CameraSelector? = null private var cameraSelector: CameraSelector? = null
@ -53,17 +48,7 @@ class CameraFragment: Fragment() {
private val screenAspectRatio: Int private val screenAspectRatio: Int
get() { get() {
val activity = requireActivity() return display.aspectRatio
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)
}
} }
override fun onCreateView( override fun onCreateView(
@ -72,6 +57,7 @@ class CameraFragment: Fragment() {
): View { ): View {
_binding = FragmentCameraBinding.inflate(inflater, container, false) _binding = FragmentCameraBinding.inflate(inflater, container, false)
display = Display(requireActivity())
setupCamera() setupCamera()
return binding.root 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( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
permissions: Array<String>, permissions: Array<String>,

View File

@ -2,23 +2,25 @@ package com.maenle.bump.ui
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment 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 androidx.navigation.fragment.findNavController
import com.google.firebase.messaging.FirebaseMessaging 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.R
import com.maenle.bump.databinding.FragmentFirstBinding import com.maenle.bump.databinding.FragmentFirstBinding
import com.maenle.bump.util.LocalData import com.maenle.bump.util.*
import org.json.JSONArray 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. * A simple [Fragment] subclass as the default destination in the navigation.
@ -31,9 +33,8 @@ class FirstFragment : Fragment() {
// onDestroyView. // onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var rest: RestSingleton private lateinit var local: LocalData
private lateinit var log: JSONArray private var message: MessageProcessor? = null
private lateinit var bump: BumpProcessor private lateinit var bump: BumpProcessor
override fun onCreateView( override fun onCreateView(
@ -41,6 +42,8 @@ class FirstFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
local = LocalData(requireContext())
message = local.code?.let { MessageProcessor(it) }
_binding = FragmentFirstBinding.inflate(inflater, container, false) _binding = FragmentFirstBinding.inflate(inflater, container, false)
bump = BumpProcessor(requireContext()) bump = BumpProcessor(requireContext())
@ -65,11 +68,25 @@ class FirstFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.buttonFirst.setOnClickListener { binding.buttonScan.setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_CameraFragment) findNavController().navigate(R.id.action_FirstFragment_to_CameraFragment)
} }
val local = LocalData(requireContext()) binding.buttonView.setOnClickListener {
local.code?.let { binding.textviewFirst.text = it} 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) { private fun createChannel(channelId: String, channelName: String) {
@ -93,22 +110,65 @@ class FirstFragment : Fragment() {
notificationManager.createNotificationChannel(notificationChannel) 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() { private fun subscribeTopic() {
// [START subscribe_topic] // [START subscribe_topic]
FirebaseMessaging.getInstance().subscribeToTopic(TOPIC) FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
.addOnCompleteListener { task -> .addOnCompleteListener { task ->
var message = getString(R.string.message_subscribed) var curmessage = getString(R.string.message_subscribed)
if (!task.isSuccessful) { 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] // [END subscribe_topics]
} }
companion object { companion object {
private val TAG = MainActivity::class.java.simpleName
private const val TOPIC = "Bump"
fun newInstance() = FirstFragment() fun newInstance() = FirstFragment()
private val TOPIC = "Bump"
} }

View File

@ -29,10 +29,6 @@ class MainActivity : AppCompatActivity() {
appBarConfiguration = AppBarConfiguration(navController.graph) appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration) 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 { override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -110,7 +110,7 @@ class BumpProcessor constructor(context: Context) {
val tokenTask = FirebaseMessaging.getInstance().token val tokenTask = FirebaseMessaging.getInstance().token
val secret = LocalData(context).code val secret = LocalData(context).code
val messenger = secret?.let { MessageProcessor(it) } 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 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 import java.time.temporal.TemporalAmount
class MessageProcessor(code: String, private val salt: ByteArray? = null) { class MessageProcessor(code: String, private val salt: ByteArray? = null) {
var sender: String private var pSender: String
private var password: String private var pPassword: String
init { init {
codeValid(code).let { codeValid(code).let {
val codeSplit: List<String> = code.split("-") val codeSplit: List<String> = code.split("-")
sender = codeSplit.subList(0, SENDER_LENGTH).joinToString("-") pSender = codeSplit.subList(0, SENDER_LENGTH).joinToString("-")
password = codeSplit.subList(SENDER_LENGTH, codeSplit.size).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 { private fun codeValid(code: String): Boolean {
return code.split("-").size >= KEY_LENGTH + SENDER_LENGTH 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 { fun encrypt(decrypted: String): String {
val message = MessageEncrypt(decrypted) val message = MessageEncrypt(decrypted)
salt?.let { message.updateSalt(salt) } salt?.let { message.updateSalt(salt) }
return message.encryptWith(password) return message.encryptWith(pPassword)
} }
fun decrypt(messageRaw: String): String { fun decrypt(messageRaw: String): String {
val message = MessageDecrypt(messageRaw) val message = MessageDecrypt(messageRaw)
return message.validateAndDecryptWith(password) return message.validateAndDecryptWith(pPassword)
} }

View File

@ -22,13 +22,4 @@
<include layout="@layout/content_main" /> <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> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -7,22 +7,61 @@
tools:context="com.maenle.bump.ui.FirstFragment"> tools:context="com.maenle.bump.ui.FirstFragment">
<TextView <TextView
android:id="@+id/textview_first" android:id="@+id/textview_scan"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/current_sender" android:text="@string/current_sender"
app:layout_constraintBottom_toTopOf="@id/button_first" android:layout_marginTop="40dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<Button <TextView
android:id="@+id/button_first" android:id="@+id/textview_encryption"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/scan_sender" android:text="@string/current_sender"
app:layout_constraintBottom_toBottomOf="parent" android:gravity="center"
android:breakStrategy="balanced"
android:maxEms="16"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,7 +8,7 @@
<fragment <fragment
android:id="@+id/FirstFragment" android:id="@+id/FirstFragment"
android:name="com.maenle.bump.ui.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"> tools:layout="@layout/fragment_first">
<action <action

View File

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