You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/org/tasks/location/PlaceSearchGoogle.kt

146 lines
5.4 KiB
Kotlin

package org.tasks.location
import android.content.Context
import android.os.Bundle
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Request
import org.tasks.R
import org.tasks.data.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.data.CaldavDao
import org.tasks.data.Place
import org.tasks.data.Place.Companion.newPlace
import org.tasks.http.HttpClientFactory
import org.tasks.http.HttpException
import timber.log.Timber
import java.util.*
import javax.inject.Inject
class PlaceSearchGoogle @Inject constructor(
@ApplicationContext private val context: Context,
private val httpClientFactory: HttpClientFactory,
private val caldavDao: CaldavDao
) : PlaceSearch {
private val url = context.getString(R.string.tasks_places_url)
private var token: String? = null
override fun restoreState(savedInstanceState: Bundle?) {
token = savedInstanceState?.getParcelable(EXTRA_SESSION_TOKEN)
}
override fun saveState(outState: Bundle) {
outState.putString(EXTRA_SESSION_TOKEN, token)
}
override fun getAttributionRes(dark: Boolean) = if (dark) {
R.drawable.powered_by_google_on_non_white
} else {
R.drawable.powered_by_google_on_white
}
override suspend fun search(query: String, bias: MapPosition?): List<PlaceSearchResult> {
if (token == null) {
token = UUID.randomUUID().toString()
}
val proximity = bias?.let {
"&location=${bias.latitude},${bias.longitude}&radius=25000"
}
val jsonObject = execute(
"${this.url}/maps/api/place/queryautocomplete/json?input=$query&sessiontoken=$token$proximity"
)
return toSearchResults(jsonObject)
}
override suspend fun fetch(placeSearchResult: PlaceSearchResult): Place {
val jsonObject = execute(
"${this.url}/maps/api/place/details/json?place_id=${placeSearchResult.id}&fields=$FIELDS&sessiontoken=$token"
)
return toPlace(jsonObject)
}
private suspend fun execute(url: String): JsonObject = withContext(Dispatchers.IO) {
Timber.d(url)
val account = caldavDao.getAccounts(TYPE_TASKS).firstOrNull()
?: throw IllegalStateException(
context.getString(R.string.tasks_org_account_required)
)
val client = httpClientFactory
.newBuilder(
foreground = true,
username = account.username,
encryptedPassword = account.password
)
.build()
val response = client.newCall(Request.Builder().get().url(url).build()).execute()
if (response.isSuccessful) {
response.body?.string()?.toJson()?.apply { checkResult(this) }
?: throw IllegalStateException("Request failed")
} else {
throw HttpException(response.code, response.message)
}
}
companion object {
private const val EXTRA_SESSION_TOKEN = "extra_session_token"
private val FIELDS =
listOf(
"place_id",
"geometry/location",
"formatted_address",
"website",
"name",
"international_phone_number"
).joinToString(",")
internal fun String.toJson(): JsonObject = JsonParser.parseString(this).asJsonObject
private fun checkResult(json: JsonObject) {
val status = json.get("status").asString
when {
status == "OK" -> return
json.has("error_message") ->
throw IllegalStateException(json.get("error_message").asString)
else ->
throw IllegalStateException(status)
}
}
internal fun toSearchResults(json: JsonObject): List<PlaceSearchResult> =
json.get("predictions")
.asJsonArray
.map { it.asJsonObject }
.filter { it.has("place_id") }
.map { toSearchEntry(it) }
private fun toSearchEntry(json: JsonObject): PlaceSearchResult {
val place = json.get("structured_formatting").asJsonObject
return PlaceSearchResult(
json.get("place_id").asString,
place.get("main_text").asString,
place.get("secondary_text").asString
)
}
internal fun toPlace(json: JsonObject): Place {
val result = json.get("result").asJsonObject
val location = result.get("geometry").asJsonObject.get("location").asJsonObject
return newPlace().apply {
name = result.get("name").asString
address = result.getString("formatted_address")
phone = result.getString("international_phone_number")
url = result.getString("website")
latitude = location.get("lat").asDouble
longitude = location.get("lng").asDouble
}
}
private fun JsonObject.getString(field: String): String? = if (has(field)) {
get(field).asString
} else {
null
}
}
}