Merge branch 'main' of github.com:MM2-0/Kvaesitso

This commit is contained in:
MM20 2025-06-01 17:29:55 +02:00
commit cac2469b2e
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
10 changed files with 184 additions and 44 deletions

View File

@ -21,7 +21,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -49,6 +48,9 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.automirrored.rounded.NavigateNext import androidx.compose.material.icons.automirrored.rounded.NavigateNext
import androidx.compose.material.icons.automirrored.rounded.OpenInNew import androidx.compose.material.icons.automirrored.rounded.OpenInNew
import androidx.compose.material.icons.outlined.CreditCardOff
import androidx.compose.material.icons.outlined.CreditScore
import androidx.compose.material.icons.outlined.Toll
import androidx.compose.material.icons.rounded.AirplanemodeActive import androidx.compose.material.icons.rounded.AirplanemodeActive
import androidx.compose.material.icons.rounded.BugReport import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.Commute import androidx.compose.material.icons.rounded.Commute
@ -92,9 +94,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -102,7 +102,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -115,6 +114,7 @@ import blend.Blend.harmonize
import coil.compose.AsyncImage import coil.compose.AsyncImage
import de.mm20.launcher2.i18n.R import de.mm20.launcher2.i18n.R
import de.mm20.launcher2.icons.CableCar import de.mm20.launcher2.icons.CableCar
import de.mm20.launcher2.icons.TollOff
import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.Location import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.isOpen import de.mm20.launcher2.search.isOpen
@ -124,6 +124,7 @@ import de.mm20.launcher2.search.location.LineNameComparator
import de.mm20.launcher2.search.location.LineType import de.mm20.launcher2.search.location.LineType
import de.mm20.launcher2.search.location.OpeningHours import de.mm20.launcher2.search.location.OpeningHours
import de.mm20.launcher2.search.location.OpeningSchedule import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import de.mm20.launcher2.search.location.isNotEmpty import de.mm20.launcher2.search.location.isNotEmpty
import de.mm20.launcher2.ui.base.LocalTime import de.mm20.launcher2.ui.base.LocalTime
import de.mm20.launcher2.ui.component.DefaultToolbarAction import de.mm20.launcher2.ui.component.DefaultToolbarAction
@ -229,24 +230,33 @@ fun LocationItem(
) )
val formattedDistance = val formattedDistance =
distance?.metersToLocalizedString(context, imperialUnits) distance?.metersToLocalizedString(context, imperialUnits)
val isOpenString = location.openingSchedule?.isOpen() val sublabel = listOf(location.category, formattedDistance)
?.let { stringResource(if (it) R.string.location_open else R.string.location_closed) }
val sublabel = listOf(location.category, formattedDistance, isOpenString)
.fastFilterNotNull() .fastFilterNotNull()
.joinToString("") .joinToString("")
val isOpenString = location.openingSchedule?.isOpen()
?.let { stringResource(if (it) R.string.location_open else R.string.location_closed) }
if (sublabel.isNotBlank()) { Row(modifier = Modifier.padding(top = 2.dp)) {
Text( if (sublabel.isNotBlank()) {
sublabel, Text(
style = MaterialTheme.typography.bodySmall, sublabel,
color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.bodySmall,
modifier = Modifier color = MaterialTheme.colorScheme.secondary,
.padding(top = 2.dp) modifier = Modifier
.sharedElement( .sharedElement(
rememberSharedContentState("sublabel"), rememberSharedContentState("sublabel"),
this@AnimatedContent this@AnimatedContent
) )
) )
}
if (!isOpenString.isNullOrBlank()) {
Text(
"$isOpenString",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.animateEnterExit()
)
}
} }
} }
Compass( Compass(
@ -324,28 +334,55 @@ fun LocationItem(
this@AnimatedContent this@AnimatedContent
) )
) )
val category = location.category Row(
val formattedDistance = distance?.metersToLocalizedString( verticalAlignment = Alignment.CenterVertically,
context, imperialUnits modifier = Modifier.padding(top = 2.dp)
) ) {
if (category != null || formattedDistance != null) { val category = location.category
Text( val acceptedPaymentMethods = location.acceptedPaymentMethods
when { val formattedDistance = distance?.metersToLocalizedString(
category != null && formattedDistance != null -> "$category$formattedDistance" context, imperialUnits
category != null -> category
formattedDistance != null -> formattedDistance
else -> ""
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.padding(top = 2.dp)
.sharedElement(
rememberSharedContentState("sublabel"),
this@AnimatedContent
)
) )
if (category != null || formattedDistance != null) {
Text(
when {
category != null && formattedDistance != null -> "$category$formattedDistance"
category != null -> category
formattedDistance != null -> formattedDistance
else -> ""
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.sharedElement(
rememberSharedContentState("sublabel"),
this@AnimatedContent
)
)
}
if (!acceptedPaymentMethods.isNullOrEmpty()) {
Text(
"",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.animateEnterExit()
)
for ((method, available) in acceptedPaymentMethods) {
Icon(
when (method) {
PaymentMethod.Cash -> if (available) Icons.Outlined.Toll else Icons.Outlined.TollOff
PaymentMethod.Card -> if (available) Icons.Outlined.CreditScore else Icons.Outlined.CreditCardOff
},
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.size(14.5.dp)
.padding(end = 2.dp)
.animateEnterExit()
)
}
}
} }
if (location.userRating != null) { if (location.userRating != null) {
RatingBar( RatingBar(

View File

@ -75,6 +75,7 @@ import de.mm20.launcher2.search.location.Departure
import de.mm20.launcher2.search.location.LineType import de.mm20.launcher2.search.location.LineType
import de.mm20.launcher2.search.location.LocationIcon import de.mm20.launcher2.search.location.LocationIcon
import de.mm20.launcher2.search.location.OpeningSchedule import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import de.mm20.launcher2.ui.ktx.DegreesConverter import de.mm20.launcher2.ui.ktx.DegreesConverter
import de.mm20.launcher2.ui.ktx.contrast import de.mm20.launcher2.ui.ktx.contrast
import de.mm20.launcher2.ui.ktx.hue import de.mm20.launcher2.ui.ktx.hue
@ -505,6 +506,9 @@ private object MockLocation : Location {
override val openingSchedule: OpeningSchedule = override val openingSchedule: OpeningSchedule =
OpeningSchedule.TwentyFourSeven OpeningSchedule.TwentyFourSeven
override val acceptedPaymentMethods: Map<PaymentMethod, Boolean>?
get() = mapOf(PaymentMethod.Card to true, PaymentMethod.Cash to false)
override val websiteUrl: String = "https://en.wikipedia.org/wiki/Brandenburg_Gate" override val websiteUrl: String = "https://en.wikipedia.org/wiki/Brandenburg_Gate"
override val phoneNumber: String = "+49 1234567" override val phoneNumber: String = "+49 1234567"

View File

@ -1742,6 +1742,60 @@ private val _BreezyWeather = materialIcon("Icons.Rounded.BreezyWeather") {
val Icons.Rounded.BreezyWeather val Icons.Rounded.BreezyWeather
get() = _BreezyWeather get() = _BreezyWeather
private val _TollOff = materialIcon("Icons.Rounded.TollOff") {
materialPath {
moveTo(2.8066406f, 3.2207031f)
curveToRelative(-0.2556191f, 0f, -0.5111625f, 0.099053f, -0.7070312f, 0.2949219f)
curveToRelative(-0.3917371f, 0.3917372f, -0.3917371f, 1.0223253f, 0f, 1.4140625f)
lineToRelative(1.3339844f, 1.3320312f)
verticalLineToRelative(0.00195f)
curveTo(1.933574f, 7.7156369f, 0.99999996f, 9.7483802f, 1f, 12f)
curveToRelative(3e-7f, 3.23f, 1.9196876f, 6.009532f, 4.6796875f, 7.269531f)
curveTo(6.2896876f, 19.549531f, 7.0000003f, 19.129219f, 7f, 18.449219f)
verticalLineToRelative(-0.179688f)
curveToRelative(-3e-7f, -0.37f, -0.2303127f, -0.689609f, -0.5703125f, -0.849609f)
curveTo(4.399687f, 16.459922f, 2.9999999f, 14.39f, 3f, 12f)
curveToRelative(-2e-7f, -1.698485f, 0.7060878f, -3.2344795f, 1.84375f, -4.3261719f)
lineToRelative(2.3925781f, 2.3906249f)
verticalLineToRelative(0.002f)
curveTo(7.0825208f, 10.685478f, 6.9999996f, 11.333026f, 7f, 12f)
curveToRelative(0f, 4.42f, 3.58f, 8f, 8f, 8f)
curveToRelative(0.666973f, 0f, 1.314475f, -0.08252f, 1.933594f, -0.236328f)
horizontalLineToRelative(0.002f)
lineToRelative(2.134765f, 2.136719f)
curveToRelative(0.391737f, 0.391737f, 1.022326f, 0.391737f, 1.414063f, 0f)
curveToRelative(0.391737f, -0.391738f, 0.391737f, -1.024279f, 0f, -1.416016f)
lineTo(18.955078f, 18.955078f)
lineTo(17.46875f, 17.46875f)
lineTo(9.53125f, 9.53125f)
lineTo(8.0449219f, 8.0449219f)
lineTo(6.5273437f, 6.5273437f)
lineTo(5.0507812f, 5.0507812f)
lineTo(3.515625f, 3.515625f)
curveTo(3.3197565f, 3.3197565f, 3.0622597f, 3.2207031f, 2.8066406f, 3.2207031f)
close()
moveTo(15f, 4f)
curveToRelative(-2.25366f, 2e-7f, -4.288489f, 0.9314658f, -5.7421875f, 2.4296875f)
lineTo(10.673828f, 7.8457031f)
curveTo(11.766133f, 6.7086818f, 13.30143f, 6.0000002f, 15f, 6f)
curveToRelative(3.31f, 4e-7f, 6f, 2.6899997f, 6f, 6f)
curveToRelative(0f, 1.69857f, -0.708682f, 3.233867f, -1.845703f, 4.326172f)
lineToRelative(1.416015f, 1.416015f)
curveTo(22.068533f, 16.288488f, 23f, 14.25366f, 23f, 12f)
curveTo(23f, 7.5799994f, 19.42f, 3.9999997f, 15f, 4f)
close()
moveToRelative(-5.9980469f, 7.832031f)
lineToRelative(6.1660159f, 6.166016f)
curveTo(15.112392f, 17.999575f, 15.055942f, 18f, 15f, 18f)
curveTo(11.69f, 18f, 9.0000004f, 15.31f, 9f, 12f)
curveToRelative(2e-7f, -0.05594f, 0.0004292f, -0.112391f, 0.00195f, -0.167969f)
close()
}
}
val Icons.Outlined.TollOff
get() = _TollOff
val _CutCorner = materialIcon("Icons.Rounded.CutCorner") { val _CutCorner = materialIcon("Icons.Rounded.CutCorner") {
materialPath { materialPath {
moveTo(2f, 3f) moveTo(2f, 3f)

View File

@ -129,6 +129,7 @@ import de.mm20.launcher2.search.location.Departure
import de.mm20.launcher2.search.location.LocationIcon import de.mm20.launcher2.search.location.LocationIcon
import de.mm20.launcher2.search.location.OpeningHours import de.mm20.launcher2.search.location.OpeningHours
import de.mm20.launcher2.search.location.OpeningSchedule import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.temporal.TemporalAdjusters import java.time.temporal.TemporalAdjusters
import android.location.Location as AndroidLocation import android.location.Location as AndroidLocation
@ -154,6 +155,8 @@ interface Location : SavableSearchable {
val openingSchedule: OpeningSchedule? val openingSchedule: OpeningSchedule?
val departures: List<Departure>? val departures: List<Departure>?
val acceptedPaymentMethods: Map<PaymentMethod, Boolean>?
val attribution: Attribution? val attribution: Attribution?
get() = null get() = null

View File

@ -5,6 +5,7 @@ import de.mm20.launcher2.search.location.Attribution
import de.mm20.launcher2.search.location.Departure import de.mm20.launcher2.search.location.Departure
import de.mm20.launcher2.search.location.LocationIcon import de.mm20.launcher2.search.location.LocationIcon
import de.mm20.launcher2.search.location.OpeningSchedule import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
abstract class LocationPluginContract { abstract class LocationPluginContract {
object Paths { object Paths {
@ -139,5 +140,11 @@ abstract class LocationPluginContract {
* Type: String? (JSON) * Type: String? (JSON)
*/ */
val Attribution = column<Attribution>("attribution") val Attribution = column<Attribution>("attribution")
/**
* Accepted payment methods at this location, encoded as JSON.
* Type: String? (JSON)
*/
val AcceptedPaymentMethods = column<Map<PaymentMethod, Boolean>>("accepted_payment_methods")
} }
} }

View File

@ -0,0 +1,6 @@
package de.mm20.launcher2.search.location
enum class PaymentMethod {
Cash,
Card
}

View File

@ -1,7 +1,6 @@
package de.mm20.launcher2.locations package de.mm20.launcher2.locations
import android.content.Context import android.content.Context
import android.util.Log
import de.mm20.launcher2.locations.providers.PluginLocation import de.mm20.launcher2.locations.providers.PluginLocation
import de.mm20.launcher2.locations.providers.PluginLocationProvider import de.mm20.launcher2.locations.providers.PluginLocationProvider
import de.mm20.launcher2.locations.providers.openstreetmaps.OsmLocation import de.mm20.launcher2.locations.providers.openstreetmaps.OsmLocation
@ -18,10 +17,10 @@ import de.mm20.launcher2.search.location.Attribution
import de.mm20.launcher2.search.location.Departure import de.mm20.launcher2.search.location.Departure
import de.mm20.launcher2.search.location.LocationIcon import de.mm20.launcher2.search.location.LocationIcon
import de.mm20.launcher2.search.location.OpeningSchedule import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import de.mm20.launcher2.serialization.Json import de.mm20.launcher2.serialization.Json
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
@Serializable @Serializable
internal data class SerializedLocation( internal data class SerializedLocation(
@ -42,6 +41,7 @@ internal data class SerializedLocation(
val departures: List<Departure>? = null, val departures: List<Departure>? = null,
val fixMeUrl: String? = null, val fixMeUrl: String? = null,
val attribution: Attribution? = null, val attribution: Attribution? = null,
val acceptedPaymentMethods: Map<PaymentMethod, Boolean>? = null,
val authority: String? = null, val authority: String? = null,
val storageStrategy: StorageStrategy? = null, val storageStrategy: StorageStrategy? = null,
) )
@ -67,6 +67,7 @@ internal class OsmLocationSerializer : SearchableSerializer {
timestamp = searchable.timestamp, timestamp = searchable.timestamp,
departures = searchable.departures, departures = searchable.departures,
fixMeUrl = searchable.fixMeUrl, fixMeUrl = searchable.fixMeUrl,
acceptedPaymentMethods = searchable.acceptedPaymentMethods
) )
) )
} }
@ -96,6 +97,7 @@ internal class OsmLocationDeserializer(
userRating = json.userRating, userRating = json.userRating,
openingSchedule = json.openingSchedule, openingSchedule = json.openingSchedule,
timestamp = json.timestamp ?: return null, timestamp = json.timestamp ?: return null,
acceptedPaymentMethods = json.acceptedPaymentMethods,
updatedSelf = { updatedSelf = {
osmProvider.update(id) osmProvider.update(id)
} }
@ -134,6 +136,7 @@ internal class PluginLocationSerializer : SearchableSerializer {
openingSchedule = searchable.openingSchedule, openingSchedule = searchable.openingSchedule,
timestamp = searchable.timestamp, timestamp = searchable.timestamp,
departures = searchable.departures, departures = searchable.departures,
acceptedPaymentMethods = searchable.acceptedPaymentMethods,
fixMeUrl = searchable.fixMeUrl, fixMeUrl = searchable.fixMeUrl,
authority = searchable.authority, authority = searchable.authority,
storageStrategy = searchable.storageStrategy, storageStrategy = searchable.storageStrategy,
@ -185,6 +188,7 @@ internal class PluginLocationDeserializer(
departures = json.departures, departures = json.departures,
fixMeUrl = json.fixMeUrl, fixMeUrl = json.fixMeUrl,
attribution = json.attribution, attribution = json.attribution,
acceptedPaymentMethods = json.acceptedPaymentMethods,
authority = authority, authority = authority,
storageStrategy = strategy, storageStrategy = strategy,
updatedSelf = { updatedSelf = {

View File

@ -14,6 +14,7 @@ import de.mm20.launcher2.search.location.Attribution
import de.mm20.launcher2.search.location.Departure import de.mm20.launcher2.search.location.Departure
import de.mm20.launcher2.search.location.LocationIcon import de.mm20.launcher2.search.location.LocationIcon
import de.mm20.launcher2.search.location.OpeningSchedule import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -34,6 +35,7 @@ data class PluginLocation(
override val label: String, override val label: String,
override val timestamp: Long, override val timestamp: Long,
override val attribution: Attribution?, override val attribution: Attribution?,
override val acceptedPaymentMethods: Map<PaymentMethod, Boolean>?,
override val updatedSelf: (suspend (SavableSearchable) -> UpdateResult<Location>)?, override val updatedSelf: (suspend (SavableSearchable) -> UpdateResult<Location>)?,
override val labelOverride: String? = null, override val labelOverride: String? = null,
val authority: String, val authority: String,

View File

@ -84,6 +84,7 @@ internal class PluginLocationProvider(
userRatingCount = cursor[LocationColumns.UserRatingCount], userRatingCount = cursor[LocationColumns.UserRatingCount],
departures = cursor[LocationColumns.Departures], departures = cursor[LocationColumns.Departures],
attribution = cursor[LocationColumns.Attribution], attribution = cursor[LocationColumns.Attribution],
acceptedPaymentMethods = cursor[LocationColumns.AcceptedPaymentMethods],
authority = pluginAuthority, authority = pluginAuthority,
updatedSelf = { updatedSelf = {
if (it !is PluginLocation) UpdateResult.TemporarilyUnavailable() if (it !is PluginLocation) UpdateResult.TemporarilyUnavailable()
@ -117,6 +118,7 @@ internal class PluginLocationProvider(
set(LocationColumns.UserRatingCount, userRatingCount) set(LocationColumns.UserRatingCount, userRatingCount)
set(LocationColumns.Departures, departures) set(LocationColumns.Departures, departures)
set(LocationColumns.Attribution, attribution) set(LocationColumns.Attribution, attribution)
set(LocationColumns.AcceptedPaymentMethods, acceptedPaymentMethods)
} }
} }
} }

View File

@ -4,6 +4,7 @@ import android.content.Context
import de.mm20.launcher2.ktx.ifNullOrEmpty import de.mm20.launcher2.ktx.ifNullOrEmpty
import de.mm20.launcher2.ktx.into import de.mm20.launcher2.ktx.into
import de.mm20.launcher2.ktx.map import de.mm20.launcher2.ktx.map
import de.mm20.launcher2.ktx.stripStartOrNull
import de.mm20.launcher2.locations.OsmLocationSerializer import de.mm20.launcher2.locations.OsmLocationSerializer
import de.mm20.launcher2.openstreetmaps.R import de.mm20.launcher2.openstreetmaps.R
import de.mm20.launcher2.search.Location import de.mm20.launcher2.search.Location
@ -16,6 +17,7 @@ import de.mm20.launcher2.search.location.Departure
import de.mm20.launcher2.search.location.LocationIcon import de.mm20.launcher2.search.location.LocationIcon
import de.mm20.launcher2.search.location.OpeningHours import de.mm20.launcher2.search.location.OpeningHours
import de.mm20.launcher2.search.location.OpeningSchedule import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import de.westnordost.osm_opening_hours.model.ClockTime import de.westnordost.osm_opening_hours.model.ClockTime
import de.westnordost.osm_opening_hours.model.ExtendedClockTime import de.westnordost.osm_opening_hours.model.ExtendedClockTime
import de.westnordost.osm_opening_hours.model.LastNth import de.westnordost.osm_opening_hours.model.LastNth
@ -62,7 +64,8 @@ internal data class OsmLocation(
override val labelOverride: String? = null, override val labelOverride: String? = null,
override val timestamp: Long, override val timestamp: Long,
override var updatedSelf: (suspend (SavableSearchable) -> UpdateResult<Location>)? = null, override var updatedSelf: (suspend (SavableSearchable) -> UpdateResult<Location>)? = null,
override val userRating: Float? override val userRating: Float?,
override val acceptedPaymentMethods: Map<PaymentMethod, Boolean>?
) : Location, UpdatableSearchable<Location> { ) : Location, UpdatableSearchable<Location> {
override val domain: String override val domain: String
@ -115,7 +118,25 @@ internal data class OsmLocation(
emailAddress = it.tags["email"] ?: it.tags["contact:email"], emailAddress = it.tags["email"] ?: it.tags["contact:email"],
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
userRating = it.tags["stars"]?.runCatching { this.toInt() }?.getOrNull() userRating = it.tags["stars"]?.runCatching { this.toInt() }?.getOrNull()
?.let { min(it, 5) / 5.0f } ?.let { min(it, 5) / 5.0f },
acceptedPaymentMethods = with(
it.tags.mapNotNull { (key, value) ->
(key.stripStartOrNull("payment:") ?: return@mapNotNull null) to value
}.toMap()
) {
// best-effort way to take any method payment as it being available,
// otherwise as being unavailable, or undefined
mapOf(
PaymentMethod.Card to listOf("credit_cards", "debit_cards", "cards"),
PaymentMethod.Cash to listOf("cash")
).mapNotNull { (method, values) ->
when {
values.any { this[it] in listOf("yes", "only") } -> method to true
values.any { this[it] == "no" } -> method to false
else -> null
}
}.toMap().takeUnless { it.isEmpty() }
}
) )
} }
} }