Compare commits

..

7 Commits
master ... 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
19 changed files with 368 additions and 101 deletions

3
app/.gitignore vendored
View File

@ -1 +1,2 @@
/build
/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

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.maenle.bump"
android:versionName="initial">
android:versionName="0.1_alpha">
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
@ -23,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_stat_name"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/black"/> <!-- [END fcm_default_icon] -->
@ -50,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>

View File

@ -3,6 +3,7 @@ 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
@ -16,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}")
// 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 {
// Log.d(TAG, "Message data payload: " + remoteMessage.data)
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
@ -63,14 +69,11 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
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

@ -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
}
@ -84,6 +76,7 @@ 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.idIVQrcode.setImageBitmap(generateQrCode(it.code))}
@ -152,8 +145,42 @@ 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)
@ -176,4 +203,8 @@ class FirstFragment : Fragment() {
super.onDestroyView()
_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)
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 {

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,13 +78,19 @@ class MessageProcessor(code: String, private val salt: ByteArray? = null) {
super.splitMessage(messageRaw)
}
fun validateAndDecryptWith(password: String, timeToLive: Long = TIME_TO_LIVE): String {
val fernetKey = deriveMessageKeyFromPassword(password)
val validator: Validator<String> = object : StringValidator {
override fun getTimeToLive(): TemporalAmount {
return Duration.ofHours(timeToLive)
try {
val fernetKey = deriveMessageKeyFromPassword(password)
val validator: Validator<String> = object : StringValidator {
override fun getTimeToLive(): TemporalAmount {
return Duration.ofHours(timeToLive)
}
}
return token.validateAndDecrypt(fernetKey, validator)
} catch (e: Throwable) {
e.message?.let { Log.d(TAG, it) }
}
return token.validateAndDecrypt(fernetKey, validator)
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_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_stat_name)
.setContentTitle(applicationContext
.getString(R.string.notification_title))
.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

@ -33,7 +33,7 @@ class RestSingleton constructor(context: Context){
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,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,13 +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" />
@ -36,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"
@ -45,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">
@ -68,4 +78,40 @@
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,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>
@ -13,6 +15,8 @@
<string name="scan_sender">Scan</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