From 828112549077565a9b76e8dece8b9175002a48ce Mon Sep 17 00:00:00 2001 From: Skeletrobro <67814129+baronhsieh2005@users.noreply.github.com> Date: Mon, 11 Nov 2024 20:37:00 -0500 Subject: [PATCH 1/6] Draft for notifs --- PennMobile/build.gradle | 1 + PennMobile/src/main/AndroidManifest.xml | 14 +++- .../pennapps/labs/pennmobile/MainActivity.kt | 35 ++++++++++ .../notifications/PushNotificationService.kt | 67 +++++++++++++++++++ .../baseline_circle_notifications_24.xml | 5 ++ PennMobile/src/main/res/values/colors.xml | 1 + gradle/libs.versions.toml | 4 +- 7 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 PennMobile/src/main/java/com/pennapps/labs/pennmobile/notifications/PushNotificationService.kt create mode 100644 PennMobile/src/main/res/drawable/baseline_circle_notifications_24.xml 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..a28b75aaa 100644 --- a/PennMobile/src/main/AndroidManifest.xml +++ b/PennMobile/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ + - + + + + + @@ -65,6 +71,12 @@ + + + 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 +106,7 @@ class MainActivity : AppCompatActivity() { setTheme(R.style.DarkBackground) } Utils.getCurrentSystemTime() + askNotificationPermission() setSupportActionBar(binding.include.toolbar) fragmentManager = supportFragmentManager @@ -132,6 +148,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 = 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..179a1ea0a --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/notifications/PushNotificationService.kt @@ -0,0 +1,67 @@ +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.graphics.BitmapFactory +import android.util.Log +import androidx.core.app.NotificationCompat +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage +import com.pennapps.labs.pennmobile.MainActivity +import com.pennapps.labs.pennmobile.R +import java.net.URL + +class PushNotificationService : FirebaseMessagingService() { + override fun onNewToken(token: String) { + super.onNewToken(token) + // Update Server/Database + Log.d("FCM Registration", "Refreshed token: $token") + } + + override fun onMessageReceived(message: RemoteMessage) { + super.onMessageReceived(message) + Log.d("Notification", "Notification received!") + val title = message.notification?.title + val body = message.notification?.body + val imageUrl = message.notification?.imageUrl + + 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 notificationBuilder = + NotificationCompat + .Builder(this, "MAIN_CHANNEL") + .setContentTitle(title) + .setContentText(body) + .setSmallIcon(R.drawable.baseline_circle_notifications_24) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + + imageUrl?.let { + val bitmap = BitmapFactory.decodeStream(URL(imageUrl.toString()).openConnection().getInputStream()) + notificationBuilder.setLargeIcon(bitmap) + notificationBuilder.setStyle(NotificationCompat.BigPictureStyle().bigPicture(bitmap)) + } + + 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/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" } From 579101584958ebdad8c74090322aaffd398a685b Mon Sep 17 00:00:00 2001 From: Skeletrobro <67814129+baronhsieh2005@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:58:18 -0500 Subject: [PATCH 2/6] Notif Networking --- PennMobile/src/main/AndroidManifest.xml | 11 ++- .../pennapps/labs/pennmobile/MainActivity.kt | 27 ++++++++ .../labs/pennmobile/api/NotificationAPI.kt | 22 ++++++ .../api/fragments/LoginWebviewFragment.kt | 23 +++++++ .../api/viewmodels/LoginWebviewViewmodel.kt | 29 ++++++++ .../more/fragments/SettingsFragment.kt | 69 +++++++++++++------ .../more/viewmodels/SettingsViewModel.kt | 26 +++++++ .../notifications/PushNotificationService.kt | 30 +++++--- PennMobile/src/main/res/values/strings.xml | 4 ++ 9 files changed, 205 insertions(+), 36 deletions(-) create mode 100644 PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/NotificationAPI.kt create mode 100644 PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/viewmodels/LoginWebviewViewmodel.kt create mode 100644 PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/SettingsViewModel.kt diff --git a/PennMobile/src/main/AndroidManifest.xml b/PennMobile/src/main/AndroidManifest.xml index a28b75aaa..c41732ad7 100644 --- a/PennMobile/src/main/AndroidManifest.xml +++ b/PennMobile/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ + android:name="com.google.firebase.messaging.default_notification_channel_id" + android:value="default_channel_id"/> + android:resource="@mipmap/ic_launcher_foreground" /> + + + + @DELETE("user/notifications/tokens/android/{token}/") + suspend fun deleteNotificationToken( + @Header("Authorization") bearerToken: String, + @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..0481418d7 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,22 @@ class LoginWebviewFragment : Fragment() { } } + private fun sendNotifToken() { + val mNotificationAPI = MainActivity.notificationAPIInstance + + mActivity.mNetworkManager.getAccessToken { + 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/SettingsFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/fragments/SettingsFragment.kt index 0760ed6ec..a0733d60b 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 @@ -12,15 +13,21 @@ import android.webkit.CookieManager import android.widget.Button import android.widget.EditText import androidx.appcompat.view.ContextThemeWrapper +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.pennapps.labs.pennmobile.MainActivity import com.pennapps.labs.pennmobile.R +import com.pennapps.labs.pennmobile.more.viewmodels.SettingsViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class SettingsFragment : PreferenceFragmentCompat() { private var accountSettings: Preference? = null private var logInOutButton: Preference? = null + private val settingsViewModel: SettingsViewModel by viewModels() private lateinit var mActivity: MainActivity private lateinit var mContext: Context @@ -49,6 +56,8 @@ class SettingsFragment : PreferenceFragmentCompat() { ) { super.onViewCreated(view, savedInstanceState) val sp = PreferenceManager.getDefaultSharedPreferences(mActivity) + val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString() + val notifToken = sp.getString(getString(R.string.notification_token), "").toString() val editor = sp.edit() accountSettings = findPreference("pref_account_edit") accountSettings?.onPreferenceClickListener = @@ -89,28 +98,31 @@ 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, _ -> + deleteNotifToken(bearerToken, notifToken) + 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() + mActivity.startLoginFragment() + }.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() } + .create() + .show() } else { mActivity.startLoginFragment() } @@ -124,4 +136,17 @@ class SettingsFragment : PreferenceFragmentCompat() { mActivity.setTitle(R.string.action_settings) mActivity.setSelectedTab(MainActivity.MORE) } + + private fun deleteNotifToken( + bearerToken: String, + notifToken: String, + ) { + val mNotificationAPI = MainActivity.notificationAPIInstance + Log.i("Notification Token", notifToken) + + lifecycleScope.launch(Dispatchers.Main) { + settingsViewModel.deleteTokenResponse(mNotificationAPI, bearerToken, notifToken) + Log.i("TestLaunch", "Launched!") + } + } } diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/SettingsViewModel.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/SettingsViewModel.kt new file mode 100644 index 000000000..560cb573a --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/SettingsViewModel.kt @@ -0,0 +1,26 @@ +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 SettingsViewModel : ViewModel() { + suspend fun deleteTokenResponse( + mNotificationAPI: NotificationAPI, + bearerToken: String, + notifToken: String, + ) { + try { + val response = mNotificationAPI.deleteNotificationToken(bearerToken, 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 index 179a1ea0a..29c0d4cd6 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/notifications/PushNotificationService.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/notifications/PushNotificationService.kt @@ -5,28 +5,39 @@ 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 -import java.net.URL class PushNotificationService : FirebaseMessagingService() { + private lateinit var mSharedPrefs: SharedPreferences + override fun onNewToken(token: String) { super.onNewToken(token) // Update Server/Database - Log.d("FCM Registration", "Refreshed token: $token") + 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", "Notification received!") + + Log.d("Notification Received", "Notification received!") val title = message.notification?.title val body = message.notification?.body - val imageUrl = message.notification?.imageUrl val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager @@ -45,22 +56,19 @@ class PushNotificationService : FirebaseMessagingService() { 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.drawable.baseline_circle_notifications_24) + .setSmallIcon(R.mipmap.ic_launcher_foreground) + .setLargeIcon(bitMap) .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(pendingIntent) .setAutoCancel(true) - - imageUrl?.let { - val bitmap = BitmapFactory.decodeStream(URL(imageUrl.toString()).openConnection().getInputStream()) - notificationBuilder.setLargeIcon(bitmap) - notificationBuilder.setStyle(NotificationCompat.BigPictureStyle().bigPicture(bitmap)) - } + .setColor(ContextCompat.getColor(this, R.color.penn_red)) notificationManager.notify(1, notificationBuilder.build()) } 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 + From 7e05cf80bc43ae08c88546b0e2ae9821a072d240 Mon Sep 17 00:00:00 2001 From: Skeletrobro <67814129+baronhsieh2005@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:18:44 -0500 Subject: [PATCH 3/6] DeleteToken Fix --- .../more/fragments/PreferenceFragment.kt | 75 +++++++++++++------ .../more/fragments/SettingsFragment.kt | 25 +------ ...gsViewModel.kt => PreferencesViewModel.kt} | 2 +- 3 files changed, 58 insertions(+), 44 deletions(-) rename PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/{SettingsViewModel.kt => PreferencesViewModel.kt} (95%) 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..47a47ba3e 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.PreferencesViewModel 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 preferencesViewModel: PreferencesViewModel by viewModels() override fun onAttach(context: Context) { super.onAttach(context) @@ -72,27 +80,34 @@ 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, _ -> + Log.d("SettingsFragment", "Logout button clicked in 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)) + } + 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() } @@ -261,6 +276,24 @@ class PreferenceFragment : PreferenceFragmentCompat() { alert.show() } + private fun deleteNotifToken(sp: SharedPreferences) { + val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString() + val notifToken = sp.getString(getString(R.string.notification_token), "").toString() + Log.d("SettingsFragment", "deleteNotifToken called with notifToken: $notifToken") + val mNotificationAPI = MainActivity.notificationAPIInstance + Log.i("Notification Token", notifToken) + + lifecycleScope.launch(Dispatchers.IO) { + try { + preferencesViewModel.deleteTokenResponse(mNotificationAPI, bearerToken, notifToken) + Log.d("SettingsFragment", "deleteTokenResponse coroutine completed.") + } catch (e: Exception) { + Log.e("SettingsFragment", "Error in deleteNotifToken: ${e.message}") + 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 a0733d60b..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 @@ -13,21 +13,15 @@ import android.webkit.CookieManager import android.widget.Button import android.widget.EditText import androidx.appcompat.view.ContextThemeWrapper -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.pennapps.labs.pennmobile.MainActivity import com.pennapps.labs.pennmobile.R -import com.pennapps.labs.pennmobile.more.viewmodels.SettingsViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch class SettingsFragment : PreferenceFragmentCompat() { private var accountSettings: Preference? = null private var logInOutButton: Preference? = null - private val settingsViewModel: SettingsViewModel by viewModels() private lateinit var mActivity: MainActivity private lateinit var mContext: Context @@ -56,8 +50,6 @@ class SettingsFragment : PreferenceFragmentCompat() { ) { super.onViewCreated(view, savedInstanceState) val sp = PreferenceManager.getDefaultSharedPreferences(mActivity) - val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString() - val notifToken = sp.getString(getString(R.string.notification_token), "").toString() val editor = sp.edit() accountSettings = findPreference("pref_account_edit") accountSettings?.onPreferenceClickListener = @@ -103,7 +95,7 @@ class SettingsFragment : PreferenceFragmentCompat() { .setTitle("Log out") .setMessage("Are you sure you want to log out?") .setPositiveButton("Logout") { dialog, _ -> - deleteNotifToken(bearerToken, notifToken) + Log.d("SettingsFragment", "Logout button clicked in dialog.") CookieManager.getInstance().removeAllCookie() editor.apply { remove(getString(R.string.penn_password)) @@ -119,10 +111,12 @@ class SettingsFragment : PreferenceFragmentCompat() { 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() } @@ -136,17 +130,4 @@ class SettingsFragment : PreferenceFragmentCompat() { mActivity.setTitle(R.string.action_settings) mActivity.setSelectedTab(MainActivity.MORE) } - - private fun deleteNotifToken( - bearerToken: String, - notifToken: String, - ) { - val mNotificationAPI = MainActivity.notificationAPIInstance - Log.i("Notification Token", notifToken) - - lifecycleScope.launch(Dispatchers.Main) { - settingsViewModel.deleteTokenResponse(mNotificationAPI, bearerToken, notifToken) - Log.i("TestLaunch", "Launched!") - } - } } diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/SettingsViewModel.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferencesViewModel.kt similarity index 95% rename from PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/SettingsViewModel.kt rename to PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferencesViewModel.kt index 560cb573a..591fab4d3 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/SettingsViewModel.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferencesViewModel.kt @@ -6,7 +6,7 @@ import com.pennapps.labs.pennmobile.api.NotificationAPI // Currently only implemented the notification logic, other network logistics to be implemented -class SettingsViewModel : ViewModel() { +class PreferencesViewModel : ViewModel() { suspend fun deleteTokenResponse( mNotificationAPI: NotificationAPI, bearerToken: String, From 6ea307f1bdf2dcbc9751e9e3c61a8c26856ac842 Mon Sep 17 00:00:00 2001 From: Skeletrobro <67814129+baronhsieh2005@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:32:49 -0500 Subject: [PATCH 4/6] Minor fixes --- .../api/fragments/LoginWebviewFragment.kt | 14 ++++++-------- .../more/fragments/PreferenceFragment.kt | 9 ++------- 2 files changed, 8 insertions(+), 15 deletions(-) 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 0481418d7..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 @@ -270,16 +270,14 @@ class LoginWebviewFragment : Fragment() { private fun sendNotifToken() { val mNotificationAPI = MainActivity.notificationAPIInstance - mActivity.mNetworkManager.getAccessToken { - val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString() - val notifToken = sp.getString(getString(R.string.notification_token), "").toString() + 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) + 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) - } + lifecycleScope.launch(Dispatchers.IO) { + loginWebviewViewmodel.sendToken(mNotificationAPI, notGuest, bearerToken, notifToken) } } 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 47a47ba3e..36801dec8 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 @@ -82,10 +82,9 @@ class PreferenceFragment : PreferenceFragmentCompat() { if (pennKey != null) { AlertDialog .Builder(context) - .setTitle("Log out") + .setTitle("Log Out") .setMessage("Are you sure you want to log out?") .setPositiveButton("Logout") { dialog, _ -> - Log.d("SettingsFragment", "Logout button clicked in dialog.") deleteNotifToken(sp) CookieManager.getInstance().removeAllCookie() editor.apply { @@ -100,14 +99,13 @@ class PreferenceFragment : PreferenceFragmentCompat() { 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() - 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() } @@ -279,16 +277,13 @@ class PreferenceFragment : PreferenceFragmentCompat() { private fun deleteNotifToken(sp: SharedPreferences) { val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString() val notifToken = sp.getString(getString(R.string.notification_token), "").toString() - Log.d("SettingsFragment", "deleteNotifToken called with notifToken: $notifToken") val mNotificationAPI = MainActivity.notificationAPIInstance Log.i("Notification Token", notifToken) lifecycleScope.launch(Dispatchers.IO) { try { preferencesViewModel.deleteTokenResponse(mNotificationAPI, bearerToken, notifToken) - Log.d("SettingsFragment", "deleteTokenResponse coroutine completed.") } catch (e: Exception) { - Log.e("SettingsFragment", "Error in deleteNotifToken: ${e.message}") e.printStackTrace() } } From 2f35e4451ed2b854757f42507a78b742f1386ba4 Mon Sep 17 00:00:00 2001 From: Skeletrobro <67814129+baronhsieh2005@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:39:49 -0500 Subject: [PATCH 5/6] Fix MoreFragment initial bug --- .../pennmobile/more/fragments/MoreFragment.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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()) From 37ad439d18060413cb678f940d1578b29df7eb4c Mon Sep 17 00:00:00 2001 From: Skeletrobro <67814129+baronhsieh2005@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:46:15 -0500 Subject: [PATCH 6/6] Rename and Remove delete Authorization --- .../com/pennapps/labs/pennmobile/api/NotificationAPI.kt | 1 - .../labs/pennmobile/more/fragments/PreferenceFragment.kt | 7 +++---- .../{PreferencesViewModel.kt => PreferenceViewModel.kt} | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) rename PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/{PreferencesViewModel.kt => PreferenceViewModel.kt} (88%) 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 index a12c851ed..d6b85d664 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/NotificationAPI.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/NotificationAPI.kt @@ -16,7 +16,6 @@ interface NotificationAPI { @DELETE("user/notifications/tokens/android/{token}/") suspend fun deleteNotificationToken( - @Header("Authorization") bearerToken: String, @Path("token") token: String, ): Response } 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 36801dec8..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 @@ -24,7 +24,7 @@ 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.PreferencesViewModel +import com.pennapps.labs.pennmobile.more.viewmodels.PreferenceViewModel import com.pennapps.labs.pennmobile.showSneakerToast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -36,7 +36,7 @@ class PreferenceFragment : PreferenceFragmentCompat() { private lateinit var mContext: Context private lateinit var mActivity: MainActivity private lateinit var toolbar: Toolbar - private val preferencesViewModel: PreferencesViewModel by viewModels() + private val preferenceViewModel: PreferenceViewModel by viewModels() override fun onAttach(context: Context) { super.onAttach(context) @@ -275,14 +275,13 @@ class PreferenceFragment : PreferenceFragmentCompat() { } private fun deleteNotifToken(sp: SharedPreferences) { - val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString() val notifToken = sp.getString(getString(R.string.notification_token), "").toString() val mNotificationAPI = MainActivity.notificationAPIInstance Log.i("Notification Token", notifToken) lifecycleScope.launch(Dispatchers.IO) { try { - preferencesViewModel.deleteTokenResponse(mNotificationAPI, bearerToken, notifToken) + preferenceViewModel.deleteTokenResponse(mNotificationAPI, notifToken) } catch (e: Exception) { e.printStackTrace() } diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferencesViewModel.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferenceViewModel.kt similarity index 88% rename from PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferencesViewModel.kt rename to PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferenceViewModel.kt index 591fab4d3..bb7b366bc 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferencesViewModel.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/viewmodels/PreferenceViewModel.kt @@ -6,14 +6,13 @@ import com.pennapps.labs.pennmobile.api.NotificationAPI // Currently only implemented the notification logic, other network logistics to be implemented -class PreferencesViewModel : ViewModel() { +class PreferenceViewModel : ViewModel() { suspend fun deleteTokenResponse( mNotificationAPI: NotificationAPI, - bearerToken: String, notifToken: String, ) { try { - val response = mNotificationAPI.deleteNotificationToken(bearerToken, notifToken) + val response = mNotificationAPI.deleteNotificationToken(notifToken) if (response.isSuccessful) { Log.i("Notification Token", "Successfully deleted token") } else {