diff --git a/PennMobile/build.gradle b/PennMobile/build.gradle
index 77e706227..1c8b04e9b 100644
--- a/PennMobile/build.gradle
+++ b/PennMobile/build.gradle
@@ -99,6 +99,7 @@ dependencies {
testImplementation platform(libs.androidx.compose.bom)
androidTestImplementation platform(libs.androidx.compose.bom)
implementation platform(libs.firebase.bom)
+ implementation(libs.firebase.messaging)
implementation libs.bundles.compose
implementation libs.bundles.material
diff --git a/PennMobile/src/main/AndroidManifest.xml b/PennMobile/src/main/AndroidManifest.xml
index 27cc004da..c41732ad7 100644
--- a/PennMobile/src/main/AndroidManifest.xml
+++ b/PennMobile/src/main/AndroidManifest.xml
@@ -18,9 +18,11 @@
+
-
+
+
+
+
+
@@ -65,6 +72,16 @@
+
+
+
+
+ if (isGranted) {
+ // FCM SDK (and your app) can post notifications.
+ } else {
+ // TODO: Inform user that that your app will not show notifications.
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT > 28) {
setTheme(R.style.DarkModeApi29)
@@ -91,6 +107,7 @@ class MainActivity : AppCompatActivity() {
setTheme(R.style.DarkBackground)
}
Utils.getCurrentSystemTime()
+ askNotificationPermission()
setSupportActionBar(binding.include.toolbar)
fragmentManager = supportFragmentManager
@@ -132,6 +149,25 @@ class MainActivity : AppCompatActivity() {
}
}
+ private fun askNotificationPermission() {
+ // This is only necessary for API level >= 33 (TIRAMISU)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
+ PackageManager.PERMISSION_GRANTED
+ ) {
+ // FCM SDK (and your app) can post notifications.
+ } else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
+ // TODO: display an educational UI explaining to the user the features that will be enabled
+ // by them granting the POST_NOTIFICATION permission. This UI should provide the user
+ // "OK" and "No thanks" buttons. If the user selects "OK," directly request the permission.
+ // If the user selects "No thanks," allow the user to continue without notifications.
+ } else {
+ // Directly ask for the permission
+ requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
+ }
+ }
+ }
+
private fun onExpandableBottomNavigationItemSelected() {
binding.include.expandableBottomBar.setOnNavigationItemSelectedListener { item ->
val position =
@@ -319,6 +355,7 @@ class MainActivity : AppCompatActivity() {
private var mStudentLifeRf2: StudentLifeRf2? = null
private var mPlatform: Platform? = null
private var mCampusExpress: CampusExpress? = null
+ private var mNotificationAPI: NotificationAPI? = null
@JvmStatic
val campusExpressInstance: CampusExpress
@@ -383,6 +420,31 @@ class MainActivity : AppCompatActivity() {
return mStudentLifeRf2!!
}
+ val notificationAPIInstance: NotificationAPI
+ get() {
+ if (mNotificationAPI == null) {
+ val okHttpClient =
+ OkHttpClient
+ .Builder()
+ .connectTimeout(35, TimeUnit.SECONDS)
+ .readTimeout(35, TimeUnit.SECONDS)
+ .writeTimeout(35, TimeUnit.SECONDS)
+ .build()
+
+ val retrofit =
+ Retrofit
+ .Builder()
+ .baseUrl("https://pennmobile.org/api/")
+ .client(okHttpClient)
+ .addConverterFactory(ScalarsConverterFactory.create())
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .build()
+ mNotificationAPI = retrofit.create(NotificationAPI::class.java)
+ }
+ return mNotificationAPI!!
+ }
+
@JvmStatic
val studentLifeInstance: StudentLife
get() {
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/NotificationAPI.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/NotificationAPI.kt
new file mode 100644
index 000000000..d6b85d664
--- /dev/null
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/NotificationAPI.kt
@@ -0,0 +1,21 @@
+package com.pennapps.labs.pennmobile.api
+
+import okhttp3.ResponseBody
+import retrofit2.Response
+import retrofit2.http.DELETE
+import retrofit2.http.Header
+import retrofit2.http.POST
+import retrofit2.http.Path
+
+interface NotificationAPI {
+ @POST("user/notifications/tokens/android/{token}/")
+ suspend fun sendNotificationToken(
+ @Header("Authorization") bearerToken: String,
+ @Path("token") token: String,
+ ): Response
+
+ @DELETE("user/notifications/tokens/android/{token}/")
+ suspend fun deleteNotificationToken(
+ @Path("token") token: String,
+ ): Response
+}
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/fragments/LoginWebviewFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/fragments/LoginWebviewFragment.kt
index 904c49cb3..f2c164fd2 100644
--- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/fragments/LoginWebviewFragment.kt
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/fragments/LoginWebviewFragment.kt
@@ -17,6 +17,8 @@ import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
@@ -29,6 +31,9 @@ import com.pennapps.labs.pennmobile.api.StudentLife
import com.pennapps.labs.pennmobile.api.classes.AccessTokenResponse
import com.pennapps.labs.pennmobile.api.classes.Account
import com.pennapps.labs.pennmobile.api.classes.GetUserResponse
+import com.pennapps.labs.pennmobile.api.viewmodels.LoginWebviewViewmodel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import org.apache.commons.lang3.RandomStringUtils
import retrofit.Callback
import retrofit.RetrofitError
@@ -55,6 +60,7 @@ class LoginWebviewFragment : Fragment() {
lateinit var platformAuthUrl: String
lateinit var clientID: String
lateinit var redirectUri: String
+ private val loginWebviewViewmodel: LoginWebviewViewmodel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
@@ -206,6 +212,7 @@ class LoginWebviewFragment : Fragment() {
editor.putLong(getString(R.string.token_expires_at), currentTime + expiresInInt)
editor.apply()
getUser(accessToken)
+ sendNotifToken()
}
}
@@ -260,6 +267,20 @@ class LoginWebviewFragment : Fragment() {
}
}
+ private fun sendNotifToken() {
+ val mNotificationAPI = MainActivity.notificationAPIInstance
+
+ val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString()
+ val notifToken = sp.getString(getString(R.string.notification_token), "").toString()
+
+ Log.d("Notification Token", notifToken)
+ val notGuest = !sp.getBoolean(mActivity.getString(R.string.guest_mode), false)
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ loginWebviewViewmodel.sendToken(mNotificationAPI, notGuest, bearerToken, notifToken)
+ }
+ }
+
private fun getCodeChallenge(codeVerifier: String): String {
// Hash the code verifier
val md = MessageDigest.getInstance("SHA-256")
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/viewmodels/LoginWebviewViewmodel.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/viewmodels/LoginWebviewViewmodel.kt
new file mode 100644
index 000000000..b8bf6dfe0
--- /dev/null
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/viewmodels/LoginWebviewViewmodel.kt
@@ -0,0 +1,29 @@
+package com.pennapps.labs.pennmobile.api.viewmodels
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import com.pennapps.labs.pennmobile.api.NotificationAPI
+
+// Currently only include logic for notifications, would add more network handling afterwards (TBD)
+
+class LoginWebviewViewmodel : ViewModel() {
+ suspend fun sendToken(
+ mNotificationAPI: NotificationAPI,
+ notGuest: Boolean,
+ bearerToken: String,
+ notifToken: String,
+ ) {
+ try {
+ if (notGuest) {
+ val response = mNotificationAPI.sendNotificationToken(bearerToken, notifToken)
+ if (response.isSuccessful) {
+ Log.i("Notification Token", "Successfully updated token")
+ } else {
+ Log.i("Notification Token", "Error updating token: ${response.code()} ${response.message()}")
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+}
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/MoreFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/MoreFragment.kt
index b530d1514..5289832c2 100644
--- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/MoreFragment.kt
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/MoreFragment.kt
@@ -47,6 +47,21 @@ class MoreFragment : Fragment() {
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
+ val initials =
+ PreferenceManager
+ .getDefaultSharedPreferences(mActivity)
+ .getString(getString(R.string.initials), null)
+ if (initials != null && initials.isNotEmpty()) {
+ binding.initials.text = initials
+ } else {
+ binding.profileBackground.setImageDrawable(
+ ResourcesCompat.getDrawable(
+ resources,
+ R.drawable.ic_guest_avatar,
+ context?.theme,
+ ),
+ )
+ }
childFragmentManager
.beginTransaction()
.replace(R.id.more_frame, PreferenceFragment())
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/PreferenceFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/PreferenceFragment.kt
index 4a7474557..f331ef6e2 100644
--- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/PreferenceFragment.kt
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/PreferenceFragment.kt
@@ -4,14 +4,18 @@ import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
+import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.webkit.CookieManager
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentTransaction
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
@@ -20,7 +24,10 @@ import com.pennapps.labs.pennmobile.R
import com.pennapps.labs.pennmobile.components.dialog.CustomAlertDialogue
import com.pennapps.labs.pennmobile.gsr.fragments.PottruckFragment
import com.pennapps.labs.pennmobile.home.fragments.NewsFragment
+import com.pennapps.labs.pennmobile.more.viewmodels.PreferenceViewModel
import com.pennapps.labs.pennmobile.showSneakerToast
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
/**
* Created by Davies Lumumba Spring 2021
@@ -29,6 +36,7 @@ class PreferenceFragment : PreferenceFragmentCompat() {
private lateinit var mContext: Context
private lateinit var mActivity: MainActivity
private lateinit var toolbar: Toolbar
+ private val preferenceViewModel: PreferenceViewModel by viewModels()
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -72,27 +80,32 @@ class PreferenceFragment : PreferenceFragmentCompat() {
userLoginPref?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
if (pennKey != null) {
- val dialog = AlertDialog.Builder(context).create()
- dialog.setTitle("Log out")
- dialog.setMessage("Are you sure you want to log out?")
- dialog.setButton("Logout") { dialog, _ ->
- CookieManager.getInstance().removeAllCookie()
- editor.remove(getString(R.string.penn_password))
- editor.remove(getString(R.string.penn_user))
- editor.remove(getString(R.string.first_name))
- editor.remove(getString(R.string.last_name))
- editor.remove(getString(R.string.email_address))
- editor.remove(getString(R.string.pennkey))
- editor.remove(getString(R.string.accountID))
- editor.remove(getString(R.string.access_token))
- editor.remove(getString(R.string.guest_mode))
- editor.remove(getString(R.string.initials))
- editor.apply()
- dialog.cancel()
- mActivity.startLoginFragment()
- }
- // dialog.setButton(2,"Cancel") { dialog, _ -> dialog.cancel() }
- dialog.show()
+ AlertDialog
+ .Builder(context)
+ .setTitle("Log Out")
+ .setMessage("Are you sure you want to log out?")
+ .setPositiveButton("Logout") { dialog, _ ->
+ deleteNotifToken(sp)
+ CookieManager.getInstance().removeAllCookie()
+ editor.apply {
+ remove(getString(R.string.penn_password))
+ remove(getString(R.string.penn_user))
+ remove(getString(R.string.first_name))
+ remove(getString(R.string.last_name))
+ remove(getString(R.string.email_address))
+ remove(getString(R.string.pennkey))
+ remove(getString(R.string.accountID))
+ remove(getString(R.string.access_token))
+ remove(getString(R.string.guest_mode))
+ remove(getString(R.string.campus_express_token))
+ remove(getString(R.string.campus_token_expires_in))
+ remove(getString(R.string.initials))
+ }
+ dialog.dismiss()
+ mActivity.startLoginFragment()
+ }.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() }
+ .create()
+ .show()
} else {
mActivity.startLoginFragment()
}
@@ -261,6 +274,20 @@ class PreferenceFragment : PreferenceFragmentCompat() {
alert.show()
}
+ private fun deleteNotifToken(sp: SharedPreferences) {
+ val notifToken = sp.getString(getString(R.string.notification_token), "").toString()
+ val mNotificationAPI = MainActivity.notificationAPIInstance
+ Log.i("Notification Token", notifToken)
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ try {
+ preferenceViewModel.deleteTokenResponse(mNotificationAPI, notifToken)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
companion object {
private const val PENNLABS = "https://pennlabs.org"
private const val FEEDBACK = "https://airtable.com/appFRa4NQvNMEbWsA/shrn4VbSQa8QDj8OG"
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/SettingsFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/SettingsFragment.kt
index 0760ed6ec..9b6b128f3 100644
--- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/SettingsFragment.kt
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/SettingsFragment.kt
@@ -4,6 +4,7 @@ import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -89,28 +90,33 @@ class SettingsFragment : PreferenceFragmentCompat() {
logInOutButton?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
if (pennKey != null) {
- val dialog = AlertDialog.Builder(context).create()
- dialog.setTitle("Log out")
- dialog.setMessage("Are you sure you want to log out?")
- dialog.setButton("Logout") { dialog, _ ->
- CookieManager.getInstance().removeAllCookie()
- editor.remove(getString(R.string.penn_password))
- editor.remove(getString(R.string.penn_user))
- editor.remove(getString(R.string.first_name))
- editor.remove(getString(R.string.last_name))
- editor.remove(getString(R.string.email_address))
- editor.remove(getString(R.string.pennkey))
- editor.remove(getString(R.string.accountID))
- editor.remove(getString(R.string.access_token))
- editor.remove(getString(R.string.guest_mode))
- editor.remove(getString(R.string.campus_express_token))
- editor.remove(getString(R.string.campus_token_expires_in))
- editor.apply()
- dialog.cancel()
- mActivity.startLoginFragment()
- }
- dialog.setButton2("Cancel") { dialog, _ -> dialog.cancel() }
- dialog.show()
+ AlertDialog
+ .Builder(context)
+ .setTitle("Log out")
+ .setMessage("Are you sure you want to log out?")
+ .setPositiveButton("Logout") { dialog, _ ->
+ Log.d("SettingsFragment", "Logout button clicked in dialog.")
+ CookieManager.getInstance().removeAllCookie()
+ editor.apply {
+ remove(getString(R.string.penn_password))
+ remove(getString(R.string.penn_user))
+ remove(getString(R.string.first_name))
+ remove(getString(R.string.last_name))
+ remove(getString(R.string.email_address))
+ remove(getString(R.string.pennkey))
+ remove(getString(R.string.accountID))
+ remove(getString(R.string.access_token))
+ remove(getString(R.string.guest_mode))
+ remove(getString(R.string.campus_express_token))
+ remove(getString(R.string.campus_token_expires_in))
+ }
+ dialog.dismiss()
+ Log.d("SettingsFragment", "SharedPreferences cleared, navigating to Login.")
+ mActivity.startLoginFragment()
+ }.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() }
+ .create()
+ .show()
+ Log.d("SettingsFragment", "Logout confirmation dialog displayed.")
} else {
mActivity.startLoginFragment()
}
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferenceViewModel.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferenceViewModel.kt
new file mode 100644
index 000000000..bb7b366bc
--- /dev/null
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferenceViewModel.kt
@@ -0,0 +1,25 @@
+package com.pennapps.labs.pennmobile.more.viewmodels
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import com.pennapps.labs.pennmobile.api.NotificationAPI
+
+// Currently only implemented the notification logic, other network logistics to be implemented
+
+class PreferenceViewModel : ViewModel() {
+ suspend fun deleteTokenResponse(
+ mNotificationAPI: NotificationAPI,
+ notifToken: String,
+ ) {
+ try {
+ val response = mNotificationAPI.deleteNotificationToken(notifToken)
+ if (response.isSuccessful) {
+ Log.i("Notification Token", "Successfully deleted token")
+ } else {
+ Log.i("Notification Token", "Error deleting token: ${response.code()} ${response.message()}")
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+}
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/notifications/PushNotificationService.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/notifications/PushNotificationService.kt
new file mode 100644
index 000000000..29c0d4cd6
--- /dev/null
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/notifications/PushNotificationService.kt
@@ -0,0 +1,75 @@
+package com.pennapps.labs.pennmobile.notifications
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.graphics.BitmapFactory
+import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.content.ContextCompat
+import androidx.preference.PreferenceManager
+import com.google.firebase.messaging.FirebaseMessagingService
+import com.google.firebase.messaging.RemoteMessage
+import com.pennapps.labs.pennmobile.MainActivity
+import com.pennapps.labs.pennmobile.R
+
+class PushNotificationService : FirebaseMessagingService() {
+ private lateinit var mSharedPrefs: SharedPreferences
+
+ override fun onNewToken(token: String) {
+ super.onNewToken(token)
+ // Update Server/Database
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
+
+ with(mSharedPrefs.edit()) {
+ putString("Notification Token", token)
+ apply()
+ }
+
+ Log.d("FCM Registration", "Stored Notification token: $token")
+ }
+
+ override fun onMessageReceived(message: RemoteMessage) {
+ super.onMessageReceived(message)
+
+ Log.d("Notification Received", "Notification received!")
+ val title = message.notification?.title
+ val body = message.notification?.body
+
+ val notificationManager =
+ getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+ val notificationChannel =
+ NotificationChannel(
+ "MAIN_CHANNEL",
+ "Main Channel",
+ NotificationManager.IMPORTANCE_HIGH,
+ )
+ notificationManager.createNotificationChannel(notificationChannel)
+
+ val mainActivityIntent = Intent(this, MainActivity::class.java)
+ mainActivityIntent.apply {
+ flags += Intent.FLAG_ACTIVITY_NEW_TASK
+ flags += Intent.FLAG_ACTIVITY_CLEAR_TOP
+ }
+ val pendingIntent = PendingIntent.getActivity(this, 0, mainActivityIntent, PendingIntent.FLAG_IMMUTABLE)
+ val bitMap = BitmapFactory.decodeResource(this.resources, R.drawable.ic_icon)
+
+ val notificationBuilder =
+ NotificationCompat
+ .Builder(this, "MAIN_CHANNEL")
+ .setContentTitle(title)
+ .setContentText(body)
+ .setSmallIcon(R.mipmap.ic_launcher_foreground)
+ .setLargeIcon(bitMap)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true)
+ .setColor(ContextCompat.getColor(this, R.color.penn_red))
+
+ notificationManager.notify(1, notificationBuilder.build())
+ }
+}
diff --git a/PennMobile/src/main/res/drawable/baseline_circle_notifications_24.xml b/PennMobile/src/main/res/drawable/baseline_circle_notifications_24.xml
new file mode 100644
index 000000000..68c2c90a0
--- /dev/null
+++ b/PennMobile/src/main/res/drawable/baseline_circle_notifications_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/PennMobile/src/main/res/values/colors.xml b/PennMobile/src/main/res/values/colors.xml
index 8613609bb..268201e90 100644
--- a/PennMobile/src/main/res/values/colors.xml
+++ b/PennMobile/src/main/res/values/colors.xml
@@ -72,5 +72,6 @@
#FF81D4FA
#FF039BE5
#FF01579B
+ #990000
\ No newline at end of file
diff --git a/PennMobile/src/main/res/values/strings.xml b/PennMobile/src/main/res/values/strings.xml
index f79485c3b..547cd875b 100644
--- a/PennMobile/src/main/res/values/strings.xml
+++ b/PennMobile/src/main/res/values/strings.xml
@@ -239,4 +239,8 @@
EXAMPLE
Add widget
This is an app widget description
+
+
+ Notification Token
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 98fb1f815..d19bd425d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -18,9 +18,10 @@ coordinatorlayout = "1.2.0"
customalertviewdialogue = "a1fc69d54d"
espressoCore = "3.5.0"
exifinterface = "1.3.6"
-firebaseBom = "31.5.0"
+firebaseBom = "33.5.1"
firebaseCrashlyticsKtx = "18.6.0"
firebaseCrashalytics = "2.9.9"
+firebaseMessaging = "24.0.3"
glanceAppwidget = "1.1.0"
glide = "4.11.0"
googleMapsServices = "2.2.0"
@@ -103,6 +104,7 @@ firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" }
firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx", version.ref = "firebaseCrashlyticsKtx" }
+firebase-messaging = { module = "com.google.firebase:firebase-messaging", version.ref = "firebaseMessaging" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
google-maps-services = { module = "com.google.maps:google-maps-services", version.ref = "googleMapsServices" }
joda-time = { module = "joda-time:joda-time", version.ref = "jodaTime" }