Skip to content

Commit

Permalink
support for android push notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
psuzn committed Nov 25, 2023
1 parent 7117528 commit 502da19
Show file tree
Hide file tree
Showing 30 changed files with 599 additions and 174 deletions.
9 changes: 6 additions & 3 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ kotlin {
val androidMain by getting {
dependencies {
implementation(project(":shared"))
implementation("androidx.activity:activity-compose:1.7.2")
implementation("androidx.activity:activity-compose:1.8.1")
implementation("androidx.core:core-ktx:1.12.0")

implementation(platform("com.google.firebase:firebase-bom:32.3.1"))
implementation(platform("com.google.firebase:firebase-bom:${Versions.FIREBASE_BOM}"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-messaging-ktx")
}
Expand Down Expand Up @@ -54,12 +54,15 @@ android {
}

buildTypes {
getByName("release") {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
debug {
isMinifyEnabled = false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,20 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import kotlin.time.Duration.Companion.seconds


@SuppressLint("MissingFirebaseInstanceTokenRefresh")
class FcmService : FirebaseMessagingService() {

override fun onMessageReceived(message: RemoteMessage) {
val data = message.toIntent().extras

if (!NotificationParams.isNotification(data))
if (!NotificationParams.isNotification(data)) {
return super.onMessageReceived(message)
}

showNotification(NotificationParams(data!!))
}
}


private fun Context.manifestMetadata() = try {
applicationContext.packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
.metaData
Expand All @@ -41,22 +40,21 @@ private fun Context.manifestMetadata() = try {
@SuppressLint("VisibleForTests")
private fun Context.showNotification(notificationParams: NotificationParams) {
val manifestMetadata = manifestMetadata()
val notificationChannel = CommonNotificationBuilder.getOrCreateChannel(
val channel = CommonNotificationBuilder.getOrCreateChannel(
this,
notificationParams.notificationChannelId,
manifestMetadata
manifestMetadata,
)


val info = CommonNotificationBuilder.createNotificationInfo(
this,
this,
notificationParams,
notificationChannel,
manifestMetadata
channel,
manifestMetadata,
)

val url = notificationParams.getString(MessageNotificationKeys.IMAGE_URL);
val url = notificationParams.getString(MessageNotificationKeys.IMAGE_URL)

CoroutineScope(Dispatchers.IO).launch {
if (url != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package me.sujanpoudel.playdeals

import android.app.NotificationManager
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.setContent
import androidx.compose.runtime.CompositionLocalProvider
import androidx.core.view.WindowCompat
import com.google.firebase.messaging.FirebaseMessagingService
import me.sujanpoudel.playdeals.common.PlayDealsAppAndroid
import me.sujanpoudel.playdeals.common.navigation.BackPressConsumer
import me.sujanpoudel.playdeals.common.navigation.LocalBackPressConsumer
Expand All @@ -27,7 +24,6 @@ class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerNotificationChannels()

onBackPressedDispatcher.addCallback(callBack)

Expand All @@ -42,13 +38,3 @@ class MainActivity : ComponentActivity() {
}
}
}

private fun Context.registerNotificationChannels() {

val notificationManager = getSystemService(FirebaseMessagingService.NOTIFICATION_SERVICE) as NotificationManager

PushNotificationTopic.entries
.forEach {
notificationManager.createNotificationChannel(it.notificationChannel())
}
}

This file was deleted.

2 changes: 1 addition & 1 deletion androidApp/src/androidMain/res/values-night/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
</style>

</resources>
</resources>
3 changes: 2 additions & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ object Versions {
const val COMPOSE = "1.5.1"
const val SETTINGS = "1.0.0"
const val SQLDELIGHT = "2.0.0"
const val FIREBASE_BOM = "32.3.1"

const val KTOR = "2.3.4"
const val KTOR = "2.3.5"
const val KODE_IN = "7.20.2"
const val COROUTINE = "1.7.3"
const val KOTLINX_DATE_TIME = "0.4.0"
Expand Down
42 changes: 42 additions & 0 deletions iosApp/iosApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,54 @@
path = "Preview Content";
sourceTree = "<group>";
};
660552A59E317B1A0521E533 /* xcuserdata */ = {
isa = PBXGroup;
children = (
66055D0E5EE5D965A6F2784A /* psuzn.xcuserdatad */,
);
name = xcuserdata;
path = iosApp.xcworkspace/xcuserdata;
sourceTree = "<group>";
};
66055371CAB4F04A02474CCD /* psuzn.xcuserdatad */ = {
isa = PBXGroup;
children = (
660556F1E49162B926360BDA /* xcschemes */,
);
name = psuzn.xcuserdatad;
path = iosApp.xcodeproj/xcuserdata/psuzn.xcuserdatad;
sourceTree = "<group>";
};
660556F1E49162B926360BDA /* xcschemes */ = {
isa = PBXGroup;
children = (
);
path = xcschemes;
sourceTree = "<group>";
};
66055A63D5516D613AE11F11 /* xcschemes */ = {
isa = PBXGroup;
children = (
);
path = xcschemes;
sourceTree = "<group>";
};
66055D0E5EE5D965A6F2784A /* psuzn.xcuserdatad */ = {
isa = PBXGroup;
children = (
66055A63D5516D613AE11F11 /* xcschemes */,
);
path = psuzn.xcuserdatad;
sourceTree = "<group>";
};
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */,
D1373ED9D54E3DB0D54869DB /* Pods */,
660552A59E317B1A0521E533 /* xcuserdata */,
66055371CAB4F04A02474CCD /* psuzn.xcuserdatad */,
);
sourceTree = "<group>";
};
Expand Down
15 changes: 12 additions & 3 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ kotlin {
implementation("io.ktor:ktor-serialization-kotlinx-json:${Versions.KTOR}")
implementation("io.ktor:ktor-client-logging:${Versions.KTOR}")

implementation("media.kamel:kamel-image:0.8.1")
implementation("media.kamel:kamel-image:0.8.3")
implementation("com.russhwolf:multiplatform-settings:${Versions.SETTINGS}")
implementation("com.russhwolf:multiplatform-settings-no-arg:${Versions.SETTINGS}")

Expand All @@ -70,6 +70,11 @@ kotlin {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("app.cash.sqldelight:android-driver:${Versions.SQLDELIGHT}")
implementation("androidx.startup:startup-runtime:1.1.1")
implementation("com.google.accompanist:accompanist-permissions:0.32.0")

implementation(platform("com.google.firebase:firebase-bom:${Versions.FIREBASE_BOM}"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-messaging-ktx")
}
}

Expand Down Expand Up @@ -133,10 +138,14 @@ android {
}

buildTypes {
getByName("release") {
isMinifyEnabled = false
release {
isMinifyEnabled = true
proguardFiles("proguard-rules.pro")
}

debug {
isMinifyEnabled = false
}
}
}

Expand Down
1 change: 1 addition & 0 deletions shared/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<provider
android:name="androidx.startup.InitializationProvider"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package me.sujanpoudel.playdeals.common

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale

@OptIn(ExperimentalPermissionsApi::class)
class AndroidPermissionManager
private constructor(
private val context: Context,
private val state: PermissionState,
) : PermissionManager {

override val permissionState: State<PermissionStatus> = derivedStateOf {
when (state.status) {
is com.google.accompanist.permissions.PermissionStatus.Denied -> if (state.status.shouldShowRationale) {
PermissionStatus.Denied
} else {
PermissionStatus.NotAsked
}

com.google.accompanist.permissions.PermissionStatus.Granted -> PermissionStatus.Granted
}
}

override fun askForPermission() = state.launchPermissionRequest()

override fun showRationale() = context.openAppSettingScreen()

companion object {
@Composable
fun rememberPermissionManager(permission: String): PermissionManager {
val state = rememberPermissionState(permission = permission)
val context = LocalContext.current
return remember {
AndroidPermissionManager(context, state)
}
}
}
}

private fun Context.openAppSettingScreen() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:$packageName")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
package me.sujanpoudel.playdeals.common

import android.Manifest
import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import me.sujanpoudel.playdeals.common.pushNotification.AndroidNotificationManager
import me.sujanpoudel.playdeals.common.pushNotification.LocalNotificationManager
import me.sujanpoudel.playdeals.common.pushNotification.LocalPushNotificationPermissionManager

@Composable
fun PlayDealsAppAndroid() {
PlayDealsApp()
val permissionManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
AndroidPermissionManager.rememberPermissionManager(Manifest.permission.POST_NOTIFICATIONS)
} else {
PermissionManager.ALWAYS_GRANTED_PERMISSION
}

val context = LocalContext.current

val notificationManager = remember { AndroidNotificationManager(context) }

CompositionLocalProvider(
LocalPushNotificationPermissionManager provides permissionManager,
LocalNotificationManager provides notificationManager,
) {
PlayDealsApp()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package me.sujanpoudel.playdeals.common.pushNotification

import android.app.NotificationChannel
import android.content.Context
import com.google.firebase.messaging.FirebaseMessaging
import com.google.firebase.messaging.FirebaseMessagingService

class AndroidNotificationManager(context: Context) : NotificationManager {

init {
context.registerNotificationChannels()
}

override fun subscribeToTopic(topic: String) {
FirebaseMessaging.getInstance().subscribeToTopic(topic)
}

override fun unSubscribeFromTopic(topic: String) {
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
}
}

fun Context.registerNotificationChannels() {
val notificationManager =
getSystemService(FirebaseMessagingService.NOTIFICATION_SERVICE) as android.app.NotificationManager

PushNotificationTopic.entries
.forEach {
val channel = NotificationChannel(it.identifier, it.label, android.app.NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
}
Loading

0 comments on commit 502da19

Please sign in to comment.