Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/push notification #73

Merged
merged 49 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
083ef07
feat: Added notification logo and configure manifests
DanielGreenEngineer Nov 1, 2024
bc69448
feat: Added notification custom view
DanielGreenEngineer Nov 2, 2024
a57eb14
feat: Added actual sizes and loading image also radius for notification
DanielGreenEngineer Nov 2, 2024
bf41538
feat: Added rounded image view
DanielGreenEngineer Nov 5, 2024
380dc76
feat: Getting sizes from dimens
DanielGreenEngineer Nov 5, 2024
5a6f8bb
feat: Added click handling and sending params to receiver
DanielGreenEngineer Nov 5, 2024
4aafee0
feat: Added handling actions and switching images
DanielGreenEngineer Nov 5, 2024
47138c3
refactor: Separate creating notification function
DanielGreenEngineer Nov 5, 2024
621add3
feat: Handle views visibility
DanielGreenEngineer Nov 5, 2024
94b8513
feat: Added sending debug push notification
DanielGreenEngineer Nov 5, 2024
05283ad
feat: Added modular notification handling according to FSD structure
DanielGreenEngineer Nov 6, 2024
33dc11b
feat: Separate helper and get id from constructor
DanielGreenEngineer Nov 7, 2024
d33fc1e
feat: Added async loading bitmaps
DanielGreenEngineer Nov 7, 2024
eb5f76d
feat: Added notification model also change sending params
DanielGreenEngineer Nov 7, 2024
e0dffa3
feat: Added view helper
DanielGreenEngineer Nov 7, 2024
ee6c30d
feat: Added navigation helper
DanielGreenEngineer Nov 7, 2024
acc1b96
feat: Separate constants from helpers
DanielGreenEngineer Nov 7, 2024
5e6f082
chore: Optimized imports for constants
DanielGreenEngineer Nov 7, 2024
587c9ac
feat: Separate notification handler
DanielGreenEngineer Nov 7, 2024
2cb3258
feat: Added mapper and click handler
DanielGreenEngineer Nov 7, 2024
48b8929
feat: Remove useless provider
DanielGreenEngineer Nov 7, 2024
966a397
feat: Change worker builder to OneTimeWorkRequestBuilder
DanielGreenEngineer Nov 7, 2024
62ae417
feat: Added loading images with async await
DanielGreenEngineer Nov 7, 2024
6c2b88e
feat: Added resource object
DanielGreenEngineer Nov 7, 2024
5ed55d3
feat: Added AtomicInteger request code generator
DanielGreenEngineer Nov 7, 2024
9a7ee38
refactor: Remove useless resources
DanielGreenEngineer Nov 7, 2024
6b207f6
feat: Added action and empty field error
DanielGreenEngineer Nov 11, 2024
18a60f0
feat: Added network error
DanielGreenEngineer Nov 11, 2024
162666f
feat: Added resource, network and json errors
DanielGreenEngineer Nov 11, 2024
b722f0d
feat: Remove core module and separate handler from feature
DanielGreenEngineer Nov 11, 2024
4bd7969
chore: Moved data mapper to another package
DanielGreenEngineer Nov 11, 2024
f871c82
feat: Switch receiver to service
DanielGreenEngineer Nov 12, 2024
11acf01
chore: Remove debug logs
DanielGreenEngineer Nov 12, 2024
572b170
feat: Added progress container
DanielGreenEngineer Nov 12, 2024
9005094
feat: Added errors localization
DanielGreenEngineer Nov 12, 2024
c42cff0
feat: Getting errors from resources
DanielGreenEngineer Nov 12, 2024
d3298b0
feat: Added constant resources
DanielGreenEngineer Nov 12, 2024
e97734d
feat: Added progress indicator
DanielGreenEngineer Nov 12, 2024
b3dc61e
feat: Added DI integration notification manager
DanielGreenEngineer Nov 13, 2024
b5db422
feat: Added injection manager and initialization
DanielGreenEngineer Nov 13, 2024
eeb6fd2
feat: Added unique notification id
DanielGreenEngineer Nov 13, 2024
0fd6ddb
chore: Remove useless id
DanielGreenEngineer Nov 13, 2024
a4bfc40
feat: Change mutableMap to NotificationData
DanielGreenEngineer Nov 13, 2024
638ca7e
feat: Separate click handler to data sender and extractor
DanielGreenEngineer Nov 13, 2024
5078a9c
feat: Added handling error
DanielGreenEngineer Nov 13, 2024
24afa92
feat: Added handling error when image is loading
DanielGreenEngineer Nov 14, 2024
8f3ad53
feat: Separate navigation functions
DanielGreenEngineer Nov 14, 2024
16ab660
feat: Added notification data extension
DanielGreenEngineer Nov 14, 2024
81c8e3e
style: Reformat code and optimized imports, also add empty last lines
DanielGreenEngineer Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion personalization-sdk/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
</intent-filter>
</service>
<receiver
android:name=".notification.NotificationBroadcastReceiver"
android:name=".features.notification.data.broadcast.NotificationBroadcastReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="ACTION_NEXT_IMAGE" />
<action android:name="ACTION_PREVIOUS_IMAGE" />
</intent-filter>
</receiver>

</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import com.personalization.api.managers.RecommendationManager
import com.personalization.api.managers.SearchManager
import com.personalization.api.managers.TrackEventManager
import com.personalization.di.DaggerSdkComponent
import com.personalization.notification.NotificationHandler
import com.personalization.notification.NotificationHelper
import com.personalization.features.notification.presentation.helpers.NotificationHelper
import com.personalization.handlers.notifications.NotificationHandler
import com.personalization.sdk.domain.usecases.network.AddTaskToQueueUseCase
import com.personalization.sdk.domain.usecases.network.InitNetworkUseCase
import com.personalization.sdk.domain.usecases.network.SendNetworkMethodUseCase
Expand Down Expand Up @@ -121,7 +121,6 @@ open class SDK {

segment = getPreferencesValueUseCase.getSegment()

NotificationHelper.notificationType = notificationType
NotificationHelper.notificationId = notificationId

notificationHandler.initialize(context)
Expand Down Expand Up @@ -805,7 +804,7 @@ open class SDK {
notificationReceived(remoteMessage.data)

onMessageListener?.let { listener ->
val data = notificationHandler.prepareData(remoteMessage)
val data = notificationHandler.prepareData(remoteMessage = remoteMessage)
listener.onMessage(data)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.personalization.errors

import android.util.Log

class ActionsError(
private val tag: String,
private val functionName: String,
private val actionName: String,
private val message: String,
) {

fun logError() {
Log.e(
/* tag = */ tag,
/* msg = */ "Error caught in $functionName : $message $actionName"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.personalization.errors

import android.util.Log

class EmptyFieldError(
private val tag: String,
private val functionName: String,
private val message: String,
) {

fun logError() {
Log.e(
/* tag = */ tag,
/* msg = */ "Error caught in $functionName : $message"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.personalization.errors

import android.util.Log

class NetworkError(
private val tag: String,
private val functionName: String
) {

fun logError() {
Log.e(
/* tag = */ tag,
/* msg = */ "Error caught in $functionName : Network connection issue "
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.personalization.errors

import android.util.Log

class ParsingJsonError(
private val tag: String,
private val functionName: String,
private val message: String
) {

fun logError() {
Log.e(
/* tag = */ tag,
/* msg = */ "Error caught in $functionName: $message"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.personalization.errors

import android.util.Log

class ResourceLoadError(
private val tag: String,
private val functionName: String,
private val message: String
) {

fun logError() {
Log.e(
/* tag = */ tag,
/* msg = */ "Error caught in $functionName: $message"
)
}
}
TorinAsakura marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.personalization.features.notification.data.broadcast

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.personalization.errors.ActionsError
import com.personalization.errors.EmptyFieldError
import com.personalization.features.notification.data.worker.UpdateNotificationWorker
import com.personalization.features.notification.domain.model.NotificationConstants.ACTION_NEXT_IMAGE
import com.personalization.features.notification.domain.model.NotificationConstants.ACTION_PREVIOUS_IMAGE
import com.personalization.features.notification.domain.model.NotificationConstants.CURRENT_IMAGE_INDEX
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_BODY
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_IMAGES
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_TITLE

class NotificationBroadcastReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
val currentIndex = intent.getIntExtra(CURRENT_IMAGE_INDEX, 0)
val images = intent.getStringExtra(NOTIFICATION_IMAGES)
val title = intent.getStringExtra(NOTIFICATION_TITLE)
val body = intent.getStringExtra(NOTIFICATION_BODY)

when (action) {
ACTION_NEXT_IMAGE, ACTION_PREVIOUS_IMAGE -> {
when {
!images.isNullOrEmpty() && !title.isNullOrEmpty() && !body.isNullOrEmpty() -> {
val inputData = Data.Builder()
.putString(NOTIFICATION_IMAGES, images)
.putString(NOTIFICATION_TITLE, title)
.putString(NOTIFICATION_BODY, body)
.putInt(CURRENT_IMAGE_INDEX, currentIndex)
.build()
TorinAsakura marked this conversation as resolved.
Show resolved Hide resolved

val updateNotificationWork =
OneTimeWorkRequestBuilder<UpdateNotificationWorker>()
.setInputData(inputData)
.setConstraints(
Constraints.Builder().setRequiredNetworkType(
networkType = NetworkType.UNMETERED
).build()
)
.build()

WorkManager.getInstance(context).enqueueUniqueWork(
/* uniqueWorkName = */ UPDATE_WORK,
/* existingWorkPolicy = */ ExistingWorkPolicy.APPEND_OR_REPLACE,
/* work = */ updateNotificationWork
)
}

else -> EmptyFieldError(
tag = TAG,
functionName = FUNC_ON_RECEIVE,
message = "One of the fields is empty or null"
).logError()
}
}

else -> ActionsError(
tag = TAG,
functionName = FUNC_ON_RECEIVE,
actionName = action.orEmpty(),
message = "Due to unknown action"
).logError()
}
}

companion object {
private const val TAG = "NotificationBroadcastReceiver"
private const val FUNC_ON_RECEIVE = "OnReceive"
private const val UPDATE_WORK = "update_notification_work"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.personalization.features.notification.data.helpers

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import com.personalization.R

object NotificationChannelHelper {

fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = context.getString(R.string.notification_channel_id)
val channelName = context.getString(R.string.notification_channel_name)
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager?.createNotificationChannel(
/* channel = */ NotificationChannel(
/* id = */ channelId,
/* name = */ channelName,
/* importance = */ NotificationManager.IMPORTANCE_LOW
)
)
}
}
}
TorinAsakura marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.personalization.features.notification.data.mapper

import com.google.firebase.messaging.RemoteMessage
import com.personalization.features.notification.domain.model.NotificationConstants.ANALYTICS_LABEL_FIELD
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_BODY
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_IMAGES
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_TITLE

object NotificationDataMapper {

fun mapRemoteMessageToData(remoteMessage: RemoteMessage): MutableMap<String, String> {
val data: MutableMap<String, String> = HashMap(remoteMessage.data)
remoteMessage.notification?.let { notification ->
addNotificationData(
notification = notification,
data = data
)
}
data[NOTIFICATION_IMAGES]?.takeIf { it.isNotEmpty() }
?.let { data[NOTIFICATION_IMAGES] = it }
data[ANALYTICS_LABEL_FIELD]?.takeIf { it.isNotEmpty() }
?.let { data[ANALYTICS_LABEL_FIELD] = it }
return data
}

private fun addNotificationData(
notification: RemoteMessage.Notification,
data: MutableMap<String, String>
) {
notification.title?.takeIf { it.isNotEmpty() }?.let { data[NOTIFICATION_TITLE] = it }
notification.body?.takeIf { it.isNotEmpty() }?.let { data[NOTIFICATION_BODY] = it }
notification.imageUrl?.let { data[NOTIFICATION_IMAGES] = it.toString() }
}
}
TorinAsakura marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.personalization.features.notification.data.worker

import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.personalization.SDK
import com.personalization.errors.NetworkError
import com.personalization.features.notification.domain.model.NotificationConstants.CURRENT_IMAGE_INDEX
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_BODY
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_IMAGES
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_TITLE
import com.personalization.features.notification.domain.model.NotificationData
import com.personalization.features.notification.presentation.helpers.NotificationHelper
import com.personalization.features.notification.presentation.helpers.NotificationImageHelper
import java.io.IOException

class UpdateNotificationWorker(
context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

override suspend fun doWork(): Result {
val context = applicationContext

val newIndex = inputData.getInt(CURRENT_IMAGE_INDEX, 0)
val images = inputData.getString(NOTIFICATION_IMAGES)
val title = inputData.getString(NOTIFICATION_TITLE)
val body = inputData.getString(NOTIFICATION_BODY)

return if (images.isNullOrEmpty() || title.isNullOrEmpty() || body.isNullOrEmpty()) {
SDK.error("Invalid input data: images=$images, title=$title, body=$body")

Result.failure()
} else {

val data = NotificationData(
title = title,
body = body,
images = images
)

try {
val loadedImages = NotificationImageHelper.loadBitmaps(urls = images)

NotificationHelper.createNotification(
context = context,
notificationId = data.hashCode(),
data = data,
images = loadedImages,
currentImageIndex = newIndex
)

Result.success()
} catch (ioException: IOException) {
NetworkError(
tag = TAG,
functionName = FUNC_DO_WORK
).logError()
Result.failure()
}
}
}

companion object {
private const val TAG = "UpdateNotificationWorker"
private const val FUNC_DO_WORK = "DoWork"
}
}
TorinAsakura marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.personalization.features.notification.domain.helpers

import android.os.Bundle
import com.personalization.errors.ParsingJsonError
import com.personalization.features.notification.domain.model.NotificationConstants.CODE_PARAM
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_ID
import com.personalization.features.notification.domain.model.NotificationConstants.NOTIFICATION_TYPE
import com.personalization.features.notification.domain.model.NotificationConstants.TRACK_CLICKED
import com.personalization.features.notification.domain.model.NotificationConstants.TYPE_PARAM
import org.json.JSONException
import org.json.JSONObject

object NotificationClickHandler {

fun handleNotificationClick(
extras: Bundle?,
sendAsync: (String, JSONObject) -> Unit,
onResult: (type: String, code: String) -> Unit
) {
if (extras == null) {
return
} else {
val type = extras.getString(NOTIFICATION_TYPE, null)
val code = extras.getString(NOTIFICATION_ID, null)

if (type != null && code != null) {
val params = JSONObject()
try {
params.put(TYPE_PARAM, type)
params.put(CODE_PARAM, code)
sendAsync(TRACK_CLICKED, params)

onResult(type, code)
} catch (jsonException: JSONException) {
ParsingJsonError(
tag = this@NotificationClickHandler.javaClass.name,
functionName = "handleNotificationClick",
message = jsonException.message.orEmpty()
).logError()
}
}
}
}
}
Loading