mirror of https://github.com/tasks/tasks
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.
146 lines
5.4 KiB
Kotlin
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
|
|
}
|
|
}
|
|
} |