Replace Places SDK with HTTP API

pull/1376/head
Alex Baker 3 years ago
parent 4f9d229c4b
commit 0e728152c9

@ -96,6 +96,7 @@ android {
resValue("string", "google_key", tasks_google_key_debug ?: "") resValue("string", "google_key", tasks_google_key_debug ?: "")
resValue("string", "tasks_caldav_url", tasks_caldav_url ?: "https://caldav.tasks.org") resValue("string", "tasks_caldav_url", tasks_caldav_url ?: "https://caldav.tasks.org")
resValue("string", "tasks_nominatim_url", tasks_caldav_url ?: "https://nominatim.tasks.org") resValue("string", "tasks_nominatim_url", tasks_caldav_url ?: "https://nominatim.tasks.org")
resValue("string", "tasks_places_url", tasks_caldav_url ?: "https://places.tasks.org")
isTestCoverageEnabled = project.hasProperty("coverage") isTestCoverageEnabled = project.hasProperty("coverage")
} }
getByName("release") { getByName("release") {
@ -215,7 +216,6 @@ dependencies {
googleplayImplementation("com.google.firebase:firebase-config-ktx:${Versions.remote_config}") googleplayImplementation("com.google.firebase:firebase-config-ktx:${Versions.remote_config}")
googleplayImplementation("com.google.android.gms:play-services-location:17.1.0") googleplayImplementation("com.google.android.gms:play-services-location:17.1.0")
googleplayImplementation("com.google.android.gms:play-services-maps:17.0.0") googleplayImplementation("com.google.android.gms:play-services-maps:17.0.0")
googleplayImplementation("com.google.android.libraries.places:places:2.4.0")
googleplayImplementation("com.android.billingclient:billing:1.2.2") googleplayImplementation("com.android.billingclient:billing:1.2.2")
androidTestImplementation("com.google.dagger:hilt-android-testing:${Versions.hilt}") androidTestImplementation("com.google.dagger:hilt-android-testing:${Versions.hilt}")

@ -5,20 +5,16 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.ActivityScoped
import dagger.hilt.android.scopes.ViewModelScoped import org.tasks.location.AndroidLocationProvider
import org.tasks.location.* import org.tasks.location.LocationProvider
import org.tasks.location.MapFragment
import org.tasks.location.OsmMapFragment
@Module @Module
@InstallIn(ActivityComponent::class, ViewModelComponent::class) @InstallIn(ActivityComponent::class)
class LocationModule { class LocationModule {
@Provides
@ViewModelScoped
fun getPlaceSearchProvider(mapboxSearchProvider: PlaceSearchMapbox): PlaceSearch =
mapboxSearchProvider
@Provides @Provides
@ActivityScoped @ActivityScoped
fun getLocationProvider(provider: AndroidLocationProvider): LocationProvider = provider fun getLocationProvider(provider: AndroidLocationProvider): LocationProvider = provider

@ -5,37 +5,15 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.ActivityScoped
import dagger.hilt.android.scopes.ViewModelScoped
import org.tasks.R import org.tasks.R
import org.tasks.billing.Inventory
import org.tasks.gtasks.PlayServices
import org.tasks.location.* import org.tasks.location.*
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
@Module @Module
@InstallIn(ActivityComponent::class, ViewModelComponent::class) @InstallIn(ActivityComponent::class)
internal class LocationModule { internal class LocationModule {
@Provides
@ViewModelScoped
fun getPlaceSearchProvider(
@ApplicationContext context: Context,
preferences: Preferences,
playServices: PlayServices,
inventory: Inventory,
mapboxSearchProvider: MapboxSearchProvider
): PlaceSearchProvider {
return if (preferences.useGooglePlaces()
&& playServices.isPlayServicesAvailable
&& inventory.hasPro) {
PlaceSearchGoogle(context)
} else {
mapboxSearchProvider
}
}
@Provides @Provides
@ActivityScoped @ActivityScoped
fun getLocationProvider(@ApplicationContext context: Context): LocationProvider = fun getLocationProvider(@ApplicationContext context: Context): LocationProvider =

@ -1,128 +0,0 @@
package org.tasks.location
import android.content.Context
import android.os.Bundle
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.AutocompletePrediction
import com.google.android.libraries.places.api.model.AutocompleteSessionToken
import com.google.android.libraries.places.api.model.Place.Field
import com.google.android.libraries.places.api.model.RectangularBounds
import com.google.android.libraries.places.api.net.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.tasks.R
import org.tasks.data.Place
import org.tasks.data.Place.Companion.newPlace
import kotlin.coroutines.suspendCoroutine
class PlaceSearchGoogle(private val context: Context) : PlaceSearch {
private var token: AutocompleteSessionToken? = null
private var placesClient: PlacesClient? = null
override fun restoreState(savedInstanceState: Bundle?) {
token = savedInstanceState?.getParcelable(EXTRA_SESSION_TOKEN)
}
override fun saveState(outState: Bundle) {
outState.putParcelable(EXTRA_SESSION_TOKEN, token)
}
override fun getAttributionRes(dark: Boolean): Int {
return if (dark) R.drawable.places_powered_by_google_dark else R.drawable.places_powered_by_google_light
}
override suspend fun search(query: String, bias: MapPosition?): List<PlaceSearchResult> =
withContext(Dispatchers.IO) {
suspendCoroutine { cont ->
if (!Places.isInitialized()) {
Places.initialize(context, context.getString(R.string.google_key))
}
if (placesClient == null) {
placesClient = Places.createClient(context)
}
if (token == null) {
token = AutocompleteSessionToken.newInstance()
}
val request = FindAutocompletePredictionsRequest.builder().setSessionToken(token).setQuery(query)
if (bias != null) {
request.locationBias =
RectangularBounds.newInstance(
LatLngBounds.builder()
.include(LatLng(bias.latitude, bias.longitude))
.build())
}
placesClient!!
.findAutocompletePredictions(request.build())
.addOnSuccessListener { response: FindAutocompletePredictionsResponse ->
val places = toSearchResults(response.autocompletePredictions)
cont.resumeWith(Result.success(places))
}
.addOnFailureListener { e: Exception ->
cont.resumeWith(Result.failure(e))
}
}
}
override suspend fun fetch(placeSearchResult: PlaceSearchResult): Place =
withContext(Dispatchers.IO) {
suspendCoroutine { cont ->
placesClient!!
.fetchPlace(
FetchPlaceRequest.builder(
placeSearchResult.id,
listOf(
Field.ID,
Field.LAT_LNG,
Field.ADDRESS,
Field.WEBSITE_URI,
Field.NAME,
Field.PHONE_NUMBER))
.setSessionToken(token)
.build())
.addOnSuccessListener { result: FetchPlaceResponse ->
cont.resumeWith(Result.success(toPlace(result)))
}
.addOnFailureListener { e: Exception ->
cont.resumeWith(Result.failure(e))
}
}
}
private fun toSearchResults(predictions: List<AutocompletePrediction>): List<PlaceSearchResult> {
return predictions.map {
PlaceSearchResult(
it.placeId,
it.getPrimaryText(null).toString(),
it.getSecondaryText(null).toString())
}
}
private fun toPlace(fetchPlaceResponse: FetchPlaceResponse): Place {
val place = fetchPlaceResponse.place
val result = newPlace()
result.name = place.name
val address: CharSequence? = place.address
if (address != null) {
result.address = place.address
}
val phoneNumber: CharSequence? = place.phoneNumber
if (phoneNumber != null) {
result.phone = phoneNumber.toString()
}
val uri = place.websiteUri
if (uri != null) {
result.url = uri.toString()
}
val latLng = place.latLng
result.latitude = latLng!!.latitude
result.longitude = latLng.longitude
return result
}
companion object {
private const val EXTRA_SESSION_TOKEN = "extra_session_token"
}
}

@ -140,6 +140,7 @@ _${getString(R.string.account_not_included)}_
#### ${getString(R.string.tasks_org_account)} #### ${getString(R.string.tasks_org_account)}
* ${getString(R.string.tasks_org_description)} * ${getString(R.string.tasks_org_description)}
* [${getString(R.string.upgrade_third_party_apps)}](${getString(R.string.url_app_passwords)}) * [${getString(R.string.upgrade_third_party_apps)}](${getString(R.string.url_app_passwords)})
* ${getString(R.string.upgrade_google_places)}
* [${getString(R.string.upgrade_coming_soon)}](${getString(R.string.help_url_sync)}) * [${getString(R.string.upgrade_coming_soon)}](${getString(R.string.help_url_sync)})
""" """
} }
@ -155,7 +156,6 @@ _${getString(R.string.account_not_included)}_
--- ---
#### ${getString(R.string.upgrade_additional_features)} #### ${getString(R.string.upgrade_additional_features)}
* ${getString(R.string.upgrade_themes)} * ${getString(R.string.upgrade_themes)}
* ${getString(R.string.upgrade_google_places)}
* [${getString(R.string.upgrade_tasker)}](${getString(R.string.url_tasker)}) * [${getString(R.string.upgrade_tasker)}](${getString(R.string.url_tasker)})
--- ---
* ${getString(R.string.upgrade_free_trial)} * ${getString(R.string.upgrade_free_trial)}

@ -2,6 +2,7 @@ package org.tasks.http
import android.content.Context import android.content.Context
import at.bitfire.cert4android.CustomCertManager import at.bitfire.cert4android.CustomCertManager
import at.bitfire.dav4jvm.BasicDigestAuthHandler
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -9,6 +10,7 @@ import okhttp3.OkHttpClient
import okhttp3.internal.tls.OkHostnameVerifier import okhttp3.internal.tls.OkHostnameVerifier
import org.tasks.DebugNetworkInterceptor import org.tasks.DebugNetworkInterceptor
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.security.KeyStoreEncryption
import javax.inject.Inject import javax.inject.Inject
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
@ -16,16 +18,30 @@ class HttpClientFactory @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val preferences: Preferences, private val preferences: Preferences,
private val interceptor: DebugNetworkInterceptor, private val interceptor: DebugNetworkInterceptor,
private val encryption: KeyStoreEncryption,
) { ) {
suspend fun newCertManager() = withContext(Dispatchers.Default) { suspend fun newCertManager() = withContext(Dispatchers.Default) {
CustomCertManager(context) CustomCertManager(context)
} }
suspend fun newBuilder(foreground: Boolean = false): OkHttpClient.Builder = suspend fun newBuilder(
newBuilder(newCertManager(), foreground) foreground: Boolean = false,
username: String? = null,
encryptedPassword: String? = null
): OkHttpClient.Builder = newBuilder(
newCertManager(),
foreground = foreground,
username = username,
password = encryptedPassword?.let { encryption.decrypt(it) }
)
fun newBuilder(customCertManager: CustomCertManager, foreground: Boolean = false): OkHttpClient.Builder { fun newBuilder(
customCertManager: CustomCertManager,
foreground: Boolean = false,
username: String? = null,
password: String? = null
): OkHttpClient.Builder {
customCertManager.appInForeground = foreground customCertManager.appInForeground = foreground
val hostnameVerifier = customCertManager.hostnameVerifier(OkHostnameVerifier) val hostnameVerifier = customCertManager.hostnameVerifier(OkHostnameVerifier)
val sslContext = SSLContext.getInstance("TLS") val sslContext = SSLContext.getInstance("TLS")
@ -37,6 +53,11 @@ class HttpClientFactory @Inject constructor(
if (preferences.isFlipperEnabled) { if (preferences.isFlipperEnabled) {
interceptor.apply(builder) interceptor.apply(builder)
} }
if (!username.isNullOrBlank() && !password.isNullOrBlank()) {
val auth = BasicDigestAuthHandler(null, username, password)
builder.addNetworkInterceptor(auth)
builder.authenticator(auth)
}
return builder return builder
} }
} }

@ -0,0 +1,24 @@
package org.tasks.injection
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
import org.tasks.billing.Inventory
import org.tasks.location.PlaceSearch
import org.tasks.location.PlaceSearchGoogle
import org.tasks.location.PlaceSearchMapbox
@Module
@InstallIn(ViewModelComponent::class)
class ViewModelModule {
@Provides
@ViewModelScoped
fun getPlaceSearchProvider(
inventory: Inventory,
google: Lazy<PlaceSearchGoogle>,
mapbox: Lazy<PlaceSearchMapbox>
): PlaceSearch = if (inventory.hasTasksSubscription) google.get() else mapbox.get()
}

@ -23,7 +23,7 @@ class GeocoderNominatim @Inject constructor(
override suspend fun reverseGeocode(mapPosition: MapPosition): Place? = override suspend fun reverseGeocode(mapPosition: MapPosition): Place? =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val client = httpClientFactory.newBuilder(true).build() val client = httpClientFactory.newBuilder(foreground = true).build()
val url = "$url/reverse?format=geocodejson&lat=${mapPosition.latitude}&lon=${mapPosition.longitude}" val url = "$url/reverse?format=geocodejson&lat=${mapPosition.latitude}&lon=${mapPosition.longitude}"
val response = client.newCall( val response = client.newCall(
Request.Builder().get().url(url).addHeader(USER_AGENT, UA_VALUE).build() Request.Builder().get().url(url).addHeader(USER_AGENT, UA_VALUE).build()

@ -303,7 +303,7 @@ class LocationPickerActivity : InjectingAppCompatActivity(), Toolbar.OnMenuItemC
searchSubject searchSubject
.debounce(SEARCH_DEBOUNCE_TIMEOUT.toLong(), TimeUnit.MILLISECONDS) .debounce(SEARCH_DEBOUNCE_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { query: String? -> viewModel.query(query, mapPosition) }) .subscribe { query: String? -> viewModel.query(query, map.mapPosition) })
} }
override fun onDestroy() { override fun onDestroy() {

@ -0,0 +1,146 @@
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
}
}
}

@ -423,8 +423,6 @@ class Preferences @JvmOverloads constructor(
syncFlags.forEach { setBoolean(it, value) } syncFlags.forEach { setBoolean(it, value) }
} }
fun useGooglePlaces(): Boolean = getIntegerFromString(R.string.p_place_provider, 0) == 1
fun <T> getPrefs(c: Class<T>): Map<String, T> { fun <T> getPrefs(c: Class<T>): Map<String, T> {
val result: MutableMap<String, T> = HashMap() val result: MutableMap<String, T> = HashMap()
val entries: Iterable<Map.Entry<String, *>> = prefs.all.entries.filter { e: Map.Entry<String?, Any?> -> c.isInstance(e.value) } val entries: Iterable<Map.Entry<String, *>> = prefs.all.entries.filter { e: Map.Entry<String?, Any?> -> c.isInstance(e.value) }

@ -35,16 +35,10 @@ class LocationPreferences : InjectingPreferenceFragment() {
override suspend fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
if (IS_GOOGLE_PLAY) { if (IS_GOOGLE_PLAY) {
findPreference(R.string.p_place_provider)
.setOnPreferenceChangeListener(this::onPlaceSearchChanged)
findPreference(R.string.p_geofence_service) findPreference(R.string.p_geofence_service)
.setOnPreferenceChangeListener(this::onGeofenceServiceChanged) .setOnPreferenceChangeListener(this::onGeofenceServiceChanged)
} else { } else {
disable( disable(R.string.p_map_tiles, R.string.p_geofence_service)
R.string.p_map_tiles,
R.string.p_place_provider,
R.string.p_geofence_service
)
} }
} }
@ -71,21 +65,6 @@ class LocationPreferences : InjectingPreferenceFragment() {
findPreference(R.string.p_geofence_service).isEnabled = hasPermissions && IS_GOOGLE_PLAY findPreference(R.string.p_geofence_service).isEnabled = hasPermissions && IS_GOOGLE_PLAY
} }
private fun onPlaceSearchChanged(preference: Preference, newValue: Any): Boolean =
if (newValue.toString().toIntOrNull() ?: 0 == 1) {
if (!playServices.refreshAndCheck()) {
playServices.resolve(activity)
false
} else if (!inventory.hasPro) {
toaster.longToast(R.string.requires_pro_subscription)
false
} else {
true
}
} else {
true
}
private fun onGeofenceServiceChanged(preference: Preference, newValue: Any): Boolean = private fun onGeofenceServiceChanged(preference: Preference, newValue: Any): Boolean =
if (newValue.toString().toIntOrNull() ?: 0 == 1) { if (newValue.toString().toIntOrNull() ?: 0 == 1) {
if (!playServices.refreshAndCheck()) { if (!playServices.refreshAndCheck()) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

@ -188,7 +188,6 @@
<string name="building_notifications">توليد الإخطارات</string> <string name="building_notifications">توليد الإخطارات</string>
<string name="bundle_notifications_summary">ادمج عدة إشعارات في شعار واحد</string> <string name="bundle_notifications_summary">ادمج عدة إشعارات في شعار واحد</string>
<string name="notification_troubleshooting_summary">انقر هنا إذا كنت تواجه مشاكل في الإشعارات</string> <string name="notification_troubleshooting_summary">انقر هنا إذا كنت تواجه مشاكل في الإشعارات</string>
<string name="map_search_provider">مزود الخرائط</string>
<string name="TEA_control_location">الموقع</string> <string name="TEA_control_location">الموقع</string>
<string name="EPr_show_task_edit_comments">إظهار التعليقات في شاشة تحرير المهمة</string> <string name="EPr_show_task_edit_comments">إظهار التعليقات في شاشة تحرير المهمة</string>
<string name="back_button_saves_task">الضغط على زر الرجوع يحفظ المهمة</string> <string name="back_button_saves_task">الضغط على زر الرجوع يحفظ المهمة</string>

@ -438,7 +438,6 @@
<string name="choose_a_location">Изберете местоположение</string> <string name="choose_a_location">Изберете местоположение</string>
<string name="pick_this_location">Избери това местоположение</string> <string name="pick_this_location">Избери това местоположение</string>
<string name="or_choose_a_location">Или избери местоположение</string> <string name="or_choose_a_location">Или избери местоположение</string>
<string name="map_search_provider">Намери доставчик</string>
<string name="missing_permissions">Липсващи разрешения</string> <string name="missing_permissions">Липсващи разрешения</string>
<string name="location_permission_required_location">Необходими са разрешения за местоположение, за да откриете текущото ви местоположение</string> <string name="location_permission_required_location">Необходими са разрешения за местоположение, за да откриете текущото ви местоположение</string>
<string name="open_map">Отворете картата</string> <string name="open_map">Отворете картата</string>

@ -464,7 +464,6 @@
<string name="theme_system_default">Dle systému</string> <string name="theme_system_default">Dle systému</string>
<string name="repeat_monthly_on_every_day_of_nth_week">každý %1$s %2$s</string> <string name="repeat_monthly_on_every_day_of_nth_week">každý %1$s %2$s</string>
<string name="or_choose_a_location">Nebo vyberte místo</string> <string name="or_choose_a_location">Nebo vyberte místo</string>
<string name="map_search_provider">Poskytovatel vyhledávání</string>
<string name="missing_permissions">Chybí oprávnění</string> <string name="missing_permissions">Chybí oprávnění</string>
<string name="location_permission_required_location">Oprávnění k přístupu k poloze je nutné pro zjištění Vaší aktuální polohy</string> <string name="location_permission_required_location">Oprávnění k přístupu k poloze je nutné pro zjištění Vaší aktuální polohy</string>
<string name="open_map">Otevřít mapu</string> <string name="open_map">Otevřít mapu</string>

@ -211,7 +211,6 @@
<string name="open_map">Åbn kort</string> <string name="open_map">Åbn kort</string>
<string name="location_permission_required_location">Placeringstilladelse kræves for at finde din nuværende placering</string> <string name="location_permission_required_location">Placeringstilladelse kræves for at finde din nuværende placering</string>
<string name="missing_permissions">Manglende tilladelser</string> <string name="missing_permissions">Manglende tilladelser</string>
<string name="map_search_provider">Søgeudbyder</string>
<string name="or_choose_a_location">Eller vælg et sted</string> <string name="or_choose_a_location">Eller vælg et sted</string>
<string name="pick_this_location">Vælg dette sted</string> <string name="pick_this_location">Vælg dette sted</string>
<string name="choose_a_location">Vælg sted</string> <string name="choose_a_location">Vælg sted</string>

@ -435,7 +435,6 @@
<string name="choose_a_location">Ort auswählen</string> <string name="choose_a_location">Ort auswählen</string>
<string name="pick_this_location">Diesen Ort auswählen</string> <string name="pick_this_location">Diesen Ort auswählen</string>
<string name="or_choose_a_location">Oder Ort auswählen</string> <string name="or_choose_a_location">Oder Ort auswählen</string>
<string name="map_search_provider">Suchanbieter</string>
<string name="missing_permissions">Fehlende Berechtigungen</string> <string name="missing_permissions">Fehlende Berechtigungen</string>
<string name="location_permission_required_location">Standortberechtigung wird zur Ermittlung des aktuellen Standortes benötigt</string> <string name="location_permission_required_location">Standortberechtigung wird zur Ermittlung des aktuellen Standortes benötigt</string>
<string name="open_map">Karte öffnen</string> <string name="open_map">Karte öffnen</string>

@ -438,7 +438,6 @@
<string name="choose_a_location">Escoger ubicación</string> <string name="choose_a_location">Escoger ubicación</string>
<string name="pick_this_location">Seleccionar esta ubicación</string> <string name="pick_this_location">Seleccionar esta ubicación</string>
<string name="or_choose_a_location">O elija una ubicación</string> <string name="or_choose_a_location">O elija una ubicación</string>
<string name="map_search_provider">Proveedor de búsquedas</string>
<string name="missing_permissions">Permisos ausentes</string> <string name="missing_permissions">Permisos ausentes</string>
<string name="location_permission_required_location">Son necesarios permisos de ubicación para encontrar tu ubicación actual</string> <string name="location_permission_required_location">Son necesarios permisos de ubicación para encontrar tu ubicación actual</string>
<string name="open_map">Abrir mapa</string> <string name="open_map">Abrir mapa</string>

@ -444,7 +444,6 @@
<string name="choose_a_location">Hautatu kokaleku bat</string> <string name="choose_a_location">Hautatu kokaleku bat</string>
<string name="pick_this_location">Hautatu kokaleku hau</string> <string name="pick_this_location">Hautatu kokaleku hau</string>
<string name="or_choose_a_location">Edo hautatu kokaleku bat</string> <string name="or_choose_a_location">Edo hautatu kokaleku bat</string>
<string name="map_search_provider">Bilatu hornitzailea</string>
<string name="missing_permissions">Baimenak falta dira</string> <string name="missing_permissions">Baimenak falta dira</string>
<string name="location_permission_required_location">Kokapen baimena behar da zure uneko kokalekua aurkitzeko</string> <string name="location_permission_required_location">Kokapen baimena behar da zure uneko kokalekua aurkitzeko</string>
<string name="open_map">Ireki mapa</string> <string name="open_map">Ireki mapa</string>

@ -441,7 +441,6 @@
<string name="open_map">Avaa kartta</string> <string name="open_map">Avaa kartta</string>
<string name="location_permission_required_location">Sijaintioikeudet tarvitaan nykysijainnin paikannukseen</string> <string name="location_permission_required_location">Sijaintioikeudet tarvitaan nykysijainnin paikannukseen</string>
<string name="missing_permissions">Oikeuksia puuttuu</string> <string name="missing_permissions">Oikeuksia puuttuu</string>
<string name="map_search_provider">Hauntarjoaja</string>
<string name="or_choose_a_location">Tai valitse muu sijainti</string> <string name="or_choose_a_location">Tai valitse muu sijainti</string>
<string name="pick_this_location">Valitse tämä sijainti</string> <string name="pick_this_location">Valitse tämä sijainti</string>
<string name="choose_a_location">Valitse sijainti</string> <string name="choose_a_location">Valitse sijainti</string>

@ -420,7 +420,6 @@
<string name="choose_a_location">Choisir une localisation</string> <string name="choose_a_location">Choisir une localisation</string>
<string name="pick_this_location">Sélectionner cette localisation</string> <string name="pick_this_location">Sélectionner cette localisation</string>
<string name="or_choose_a_location">Ou choisir une localisation</string> <string name="or_choose_a_location">Ou choisir une localisation</string>
<string name="map_search_provider">Moteurs de recherche</string>
<string name="missing_permissions">Permissions manquantes</string> <string name="missing_permissions">Permissions manquantes</string>
<string name="location_permission_required_location">La permission de la localisation est nécessaire pour trouver votre localisation actuelle</string> <string name="location_permission_required_location">La permission de la localisation est nécessaire pour trouver votre localisation actuelle</string>
<string name="open_map">Ouvrir la carte</string> <string name="open_map">Ouvrir la carte</string>

@ -439,7 +439,6 @@
<string name="choose_a_location">Hely kiválasztása</string> <string name="choose_a_location">Hely kiválasztása</string>
<string name="pick_this_location">Hely kiválasztása</string> <string name="pick_this_location">Hely kiválasztása</string>
<string name="or_choose_a_location">Vagy másik hely keresése</string> <string name="or_choose_a_location">Vagy másik hely keresése</string>
<string name="map_search_provider">Keresési szolgáltató</string>
<string name="missing_permissions">Hiányzó jogosultságok</string> <string name="missing_permissions">Hiányzó jogosultságok</string>
<string name="location_permission_required_location">A jelenlegi pozíció meghatározásához helymeghatározási jogosultság szükséges</string> <string name="location_permission_required_location">A jelenlegi pozíció meghatározásához helymeghatározási jogosultság szükséges</string>
<string name="open_map">Térkép megnyitása</string> <string name="open_map">Térkép megnyitása</string>

@ -368,7 +368,6 @@
<string name="choose_a_location">Pilih sebuah lokasi</string> <string name="choose_a_location">Pilih sebuah lokasi</string>
<string name="pick_this_location">Pilih lokasi ini</string> <string name="pick_this_location">Pilih lokasi ini</string>
<string name="or_choose_a_location">Atau pilih sebuah lokasi</string> <string name="or_choose_a_location">Atau pilih sebuah lokasi</string>
<string name="map_search_provider">Penyedia pencarian</string>
<string name="location_permission_required_location">Izin akses lokasi diperlukan untuk mencari lokasi Anda saat ini</string> <string name="location_permission_required_location">Izin akses lokasi diperlukan untuk mencari lokasi Anda saat ini</string>
<string name="open_map">Buka peta</string> <string name="open_map">Buka peta</string>
<string name="choose_new_location">Pilih lokasi baru</string> <string name="choose_new_location">Pilih lokasi baru</string>

@ -439,7 +439,6 @@
<string name="choose_a_location">Scegli una posizione</string> <string name="choose_a_location">Scegli una posizione</string>
<string name="pick_this_location">Seleziona questa posizione</string> <string name="pick_this_location">Seleziona questa posizione</string>
<string name="or_choose_a_location">O scegli una posizione</string> <string name="or_choose_a_location">O scegli una posizione</string>
<string name="map_search_provider">Seleziona fornitore</string>
<string name="missing_permissions">Permessi mancanti</string> <string name="missing_permissions">Permessi mancanti</string>
<string name="location_permission_required_location">Per trovare la tua posizione sono richiesti i permessi di localizzazione</string> <string name="location_permission_required_location">Per trovare la tua posizione sono richiesti i permessi di localizzazione</string>
<string name="open_map">Apri mappa</string> <string name="open_map">Apri mappa</string>

@ -473,7 +473,6 @@
<string name="choose_a_location">בחרו מיקום</string> <string name="choose_a_location">בחרו מיקום</string>
<string name="pick_this_location">בחרו את המיקום הזה</string> <string name="pick_this_location">בחרו את המיקום הזה</string>
<string name="or_choose_a_location">או בחרו מיקום</string> <string name="or_choose_a_location">או בחרו מיקום</string>
<string name="map_search_provider">ספק חיפוש</string>
<string name="missing_permissions">חסרות הרשאות</string> <string name="missing_permissions">חסרות הרשאות</string>
<string name="location_permission_required_location">הרשאות מיקום נדרשות על מנת למצוא את מיקומך הנוכחי</string> <string name="location_permission_required_location">הרשאות מיקום נדרשות על מנת למצוא את מיקומך הנוכחי</string>
<string name="open_map">פתיחת מפה</string> <string name="open_map">פתיחת מפה</string>

@ -436,7 +436,6 @@
<string name="choose_a_location">場所を選択</string> <string name="choose_a_location">場所を選択</string>
<string name="pick_this_location">この場所を選択</string> <string name="pick_this_location">この場所を選択</string>
<string name="or_choose_a_location">または場所を選ぶ</string> <string name="or_choose_a_location">または場所を選ぶ</string>
<string name="map_search_provider">プロバイダーを検索</string>
<string name="missing_permissions">アクセス許可がありません</string> <string name="missing_permissions">アクセス許可がありません</string>
<string name="location_permission_required_location">現在の場所を見つけるには位置のアクセス許可が必要です</string> <string name="location_permission_required_location">現在の場所を見つけるには位置のアクセス許可が必要です</string>
<string name="open_map">地図を開く</string> <string name="open_map">地図を開く</string>

@ -441,7 +441,6 @@
<string name="choose_a_location">위치 선택</string> <string name="choose_a_location">위치 선택</string>
<string name="pick_this_location">현위치 선택</string> <string name="pick_this_location">현위치 선택</string>
<string name="or_choose_a_location">위치 고르기</string> <string name="or_choose_a_location">위치 고르기</string>
<string name="map_search_provider">검색 제공업체</string>
<string name="missing_permissions">권한 누락</string> <string name="missing_permissions">권한 누락</string>
<string name="location_permission_required_location">기기의 현재 위치를 확인하려면 위치 권한이 필요합니다</string> <string name="location_permission_required_location">기기의 현재 위치를 확인하려면 위치 권한이 필요합니다</string>
<string name="open_map">지도 열기</string> <string name="open_map">지도 열기</string>

@ -435,7 +435,6 @@
<string name="choose_a_location">Pasirinkti vietą</string> <string name="choose_a_location">Pasirinkti vietą</string>
<string name="pick_this_location">Pasirinkti šią vietą</string> <string name="pick_this_location">Pasirinkti šią vietą</string>
<string name="or_choose_a_location">Arba pasirinkti vietą</string> <string name="or_choose_a_location">Arba pasirinkti vietą</string>
<string name="map_search_provider">Paieškos tiekėjas</string>
<string name="missing_permissions">Dingę leidimai</string> <string name="missing_permissions">Dingę leidimai</string>
<string name="location_permission_required_location">Vietos leidimai reikalingi surasti jūsų esamą vietą</string> <string name="location_permission_required_location">Vietos leidimai reikalingi surasti jūsų esamą vietą</string>
<string name="open_map">Atidaryti žemėlapį</string> <string name="open_map">Atidaryti žemėlapį</string>

@ -444,7 +444,6 @@
<string name="choose_a_location">Velg et sted</string> <string name="choose_a_location">Velg et sted</string>
<string name="pick_this_location">Velg dette stedet</string> <string name="pick_this_location">Velg dette stedet</string>
<string name="or_choose_a_location">Eller velg et sted</string> <string name="or_choose_a_location">Eller velg et sted</string>
<string name="map_search_provider">Søketilbyder</string>
<string name="missing_permissions">Manglende tilganger</string> <string name="missing_permissions">Manglende tilganger</string>
<string name="location_permission_required_location">Plasseringstilganger trengs for å finne ditt nåværende sted</string> <string name="location_permission_required_location">Plasseringstilganger trengs for å finne ditt nåværende sted</string>
<string name="open_map">Åpne kart</string> <string name="open_map">Åpne kart</string>

@ -428,7 +428,6 @@
<string name="choose_a_location">Kies een locatie</string> <string name="choose_a_location">Kies een locatie</string>
<string name="pick_this_location">Selecteer deze locatie</string> <string name="pick_this_location">Selecteer deze locatie</string>
<string name="or_choose_a_location">Of kies een locatie</string> <string name="or_choose_a_location">Of kies een locatie</string>
<string name="map_search_provider">Zoek aanbieder</string>
<string name="missing_permissions">Missende rechten</string> <string name="missing_permissions">Missende rechten</string>
<string name="location_permission_required_location">Locatie rechten zijn nodig om je huidige locatie te vinden</string> <string name="location_permission_required_location">Locatie rechten zijn nodig om je huidige locatie te vinden</string>
<string name="open_map">Open kaart</string> <string name="open_map">Open kaart</string>

@ -448,7 +448,6 @@
<string name="choose_a_location">Wybierz lokalizację</string> <string name="choose_a_location">Wybierz lokalizację</string>
<string name="pick_this_location">Ustaw wybraną lokalizację</string> <string name="pick_this_location">Ustaw wybraną lokalizację</string>
<string name="or_choose_a_location">Lub wybierz lokalizację</string> <string name="or_choose_a_location">Lub wybierz lokalizację</string>
<string name="map_search_provider">Silnik wyszukiwania</string>
<string name="missing_permissions">Brakujące uprawnienia</string> <string name="missing_permissions">Brakujące uprawnienia</string>
<string name="location_permission_required_location">Uprawnienia lokalizacji są wymagane do ustalenia twojej aktualnej lokalizacji</string> <string name="location_permission_required_location">Uprawnienia lokalizacji są wymagane do ustalenia twojej aktualnej lokalizacji</string>
<string name="open_map">Otwórz mapę</string> <string name="open_map">Otwórz mapę</string>

@ -436,7 +436,6 @@
<string name="choose_a_location">Escolher uma localização</string> <string name="choose_a_location">Escolher uma localização</string>
<string name="pick_this_location">Selecionar essa localização</string> <string name="pick_this_location">Selecionar essa localização</string>
<string name="or_choose_a_location">Ou escolha uma localização</string> <string name="or_choose_a_location">Ou escolha uma localização</string>
<string name="map_search_provider">Provedor de pesquisa</string>
<string name="missing_permissions">Permissões faltando</string> <string name="missing_permissions">Permissões faltando</string>
<string name="location_permission_required_location">Permissão de Localização é necessária para encontrar sua localização atual</string> <string name="location_permission_required_location">Permissão de Localização é necessária para encontrar sua localização atual</string>
<string name="open_map">Abrir mapa</string> <string name="open_map">Abrir mapa</string>

@ -399,7 +399,6 @@
<string name="open_map">Abrir mapa</string> <string name="open_map">Abrir mapa</string>
<string name="location_permission_required_location">Permissão de localização é necessária para encontrar a sua localização atual</string> <string name="location_permission_required_location">Permissão de localização é necessária para encontrar a sua localização atual</string>
<string name="missing_permissions">Permissões faltando</string> <string name="missing_permissions">Permissões faltando</string>
<string name="map_search_provider">Provedor de pesquisa</string>
<string name="or_choose_a_location">Ou escolha uma localização</string> <string name="or_choose_a_location">Ou escolha uma localização</string>
<string name="pick_this_location">Selecione esta localização</string> <string name="pick_this_location">Selecione esta localização</string>
<string name="choose_a_location">Escolher uma localização</string> <string name="choose_a_location">Escolher uma localização</string>

@ -455,7 +455,6 @@
<string name="choose_a_location">Выбрать местоположение</string> <string name="choose_a_location">Выбрать местоположение</string>
<string name="pick_this_location">Выбрать это местоположение</string> <string name="pick_this_location">Выбрать это местоположение</string>
<string name="or_choose_a_location">Или выбрать местоположение</string> <string name="or_choose_a_location">Или выбрать местоположение</string>
<string name="map_search_provider">Поставщик системы поиска</string>
<string name="missing_permissions">Отсутствующие разрешения</string> <string name="missing_permissions">Отсутствующие разрешения</string>
<string name="version_string">Версия %s</string> <string name="version_string">Версия %s</string>
<string name="gtasks_GPr_header">Google Tasks</string> <string name="gtasks_GPr_header">Google Tasks</string>

@ -435,7 +435,6 @@
<string name="choose_a_location">Vybrať polohu</string> <string name="choose_a_location">Vybrať polohu</string>
<string name="pick_this_location">Vybrať túto polohu</string> <string name="pick_this_location">Vybrať túto polohu</string>
<string name="or_choose_a_location">Alebo vybrať polohu ručne</string> <string name="or_choose_a_location">Alebo vybrať polohu ručne</string>
<string name="map_search_provider">Vyhľadávanie poskytuje</string>
<string name="missing_permissions">Chýbajúce oprávnenia</string> <string name="missing_permissions">Chýbajúce oprávnenia</string>
<string name="location_permission_required_location">Pre učenie súčasnej polohy sú potrebné povolenia</string> <string name="location_permission_required_location">Pre učenie súčasnej polohy sú potrebné povolenia</string>
<string name="open_map">Otvoriť mapu</string> <string name="open_map">Otvoriť mapu</string>

@ -439,7 +439,6 @@
<string name="choose_a_location">Välj en plats</string> <string name="choose_a_location">Välj en plats</string>
<string name="pick_this_location">Välj den här platsen</string> <string name="pick_this_location">Välj den här platsen</string>
<string name="or_choose_a_location">Eller Välj en plats</string> <string name="or_choose_a_location">Eller Välj en plats</string>
<string name="map_search_provider">Sökleverantör</string>
<string name="missing_permissions">Saknar behörigheter</string> <string name="missing_permissions">Saknar behörigheter</string>
<string name="location_permission_required_location">Plats behörigheter behövs för att hitta din aktuella plats</string> <string name="location_permission_required_location">Plats behörigheter behövs för att hitta din aktuella plats</string>
<string name="open_map">Öppna karta</string> <string name="open_map">Öppna karta</string>

@ -273,7 +273,6 @@
<string name="open_map">வரைபடத்தைத் திறக்கவும்</string> <string name="open_map">வரைபடத்தைத் திறக்கவும்</string>
<string name="location_permission_required_location">உங்கள் தற்போதைய இருப்பிடத்தைக் கண்டுபிடிக்க இருப்பிட அனுமதிகள் தேவை</string> <string name="location_permission_required_location">உங்கள் தற்போதைய இருப்பிடத்தைக் கண்டுபிடிக்க இருப்பிட அனுமதிகள் தேவை</string>
<string name="missing_permissions">அனுமதிகள் இல்லை</string> <string name="missing_permissions">அனுமதிகள் இல்லை</string>
<string name="map_search_provider">தேடல் வழங்குநர்</string>
<string name="or_choose_a_location">அல்லது இருப்பிடத்தைத் தேர்வுசெய்க</string> <string name="or_choose_a_location">அல்லது இருப்பிடத்தைத் தேர்வுசெய்க</string>
<string name="pick_this_location">இந்த இருப்பிடத்தைத் தேர்ந்தெடுக்கவும்</string> <string name="pick_this_location">இந்த இருப்பிடத்தைத் தேர்ந்தெடுக்கவும்</string>
<string name="choose_a_location">இருப்பிடத்தைத் தேர்வுசெய்க</string> <string name="choose_a_location">இருப்பிடத்தைத் தேர்வுசெய்க</string>

@ -441,7 +441,6 @@
<string name="choose_a_location">Konum seç</string> <string name="choose_a_location">Konum seç</string>
<string name="pick_this_location">Bu konumu seç</string> <string name="pick_this_location">Bu konumu seç</string>
<string name="or_choose_a_location">Ya da konum seç</string> <string name="or_choose_a_location">Ya da konum seç</string>
<string name="map_search_provider">Arama sağlayıcı</string>
<string name="missing_permissions">Eksik izinler</string> <string name="missing_permissions">Eksik izinler</string>
<string name="location_permission_required_location">Geçerli konumunuzu bulmak için konum izinleri gereklidir</string> <string name="location_permission_required_location">Geçerli konumunuzu bulmak için konum izinleri gereklidir</string>
<string name="open_map">Haritayı</string> <string name="open_map">Haritayı</string>

@ -457,7 +457,6 @@
<string name="choose_a_location">Обрати місце</string> <string name="choose_a_location">Обрати місце</string>
<string name="pick_this_location">Обрати це місце</string> <string name="pick_this_location">Обрати це місце</string>
<string name="or_choose_a_location">Або вказати інше місце</string> <string name="or_choose_a_location">Або вказати інше місце</string>
<string name="map_search_provider">Знайти провайдера</string>
<string name="missing_permissions">Відсутні дозволи</string> <string name="missing_permissions">Відсутні дозволи</string>
<string name="location_permission_required_location">Щоб визначити ваше місцезнаходження необхідний дозвіл на перегляд місцезнаходження </string> <string name="location_permission_required_location">Щоб визначити ваше місцезнаходження необхідний дозвіл на перегляд місцезнаходження </string>
<string name="open_map">Відкрити карту</string> <string name="open_map">Відкрити карту</string>

@ -434,7 +434,6 @@
<string name="choose_a_location">选择一个位置</string> <string name="choose_a_location">选择一个位置</string>
<string name="pick_this_location">选中这个位置</string> <string name="pick_this_location">选中这个位置</string>
<string name="or_choose_a_location">或选择一个位置</string> <string name="or_choose_a_location">或选择一个位置</string>
<string name="map_search_provider">搜索引擎供应商</string>
<string name="missing_permissions">缺少权限</string> <string name="missing_permissions">缺少权限</string>
<string name="location_permission_required_location">需要位置权限来发现您当前位置</string> <string name="location_permission_required_location">需要位置权限来发现您当前位置</string>
<string name="open_map">打开地图</string> <string name="open_map">打开地图</string>

@ -444,7 +444,6 @@
<string name="open_map">開啟地圖</string> <string name="open_map">開啟地圖</string>
<string name="location_permission_required_location">需要位置權限以找到您現在的位置</string> <string name="location_permission_required_location">需要位置權限以找到您現在的位置</string>
<string name="missing_permissions">缺少權限</string> <string name="missing_permissions">缺少權限</string>
<string name="map_search_provider">搜尋提供者</string>
<string name="or_choose_a_location">或選擇一個地點</string> <string name="or_choose_a_location">或選擇一個地點</string>
<string name="pick_this_location">選擇此地點</string> <string name="pick_this_location">選擇此地點</string>
<string name="choose_a_location">選擇一個地點</string> <string name="choose_a_location">選擇一個地點</string>

@ -292,16 +292,6 @@
<item>2</item> <item>2</item>
</string-array> </string-array>
<string-array name="map_search_provider_names">
<item>@string/mapbox</item>
<item>@string/map_search_google_places</item>
</string-array>
<string-array name="map_search_provider_values">
<item>0</item>
<item>1</item>
</string-array>
<string-array name="geofence_service_names"> <string-array name="geofence_service_names">
<item>@string/google_play_location_service</item> <item>@string/google_play_location_service</item>
<item>@string/android_location_services</item> <item>@string/android_location_services</item>

@ -396,12 +396,10 @@
<string name="p_linkify_task_list">linkify_task_list</string> <string name="p_linkify_task_list">linkify_task_list</string>
<string name="p_linkify_task_edit">linkify_task_edit</string> <string name="p_linkify_task_edit">linkify_task_edit</string>
<string name="mapbox">Mapbox</string> <string name="mapbox">Mapbox</string>
<string name="map_search_google_places">Google Places</string>
<string name="nominatim">Nominatim</string> <string name="nominatim">Nominatim</string>
<string name="google_maps">Google Maps</string> <string name="google_maps">Google Maps</string>
<string name="openstreetmap">OpenStreetMap</string> <string name="openstreetmap">OpenStreetMap</string>
<string name="android">Android</string> <string name="android">Android</string>
<string name="p_place_provider">place_provider_v2</string>
<string name="p_location_based_reminders">location_based_reminders</string> <string name="p_location_based_reminders">location_based_reminders</string>
<string name="p_geofence_service">geofence_service</string> <string name="p_geofence_service">geofence_service</string>
<string name="preference_screen">preference_screen</string> <string name="preference_screen">preference_screen</string>

@ -523,7 +523,6 @@ File %1$s contained %2$s.\n\n
<string name="choose_a_location">Choose a location</string> <string name="choose_a_location">Choose a location</string>
<string name="pick_this_location">Select this location</string> <string name="pick_this_location">Select this location</string>
<string name="or_choose_a_location">Or choose a location</string> <string name="or_choose_a_location">Or choose a location</string>
<string name="map_search_provider">Search provider</string>
<string name="missing_permissions">Missing permissions</string> <string name="missing_permissions">Missing permissions</string>
<string name="background_location_permission_required">This app collects location data to enable location-based reminders even when the app is closed or not in use.</string> <string name="background_location_permission_required">This app collects location data to enable location-based reminders even when the app is closed or not in use.</string>
<string name="location_permission_required_location">Location permissions are needed to find your current location</string> <string name="location_permission_required_location">Location permissions are needed to find your current location</string>

@ -54,14 +54,6 @@
android:entryValues="@array/reverse_geocoder_values" android:entryValues="@array/reverse_geocoder_values"
android:summary="%s"/> android:summary="%s"/>
<ListPreference
android:defaultValue="0"
android:key="@string/p_place_provider"
android:title="@string/map_search_provider"
android:entries="@array/map_search_provider_names"
android:entryValues="@array/map_search_provider_values"
android:summary="%s" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

@ -3,4 +3,5 @@
<string name="app_name">Tasks</string> <string name="app_name">Tasks</string>
<string name="tasks_caldav_url">https://caldav.tasks.org</string> <string name="tasks_caldav_url">https://caldav.tasks.org</string>
<string name="tasks_nominatim_url">https://nominatim.tasks.org</string> <string name="tasks_nominatim_url">https://nominatim.tasks.org</string>
<string name="tasks_places_url">https://places.tasks.org</string>
</resources> </resources>

@ -0,0 +1,49 @@
package org.tasks.location
import org.junit.Assert.assertEquals
import org.junit.Test
import org.tasks.TestUtilities.readFile
import org.tasks.location.PlaceSearchGoogle.Companion.toJson
import org.tasks.location.PlaceSearchGoogle.Companion.toPlace
import org.tasks.location.PlaceSearchGoogle.Companion.toSearchResults
class PlaceSearchGoogleTest {
@Test
fun placeSearchWithMultipleResults() {
val results = toSearchResults(readFile("google_places/search.json").toJson())
assertEquals(
listOf(
"ChIJfQQVCCMzjoARce0POzONI8I",
"ChIJQXqgXsiAj4ARqtl6U4GD-Cw",
"ChIJCWNdVNgr3YAR4pLlOt8CfEk",
"ChIJhTEH6lev3IARDMKC_pGF6nI"
),
results.map { it.id }
)
}
@Test
fun validatePlace() {
val result = toSearchResults(readFile("google_places/search.json").toJson())[2]
assertEquals("ChIJCWNdVNgr3YAR4pLlOt8CfEk", result.id)
assertEquals("Portillo's Hot Dogs", result.name)
assertEquals("La Palma Avenue, Buena Park, CA, USA", result.address)
}
@Test
fun fetchPlace() {
val result = toPlace(readFile("google_places/fetch.json").toJson())
assertEquals("Magic Kingdom Park", result.name)
assertEquals("1180 Seven Seas Drive, Lake Buena Vista, FL 32836, USA", result.address)
assertEquals(28.417663, result.latitude, 0.0)
assertEquals(-81.581212, result.longitude, 0.0)
assertEquals("+1 407-939-5277", result.phone)
assertEquals(
"https://disneyworld.disney.go.com/destinations/magic-kingdom/?CMP=OKC-80007944_GM_WDW_destination_magickingdompark_NA",
result.url
)
}
}

@ -0,0 +1,17 @@
{
"html_attributions": [],
"result": {
"formatted_address": "1180 Seven Seas Drive, Lake Buena Vista, FL 32836, USA",
"geometry": {
"location": {
"lat": 28.417663,
"lng": -81.581212
}
},
"international_phone_number": "+1 407-939-5277",
"name": "Magic Kingdom Park",
"place_id": "ChIJgUulalN-3YgRGoTaWM2LawY",
"website": "https://disneyworld.disney.go.com/destinations/magic-kingdom/?CMP=OKC-80007944_GM_WDW_destination_magickingdompark_NA"
},
"status": "OK"
}

@ -0,0 +1,225 @@
{
"predictions": [
{
"description": "portillo's hot dogs",
"matched_substrings": [
{
"length": 19,
"offset": 0
}
],
"structured_formatting": {
"main_text": "portillo's hot dogs",
"main_text_matched_substrings": [
{
"length": 19,
"offset": 0
}
]
},
"terms": [
{
"offset": 0,
"value": "portillo's hot dogs"
}
]
},
{
"description": "Portillo Diesel, Leo Avenue, San Jose, CA, USA",
"matched_substrings": [
{
"length": 15,
"offset": 0
}
],
"place_id": "ChIJfQQVCCMzjoARce0POzONI8I",
"reference": "ChIJfQQVCCMzjoARce0POzONI8I",
"structured_formatting": {
"main_text": "Portillo Diesel",
"main_text_matched_substrings": [
{
"length": 15,
"offset": 0
}
],
"secondary_text": "Leo Avenue, San Jose, CA, USA"
},
"terms": [
{
"offset": 0,
"value": "Portillo Diesel"
},
{
"offset": 17,
"value": "Leo Avenue"
},
{
"offset": 29,
"value": "San Jose"
},
{
"offset": 39,
"value": "CA"
},
{
"offset": 43,
"value": "USA"
}
],
"types": [
"car_repair",
"point_of_interest",
"establishment"
]
},
{
"description": "Portillo's Trucking, Franklin Street, Oakland, CA, USA",
"matched_substrings": [
{
"length": 19,
"offset": 0
}
],
"place_id": "ChIJQXqgXsiAj4ARqtl6U4GD-Cw",
"reference": "ChIJQXqgXsiAj4ARqtl6U4GD-Cw",
"structured_formatting": {
"main_text": "Portillo's Trucking",
"main_text_matched_substrings": [
{
"length": 19,
"offset": 0
}
],
"secondary_text": "Franklin Street, Oakland, CA, USA"
},
"terms": [
{
"offset": 0,
"value": "Portillo's Trucking"
},
{
"offset": 21,
"value": "Franklin Street"
},
{
"offset": 38,
"value": "Oakland"
},
{
"offset": 47,
"value": "CA"
},
{
"offset": 51,
"value": "USA"
}
],
"types": [
"moving_company",
"point_of_interest",
"establishment"
]
},
{
"description": "Portillo's Hot Dogs, La Palma Avenue, Buena Park, CA, USA",
"matched_substrings": [
{
"length": 19,
"offset": 0
}
],
"place_id": "ChIJCWNdVNgr3YAR4pLlOt8CfEk",
"reference": "ChIJCWNdVNgr3YAR4pLlOt8CfEk",
"structured_formatting": {
"main_text": "Portillo's Hot Dogs",
"main_text_matched_substrings": [
{
"length": 19,
"offset": 0
}
],
"secondary_text": "La Palma Avenue, Buena Park, CA, USA"
},
"terms": [
{
"offset": 0,
"value": "Portillo's Hot Dogs"
},
{
"offset": 21,
"value": "La Palma Avenue"
},
{
"offset": 38,
"value": "Buena Park"
},
{
"offset": 50,
"value": "CA"
},
{
"offset": 54,
"value": "USA"
}
],
"types": [
"meal_takeaway",
"restaurant",
"food",
"point_of_interest",
"establishment"
]
},
{
"description": "Portillo's Hot Dogs, Day Street, Moreno Valley, CA, USA",
"matched_substrings": [
{
"length": 19,
"offset": 0
}
],
"place_id": "ChIJhTEH6lev3IARDMKC_pGF6nI",
"reference": "ChIJhTEH6lev3IARDMKC_pGF6nI",
"structured_formatting": {
"main_text": "Portillo's Hot Dogs",
"main_text_matched_substrings": [
{
"length": 19,
"offset": 0
}
],
"secondary_text": "Day Street, Moreno Valley, CA, USA"
},
"terms": [
{
"offset": 0,
"value": "Portillo's Hot Dogs"
},
{
"offset": 21,
"value": "Day Street"
},
{
"offset": 33,
"value": "Moreno Valley"
},
{
"offset": 48,
"value": "CA"
},
{
"offset": 52,
"value": "USA"
}
],
"types": [
"meal_takeaway",
"restaurant",
"food",
"point_of_interest",
"establishment"
]
}
],
"status": "OK"
}

@ -186,8 +186,32 @@
+| +--- androidx.fragment:fragment:1.0.0 -> 1.2.5 (*) +| +--- androidx.fragment:fragment:1.0.0 -> 1.2.5 (*)
+| +--- com.google.android.gms:play-services-base:17.0.0 -> 17.3.0 (*) +| +--- com.google.android.gms:play-services-base:17.0.0 -> 17.3.0 (*)
+| \--- com.google.android.gms:play-services-basement:17.0.0 -> 17.3.0 (*) +| \--- com.google.android.gms:play-services-basement:17.0.0 -> 17.3.0 (*)
++--- com.google.android.libraries.places:places:2.4.0 ++--- com.android.billingclient:billing:1.2.2
+| +--- androidx.appcompat:appcompat:1.0.0 -> 1.2.0 ++--- com.gitlab.bitfireAT:dav4jvm:2.1.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.30 (*)
+| \--- org.apache.commons:commons-lang3:3.9
++--- com.gitlab.abaker:ical4android:0e928b567c
+| +--- org.mnode.ical4j:ical4j:3.0.21
+| | +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30
+| | +--- commons-codec:commons-codec:1.11
+| | +--- org.apache.commons:commons-lang3:3.8.1 -> 3.9
+| | \--- org.apache.commons:commons-collections4:4.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.21 (*)
+| +--- com.sun.mail:android-mail:1.6.5
+| | \--- com.sun.mail:android-activation:1.6.5
+| +--- commons-io:commons-io:2.6
+| +--- org.slf4j:slf4j-jdk14:1.7.30
+| | \--- org.slf4j:slf4j-api:1.7.30
+| \--- androidx.core:core-ktx:1.3.2
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.71 -> 1.4.30 (*)
+| +--- androidx.annotation:annotation:1.1.0
+| \--- androidx.core:core:1.3.2 (*)
++--- com.gitlab.bitfireAT:cert4android:26a91a729f
+| +--- androidx.databinding:databinding-common:4.1.1 -> 4.1.2
+| +--- androidx.databinding:databinding-runtime:4.1.1 -> 4.1.2 (*)
+| +--- androidx.databinding:databinding-adapters:4.1.1 -> 4.1.2 (*)
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.21 (*)
+| +--- androidx.appcompat:appcompat:1.2.0
+| | +--- androidx.annotation:annotation:1.1.0 +| | +--- androidx.annotation:annotation:1.1.0
+| | +--- androidx.core:core:1.3.0 -> 1.3.2 (*) +| | +--- androidx.core:core:1.3.0 -> 1.3.2 (*)
+| | +--- androidx.cursoradapter:cursoradapter:1.0.0 +| | +--- androidx.cursoradapter:cursoradapter:1.0.0
@ -213,8 +237,7 @@
+| | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*) +| | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
+| +--- androidx.cardview:cardview:1.0.0 +| +--- androidx.cardview:cardview:1.0.0
+| | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0 +| | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
+| +--- androidx.fragment:fragment:1.1.0 -> 1.2.5 (*) +| +--- androidx.lifecycle:lifecycle-extensions:2.2.0
+| +--- androidx.lifecycle:lifecycle-extensions:2.1.0 -> 2.2.0
+| | +--- androidx.lifecycle:lifecycle-runtime:2.2.0 (*) +| | +--- androidx.lifecycle:lifecycle-runtime:2.2.0 (*)
+| | +--- androidx.arch.core:core-common:2.1.0 (*) +| | +--- androidx.arch.core:core-common:2.1.0 (*)
+| | +--- androidx.arch.core:core-runtime:2.1.0 (*) +| | +--- androidx.arch.core:core-runtime:2.1.0 (*)
@ -226,50 +249,6 @@
+| | +--- androidx.lifecycle:lifecycle-service:2.2.0 +| | +--- androidx.lifecycle:lifecycle-service:2.2.0
+| | | \--- androidx.lifecycle:lifecycle-runtime:2.2.0 (*) +| | | \--- androidx.lifecycle:lifecycle-runtime:2.2.0 (*)
+| | \--- androidx.lifecycle:lifecycle-viewmodel:2.2.0 (*) +| | \--- androidx.lifecycle:lifecycle-viewmodel:2.2.0 (*)
+| +--- androidx.recyclerview:recyclerview:1.0.0 -> 1.1.0
+| | +--- androidx.annotation:annotation:1.1.0
+| | +--- androidx.core:core:1.1.0 -> 1.3.2 (*)
+| | +--- androidx.customview:customview:1.0.0 (*)
+| | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
+| +--- com.android.volley:volley:1.1.1
+| +--- com.google.android.datatransport:transport-api:2.2.0 -> 2.2.1 (*)
+| +--- com.google.android.datatransport:transport-backend-cct:2.3.0 -> 2.3.3 (*)
+| +--- com.google.android.datatransport:transport-runtime:2.2.3 -> 2.2.5 (*)
+| +--- com.google.android.gms:play-services-base:17.2.1 -> 17.3.0 (*)
+| +--- com.google.android.gms:play-services-basement:17.0.0 -> 17.3.0 (*)
+| +--- com.google.android.gms:play-services-location:17.0.0 -> 17.1.0 (*)
+| +--- com.google.android.gms:play-services-maps:17.0.0 (*)
+| +--- com.google.android.gms:play-services-tasks:17.0.0 -> 17.1.0 (*)
+| +--- com.google.auto.value:auto-value-annotations:1.6.2 -> 1.7.4
+| \--- com.google.code.gson:gson:2.8.5 -> 2.8.6
++--- com.android.billingclient:billing:1.2.2
++--- com.gitlab.bitfireAT:dav4jvm:2.1.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 -> 1.4.30 (*)
+| \--- org.apache.commons:commons-lang3:3.9
++--- com.gitlab.abaker:ical4android:0e928b567c
+| +--- org.mnode.ical4j:ical4j:3.0.21
+| | +--- org.slf4j:slf4j-api:1.7.25 -> 1.7.30
+| | +--- commons-codec:commons-codec:1.11
+| | +--- org.apache.commons:commons-lang3:3.8.1 -> 3.9
+| | \--- org.apache.commons:commons-collections4:4.1
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.21 (*)
+| +--- com.sun.mail:android-mail:1.6.5
+| | \--- com.sun.mail:android-activation:1.6.5
+| +--- commons-io:commons-io:2.6
+| +--- org.slf4j:slf4j-jdk14:1.7.30
+| | \--- org.slf4j:slf4j-api:1.7.30
+| \--- androidx.core:core-ktx:1.3.2
+| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.71 -> 1.4.30 (*)
+| +--- androidx.annotation:annotation:1.1.0
+| \--- androidx.core:core:1.3.2 (*)
++--- com.gitlab.bitfireAT:cert4android:26a91a729f
+| +--- androidx.databinding:databinding-common:4.1.1 -> 4.1.2
+| +--- androidx.databinding:databinding-runtime:4.1.1 -> 4.1.2 (*)
+| +--- androidx.databinding:databinding-adapters:4.1.1 -> 4.1.2 (*)
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.21 (*)
+| +--- androidx.appcompat:appcompat:1.2.0 (*)
+| +--- androidx.cardview:cardview:1.0.0 (*)
+| +--- androidx.lifecycle:lifecycle-extensions:2.2.0 (*)
+| +--- androidx.lifecycle:lifecycle-livedata-ktx:2.2.0 +| +--- androidx.lifecycle:lifecycle-livedata-ktx:2.2.0
+| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.50 -> 1.4.30 (*) +| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.50 -> 1.4.30 (*)
+| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0 -> 1.4.1 +| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0 -> 1.4.1
@ -307,7 +286,11 @@
+| | +--- androidx.annotation:annotation-experimental:1.0.0 +| | +--- androidx.annotation:annotation-experimental:1.0.0
+| | +--- androidx.fragment:fragment:1.0.0 -> 1.2.5 (*) +| | +--- androidx.fragment:fragment:1.0.0 -> 1.2.5 (*)
+| | +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.2.0 (*) +| | +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.2.0 (*)
+| | +--- androidx.recyclerview:recyclerview:1.0.0 -> 1.1.0 (*) +| | +--- androidx.recyclerview:recyclerview:1.0.0 -> 1.1.0
+| | | +--- androidx.annotation:annotation:1.1.0
+| | | +--- androidx.core:core:1.1.0 -> 1.3.2 (*)
+| | | +--- androidx.customview:customview:1.0.0 (*)
+| | | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
+| | +--- androidx.transition:transition:1.2.0 +| | +--- androidx.transition:transition:1.2.0
+| | | +--- androidx.annotation:annotation:1.1.0 +| | | +--- androidx.annotation:annotation:1.1.0
+| | | +--- androidx.core:core:1.0.1 -> 1.3.2 (*) +| | | +--- androidx.core:core:1.0.1 -> 1.3.2 (*)

Loading…
Cancel
Save