diff --git a/app/build.gradle b/app/build.gradle index 65faab4..34c6693 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/java/com/maenle/bump/ui/CameraFragment.kt b/app/src/main/java/com/maenle/bump/ui/CameraFragment.kt index a1b0dd3..abdf6c0 100644 --- a/app/src/main/java/com/maenle/bump/ui/CameraFragment.kt +++ b/app/src/main/java/com/maenle/bump/ui/CameraFragment.kt @@ -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, diff --git a/app/src/main/java/com/maenle/bump/ui/FirstFragment.kt b/app/src/main/java/com/maenle/bump/ui/FirstFragment.kt index 0f491c4..8b5504b 100644 --- a/app/src/main/java/com/maenle/bump/ui/FirstFragment.kt +++ b/app/src/main/java/com/maenle/bump/ui/FirstFragment.kt @@ -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" } diff --git a/app/src/main/java/com/maenle/bump/ui/MainActivity.kt b/app/src/main/java/com/maenle/bump/ui/MainActivity.kt index 2442ccc..7130455 100644 --- a/app/src/main/java/com/maenle/bump/ui/MainActivity.kt +++ b/app/src/main/java/com/maenle/bump/ui/MainActivity.kt @@ -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 { diff --git a/app/src/main/java/com/maenle/bump/util/BumpProcessor.kt b/app/src/main/java/com/maenle/bump/util/BumpProcessor.kt index 178a3c8..8320e96 100644 --- a/app/src/main/java/com/maenle/bump/util/BumpProcessor.kt +++ b/app/src/main/java/com/maenle/bump/util/BumpProcessor.kt @@ -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())} } ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/maenle/bump/util/CodeProcessor.kt b/app/src/main/java/com/maenle/bump/util/CodeProcessor.kt index 96f4764..cbddd73 100644 --- a/app/src/main/java/com/maenle/bump/util/CodeProcessor.kt +++ b/app/src/main/java/com/maenle/bump/util/CodeProcessor.kt @@ -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 + } } \ No newline at end of file diff --git a/app/src/main/java/com/maenle/bump/util/Display.kt b/app/src/main/java/com/maenle/bump/util/Display.kt new file mode 100644 index 0000000..753a293 --- /dev/null +++ b/app/src/main/java/com/maenle/bump/util/Display.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/maenle/bump/util/MessageProcessor.kt b/app/src/main/java/com/maenle/bump/util/MessageProcessor.kt index 5fb8fc8..5e5e2d7 100644 --- a/app/src/main/java/com/maenle/bump/util/MessageProcessor.kt +++ b/app/src/main/java/com/maenle/bump/util/MessageProcessor.kt @@ -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 = 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) } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 133a703..1a7ea48 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -22,13 +22,4 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_first.xml b/app/src/main/res/layout/fragment_first.xml index 0311097..4585ef5 100644 --- a/app/src/main/res/layout/fragment_first.xml +++ b/app/src/main/res/layout/fragment_first.xml @@ -7,22 +7,61 @@ tools:context="com.maenle.bump.ui.FirstFragment"> -