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:
parent
e1f3e468cf
commit
a72cc71cd9
@ -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
|
||||
|
@ -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>,
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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())} } ) }
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
80
app/src/main/java/com/maenle/bump/util/Display.kt
Normal file
80
app/src/main/java/com/maenle/bump/util/Display.kt
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -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
|
||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user