From 2315436ae7e79d264c43b9e82aaeeb7adec37f3f Mon Sep 17 00:00:00 2001 From: Ian Atha Date: Sat, 11 Nov 2023 07:52:24 +0200 Subject: [PATCH] feat: integrate with Firebase In-App Messaging & Notifications --- app/build.gradle.kts | 13 +- app/src/main/AndroidManifest.xml | 27 +++- .../java/io/atha/bababasic/ActivityAbout.kt | 4 +- .../java/io/atha/bababasic/ActivityMain.kt | 49 +----- .../java/io/atha/bababasic/ActivityRun.kt | 3 +- .../java/io/atha/bababasic/BabaActivity.kt | 145 ++++++++++++++++++ .../bababasic/MyFirebaseMessagingService.kt | 69 +++++++++ app/src/main/res/values/strings.xml | 4 + 8 files changed, 254 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/io/atha/bababasic/BabaActivity.kt create mode 100644 app/src/main/java/io/atha/bababasic/MyFirebaseMessagingService.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 99bc18c..6b00b7c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -64,32 +64,35 @@ dependencies { implementation("com.google.android.play:app-update-ktx:2.0.1") - implementation(platform("com.google.firebase:firebase-bom:32.4.1")) + implementation(platform("com.google.firebase:firebase-bom:32.5.0")) + implementation("com.google.firebase:firebase-config") implementation("com.google.firebase:firebase-analytics") implementation("com.google.firebase:firebase-crashlytics") - implementation("com.google.firebase:firebase-analytics") + implementation("com.google.firebase:firebase-inappmessaging-display") + implementation("com.google.firebase:firebase-config-ktx") + implementation("com.google.firebase:firebase-messaging-ktx") implementation("com.google.android.gms:play-services-tagmanager:18.0.4") + implementation("com.google.android.gms:play-services-measurement-api:21.4.0") implementation("com.github.ianatha.termux:termux-shared:feat-non-proc-terms-SNAPSHOT") implementation("com.github.ianatha.termux:terminal-view:feat-non-proc-terms-SNAPSHOT") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") implementation(platform("io.github.Rosemoe.sora-editor:bom:0.22.1")) implementation("io.github.Rosemoe.sora-editor:editor") implementation("io.github.Rosemoe.sora-editor:language-java") implementation("io.github.Rosemoe.sora-editor:language-textmate") implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.9.0") implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") implementation("androidx.activity:activity-compose:1.8.0") + implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") - implementation("com.google.android.gms:play-services-measurement-api:21.4.0") + implementation("com.google.android.material:material:1.10.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3db0d70..d13043b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,14 +2,20 @@ - - - + + + - + + + + + + + + + + + + + @@ -61,5 +75,4 @@ - \ No newline at end of file diff --git a/app/src/main/java/io/atha/bababasic/ActivityAbout.kt b/app/src/main/java/io/atha/bababasic/ActivityAbout.kt index 1d777b1..ee0effe 100644 --- a/app/src/main/java/io/atha/bababasic/ActivityAbout.kt +++ b/app/src/main/java/io/atha/bababasic/ActivityAbout.kt @@ -7,7 +7,7 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import io.atha.bababasic.databinding.ActivityAboutBinding -class ActivityAbout : AppCompatActivity() { +class ActivityAbout : BabaActivity() { private lateinit var binding: ActivityAboutBinding override fun onCreate(savedInstanceState: Bundle?) { @@ -27,5 +27,7 @@ class ActivityAbout : AppCompatActivity() { intent.data = Uri.parse("https://github.com/ianatha/bababasic") startActivity(intent) } + + initFirebase() } } \ No newline at end of file diff --git a/app/src/main/java/io/atha/bababasic/ActivityMain.kt b/app/src/main/java/io/atha/bababasic/ActivityMain.kt index 692dafc..0d8fa9a 100644 --- a/app/src/main/java/io/atha/bababasic/ActivityMain.kt +++ b/app/src/main/java/io/atha/bababasic/ActivityMain.kt @@ -8,7 +8,6 @@ import android.content.res.Configuration import android.graphics.Typeface import android.net.Uri import android.os.Bundle -import android.provider.Settings import android.text.Editable import android.text.TextWatcher import android.view.KeyEvent @@ -17,13 +16,6 @@ import android.view.MenuItem import android.view.View import android.widget.PopupMenu import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import com.google.android.play.core.appupdate.AppUpdateManagerFactory -import com.google.android.play.core.install.model.AppUpdateType -import com.google.android.play.core.install.model.UpdateAvailability -import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.analytics.ktx.analytics -import com.google.firebase.ktx.Firebase import io.atha.bababasic.databinding.ActivityMainBinding import io.atha.bababasic.editor.switchThemeIfRequired import io.atha.libbababasic.Interpreter.checkSyntax @@ -51,9 +43,6 @@ import io.github.rosemoe.sora.widget.component.EditorAutoCompletion import io.github.rosemoe.sora.widget.component.Magnifier import io.github.rosemoe.sora.widget.getComponent import io.github.rosemoe.sora.widget.subscribeEvent -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import org.eclipse.tm4e.core.registry.IThemeSource import java.io.BufferedReader import java.io.IOException @@ -62,49 +51,17 @@ import java.io.OutputStream import java.util.regex.PatternSyntaxException import java.util.stream.Collectors - -class ActivityMain : AppCompatActivity() { - lateinit var firebaseAnalytics: FirebaseAnalytics +class ActivityMain : BabaActivity() { lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - firebaseAnalytics = Firebase.analytics - CrashHandler.INSTANCE.init(this) + binding = ActivityMainBinding.inflate(layoutInflater) prepareView() setContentView(binding.root) - val testLabSetting = Settings.System.getString(contentResolver, "firebase.test.lab") - if ("true" == testLabSetting) { - val bundle = Bundle().apply { - putString("traffic_type", "testlab") - } - Firebase.analytics.setDefaultEventParameters(bundle) - } - - checkForUpdates() - } - - private fun checkForUpdates() { - val context = this - CoroutineScope(Dispatchers.Main).launch { - val appUpdateManager = AppUpdateManagerFactory.create(context) - val appUpdateInfoTask = appUpdateManager.appUpdateInfo - - appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> - if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE - && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) - ) { - appUpdateManager.startUpdateFlowForResult( - appUpdateInfo, - AppUpdateType.IMMEDIATE, - context, - 1011 - ) - } - } - } + initFirebase() } private var searchOptions = SearchOptions(false, false) diff --git a/app/src/main/java/io/atha/bababasic/ActivityRun.kt b/app/src/main/java/io/atha/bababasic/ActivityRun.kt index 3b69c0d..3a2c723 100644 --- a/app/src/main/java/io/atha/bababasic/ActivityRun.kt +++ b/app/src/main/java/io/atha/bababasic/ActivityRun.kt @@ -18,7 +18,7 @@ data class RunDatum( val src: String, ) : Serializable -class ActivityRun : AppCompatActivity() { +class ActivityRun : BabaActivity() { private lateinit var viewClient: TermuxTerminalViewClient private lateinit var mPreferences: AppSharedPreferences lateinit var binding: ActivityRunBinding @@ -144,6 +144,7 @@ class ActivityRun : AppCompatActivity() { session.initializeEmulator(1, 1) setContentView(binding.root) viewClient.onCreate() + initFirebase() } fun getTerminalView(): TerminalView = binding.terminal diff --git a/app/src/main/java/io/atha/bababasic/BabaActivity.kt b/app/src/main/java/io/atha/bababasic/BabaActivity.kt new file mode 100644 index 0000000..ccdc9ec --- /dev/null +++ b/app/src/main/java/io/atha/bababasic/BabaActivity.kt @@ -0,0 +1,145 @@ +package io.atha.bababasic + +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.Settings +import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS +import android.util.Log +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import com.google.android.gms.tasks.OnCompleteListener +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.install.model.AppUpdateType +import com.google.android.play.core.install.model.UpdateAvailability +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.inappmessaging.ktx.inAppMessaging +import com.google.firebase.ktx.Firebase +import com.google.firebase.messaging.FirebaseMessaging +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +abstract class BabaActivity : AppCompatActivity() { + lateinit var firebaseAnalytics: FirebaseAnalytics + + protected fun initFirebase() { + firebaseAnalytics = Firebase.analytics + CrashHandler.INSTANCE.init(this) + val remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig + val configSettings = remoteConfigSettings { + minimumFetchIntervalInSeconds = 3600 + } + remoteConfig.setConfigSettingsAsync(configSettings) + remoteConfig.setDefaultsAsync(mapOf( + "feature_docs" to "0" + )) + + Firebase.inAppMessaging.addClickListener { inAppMessage, action -> + Log.i("inapp", "clicked ${inAppMessage} ${action}") + } + + val testLabSetting = Settings.System.getString(contentResolver, "firebase.test.lab") + if ("true" == testLabSetting) { + val bundle = Bundle().apply { + putString("traffic_type", "testlab") + } + Firebase.analytics.setDefaultEventParameters(bundle) + } + + FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task -> + if (!task.isSuccessful) { + Log.w("fcm", "Fetching FCM registration token failed", task.exception) + return@OnCompleteListener + } + + // Get new FCM registration token + val token = task.result + + // Log and toast + Log.d("fcm", token) + }) + + if (Build.VERSION.SDK_INT >= 33) { +// notificationPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) + } else { + hasNotificationPermissionGranted = true + } + } + + private fun showSettingDialog() { + MaterialAlertDialogBuilder(this, com.google.android.material.R.style.MaterialAlertDialog_Material3) + .setTitle("Notification Permission") + .setMessage("Notification permission is required, Please allow notification permission from setting") + .setPositiveButton("Ok") { _, _ -> + val intent = Intent(ACTION_APPLICATION_DETAILS_SETTINGS) + intent.data = Uri.parse("package:$packageName") + startActivity(intent) + } + .setNegativeButton("Cancel", null) + .show() + } + + private fun showNotificationPermissionRationale() { + MaterialAlertDialogBuilder(this, com.google.android.material.R.style.MaterialAlertDialog_Material3) + .setTitle("Alert") + .setMessage("Notification permission is required, to show notification") + .setPositiveButton("Ok") { _, _ -> + if (Build.VERSION.SDK_INT >= 33) { + notificationPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) + } + } + .setNegativeButton("Cancel", null) + .show() + } + + var hasNotificationPermissionGranted = false + + private val notificationPermissionLauncher = + this.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + hasNotificationPermissionGranted = isGranted + if (!isGranted) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= 33) { + if (shouldShowRequestPermissionRationale(android.Manifest.permission.POST_NOTIFICATIONS)) { + showNotificationPermissionRationale() + } else { + showSettingDialog() + } + } + } + } else { + Toast.makeText(applicationContext, "notification permission granted", Toast.LENGTH_SHORT) + .show() + } + } + + + private fun checkForUpdates() { + val context = this + CoroutineScope(Dispatchers.Main).launch { + val appUpdateManager = AppUpdateManagerFactory.create(context) + val appUpdateInfoTask = appUpdateManager.appUpdateInfo + + appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> + if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE + && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) + ) { + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + AppUpdateType.IMMEDIATE, + context, + 1011 + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/atha/bababasic/MyFirebaseMessagingService.kt b/app/src/main/java/io/atha/bababasic/MyFirebaseMessagingService.kt new file mode 100644 index 0000000..2adbadc --- /dev/null +++ b/app/src/main/java/io/atha/bababasic/MyFirebaseMessagingService.kt @@ -0,0 +1,69 @@ +package io.atha.bababasic + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.media.RingtoneManager +import android.os.Build +import android.util.Log +import androidx.core.app.NotificationCompat +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage + +class MyFirebaseMessagingService : FirebaseMessagingService() { + override fun onMessageReceived(remoteMessage: RemoteMessage) { + remoteMessage.notification?.let { + it.body?.let { body -> sendNotification(body) } + } + } + + override fun onNewToken(token: String) { + sendRegistrationToServer(token) + } + + private fun sendRegistrationToServer(token: String?) { + Log.d(TAG, "FCM registrationToken = $token") + } + + private fun sendNotification(messageBody: String) { + val requestCode = 0 + val intent = Intent(this, ActivityMain::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + val pendingIntent = PendingIntent.getActivity( + this, + requestCode, + intent, + PendingIntent.FLAG_IMMUTABLE, + ) + + val channelId = getString(R.string.default_notification_channel_id) + val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + val notificationBuilder = NotificationCompat.Builder(this, channelId) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentTitle(getString(R.string.fcm_message)) + .setContentText(messageBody) + .setAutoCancel(true) + .setSound(defaultSoundUri) + .setContentIntent(pendingIntent) + + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + channelId, + "Channel human readable title", + NotificationManager.IMPORTANCE_DEFAULT, + ) + notificationManager.createNotificationChannel(channel) + } + + val notificationId = 29330 + notificationManager.notify(notificationId, notificationBuilder.build()) + } + + companion object { + private const val TAG = "MyFirebaseMsgService" + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e976d8..188f5a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,8 @@ + fcm_default_channel + Weather + FCM Message + Last Next Replace