Skip to content

Commit

Permalink
Notify users of new merchants nearby
Browse files Browse the repository at this point in the history
  • Loading branch information
bubelov committed Apr 25, 2024
1 parent 1a11ba5 commit 0c5c219
Show file tree
Hide file tree
Showing 40 changed files with 156 additions and 40 deletions.
12 changes: 11 additions & 1 deletion app/src/main/kotlin/conf/Conf.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package conf

import org.osmdroid.util.BoundingBox
import java.time.ZonedDateTime

data class Conf(
Expand All @@ -10,4 +11,13 @@ data class Conf(
val viewportWestLon: Double,
val showAtms: Boolean,
val showOsmAttribution: Boolean,
)
)

fun Conf.mapViewport(): BoundingBox {
return BoundingBox(
viewportNorthLat,
viewportEastLon,
viewportSouthLat,
viewportWestLon,
)
}
36 changes: 36 additions & 0 deletions app/src/main/kotlin/element/ElementQueries.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,42 @@ class ElementQueries(private val db: SQLiteOpenHelper) {
}
}

suspend fun selectByOsmId(osmId: String): Element? {
return withContext(Dispatchers.IO) {
val cursor = db.readableDatabase.query(
"""
SELECT
id,
osm_id,
lat,
lon,
osm_json,
tags,
updated_at,
deleted_at
FROM element
WHERE osm_id = ?;
""",
arrayOf(osmId),
)

if (!cursor.moveToNext()) {
return@withContext null
}

Element(
id = cursor.getLong(0),
osmId = cursor.getString(1),
lat = cursor.getDouble(2),
lon = cursor.getDouble(3),
osmJson = cursor.getJsonObject(4),
tags = cursor.getJsonObject(5),
updatedAt = cursor.getString(6)!!,
deletedAt = cursor.getStringOrNull(7),
)
}
}

suspend fun selectBySearchString(searchString: String): List<Element> {
return withContext(Dispatchers.IO) {
val cursor = db.readableDatabase.query(
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/kotlin/element/ElementsRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class ElementsRepo(

suspend fun selectById(id: Long) = queries.selectById(id)

suspend fun selectByOsmId(osmId: String) = queries.selectByOsmId(osmId)

suspend fun selectBySearchString(searchString: String): List<Element> {
return queries.selectBySearchString(searchString)
}
Expand Down
11 changes: 2 additions & 9 deletions app/src/main/kotlin/map/MapModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import area.AreasRepo
import conf.ConfRepo
import conf.mapViewport
import element.Element
import element.ElementsRepo
import kotlinx.coroutines.*
Expand All @@ -29,15 +30,7 @@ class MapModel(
)

private val _mapViewport: MutableStateFlow<MapViewport> = MutableStateFlow(
MapViewport(
zoom = null,
boundingBox = BoundingBox(
conf.conf.value.viewportNorthLat,
conf.conf.value.viewportEastLon,
conf.conf.value.viewportSouthLat,
conf.conf.value.viewportWestLon,
),
)
MapViewport(zoom = null, boundingBox = conf.conf.value.mapViewport())
)

val mapViewport = _mapViewport.asStateFlow()
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/kotlin/sync/Sync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sync
import android.util.Log
import area.AreasRepo
import conf.ConfRepo
import conf.mapViewport
import reports.ReportsRepo
import element.ElementsRepo
import event.EventsRepo
Expand Down Expand Up @@ -76,8 +77,9 @@ class Sync(
confRepo.update { it.copy(lastSyncDate = ZonedDateTime.now(ZoneOffset.UTC)) }
if (initialSyncComplete) {
syncNotificationController.showPostSyncNotifications(
syncTimeMs,
eventsSyncReport?.newEvents ?: emptyList(),
syncTimeMs = syncTimeMs,
newEvents = eventsSyncReport?.newEvents ?: emptyList(),
mapViewport = confRepo.conf.value.mapViewport(),
)
}
_active.update { false }
Expand Down
95 changes: 67 additions & 28 deletions app/src/main/kotlin/sync/SyncNotificationController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,29 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import app.isDebuggable
import element.ElementsRepo
import element.name
import event.Event
import kotlinx.coroutines.runBlocking
import org.btcmap.R
import org.json.JSONObject
import org.osmdroid.util.BoundingBox
import kotlin.random.Random

class SyncNotificationController(private val context: Context) {
class SyncNotificationController(
private val context: Context,
private val elementsRepo: ElementsRepo,
) {

fun showPostSyncNotifications(
syncTimeMs: Long,
newEvents: List<Event>,
mapViewport: BoundingBox,
) {
if (ActivityCompat.checkSelfPermission(
context,
Expand All @@ -30,49 +40,67 @@ class SyncNotificationController(private val context: Context) {
return
}

if (context.isDebuggable()) {
createSyncSummaryNotificationChannel(context)
val showSyncSummary = context.isDebuggable()

val intent = Intent(context, Activity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}

val pendingIntent =
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
if (showSyncSummary) {
createSyncSummaryNotificationChannel(context)

val builder = NotificationCompat.Builder(context, SYNC_SUMMARY_CHANNEL_ID)
.setSmallIcon(R.drawable.area_placeholder_icon)
.setContentTitle(context.getString(R.string.app_name))
.setContentText("Finished sync in $syncTimeMs ms. Got ${newEvents.size} new events.")
.setContentTitle("Finished sync")
.setContentText("Finished sync in $syncTimeMs ms")
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(
"""
|Time: $syncTimeMs ms
|New events: ${newEvents.size}
""".trimMargin()
)
)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)

NotificationManagerCompat.from(context)
.notify(SYNC_SUMMARY_NOTIFICATION_ID, builder.build())
.notify(Random.Default.nextInt(1, Int.MAX_VALUE), builder.build())
}

createNewMerchantsNotificationChannel(context)

newEvents.forEach { newEvent ->
if (newEvent.type == "create") {
val intent = Intent(context, Activity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val element =
runBlocking { elementsRepo.selectByOsmId(newEvent.elementId) } ?: return

val distanceMeters = getDistanceInMeters(
startLatitude = mapViewport.centerLatitude,
startLongitude = mapViewport.centerLongitude,
endLatitude = element.lat,
endLongitude = element.lon,
)

val distanceThresholdMeters = if (context.isDebuggable()) {
100_000_000f
} else {
100_000f
}

val pendingIntent =
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
if (distanceMeters > distanceThresholdMeters) {
return
}

val builder = NotificationCompat.Builder(context, NEW_MERCHANTS_CHANNEL_ID)
.setSmallIcon(R.drawable.add_location)
.setContentTitle(context.getString(R.string.app_name))
.setContentText("There is a new place to spend sats in your area: ${newEvent.elementId}")
.setContentTitle(
(element.osmJson.optJSONObject("tags") ?: JSONObject()).name(
context.resources,
)
)
.setContentText(context.getString(R.string.new_local_merchant_accepts_bitcoins))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)

NotificationManagerCompat.from(context)
.notify(newEvent.id.toInt(), builder.build())
.notify(Random.Default.nextInt(1, Int.MAX_VALUE), builder.build())
}
}
}
Expand Down Expand Up @@ -102,7 +130,7 @@ class SyncNotificationController(private val context: Context) {
) == PackageManager.PERMISSION_GRANTED
) {
with(NotificationManagerCompat.from(context)) {
notify(SYNC_FAILED_NOTIFICATION_ID, builder.build())
notify(Random.Default.nextInt(1, Int.MAX_VALUE), builder.build())
}
}
}
Expand All @@ -115,7 +143,8 @@ class SyncNotificationController(private val context: Context) {
val channel = NotificationChannel(SYNC_SUMMARY_CHANNEL_ID, name, importance).apply {
description = descriptionText
}
val notificationManager = context.getSystemService<NotificationManager>()!!
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}

Expand All @@ -126,14 +155,24 @@ class SyncNotificationController(private val context: Context) {
val channel = NotificationChannel(NEW_MERCHANTS_CHANNEL_ID, name, importance).apply {
description = descriptionText
}
val notificationManager = context.getSystemService<NotificationManager>()!!
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}

private fun getDistanceInMeters(
startLatitude: Double,
startLongitude: Double,
endLatitude: Double,
endLongitude: Double,
): Double {
val distance = FloatArray(1)
Location.distanceBetween(startLatitude, startLongitude, endLatitude, endLongitude, distance)
return distance[0].toDouble()
}

companion object {
private const val SYNC_SUMMARY_CHANNEL_ID = "sync_summary"
private const val SYNC_SUMMARY_NOTIFICATION_ID = 1
private const val SYNC_FAILED_NOTIFICATION_ID = 2
private const val NEW_MERCHANTS_CHANNEL_ID = "new_merchants"
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values-af/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d change</item>
<item quantity="other">%d changes</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="zero">%d changes</item>
<item quantity="one">%d change</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-bg/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Снимка</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d промяна</item>
<item quantity="other">%d промени</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-bn/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d change</item>
<item quantity="other">%d changes</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-ca/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d change</item>
<item quantity="many">%d changes</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-cs/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d změn</item>
<item quantity="few">%d změn</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-da/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d change</item>
<item quantity="other">%d changes</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d Änderung</item>
<item quantity="other">%d Änderungen</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-el/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d αλλαγή</item>
<item quantity="other">%d αλλαγές</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d cambio</item>
<item quantity="many">%d cambios</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-fa/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d change</item>
<item quantity="other">%d changes</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-fi/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d change</item>
<item quantity="other">%d changes</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<string name="image">Image</string>
<string name="issues">Issues</string>
<string name="sync_failed_s">Sync failed: %1$s</string>
<string name="new_local_merchant_accepts_bitcoins">New local merchant accepts bitcoins</string>
<plurals name="d_changes">
<item quantity="one">%d modification</item>
<item quantity="many">%d modifications</item>
Expand Down
Loading

0 comments on commit 0c5c219

Please sign in to comment.