10 Commits
decrypt ... dev

Author SHA1 Message Date
d102004e6b adds help page 2022-03-11 13:59:52 +01:00
d5ceace59f async log updates added 2022-03-11 12:27:57 +01:00
625d5496e3 adds animation, updates card design 2022-03-11 09:50:39 +01:00
67fe7381aa list now scrollable, reformats list handling
- urls are now auto-generated from android
  and no longer self-handled
- long-clicking a message starts decryption
  process, short clicking does nothing right
  now
- only first three messages are decrypted
  on app load, to improve performance
- fragment structure now inside a list view
  which makes the listview inside scrollable
  automatically
2022-03-05 15:36:31 +01:00
22b2ac96b9 log of past bumps updated
- clicking on log entry shows
  the encrypted message behind
  the title
- anything starting with 'http'
  opens in the browser
2022-03-05 14:26:42 +01:00
0e307c947a adds clear log button to menu
- clear log now static function, with
  the log being updated from file
  every time the update function is called
- menu actions always act from main activity
  so the menu isn't able to use the existing
  bump instance
2022-02-06 23:50:38 +01:00
1f5fc5288b adds log update function and log list
- log updates in background automatically
  from server data
- log list in main screen of app is updated
  showing the title of each notification
2022-02-06 23:24:44 +01:00
6df001ed4c makes app production ready 2022-01-11 14:06:20 +01:00
c213e51cdd removes three-dot menu 2022-01-06 18:33:36 +01:00
538974c6f6 new logo added, primary screen design update
- logo of app and notification updated to
  new orange design
- primary screen has prettier colors now
  and larger text for sender information
2022-01-06 18:24:55 +01:00
56 changed files with 434 additions and 327 deletions

1
.gitignore vendored
View File

@ -28,3 +28,4 @@ google-services.json
# Android Profiling # Android Profiling
*.hprof *.hprof
*.aab

1
app/.gitignore vendored
View File

@ -1 +1,2 @@
/build /build
/release

View File

@ -8,10 +8,10 @@ android {
defaultConfig { defaultConfig {
applicationId "com.maenle.bump" applicationId "com.maenle.bump"
minSdk 29 minSdk 26
targetSdk 30 targetSdk 30
versionCode 1 versionCode 4
versionName "1.0" versionName "0.1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.maenle.bump"> package="com.maenle.bump"
android:versionName="0.1_alpha">
<uses-feature android:name="android.hardware.camera.any" /> <uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
@ -22,13 +23,13 @@
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/> <action android:name="com.google.firebase.MESSAGING_EVENT"/>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
</intent-filter> </intent-filter>
</service> </service>
<meta-data <meta-data
android:name="com.google.firebase.messaging.default_notification_icon" android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_launcher_foreground"/> android:resource="@drawable/ic_stat_name"/>
<meta-data <meta-data
android:name="com.google.firebase.messaging.default_notification_color" android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/black"/> <!-- [END fcm_default_icon] --> android:resource="@color/black"/> <!-- [END fcm_default_icon] -->
@ -49,6 +50,10 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name=".ui.MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity> </activity>
</application> </application>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -3,12 +3,12 @@ package com.maenle.bump
import android.app.NotificationManager import android.app.NotificationManager
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.maenle.bump.util.sendNotification import com.maenle.bump.util.sendNotification
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import com.maenle.bump.util.BumpProcessor import com.maenle.bump.util.BumpProcessor
import com.maenle.bump.util.RestSingleton
class MyFirebaseMessagingService : FirebaseMessagingService() { class MyFirebaseMessagingService : FirebaseMessagingService() {
@ -17,27 +17,32 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
* *
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging. * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/ */
// [START receive_message]
override fun onMessageReceived(remoteMessage: RemoteMessage) { override fun onMessageReceived(remoteMessage: RemoteMessage) {
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
Log.d(TAG, "From: ${remoteMessage.from}") // Log.d(TAG, "From: ${remoteMessage.from}")
// TODO Step 3.5 check messages for data
// Check if message contains a data payload. // Check if message contains a data payload.
var body: String? = null;
var header: String? = null;
remoteMessage.data.let { remoteMessage.data.let {
val bump = BumpProcessor.getInstance(applicationContext)
Log.d(TAG, "Message data payload: " + remoteMessage.data) Log.d(TAG, "Message data payload: " + remoteMessage.data)
val a = remoteMessage.data.keys
val value = remoteMessage.data["data"]
value?.let {
body = bump.decryptMessage(it)
}
} }
// TODO Step 3.6 check messages for notification and call sendNotification
// Check if message contains a notification payload.
remoteMessage.notification?.let { remoteMessage.notification?.let {
Log.d(TAG, "Message Notification Body: ${it.body}") header = it.body
sendNotification(it.body!!)
} }
}
// [END receive_message]
//TODO Step 3.2 log registration token sendNotification(header, body)
}
// log registration token
// [START on_new_token] // [START on_new_token]
/** /**
* Called if InstanceID token is updated. This may occur if the security of * Called if InstanceID token is updated. This may occur if the security of
@ -45,12 +50,11 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
* is initially generated so this is where you would retrieve the token. * is initially generated so this is where you would retrieve the token.
*/ */
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
Log.d(TAG, "Refreshed token: $token")
// If you want to send messages to this application instance or // If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the // manage this apps subscriptions on the server side, send the
// Instance ID token to your app server. // Instance ID token to your app server.
sendRegistrationToServer(token) sendRegistrationToServer()
} }
// [END on_new_token] // [END on_new_token]
@ -60,17 +64,16 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
* *
* @param token The new token. * @param token The new token.
*/ */
private fun sendRegistrationToServer(token: String?) { private fun sendRegistrationToServer() {
val bump = BumpProcessor.getInstance(baseContext)
bump.setFirebaseToken(baseContext)
} }
/** private fun sendNotification(messageHeader: String?, messageBody: String?) {
* Create and show a simple notification containing the received FCM message.
*
* @param messageBody FCM message body received.
*/
private fun sendNotification(messageBody: String) {
val notificationManager = ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) as NotificationManager val notificationManager = ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) as NotificationManager
notificationManager.sendNotification(messageBody, applicationContext) notificationManager.sendNotification(messageHeader, messageBody, applicationContext)
} }
companion object { companion object {

View File

@ -110,9 +110,9 @@ class CameraFragment: Fragment() {
previewUseCase previewUseCase
) )
} catch (illegalStateException: IllegalStateException) { } catch (illegalStateException: IllegalStateException) {
Log.e(TAG, illegalStateException.message ?: "IllegalStateException") // Log.e(TAG, illegalStateException.message ?: "IllegalStateException")
} catch (illegalArgumentException: IllegalArgumentException) { } catch (illegalArgumentException: IllegalArgumentException) {
Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException") // Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException")
} }
} }
@ -153,9 +153,9 @@ class CameraFragment: Fragment() {
analysisUseCase analysisUseCase
) )
} catch (illegalStateException: IllegalStateException) { } catch (illegalStateException: IllegalStateException) {
Log.e(TAG, illegalStateException.message ?: "IllegalStateException") // Log.e(TAG, illegalStateException.message ?: "IllegalStateException")
} catch (illegalArgumentException: IllegalArgumentException) { } catch (illegalArgumentException: IllegalArgumentException) {
Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException") // Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException")
} }
} }
@ -189,7 +189,7 @@ class CameraFragment: Fragment() {
blockingScanned = false blockingScanned = false
} }
.addOnFailureListener { .addOnFailureListener {
Log.e(TAG, it.message ?: it.toString()) // Log.e(TAG, it.message ?: it.toString())
}.addOnCompleteListener { }.addOnCompleteListener {
// When the image is from CameraX analysis use case, must call image.close() on received // When the image is from CameraX analysis use case, must call image.close() on received
// images when finished using them. Otherwise, new images may not be received or the camera // images when finished using them. Otherwise, new images may not be received or the camera
@ -207,7 +207,7 @@ class CameraFragment: Fragment() {
if (isCameraPermissionGranted()) { if (isCameraPermissionGranted()) {
bindCameraUseCases() bindCameraUseCases()
} else { } else {
Log.e(TAG, "no camera permission") // Log.e(TAG, "no camera permission")
} }
} }
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)

View File

@ -2,29 +2,27 @@ package com.maenle.bump.ui
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.google.firebase.messaging.FirebaseMessaging
import com.maenle.bump.R
import com.maenle.bump.databinding.FragmentFirstBinding
import com.maenle.bump.util.*
import com.google.zxing.WriterException
import android.graphics.Bitmap import android.graphics.Bitmap
import android.util.Log import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.view.* import android.view.*
import androidmads.library.qrgenearator.QRGContents import androidmads.library.qrgenearator.QRGContents
import androidmads.library.qrgenearator.QRGEncoder import androidmads.library.qrgenearator.QRGEncoder
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.google.firebase.messaging.FirebaseMessaging
import com.google.zxing.WriterException
import com.maenle.bump.R
import com.maenle.bump.databinding.FragmentFirstBinding
import com.maenle.bump.util.*
import com.maenle.bump.util.Display import com.maenle.bump.util.Display
import org.json.JSONArray
import org.json.JSONObject
/**
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class FirstFragment : Fragment() { class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null private var _binding: FragmentFirstBinding? = null
@ -53,16 +51,10 @@ class FirstFragment : Fragment() {
getString(R.string.bump_notification_channel_name) getString(R.string.bump_notification_channel_name)
) )
/*
val notificationManager = ContextCompat.getSystemService(
requireContext(),
NotificationManager::class.java
) as NotificationManager
notificationManager.sendNotification(getString(R.string.notification), requireContext())
*/
subscribeTopic() subscribeTopic()
bump.logUpdateCallback = { log -> updateMessageList(log) }
updateMessageList(bump.log)
return binding.root return binding.root
} }
@ -76,7 +68,7 @@ class FirstFragment : Fragment() {
binding.textviewEncryption.visibility = View.VISIBLE binding.textviewEncryption.visibility = View.VISIBLE
binding.idIVQrcode.visibility = View.VISIBLE binding.idIVQrcode.visibility = View.VISIBLE
binding.buttonView.text = getString(R.string.hide_sender) binding.buttonView.text = getString(R.string.hide_sender)
message?.let { binding.textviewScan.text = it.sender.replace("-", "-\u200b")+"-"} message?.let { binding.textviewScan.text = it.sender.replace("-", "-\u200b")}
} else { } else {
binding.textviewEncryption.visibility = View.GONE binding.textviewEncryption.visibility = View.GONE
binding.idIVQrcode.visibility = View.GONE binding.idIVQrcode.visibility = View.GONE
@ -84,8 +76,9 @@ class FirstFragment : Fragment() {
message?.let { binding.textviewScan.text = it.sender.replace("-", "-\u200b")} message?.let { binding.textviewScan.text = it.sender.replace("-", "-\u200b")}
} }
} }
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.textviewEncryption.text = "-" + it.password.replace("-", "-\u200b")}
message?.let {binding.idIVQrcode.setImageBitmap(generateQrCode(it.code))} message?.let {binding.idIVQrcode.setImageBitmap(generateQrCode(it.code))}
} }
@ -125,7 +118,7 @@ class FirstFragment : Fragment() {
try { try {
bitmap = qrgEncoder.encodeAsBitmap() bitmap = qrgEncoder.encodeAsBitmap()
} catch (e: WriterException) { } catch (e: WriterException) {
Log.d("Tag", e.toString()) // Log.d("Tag", e.toString())
} }
if(nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { if(nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
@ -152,15 +145,49 @@ class FirstFragment : Fragment() {
return newBitmap return newBitmap
} }
private fun updateMessageList(log: JSONArray) {
val messages: MutableList<MessageItem> = mutableListOf()
val adapter = NotificationAdapter(requireContext(), messages)
binding.nrMessages.text = log.length().toString()
for (l in log.length() - 1 downTo 0) {
val m = log[l] as JSONObject
val msg = MessageItem(m.get("title") as String, message = "...")
messages.add(msg)
bump.asyncDecryptMessage(m.get("data") as String, messages.size-1) { data, location ->
messages[location].message = data
adapter.notifyDataSetChanged()
}
}
binding.notificationList.adapter = adapter
adapter.notifyDataSetChanged()
binding.notificationList.setOnItemClickListener { adapterView, view, i, l ->
run {
if (messages[i].header.startsWith("http")) {
val browserIntent =
Intent(Intent.ACTION_VIEW, Uri.parse(messages[i].header))
startActivity(browserIntent)
}
}
}
binding.notificationList.setOnItemLongClickListener { adapterView, view, i, l ->
messages[i].encrypted?.let {
messages[i].message = bump.decryptMessage(messages[i].encrypted!!)
}
adapter.notifyDataSetChanged()
true
}
}
private fun subscribeTopic() { private fun subscribeTopic() {
// [START subscribe_topic]
FirebaseMessaging.getInstance().subscribeToTopic(TOPIC) FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
.addOnCompleteListener { task -> .addOnCompleteListener { task ->
var curmessage = getString(R.string.message_subscribed) var curmessage = getString(R.string.message_subscribed)
if (!task.isSuccessful) { if (!task.isSuccessful) {
curmessage = getString(R.string.message_subscribe_failed) curmessage = getString(R.string.message_subscribe_failed)
} }
Log.d(TAG, curmessage.toString()) // Log.d(TAG, curmessage.toString())
} }
// [END subscribe_topics] // [END subscribe_topics]
} }
@ -177,3 +204,7 @@ class FirstFragment : Fragment() {
_binding = null _binding = null
} }
} }
class MessageItem(public val title: String, var message: String = "...", var encrypted: String? = null) {
public var header: String = title
}

View File

@ -1,16 +1,23 @@
package com.maenle.bump.ui package com.maenle.bump.ui
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import com.google.android.material.snackbar.Snackbar import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import com.maenle.bump.R import com.maenle.bump.R
import com.maenle.bump.databinding.ActivityMainBinding import com.maenle.bump.databinding.ActivityMainBinding
import com.maenle.bump.util.BumpProcessor
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@ -29,11 +36,31 @@ class MainActivity : AppCompatActivity() {
appBarConfiguration = AppBarConfiguration(navController.graph) appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration) setupActionBarWithNavController(navController, appBarConfiguration)
val bundle = intent.extras
if (bundle != null) {
Log.d("MainActivity", bundle.toString())
}
}
fun showHelp() {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://raphael.maenle.net/post/projects/bump/"));
startActivity(browserIntent);
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu) menuInflater.inflate(R.menu.menu_main, menu)
for (i in 0 until menu.size()) {
val item = menu.getItem(i)
val spanString = SpannableString(menu.getItem(i).title.toString())
spanString.setSpan(
ForegroundColorSpan(Color.WHITE),
0,
spanString.length,
0
) //fix the color to white
item.title = spanString
}
return true return true
} }
@ -42,9 +69,18 @@ class MainActivity : AppCompatActivity() {
// automatically handle clicks on the Home/Up button, so long // automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml. // as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) { return when (item.itemId) {
R.id.action_settings -> true R.id.action_settings -> {true}
R.id.action_clear_log -> {
BumpProcessor.clearLog(applicationContext)
true
}
R.id.action_info -> {
showHelp()
true
}
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {

View File

@ -4,20 +4,24 @@ import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import com.google.firebase.ktx.Firebase
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.io.File import java.io.File
import kotlin.concurrent.thread
class BumpProcessor constructor(context: Context) { class BumpProcessor constructor(context: Context) {
private var rest: RestSingleton = RestSingleton.getInstance(context) private val rest: RestSingleton = RestSingleton.getInstance(context)
private lateinit var log: JSONArray private val local = LocalData(context)
private var _log: JSONArray
private val messenger = local.code?.let { MessageProcessor(it) }
var logUpdateCallback: ((JSONArray)->Unit)? = null
init { init {
log = getLog(context) _log = getLogFromFile(context)
startUpdateHandler(context)
} }
companion object { companion object {
@ -29,22 +33,50 @@ class BumpProcessor constructor(context: Context) {
INSTANCE = it INSTANCE = it
} }
} }
fun clearLog(context: Context) {
val logFile = File(context.filesDir, ".bump_log")
logFile.writeText("")
}
}
val log: JSONArray
get() {
return _log
}
fun asyncDecryptMessage(message: String, location: Int, callback: (String, Int) -> Unit) {
thread(start = true) {
val decrypt = decryptMessage(message)
Handler(Looper.getMainLooper()).post {callback( decrypt, location)}
}
}
fun decryptMessage(message: String): String {
return messenger!!.decrypt(message)
} }
private fun updateLog(context: Context, list: JSONArray) { private fun updateLog(context: Context, list: JSONArray) {
_log = getLogFromFile(context)
var change = false
for(i in 0 until list.length()) { for(i in 0 until list.length()) {
var exists = false var exists = false
for(j in 0 until log.length()) { for(j in 0 until _log.length()) {
if (list[i] == log[j]) { if (list[i].toString() == _log[j].toString()) {
exists = true exists = true
continue break
} }
} }
exists.let { if(!exists) {
log.put(list[i]) _log.put(list[i])
addToLog(context, list[i] as JSONObject) addToLog(context, list[i] as JSONObject)
change = true
} }
} }
if(change) {
logUpdateCallback?.invoke(_log)
}
} }
private fun getSecret(context: Context):String? { private fun getSecret(context: Context):String? {
@ -52,11 +84,11 @@ class BumpProcessor constructor(context: Context) {
return local.code return local.code
} }
fun startUpdateHandler(context: Context) { private fun startUpdateHandler(context: Context) {
val delay: Long = 60_000 //milliseconds
val delay: Long = 30_000 //milliseconds
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
updateFromServer(context)
handler.postDelayed(object : Runnable { handler.postDelayed(object : Runnable {
override fun run() { override fun run() {
updateFromServer(context) updateFromServer(context)
@ -65,7 +97,7 @@ class BumpProcessor constructor(context: Context) {
}, delay) }, delay)
} }
private fun updateFromServer(context: Context) { fun updateFromServer(context: Context) {
LocalData(context).code?.let { LocalData(context).code?.let {
val messenger = MessageProcessor(it) val messenger = MessageProcessor(it)
rest.list(messenger.sender) { list -> updateLog(context, list) } rest.list(messenger.sender) { list -> updateLog(context, list) }
@ -74,7 +106,7 @@ class BumpProcessor constructor(context: Context) {
private fun addToLog(context: Context, line: JSONObject) { private fun addToLog(context: Context, line: JSONObject) {
val logFile = File(context.filesDir, ".bump_log") val logFile = File(context.filesDir, ".bump_log")
logFile.appendText(line.toString()) logFile.appendText(line.toString() + "\n")
} }
fun addSecret(context: Context, newCode: String): Boolean { fun addSecret(context: Context, newCode: String): Boolean {
@ -94,7 +126,7 @@ class BumpProcessor constructor(context: Context) {
return newCode.length == validChars && newCode.length >= 32 return newCode.length == validChars && newCode.length >= 32
} }
private fun getLog(context: Context): JSONArray { private fun getLogFromFile(context: Context): JSONArray {
val logFile = File(context.filesDir, ".bump_log") val logFile = File(context.filesDir, ".bump_log")
val log = JSONArray() val log = JSONArray()
!logFile.exists().let { !logFile.exists().let {
@ -106,7 +138,7 @@ class BumpProcessor constructor(context: Context) {
return log return log
} }
private fun setFirebaseToken(context: Context) { fun setFirebaseToken(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) }

View File

@ -29,9 +29,9 @@ class CameraXViewModel(application: Application) : AndroidViewModel(application)
cameraProviderLiveData!!.setValue(cameraProviderFuture.get()) cameraProviderLiveData!!.setValue(cameraProviderFuture.get())
} catch (e: ExecutionException) { } catch (e: ExecutionException) {
// Handle any errors (including cancellation) here. // Handle any errors (including cancellation) here.
Log.e(TAG, "Unhandled exception", e) // Log.e(TAG, "Unhandled exception", e)
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
Log.e(TAG, "Unhandled exception", e) // Log.e(TAG, "Unhandled exception", e)
} }
}, },
ContextCompat.getMainExecutor(getApplication()) ContextCompat.getMainExecutor(getApplication())

View File

@ -1,5 +1,6 @@
package com.maenle.bump.util package com.maenle.bump.util
import android.util.Log
import java.util.Base64 import java.util.Base64
import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.PBEKeySpec
import javax.crypto.SecretKeyFactory import javax.crypto.SecretKeyFactory
@ -77,6 +78,7 @@ class MessageProcessor(code: String, private val salt: ByteArray? = null) {
super.splitMessage(messageRaw) super.splitMessage(messageRaw)
} }
fun validateAndDecryptWith(password: String, timeToLive: Long = TIME_TO_LIVE): String { fun validateAndDecryptWith(password: String, timeToLive: Long = TIME_TO_LIVE): String {
try {
val fernetKey = deriveMessageKeyFromPassword(password) val fernetKey = deriveMessageKeyFromPassword(password)
val validator: Validator<String> = object : StringValidator { val validator: Validator<String> = object : StringValidator {
override fun getTimeToLive(): TemporalAmount { override fun getTimeToLive(): TemporalAmount {
@ -84,6 +86,11 @@ class MessageProcessor(code: String, private val salt: ByteArray? = null) {
} }
} }
return token.validateAndDecrypt(fernetKey, validator) return token.validateAndDecrypt(fernetKey, validator)
} catch (e: Throwable) {
e.message?.let { Log.d(TAG, it) }
}
return ""
} }
} }

View File

@ -0,0 +1,45 @@
package com.maenle.bump.util
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.maenle.bump.R
import com.maenle.bump.ui.MessageItem
class NotificationAdapter(context: Context, messages: MutableList<MessageItem>) :
ArrayAdapter<MessageItem?>(context, 0, messages as List<MessageItem?>) {
val bump = BumpProcessor(context)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
// Get the data item for this position
var view: View? = convertView
val message: MessageItem? = getItem(position)
// Check if an existing view is being reused, otherwise inflate the view
if (view == null) {
view = LayoutInflater.from(context).inflate(R.layout.notification_item, parent, false)
}
// Lookup view for data population
val title = view?.findViewById(R.id.title) as TextView
val data = view.findViewById(R.id.data) as TextView
// Populate the data into the template view using the data object
if (message != null) {
title.text = message.title
data.text = message.message
if(data.text == "") {
data.text = "-"
}
}
// Return the completed view to render on screen
return view
}
}

View File

@ -1,6 +1,5 @@
package com.maenle.bump.util package com.maenle.bump.util
import android.annotation.SuppressLint
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
@ -10,16 +9,32 @@ import androidx.core.app.NotificationCompat
import com.maenle.bump.ui.MainActivity import com.maenle.bump.ui.MainActivity
import com.maenle.bump.R import com.maenle.bump.R
// Notification ID.
private val NOTIFICATION_ID = 0
@SuppressLint("WrongConstant")
fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
// Notification ID.
private const val NOTIFICATION_ID = 0
fun NotificationManager.sendNotification(messageHeader: String?, messageBody: String?, applicationContext: Context) {
var title = applicationContext.getString(R.string.notification_title)
var body = ""
if(messageBody != null) {
if(messageHeader != null) {
title = messageHeader
}
body = messageBody
} else if(messageHeader != null) {
body = messageHeader
}
sendParsedNotification(title, body, applicationContext)
}
fun NotificationManager.sendParsedNotification(messageHeader: String, messageBody: String, applicationContext: Context) {
// Create the content intent for the notification, which launches // Create the content intent for the notification, which launches
// this activity // this activity
// TODO: Step 1.11 create intent
val contentIntent = Intent(applicationContext, MainActivity::class.java) val contentIntent = Intent(applicationContext, MainActivity::class.java)
// TODO: Step 1.12 create PendingIntent
val contentPendingIntent = PendingIntent.getActivity( val contentPendingIntent = PendingIntent.getActivity(
applicationContext, applicationContext,
NOTIFICATION_ID, NOTIFICATION_ID,
@ -27,43 +42,39 @@ fun NotificationManager.sendNotification(messageBody: String, applicationContext
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT
) )
// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource( val eggImage = BitmapFactory.decodeResource(
applicationContext.resources, applicationContext.resources,
R.drawable.ic_launcher_foreground R.drawable.ic_stat_name
) )
// TODO: Step 1.2 get an instance of NotificationCompat.Builder val intent = Intent(applicationContext, this.javaClass)
val action =
PendingIntent.getBroadcast(applicationContext, 0, intent, 0)
// Build the notification // Build the notification
val builder = NotificationCompat.Builder( val builder = NotificationCompat.Builder(
applicationContext, applicationContext,
applicationContext.getString(R.string.bump_notification_channel_id) applicationContext.getString(R.string.bump_notification_channel_id)
) )
// TODO: Step 1.3 set title, text and icon to builder .setSmallIcon(R.drawable.ic_stat_name)
.setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle(messageHeader)
.setContentTitle(applicationContext
.getString(R.string.notification_title))
.setContentText(messageBody) .setContentText(messageBody)
// TODO: Step 1.13 set content intent // set content intent
.setContentIntent(contentPendingIntent) .setContentIntent(contentPendingIntent)
.setAutoCancel(true) .setAutoCancel(true)
// TODO: Step 2.1 add style to builder // add style to builder
.setLargeIcon(eggImage) .setLargeIcon(eggImage)
// TODO: Step 2.5 set priority // set priority
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
// TODO: Step 1.4 call notify .addAction(0, "Action", action)
// notify
notify(NOTIFICATION_ID, builder.build()) notify(NOTIFICATION_ID, builder.build())
} }
// TODO: Step 1.14 Cancel all notifications
/**
* Cancels all notifications.
*
*/
fun NotificationManager.cancelNotifications() { fun NotificationManager.cancelNotifications() {
cancelAll() cancelAll()
} }

View File

@ -26,14 +26,14 @@ class RestSingleton constructor(context: Context){
} }
private val TAG = MainActivity::class.java.simpleName private val TAG = MainActivity::class.java.simpleName
private const val URL = "http://192.168.68.127:4000/api/" private const val URL = "https://bump.maenle.net/api/"
} }
fun list(sender: String, callback: (JSONArray) -> Unit){ fun list(sender: String, callback: (JSONArray) -> Unit){
val url = URL + "list/" val url = URL + "list/"
val data = JSONObject() val data = JSONObject()
data.put("sender", sender) data.put("sender", sender)
data.put("minutes", (60*24*365*100).toString()) data.put("minutes", (60*24).toString())
val jsonRequest = JsonObjectRequest(Request.Method.POST, url, val jsonRequest = JsonObjectRequest(Request.Method.POST, url,
data, data,
{ response -> { response ->
@ -100,7 +100,6 @@ class RestSingleton constructor(context: Context){
} }
private val requestQueue: RequestQueue by lazy { private val requestQueue: RequestQueue by lazy {
// applicationContext is key, it keeps you from leaking the // applicationContext is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in. // Activity or BroadcastReceiver if someone passes one in.

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.1725"
android:scaleY="0.1725"
android:translateX="0.96"
android:translateY="0.96">
<path
android:pathData="M53.38,18a12,12 0,0 1,12 12v24.3A9.7,9.7 0,0 0,75 64h0a9.69,9.69 0,0 0,9.66 -9.67L84.8,30a12,12 0,0 1,24 0l-0.94,66.3a12,12 0,0 1,-12 12H31.2a12,12 0,0 1,-12 -12c0,-9.33 8,-7.41 8,-17.58 0,-8.31 -8,-7.87 -8,-16.57 0,-8 8,-8.12 8,-15.81 0.05,-8.12 -8,-9.81 -8,-16.34a12,12 0,0 1,12 -12Z"
android:fillColor="#f7941d"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

View File

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

View File

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.3375"
android:scaleY="0.3375"
android:translateX="32.4"
android:translateY="32.4">
<path
android:pathData="M53.38,18a12,12 0,0 1,12 12v24.3A9.7,9.7 0,0 0,75 64h0a9.69,9.69 0,0 0,9.66 -9.67L84.8,30a12,12 0,0 1,24 0l-0.94,66.3a12,12 0,0 1,-12 12H31.2a12,12 0,0 1,-12 -12c0,-9.33 8,-7.41 8,-17.58 0,-8.31 -8,-7.87 -8,-16.57 0,-8 8,-8.12 8,-15.81 0.05,-8.12 -8,-9.81 -8,-16.34a12,12 0,0 1,12 -12Z"
android:fillColor="#f7941d"/>
</group>
</vector>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/cardview_dark_background"/>
<stroke android:width="8dp" android:color="#000000FF" />
<corners android:radius="10dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View File

@ -6,12 +6,20 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.maenle.bump.ui.FirstFragment"> tools:context="com.maenle.bump.ui.FirstFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<TextView <TextView
android:id="@+id/textview_scan" 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"
android:layout_marginTop="40dp" android:layout_marginTop="30dp"
android:textSize="20sp"
android:layout_gravity="center"
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" />
@ -25,6 +33,7 @@
android:breakStrategy="balanced" android:breakStrategy="balanced"
android:maxEms="16" android:maxEms="16"
android:visibility="gone" android:visibility="gone"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -34,7 +43,8 @@
android:id="@+id/idIVQrcode" android:id="@+id/idIVQrcode"
android:layout_width="300dp" android:layout_width="300dp"
android:layout_height="300dp" android:layout_height="300dp"
android:layout_centerHorizontal="true" android:gravity="center"
android:layout_gravity="center"
android:contentDescription="@string/qr_code" android:contentDescription="@string/qr_code"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:visibility="gone" android:visibility="gone"
@ -43,10 +53,12 @@
app:layout_constraintStart_toStartOf="parent"/> app:layout_constraintStart_toStartOf="parent"/>
<LinearLayout <LinearLayout
android:id="@+id/buttons"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:layout_gravity="center"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/idIVQrcode" app:layout_constraintTop_toBottomOf="@id/idIVQrcode"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent">
@ -56,12 +68,50 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:textColor="@color/white"
android:text="@string/scan_sender" /> android:text="@string/scan_sender" />
<Button <Button
android:id="@+id/button_view" android:id="@+id/button_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/white"
android:text="@string/view_sender" /> android:text="@string/view_sender" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="20dp"
android:layout_gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/buttons"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/nr_messages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/default_nr_messages"
android:layout_marginEnd="10dp"
android:textSize="20sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/message_header"
android:textSize="20sp"/>
</LinearLayout>
<ListView
android:id="@+id/notification_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:animateLayoutChanges="true"
android:layout_weight="1"
android:layout_margin="20sp"
android:scrollbars="vertical"
app:layout_constraintTop_toBottomOf="@id/header">
</ListView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/rounded_card"
android:padding="10dp"
android:paddingStart="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:autoLink="web"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text="title"
android:textSize="20sp" />
<TextView
android:id="@+id/data"
android:autoLink="web"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="no data\nfor\nme"
android:textSize="12sp"
android:ellipsize="marquee"/>
</LinearLayout>

View File

@ -3,8 +3,18 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context="com.maenle.bump.ui.MainActivity"> tools:context="com.maenle.bump.ui.MainActivity">
<item <item
android:id="@+id/action_settings" android:id="@+id/action_clear_log"
android:orderInCategory="100" android:orderInCategory="100"
android:title="@string/action_clear_log"
app:showAsAction="never" />
<item
android:id="@+id/action_info"
android:orderInCategory="101"
android:title="@string/action_info"
app:showAsAction="never" />
<item
android:id="@+id/action_settings"
android:orderInCategory="102"
android:title="@string/action_settings" android:title="@string/action_settings"
app:showAsAction="never" /> app:showAsAction="never" />
</menu> </menu>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -2,7 +2,7 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.BumpForAndroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.BumpForAndroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item> <item name="colorPrimary">@color/purple_700</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item> <item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. --> <!-- Secondary brand color. -->

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="orange_200">#f7941d</color>
<color name="purple_200">#FFBB86FC</color> <color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color> <color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color> <color name="purple_700">#FF3700B3</color>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -1,6 +1,8 @@
<resources> <resources>
<string name="app_name">Bump</string> <string name="app_name">Bump</string>
<string name="action_settings">Settings</string> <string name="action_settings">Settings</string>
<string name="action_info">Help</string>
<string name="action_clear_log">Clear Log</string>
<!-- Strings used for fragments for navigation --> <!-- Strings used for fragments for navigation -->
<string name="main_fragment_label">Bump</string> <string name="main_fragment_label">Bump</string>
<string name="second_fragment_label">Second Fragment</string> <string name="second_fragment_label">Second Fragment</string>
@ -11,8 +13,10 @@
<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">Scan new Fragment</string> <string name="camera_fragment_label">Scan Sender QR</string>
<string name="preference_file_key">code_file_key</string> <string name="preference_file_key">code_file_key</string>
<string name="message_header">messages received:</string>
<string name="default_nr_messages">0</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>
<string name="bump_notification_channel_id">bump_id</string> <string name="bump_notification_channel_id">bump_id</string>

View File

@ -12,6 +12,7 @@
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="actionMenuTextColor">@color/white</item>
</style> </style>
<style name="Theme.BumpForAndroid.NoActionBar"> <style name="Theme.BumpForAndroid.NoActionBar">

View File

@ -5,7 +5,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "com.android.tools.build:gradle:7.0.4" classpath 'com.android.tools.build:gradle:7.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0"
classpath 'com.google.gms:google-services:4.3.2' // Google Services plugin classpath 'com.google.gms:google-services:4.3.2' // Google Services plugin

View File

@ -1,6 +1,6 @@
#Sat Dec 11 13:26:50 CET 2021 #Sat Dec 11 13:26:50 CET 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME