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
*.hprof
*.aab

1
app/.gitignore vendored
View File

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

View File

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

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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-permission android:name="android.permission.CAMERA" />
@ -22,13 +23,13 @@
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
</intent-filter>
</service>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_launcher_foreground"/>
android:resource="@drawable/ic_stat_name"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/black"/> <!-- [END fcm_default_icon] -->
@ -49,6 +50,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name=".ui.MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</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.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.maenle.bump.util.sendNotification
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.maenle.bump.util.BumpProcessor
import com.maenle.bump.util.RestSingleton
class MyFirebaseMessagingService : FirebaseMessagingService() {
@ -17,27 +17,32 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
// [START receive_message]
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// 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.
var body: String? = null;
var header: String? = null;
remoteMessage.data.let {
val bump = BumpProcessor.getInstance(applicationContext)
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 {
Log.d(TAG, "Message Notification Body: ${it.body}")
sendNotification(it.body!!)
header = it.body
}
}
// [END receive_message]
//TODO Step 3.2 log registration token
sendNotification(header, body)
}
// log registration token
// [START on_new_token]
/**
* 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.
*/
override fun onNewToken(token: String) {
Log.d(TAG, "Refreshed token: $token")
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(token)
sendRegistrationToServer()
}
// [END on_new_token]
@ -60,17 +64,16 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
*
* @param token The new token.
*/
private fun sendRegistrationToServer(token: String?) {
private fun sendRegistrationToServer() {
val bump = BumpProcessor.getInstance(baseContext)
bump.setFirebaseToken(baseContext)
}
/**
* Create and show a simple notification containing the received FCM message.
*
* @param messageBody FCM message body received.
*/
private fun sendNotification(messageBody: String) {
private fun sendNotification(messageHeader: String?, messageBody: String?) {
val notificationManager = ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) as NotificationManager
notificationManager.sendNotification(messageBody, applicationContext)
notificationManager.sendNotification(messageHeader, messageBody, applicationContext)
}
companion object {

View File

@ -110,9 +110,9 @@ class CameraFragment: Fragment() {
previewUseCase
)
} catch (illegalStateException: IllegalStateException) {
Log.e(TAG, illegalStateException.message ?: "IllegalStateException")
// Log.e(TAG, illegalStateException.message ?: "IllegalStateException")
} catch (illegalArgumentException: IllegalArgumentException) {
Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException")
// Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException")
}
}
@ -153,9 +153,9 @@ class CameraFragment: Fragment() {
analysisUseCase
)
} catch (illegalStateException: IllegalStateException) {
Log.e(TAG, illegalStateException.message ?: "IllegalStateException")
// Log.e(TAG, illegalStateException.message ?: "IllegalStateException")
} catch (illegalArgumentException: IllegalArgumentException) {
Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException")
// Log.e(TAG, illegalArgumentException.message ?: "IllegalArgumentException")
}
}
@ -189,7 +189,7 @@ class CameraFragment: Fragment() {
blockingScanned = false
}
.addOnFailureListener {
Log.e(TAG, it.message ?: it.toString())
// Log.e(TAG, it.message ?: it.toString())
}.addOnCompleteListener {
// 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
@ -207,7 +207,7 @@ class CameraFragment: Fragment() {
if (isCameraPermissionGranted()) {
bindCameraUseCases()
} else {
Log.e(TAG, "no camera permission")
// Log.e(TAG, "no camera permission")
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

View File

@ -2,29 +2,27 @@ package com.maenle.bump.ui
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Intent
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.util.Log
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.view.*
import androidmads.library.qrgenearator.QRGContents
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 org.json.JSONArray
import org.json.JSONObject
/**
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
@ -53,16 +51,10 @@ class FirstFragment : Fragment() {
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()
bump.logUpdateCallback = { log -> updateMessageList(log) }
updateMessageList(bump.log)
return binding.root
}
@ -76,7 +68,7 @@ class FirstFragment : Fragment() {
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")+"-"}
message?.let { binding.textviewScan.text = it.sender.replace("-", "-\u200b")}
} else {
binding.textviewEncryption.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.textviewEncryption.text = it.password.replace("-", "-\u200b")}
message?.let { binding.textviewEncryption.text = "-" + it.password.replace("-", "-\u200b")}
message?.let {binding.idIVQrcode.setImageBitmap(generateQrCode(it.code))}
}
@ -125,7 +118,7 @@ class FirstFragment : Fragment() {
try {
bitmap = qrgEncoder.encodeAsBitmap()
} catch (e: WriterException) {
Log.d("Tag", e.toString())
// Log.d("Tag", e.toString())
}
if(nightModeFlags == Configuration.UI_MODE_NIGHT_YES) {
@ -152,15 +145,49 @@ class FirstFragment : Fragment() {
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() {
// [START subscribe_topic]
FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
.addOnCompleteListener { task ->
var curmessage = getString(R.string.message_subscribed)
if (!task.isSuccessful) {
curmessage = getString(R.string.message_subscribe_failed)
}
Log.d(TAG, curmessage.toString())
// Log.d(TAG, curmessage.toString())
}
// [END subscribe_topics]
}
@ -177,3 +204,7 @@ class FirstFragment : Fragment() {
_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
import android.content.Intent
import android.graphics.Color
import android.net.Uri
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.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import com.maenle.bump.R
import com.maenle.bump.databinding.ActivityMainBinding
import com.maenle.bump.util.BumpProcessor
class MainActivity : AppCompatActivity() {
@ -29,11 +36,31 @@ class MainActivity : AppCompatActivity() {
appBarConfiguration = AppBarConfiguration(navController.graph)
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 {
// Inflate the menu; this adds items to the action bar if it is present.
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
}
@ -42,9 +69,18 @@ class MainActivity : AppCompatActivity() {
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
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)
}
}
override fun onSupportNavigateUp(): Boolean {

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package com.maenle.bump.util
import android.util.Log
import java.util.Base64
import javax.crypto.spec.PBEKeySpec
import javax.crypto.SecretKeyFactory
@ -77,6 +78,7 @@ class MessageProcessor(code: String, private val salt: ByteArray? = null) {
super.splitMessage(messageRaw)
}
fun validateAndDecryptWith(password: String, timeToLive: Long = TIME_TO_LIVE): String {
try {
val fernetKey = deriveMessageKeyFromPassword(password)
val validator: Validator<String> = object : StringValidator {
override fun getTimeToLive(): TemporalAmount {
@ -84,6 +86,11 @@ class MessageProcessor(code: String, private val salt: ByteArray? = null) {
}
}
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
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
@ -10,16 +9,32 @@ import androidx.core.app.NotificationCompat
import com.maenle.bump.ui.MainActivity
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
// this activity
// TODO: Step 1.11 create intent
val contentIntent = Intent(applicationContext, MainActivity::class.java)
// TODO: Step 1.12 create PendingIntent
val contentPendingIntent = PendingIntent.getActivity(
applicationContext,
NOTIFICATION_ID,
@ -27,43 +42,39 @@ fun NotificationManager.sendNotification(messageBody: String, applicationContext
PendingIntent.FLAG_UPDATE_CURRENT
)
// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
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
val builder = NotificationCompat.Builder(
applicationContext,
applicationContext.getString(R.string.bump_notification_channel_id)
)
// TODO: Step 1.3 set title, text and icon to builder
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(applicationContext
.getString(R.string.notification_title))
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(messageHeader)
.setContentText(messageBody)
// TODO: Step 1.13 set content intent
// set content intent
.setContentIntent(contentPendingIntent)
.setAutoCancel(true)
// TODO: Step 2.1 add style to builder
// add style to builder
.setLargeIcon(eggImage)
// TODO: Step 2.5 set priority
// set priority
.setPriority(NotificationCompat.PRIORITY_HIGH)
// TODO: Step 1.4 call notify
.addAction(0, "Action", action)
// notify
notify(NOTIFICATION_ID, builder.build())
}
// TODO: Step 1.14 Cancel all notifications
/**
* Cancels all notifications.
*
*/
fun NotificationManager.cancelNotifications() {
cancelAll()
}

View File

@ -26,14 +26,14 @@ class RestSingleton constructor(context: Context){
}
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){
val url = URL + "list/"
val data = JSONObject()
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,
data,
{ response ->
@ -100,7 +100,6 @@ class RestSingleton constructor(context: Context){
}
private val requestQueue: RequestQueue by lazy {
// applicationContext is key, it keeps you from leaking the
// 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"
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
android:id="@+id/textview_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -25,6 +33,7 @@
android:breakStrategy="balanced"
android:maxEms="16"
android:visibility="gone"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -34,7 +43,8 @@
android:id="@+id/idIVQrcode"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_centerHorizontal="true"
android:gravity="center"
android:layout_gravity="center"
android:contentDescription="@string/qr_code"
android:layout_marginTop="20dp"
android:visibility="gone"
@ -43,10 +53,12 @@
app:layout_constraintStart_toStartOf="parent"/>
<LinearLayout
android:id="@+id/buttons"
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/idIVQrcode"
app:layout_constraintStart_toStartOf="parent">
@ -56,12 +68,50 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:textColor="@color/white"
android:text="@string/scan_sender" />
<Button
android:id="@+id/button_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:text="@string/view_sender" />
</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>

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"
tools:context="com.maenle.bump.ui.MainActivity">
<item
android:id="@+id/action_settings"
android:id="@+id/action_clear_log"
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"
app:showAsAction="never" />
</menu>

View File

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

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</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. -->
<style name="Theme.BumpForAndroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- 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="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="orange_200">#f7941d</color>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</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>
<string name="app_name">Bump</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 -->
<string name="main_fragment_label">Bump</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="current_sender">No Sender Added</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="message_header">messages received:</string>
<string name="default_nr_messages">0</string>
<string name="code_key">code_key</string>
<string name="notification_title">Bump</string>
<string name="bump_notification_channel_id">bump_id</string>

View File

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

View File

@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
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 '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
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
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME