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 0cc3cde..08c6927 100644 --- a/app/src/main/java/io/atha/bababasic/ActivityAbout.kt +++ b/app/src/main/java/io/atha/bababasic/ActivityAbout.kt @@ -5,7 +5,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?) { @@ -19,5 +19,7 @@ class ActivityAbout : AppCompatActivity() { binding.okButton.setOnClickListener { finish() } + + 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 2a672af..36f0436 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 @@ -50,9 +42,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 @@ -61,49 +50,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..6eecff2 --- /dev/null +++ b/app/src/main/java/io/atha/bababasic/MyFirebaseMessagingService.kt @@ -0,0 +1,140 @@ +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 android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.NotificationCompat +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage + +class MyFirebaseMessagingService : FirebaseMessagingService() { + + /** + * Called when message is received. + * + * @param remoteMessage Object representing the message received from Firebase Cloud Messaging. + */ + // [START receive_message] + override fun onMessageReceived(remoteMessage: RemoteMessage) { + // [START_EXCLUDE] + // There are two types of messages data messages and notification messages. Data messages are handled + // here in onMessageReceived whether the app is in the foreground or background. Data messages are the type + // traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app + // is in the foreground. When the app is in the background an automatically generated notification is displayed. + // When the user taps on the notification they are returned to the app. Messages containing both notification + // and data payloads are treated as notification messages. The Firebase console always sends notification + // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options + // [END_EXCLUDE] + + // TODO(developer): Handle FCM messages here. + // Not getting messages here? See why this may be: https://goo.gl/39bRNJ + Log.d(TAG, "From: ${remoteMessage.from}") + + // Check if message contains a data payload. + if (remoteMessage.data.isNotEmpty()) { + Log.d(TAG, "Message data payload: ${remoteMessage.data}") + handleNow() + } + + // Check if message contains a notification payload. + remoteMessage.notification?.let { + Log.d(TAG, "Message Notification Body: ${it.body}") + it.body?.let { body -> sendNotification(body) } + } + + // Also if you intend on generating your own notifications as a result of a received FCM + // message, here is where that should be initiated. See sendNotification method below. + } + // [END receive_message] + + + // [START on_new_token] + /** + * Called if the FCM registration token is updated. This may occur if the security of + * the previous token had been compromised. Note that this is called when the + * FCM registration token is initially generated so this is where you would retrieve the token. + */ + override fun onNewToken(token: String) { + Log.d(TAG, "Refreshed token: $token") + + // If you want to send messages to this application instance or + // manage this apps subscriptions on the server side, send the + // FCM registration token to your app server. + sendRegistrationToServer(token) + } + // [END on_new_token] + + /** + * Handle time allotted to BroadcastReceivers. + */ + private fun handleNow() { + Log.d(TAG, "Short lived task is done.") + } + + /** + * Persist token to third-party servers. + * + * Modify this method to associate the user's FCM registration token with any server-side account + * maintained by your application. + * + * @param token The new token. + */ + private fun sendRegistrationToServer(token: String?) { + // TODO: Implement this method to send token to your app server. + Log.d(TAG, "sendRegistrationTokenToServer($token)") + } + + /** + * Create and show a simple notification containing the received FCM message. + * + * @param messageBody FCM message body received. + */ + 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 + + // Since android Oreo notification channel is needed. + 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 0a13c3e..c54807c 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