Skip to content

Commit

Permalink
Added pharmacy notifications.
Browse files Browse the repository at this point in the history
  • Loading branch information
devandrepascoa committed May 31, 2024
1 parent df11ee8 commit 35498e4
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 4 deletions.
4 changes: 4 additions & 0 deletions src/frontend/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.material.icons.extended)
implementation(libs.androidx.hilt.work)
ksp(libs.androidx.hilt.compiler)

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down Expand Up @@ -96,6 +99,7 @@ dependencies {
implementation(libs.androidx.room.guava)
testImplementation(libs.androidx.room.testing)
implementation(libs.androidx.room.paging)
implementation(libs.androidx.work)

implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler)
Expand Down
5 changes: 5 additions & 0 deletions src/frontend/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,10 @@

<service android:name="pt.ulisboa.ist.pharmacist.service.real_time_updates.MedicineNotificationsBackgroundService" />
<receiver android:name="pt.ulisboa.ist.pharmacist.service.real_time_updates.MedicineNotificationsBroadcastReceiver" />

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import okhttp3.OkHttpClient
import pt.ulisboa.ist.pharmacist.repository.local.PharmacistDatabase
import pt.ulisboa.ist.pharmacist.repository.remote.medicines.MedicineApi
import pt.ulisboa.ist.pharmacist.repository.remote.pharmacies.PharmacyApi
import pt.ulisboa.ist.pharmacist.repository.remote.upload.UploaderApi
import pt.ulisboa.ist.pharmacist.repository.remote.users.UsersApi
import pt.ulisboa.ist.pharmacist.service.PharmacyNotificationService
import pt.ulisboa.ist.pharmacist.service.real_time_updates.RealTimeUpdatesService
import pt.ulisboa.ist.pharmacist.session.SessionManager
import pt.ulisboa.ist.pharmacist.session.SessionManagerSharedPrefs
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
Expand Down Expand Up @@ -140,4 +141,16 @@ object ApplicationModule {
Places.initialize(context, context.getString(R.string.google_maps_key))
return Places.createClient(context)
}

@Provides
@Singleton
fun providePharmacyNotificationService(
@ApplicationContext context: Context,
database: PharmacistDatabase
): PharmacyNotificationService {
return PharmacyNotificationService(
applicationContext = context,
database = database
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
Expand All @@ -15,14 +19,18 @@ import coil.request.CachePolicy
import coil.util.DebugLogger
import com.google.gson.Gson
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import pt.ulisboa.ist.pharmacist.service.PharmacyNotificationService
import pt.ulisboa.ist.pharmacist.service.PharmacyNotificationWork
import pt.ulisboa.ist.pharmacist.service.real_time_updates.MedicineNotificationsBackgroundService
import pt.ulisboa.ist.pharmacist.service.real_time_updates.RealTimeUpdatesService
import pt.ulisboa.ist.pharmacist.session.SessionManager
import javax.inject.Inject


/**
* The Pharmacist application.
Expand All @@ -31,7 +39,8 @@ import javax.inject.Inject
* @property sessionManager the manager used to handle the user session
*/
@HiltAndroidApp
class PharmacistApplication : DependenciesContainer, Application(), ImageLoaderFactory {
class PharmacistApplication : DependenciesContainer, Application(), ImageLoaderFactory,
Configuration.Provider {

@Inject
override lateinit var httpClient: OkHttpClient
Expand All @@ -47,6 +56,12 @@ class PharmacistApplication : DependenciesContainer, Application(), ImageLoaderF

private val serviceScope = CoroutineScope(Dispatchers.Default)

@Inject
lateinit var workerFactory: HiltWorkerFactory

@Inject
lateinit var notificationService: PharmacyNotificationService

override fun onCreate() {
super.onCreate()

Expand All @@ -59,6 +74,22 @@ class PharmacistApplication : DependenciesContainer, Application(), ImageLoaderF
serviceScope.launch {
realTimeUpdatesService.startService()
}

val workRequest = PeriodicWorkRequestBuilder<PharmacyNotificationWork>(
PERIODIC_PHARMACY_NOTIFICATION_INTERVAL,
java.util.concurrent.TimeUnit.MINUTES
)
.build()

WorkManager.getInstance(this).enqueue(workRequest)


serviceScope.launch {
while (true) {
notificationService.verifyNotifications()
delay(PHARMACY_NOTIFICATIONS_DELAY)
}
}
}

@RequiresApi(Build.VERSION_CODES.O)
Expand All @@ -78,7 +109,7 @@ class PharmacistApplication : DependenciesContainer, Application(), ImageLoaderF
companion object {
const val MEDICINE_NOTIFICATION_CHANNEL = "MedicineNotifications"

private const val API_ENDPOINT_TYPE = "domain"
private const val API_ENDPOINT_TYPE = "render"
val API_ENDPOINT = when (API_ENDPOINT_TYPE) {
"localhost" -> "http://10.0.2.2:8080"
"ngrok" -> "https://2b02-2001-818-e871-b700-c937-8172-33bf-a88.ngrok-free.app"
Expand All @@ -89,6 +120,10 @@ class PharmacistApplication : DependenciesContainer, Application(), ImageLoaderF
}
}
const val TAG = "PharmacistApp"
private const val PERIODIC_PHARMACY_NOTIFICATION_INTERVAL = 15L
private const val PHARMACY_NOTIFICATIONS_DELAY = 5000L


}

override fun newImageLoader(): ImageLoader {
Expand All @@ -110,4 +145,10 @@ class PharmacistApplication : DependenciesContainer, Application(), ImageLoaderF
.logger(DebugLogger())
.build()
}

override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package pt.ulisboa.ist.pharmacist.service

import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.os.Build
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import pt.ulisboa.ist.pharmacist.PharmacistApplication
import pt.ulisboa.ist.pharmacist.R
import pt.ulisboa.ist.pharmacist.repository.local.PharmacistDatabase
import pt.ulisboa.ist.pharmacist.repository.local.pharmacies.PharmacyEntity
import pt.ulisboa.ist.pharmacist.ui.screens.pharmacy.PharmacyActivity


class PharmacyNotificationService(
private val applicationContext: Context,
private val database: PharmacistDatabase
) {
private val previousPharmacies = mutableSetOf<Long>()
private val mutex = Mutex()

suspend fun verifyNotifications(): Boolean = mutex.withLock {
val newPharmacies = mutableSetOf<Long>()
val location = getLocation() ?: return false

database.pharmacyDao().getAllPharmacies()
.forEach { pharmacy ->
if (pharmacyNearMe(
location,
pharmacy
) && pharmacy.pharmacyId !in previousPharmacies
) {
showPharmacyNotification(pharmacy.pharmacyId, pharmacy.name)
newPharmacies.add(pharmacy.pharmacyId)
}
}

previousPharmacies.clear()
previousPharmacies.addAll(newPharmacies)

return true
}

private suspend fun getLocation(): Location? {
val fusedLocationProviderClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(applicationContext)

if (ActivityCompat.checkSelfPermission(
applicationContext,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
applicationContext,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return null
}

return suspendCancellableCoroutine { continuation ->
fusedLocationProviderClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
.addOnSuccessListener { receivedLocation ->
receivedLocation?.let {
continuation.resume(it)
}
}
}
}

private fun pharmacyNearMe(location: Location, pharmacy: PharmacyEntity): Boolean {
val pharmacyLocation = Location("pharmacy")
pharmacyLocation.latitude = pharmacy.latitude
pharmacyLocation.longitude = pharmacy.longitude

return location.distanceTo(pharmacyLocation) <= MAX_DISTANCE_METERS
}

private fun showPharmacyNotification(pharmacyId: Long, pharmacyName: String) {
val notificationIntent = PharmacyActivity.getNavigationIntent(
applicationContext,
pharmacyId
)

//TODO: Check what happens when the user clicks on the back button in the MedicineActivity after clicking on the notification
notificationIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK

val pendingNotiIntent: PendingIntent = PendingIntent.getActivity(
applicationContext,
0,
notificationIntent,
PendingIntent.FLAG_IMMUTABLE
)


val message = applicationContext.getString(
R.string.pharmacy_notification_message,
pharmacyName,
)

// Show notification to the user
val notificationCompat = NotificationCompat.Builder(
applicationContext,
PharmacistApplication.MEDICINE_NOTIFICATION_CHANNEL
)
.setSmallIcon(R.drawable.pharmacy_logo)
.setContentTitle(
applicationContext.getString(
R.string.pharmacy_notification_message,
pharmacyName
)
)
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingNotiIntent)
.setAutoCancel(true)
.build()

with(NotificationManagerCompat.from(applicationContext)) {
if (!checkNotificationPermission()) {
return@with
}
notify(PHARMACY_NOTIFICATION_ID, notificationCompat)
}
}

private fun checkNotificationPermission(): Boolean =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ActivityCompat.checkSelfPermission(
applicationContext,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
} else {
true
}


companion object {
private const val PHARMACY_NOTIFICATION_ID = 1
private const val MAX_DISTANCE_METERS = 100
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pt.ulisboa.ist.pharmacist.service

import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject

@HiltWorker
class PharmacyNotificationWork @AssistedInject constructor(
private val service: PharmacyNotificationService,
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {

override suspend fun doWork(): Result {
val success = service.verifyNotifications()

return if (success) Result.success() else Result.failure()
}

}
1 change: 1 addition & 0 deletions src/frontend/app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,5 @@
<string name="star">Estrella</string>
<string name="favorite_pharmacy">Farmacia Favorita</string>
<string name="loading_medicine">Cargando medicamento...</string>
<string name="pharmacy_notification_message">Farmacia %1$s cerca de ti</string>
</resources>
1 change: 1 addition & 0 deletions src/frontend/app/src/main/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,5 @@
<string name="star">Estrela</string>
<string name="favorite_pharmacy">Farmácia Favorita</string>
<string name="loading_medicine">Carregando medicamento...</string>
<string name="pharmacy_notification_message">Farmácia %1$s perto de ti</string>
</resources>
1 change: 1 addition & 0 deletions src/frontend/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,5 @@
<string name="star">Star</string>
<string name="favorite_pharmacy">Favorite Pharmacy</string>
<string name="loading_medicine">Loading medicine...</string>
<string name="pharmacy_notification_message">Pharmacy %1$s near you</string>
</resources>
Loading

0 comments on commit 35498e4

Please sign in to comment.