Add new route push notifications
This commit is contained in:
@@ -22,6 +22,15 @@ class DriverFirebaseMessagingService : FirebaseMessagingService() {
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
val data = message.data
|
||||
if (data["type"] == "driver_new_route_notification") {
|
||||
NewRouteNotificationWorker.enqueue(
|
||||
context = applicationContext,
|
||||
routeId = data["routeId"],
|
||||
date = data["date"],
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (data["type"] != "driver_sync_hint") return
|
||||
|
||||
DriverSyncWorker.enqueue(
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package pl.firmatpp.kierowca.sync
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.util.Locale
|
||||
import java.time.DayOfWeek
|
||||
import java.time.format.DateTimeFormatter
|
||||
import pl.firmatpp.kierowca.data.model.DriverRouteDto
|
||||
|
||||
object NewRouteNotificationFormatter {
|
||||
private val polishLocale = Locale("pl", "PL")
|
||||
private val monthFormatter = DateTimeFormatter.ofPattern("d MMMM", polishLocale)
|
||||
|
||||
fun title(routeDate: String?, today: LocalDate = LocalDate.now()): String {
|
||||
val label = humanDate(routeDate, today) ?: return "Nowy kurs"
|
||||
|
||||
return "Nowy kurs: $label"
|
||||
}
|
||||
|
||||
fun collapsedText(route: DriverRouteDto): String =
|
||||
listOf(route.originName, route.destinationName)
|
||||
.filter(String::isNotBlank)
|
||||
.joinToString(" → ")
|
||||
.ifBlank { route.relationLabel }
|
||||
.let { relation ->
|
||||
val contract = route.contractName?.takeIf(String::isNotBlank) ?: route.contractCode
|
||||
if (contract.isNullOrBlank()) relation else "$relation · $contract"
|
||||
}
|
||||
|
||||
fun expandedText(route: DriverRouteDto): String {
|
||||
val contract = route.contractName?.takeIf(String::isNotBlank) ?: route.contractCode ?: "Brak nazwy kontraktu"
|
||||
|
||||
return listOf(
|
||||
"Skąd: ${route.originName.ifBlank { "Nie podano" }}",
|
||||
"Dokąd: ${route.destinationName.ifBlank { "Nie podano" }}",
|
||||
"Kontrakt: $contract",
|
||||
).joinToString("\n")
|
||||
}
|
||||
|
||||
private fun humanDate(routeDate: String?, today: LocalDate): String? {
|
||||
val date = runCatching { routeDate?.let(LocalDate::parse) }.getOrNull() ?: return null
|
||||
|
||||
return when {
|
||||
date == today -> "dzisiaj"
|
||||
date == today.plusDays(1) -> "jutro"
|
||||
date.isAfter(today.plusDays(1)) && !date.isAfter(today.plusDays(7)) ->
|
||||
"w ${weekdayAccusative(date.dayOfWeek)}"
|
||||
else -> date.format(monthFormatter).lowercase(polishLocale)
|
||||
}
|
||||
}
|
||||
|
||||
private fun weekdayAccusative(dayOfWeek: DayOfWeek): String =
|
||||
when (dayOfWeek) {
|
||||
DayOfWeek.MONDAY -> "poniedziałek"
|
||||
DayOfWeek.TUESDAY -> "wtorek"
|
||||
DayOfWeek.WEDNESDAY -> "środę"
|
||||
DayOfWeek.THURSDAY -> "czwartek"
|
||||
DayOfWeek.FRIDAY -> "piątek"
|
||||
DayOfWeek.SATURDAY -> "sobotę"
|
||||
DayOfWeek.SUNDAY -> "niedzielę"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package pl.firmatpp.kierowca.sync
|
||||
|
||||
import android.Manifest
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import java.util.concurrent.TimeUnit
|
||||
import pl.firmatpp.kierowca.MainActivity
|
||||
import pl.firmatpp.kierowca.R
|
||||
import pl.firmatpp.kierowca.data.ApiErrorKind
|
||||
import pl.firmatpp.kierowca.data.ApiErrorMapper
|
||||
import pl.firmatpp.kierowca.data.DriverRepository
|
||||
import retrofit2.HttpException
|
||||
|
||||
class NewRouteNotificationWorker(
|
||||
appContext: Context,
|
||||
params: WorkerParameters,
|
||||
) : CoroutineWorker(appContext, params) {
|
||||
private val repository = DriverRepository(appContext)
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val routeId = inputData.getString(KEY_ROUTE_ID)?.takeIf(String::isNotBlank) ?: return Result.success()
|
||||
|
||||
return runCatching {
|
||||
val route = repository.route(routeId).route
|
||||
if (!canShowNotifications(applicationContext)) {
|
||||
return@runCatching Result.success()
|
||||
}
|
||||
|
||||
createChannel(applicationContext)
|
||||
val contentTitle = NewRouteNotificationFormatter.title(route.routeDate ?: inputData.getString(KEY_DATE))
|
||||
val contentText = NewRouteNotificationFormatter.collapsedText(route)
|
||||
val expandedText = NewRouteNotificationFormatter.expandedText(route)
|
||||
val intent = Intent(applicationContext, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
putExtra(MainActivity.EXTRA_ROUTE_ID, route.id)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
applicationContext,
|
||||
route.id.hashCode(),
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setContentTitle(contentTitle)
|
||||
.setContentText(contentText)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(expandedText))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_REMINDER)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(applicationContext).notify(route.id.hashCode(), notification)
|
||||
Result.success()
|
||||
}.getOrElse { throwable ->
|
||||
if (throwable is HttpException && throwable.code() == 404) {
|
||||
Result.success()
|
||||
} else {
|
||||
val error = ApiErrorMapper.map(throwable)
|
||||
if (error.retryable && error.kind != ApiErrorKind.Auth) Result.retry() else Result.success()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createChannel(context: Context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"Nowe kursy",
|
||||
NotificationManager.IMPORTANCE_DEFAULT,
|
||||
).apply {
|
||||
description = "Powiadomienia o nowych kursach dodanych przez spedytora."
|
||||
}
|
||||
context.getSystemService(NotificationManager::class.java)?.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
private fun canShowNotifications(context: Context): Boolean {
|
||||
val hasRuntimePermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
||||
ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
return hasRuntimePermission && NotificationManagerCompat.from(context).areNotificationsEnabled()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ID = "new_routes"
|
||||
private const val KEY_ROUTE_ID = "routeId"
|
||||
private const val KEY_DATE = "date"
|
||||
|
||||
fun enqueue(context: Context, routeId: String?, date: String?) {
|
||||
if (routeId.isNullOrBlank()) return
|
||||
|
||||
val request = OneTimeWorkRequestBuilder<NewRouteNotificationWorker>()
|
||||
.setInputData(workDataOf(KEY_ROUTE_ID to routeId, KEY_DATE to date))
|
||||
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
"driver-new-route-notification-$routeId",
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
request,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user