Merge pull request #10566 from osmandapp/master

update test branch
This commit is contained in:
Hardy 2021-01-13 00:55:07 +01:00 committed by GitHub
commit 14b70262f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 1869 additions and 934 deletions

View file

@ -719,9 +719,10 @@ public class SearchCoreFactory {
results.put(res.pt.getKeyName(), res); results.put(res.pt.getKeyName(), res);
} }
} }
if (nmAdditional != null) { // don't spam results with unsearchable additionals like 'description', 'email', ...
addAditonals(nmAdditional, results, types.getOtherMapCategory()); // if (nmAdditional != null) {
} // addAditonals(nmAdditional, results, types.getOtherMapCategory());
// }
for (PoiCategory c : categories) { for (PoiCategory c : categories) {
PoiTypeResult res = checkPoiType(nm, c); PoiTypeResult res = checkPoiType(nm, c);
if(res != null) { if(res != null) {

View file

@ -553,11 +553,20 @@ public class GeoPointParserUtil {
} }
if (searchRequest != null) { if (searchRequest != null) {
String searchPattern = Pattern.compile("(?:\\.|,|\\s+|\\+|[+-]?\\d+(?:\\.\\d+)?)").pattern();
String[] search = searchRequest.split(searchPattern);
if (search.length > 0) {
return new GeoParsedPoint(searchRequest);
}
final Matcher positionInSearchRequestMatcher = final Matcher positionInSearchRequestMatcher =
positionPattern.matcher(searchRequest); positionPattern.matcher(searchRequest);
if (lat == 0.0 && lon == 0.0 && positionInSearchRequestMatcher.find()) { if (lat == 0.0 && lon == 0.0 && positionInSearchRequestMatcher.find()) {
lat = Double.valueOf(positionInSearchRequestMatcher.group(1)); double tempLat = Double.valueOf(positionInSearchRequestMatcher.group(1));
lon = Double.valueOf(positionInSearchRequestMatcher.group(2)); double tempLon = Double.valueOf(positionInSearchRequestMatcher.group(2));
if (tempLat >= -90 && tempLat <= 90 && tempLon >= -180 && tempLon <= 180) {
lat = tempLat;
lon = tempLon;
}
} }
} }

View file

@ -19,7 +19,10 @@
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="unspecified" android:screenOrientation="unspecified"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
android:hasFragileUserData="true"
android:requestLegacyExternalStorage="true">
<activity android:name=".ui.TrackerLogcatActivity" /> <activity android:name=".ui.TrackerLogcatActivity" />
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
@ -48,14 +51,13 @@
<service <service
android:name=".TelegramService" android:name=".TelegramService"
android:label="@string/process_service" android:label="@string/process_service"
android:foregroundServiceType="location"
android:stopWithTask="false"> android:stopWithTask="false">
<intent-filter> <intent-filter>
<action android:name="net.osmand.telegram.TelegramService" /> <action android:name="net.osmand.telegram.TelegramService" />
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name=".OnTelegramServiceAlarmReceiver" />
<receiver <receiver
android:name=".InitAppBroadcastReceiver" android:name=".InitAppBroadcastReceiver"
android:enabled="true" android:enabled="true"

View file

@ -4,8 +4,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
android { android {
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion "28.0.3" buildToolsVersion "29.0.3"
sourceSets { sourceSets {
main { main {
@ -23,7 +23,7 @@ android {
defaultConfig { defaultConfig {
applicationId "net.osmand.telegram" applicationId "net.osmand.telegram"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 28 targetSdkVersion 29
multiDexEnabled true multiDexEnabled true
versionCode 1 versionCode 1
versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="background_work_description">Endre batterioptimiseringsinnstillinger for mer stabil posisjonsdeling.</string> <string name="background_work_description">Endre batterioptimaliseringsinnstillinger for mer stabil posisjonsdeling.</string>
<string name="background_work">Bakgrunnsarbeid</string> <string name="background_work">Bakgrunnsarbeid</string>
<string name="battery_optimization_description">Skru av batterioptimisering for OsmAnd-sporeren slik at det ikke plutselig skrur seg av når det er i bakgrunnen.</string> <string name="battery_optimization_description">Skru av batterioptimalisering for OsmAnd-sporeren slik at det ikke plutselig skrur seg av når det er i bakgrunnen.</string>
<string name="sharing_in_background">Deling i bakgrunnen</string> <string name="sharing_in_background">Deling i bakgrunnen</string>
<string name="go_to_settings">Gå til innstillinger</string> <string name="go_to_settings">Gå til innstillinger</string>
<string name="shared_string_later">Senere</string> <string name="shared_string_later">Senere</string>

View file

@ -1,42 +0,0 @@
package net.osmand.telegram
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.location.LocationManager
import net.osmand.telegram.utils.AndroidUtils
class OnTelegramServiceAlarmReceiver : BroadcastReceiver() {
@SuppressLint("MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
val lock = TelegramService.getLock(context)
val app = context.applicationContext as TelegramApplication
val service = app.telegramService
// do not do nothing
if (lock.isHeld || service == null) {
return
}
lock.acquire(15 * 60 * 1000L)
// request location updates
val locationManager = service.getSystemService(Context.LOCATION_SERVICE) as LocationManager
try {
if (AndroidUtils.isLocationPermissionAvailable(app)) {
locationManager.requestLocationUpdates(service.serviceOffProvider, 0, 0f, service)
}
val handler = service.handler
if (service.serviceOffInterval > service.serviceErrorInterval && handler != null) {
handler.postDelayed({
// if lock is not anymore held
if (lock.isHeld) {
lock.release()
locationManager.removeUpdates(service)
}
}, service.serviceErrorInterval)
}
} catch (e: RuntimeException) {
e.printStackTrace()
}
}
}

View file

@ -8,12 +8,13 @@ import android.net.ConnectivityManager
import android.net.NetworkInfo import android.net.NetworkInfo
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import androidx.core.content.ContextCompat
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.ui.TrackerLogcatActivity
import net.osmand.telegram.helpers.* import net.osmand.telegram.helpers.*
import net.osmand.telegram.helpers.OsmandAidlHelper.OsmandHelperListener import net.osmand.telegram.helpers.OsmandAidlHelper.OsmandHelperListener
import net.osmand.telegram.helpers.OsmandAidlHelper.UpdatesListener import net.osmand.telegram.helpers.OsmandAidlHelper.UpdatesListener
import net.osmand.telegram.notifications.NotificationHelper import net.osmand.telegram.notifications.NotificationHelper
import net.osmand.telegram.ui.TrackerLogcatActivity
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.UiUtils import net.osmand.telegram.utils.UiUtils
import java.io.File import java.io.File
@ -146,35 +147,21 @@ class TelegramApplication : Application() {
return internetConnectionAvailable return internetConnectionAvailable
} }
private fun startTelegramService(intent: Int, serviceOffInterval: Long = 0) { private fun startTelegramService(intent: Int) {
var i = intent var i = intent
var interval = serviceOffInterval
val serviceIntent = Intent(this, TelegramService::class.java) val serviceIntent = Intent(this, TelegramService::class.java)
val telegramService = telegramService val telegramService = telegramService
if (telegramService != null) { if (telegramService != null) {
i = intent or telegramService.usedBy i = intent or telegramService.usedBy
interval = if (TelegramService.isOffIntervalDepended(intent)) {
Math.min(telegramService.serviceOffInterval, interval)
} else {
telegramService.serviceOffInterval
}
telegramService.stopSelf() telegramService.stopSelf()
} }
serviceIntent.putExtra(TelegramService.USAGE_INTENT, i) serviceIntent.putExtra(TelegramService.USAGE_INTENT, i)
serviceIntent.putExtra(TelegramService.USAGE_OFF_INTERVAL, interval)
serviceIntent.putExtra(TelegramService.SEND_LOCATION_INTERVAL, settings.sendMyLocInterval) serviceIntent.putExtra(TelegramService.SEND_LOCATION_INTERVAL, settings.sendMyLocInterval)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ContextCompat.startForegroundService(this, serviceIntent)
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
} }
fun startMyLocationService() { fun startMyLocationService() {
val interval = settings.sendMyLocInterval startTelegramService(TelegramService.USED_BY_MY_LOCATION)
startTelegramService(TelegramService.USED_BY_MY_LOCATION, TelegramService.normalizeOffInterval(interval))
} }
fun stopMyLocationService() { fun stopMyLocationService() {

View file

@ -166,7 +166,7 @@ class TelegramLocationProvider(private val app: TelegramApplication) : SensorEve
registerOrUnregisterCompassListener(true) registerOrUnregisterCompassListener(true)
} }
fun redownloadAGPS() { private fun redownloadAGPS() {
try { try {
val service = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager val service = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager
service.sendExtraCommand(LocationManager.GPS_PROVIDER, "delete_aiding_data", null) service.sendExtraCommand(LocationManager.GPS_PROVIDER, "delete_aiding_data", null)

View file

@ -1,16 +1,14 @@
package net.osmand.telegram package net.osmand.telegram
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlarmManager
import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo
import android.location.Location import android.location.Location
import android.location.LocationListener import android.location.LocationListener
import android.location.LocationManager import android.location.LocationManager
import android.os.* import android.os.*
import android.util.Log
import android.widget.Toast import android.widget.Toast
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.TelegramSettings.ShareChatInfo import net.osmand.telegram.TelegramSettings.ShareChatInfo
@ -43,21 +41,14 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
private var updateWidgetHandler: Handler? = null private var updateWidgetHandler: Handler? = null
private var updateWidgetThread = HandlerThread("WidgetUpdateServiceThread") private var updateWidgetThread = HandlerThread("WidgetUpdateServiceThread")
var handler: Handler? = null
private set
var usedBy = 0 var usedBy = 0
private set private set
var serviceOffProvider: String = LocationManager.GPS_PROVIDER var serviceOffProvider: String = LocationManager.GPS_PROVIDER
private set private set
var serviceOffInterval = 0L
private set
var serviceErrorInterval = 0L
private set
var sendLocationInterval = 0L var sendLocationInterval = 0L
private set private set
private var lastLocationSentTime = 0L private var lastLocationSentTime = 0L
private var pendingIntent: PendingIntent? = null
class LocationServiceBinder : Binder() class LocationServiceBinder : Binder()
@ -71,7 +62,7 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
updateWidgetHandler = Handler(updateWidgetThread.looper) updateWidgetHandler = Handler(updateWidgetThread.looper)
} }
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder {
return binder return binder
} }
@ -86,13 +77,7 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
ctx.stopService(serviceIntent) ctx.stopService(serviceIntent)
} }
isUsedByMyLocation(usedBy) -> { isUsedByMyLocation(usedBy) -> {
val app = app() app().notificationHelper.refreshNotification(NotificationType.LOCATION)
if (app.settings.sendMyLocInterval >= OFF_INTERVAL_THRESHOLD && serviceOffInterval == 0L) {
serviceOffInterval = app.settings.sendMyLocInterval
setupServiceErrorInterval()
setupAlarm()
}
app.notificationHelper.refreshNotification(NotificationType.LOCATION)
} }
isUsedByUsersLocations(usedBy) -> removeLocationUpdates() isUsedByUsersLocations(usedBy) -> removeLocationUpdates()
} }
@ -100,19 +85,21 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val app = app() val app = app()
handler = Handler()
val usageIntent = intent.getIntExtra(USAGE_INTENT, 0) val usageIntent = intent.getIntExtra(USAGE_INTENT, 0)
usedBy = usageIntent or usedBy usedBy = usageIntent or usedBy
serviceOffInterval = intent.getLongExtra(USAGE_OFF_INTERVAL, 0)
sendLocationInterval = intent.getLongExtra(SEND_LOCATION_INTERVAL, 0) sendLocationInterval = intent.getLongExtra(SEND_LOCATION_INTERVAL, 0)
setupServiceErrorInterval()
app.telegramHelper.addIncomingMessagesListener(this) app.telegramHelper.addIncomingMessagesListener(this)
app.telegramHelper.addOutgoingMessagesListener(this) app.telegramHelper.addOutgoingMessagesListener(this)
app.telegramService = this app.telegramService = this
val locationNotification = app.notificationHelper.locationNotification
val notification = app.notificationHelper.buildNotification(locationNotification)
startForeground(locationNotification.telegramNotificationId, notification)
app.notificationHelper.refreshNotification(locationNotification.type)
if (isUsedByMyLocation(usedBy)) { if (isUsedByMyLocation(usedBy)) {
initLocationUpdates() initLocationUpdates()
startShareInfoUpdates() startShareInfoUpdates()
@ -124,21 +111,7 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
} }
app.shareLocationHelper.checkAndSendBufferMessages() app.shareLocationHelper.checkAndSendBufferMessages()
val locationNotification = app.notificationHelper.locationNotification return START_REDELIVER_INTENT
val notification = app.notificationHelper.buildNotification(locationNotification)
startForeground(locationNotification.telegramNotificationId, notification)
app.notificationHelper.refreshNotification(locationNotification.type)
return Service.START_REDELIVER_INTENT
}
private fun setupServiceErrorInterval() {
serviceErrorInterval = serviceOffInterval / 5
// 1. not more than 12 mins
serviceErrorInterval = Math.min(serviceErrorInterval, 12 * 60 * 1000)
// 2. not less than 30 seconds
serviceErrorInterval = Math.max(serviceErrorInterval, 30 * 1000)
// 3. not more than serviceOffInterval
serviceErrorInterval = Math.min(serviceErrorInterval, serviceOffInterval)
} }
override fun onDestroy() { override fun onDestroy() {
@ -158,13 +131,6 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
removeLocationUpdates() removeLocationUpdates()
if (!isContinuous()) {
val lock = getLock(this)
if (lock.isHeld) {
lock.release()
}
}
if (shouldCleanupResources) { if (shouldCleanupResources) {
app.cleanupResources() app.cleanupResources()
} }
@ -186,22 +152,19 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
val firstLocation = getFirstTimeRunDefaultLocation() val firstLocation = getFirstTimeRunDefaultLocation()
app().shareLocationHelper.updateLocation(firstLocation) app().shareLocationHelper.updateLocation(firstLocation)
// requesting
if (isContinuous()) {
// request location updates // request location updates
/*
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
try { try {
locationManager.requestLocationUpdates(serviceOffProvider, 0, 0f, this@TelegramService) locationManager.requestLocationUpdates(serviceOffProvider, 0, 0f, this@TelegramService)
} catch (e: SecurityException) { } catch (e: SecurityException) {
Toast.makeText(this, R.string.no_location_permission, Toast.LENGTH_LONG).show() Toast.makeText(this, R.string.no_location_permission, Toast.LENGTH_LONG).show()
Log.d(PlatformUtil.TAG, "Location service permission not granted") //$NON-NLS-1$ log.debug("Location service permission not granted")
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
Toast.makeText(this, R.string.gps_not_available, Toast.LENGTH_LONG).show() Toast.makeText(this, R.string.gps_not_available, Toast.LENGTH_LONG).show()
Log.d(PlatformUtil.TAG, "GPS location provider not available") //$NON-NLS-1$ log.debug("GPS location provider not available")
}
} else {
setupAlarm()
} }
*/
} }
private fun startShareInfoUpdates() { private fun startShareInfoUpdates() {
@ -261,6 +224,8 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
if (!AndroidUtils.isLocationPermissionAvailable(app)) { if (!AndroidUtils.isLocationPermissionAvailable(app)) {
return null return null
} }
var location: net.osmand.Location? = null
/*
val service = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager val service = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val ps = service.getProviders(true) ?: return null val ps = service.getProviders(true) ?: return null
val providers = ArrayList(ps) val providers = ArrayList(ps)
@ -272,53 +237,31 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
providers.add(0, providers.removeAt(passiveFirst)) providers.add(0, providers.removeAt(passiveFirst))
} }
// find location // find location
var location: net.osmand.Location? = null
for (provider in providers) { for (provider in providers) {
val loc = convertLocation(service.getLastKnownLocation(provider)) val loc = convertLocation(service.getLastKnownLocation(provider))
if (loc != null && (location == null || loc.hasAccuracy() && loc.accuracy < location.accuracy)) { if (loc != null && (location == null || loc.hasAccuracy() && loc.accuracy < location.accuracy)) {
location = loc location = loc
} }
} }
*/
return location return location
} }
private fun setupAlarm() {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
pendingIntent = PendingIntent.getBroadcast(this, 0, Intent(this, OnTelegramServiceAlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 500, serviceOffInterval, pendingIntent)
}
private fun removeLocationUpdates() { private fun removeLocationUpdates() {
// remove updates // remove updates
/*
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
try { try {
locationManager.removeUpdates(this) locationManager.removeUpdates(this)
} catch (e: SecurityException) { } catch (e: SecurityException) {
Log.d(PlatformUtil.TAG, "Location service permission not granted") log.debug("Location service permission not granted")
} }
} */
private fun isContinuous(): Boolean {
return serviceOffInterval == 0L
} }
override fun onLocationChanged(l: Location?) { override fun onLocationChanged(l: Location?) {
val location = convertLocation(l) val location = convertLocation(l)
if (!isContinuous()) { if (System.currentTimeMillis() - lastLocationSentTime > sendLocationInterval * 1000) {
// unregister listener and wait next time
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
try {
locationManager.removeUpdates(this)
} catch (e: Throwable) {
Log.d(PlatformUtil.TAG, "Location service permission not granted") //$NON-NLS-1$
}
val lock = getLock(this)
if (lock.isHeld) {
lock.release()
}
app().shareLocationHelper.updateLocation(location)
} else if (System.currentTimeMillis() - lastLocationSentTime > sendLocationInterval * 1000) {
lastLocationSentTime = System.currentTimeMillis() lastLocationSentTime = System.currentTimeMillis()
app().shareLocationHelper.updateLocation(location) app().shareLocationHelper.updateLocation(location)
} }
@ -373,7 +316,7 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
} }
override fun onSendLiveLocationError(code: Int, message: String, shareInfo: ShareChatInfo, messageType: Int) { override fun onSendLiveLocationError(code: Int, message: String, shareInfo: ShareChatInfo, messageType: Int) {
Log.d(PlatformUtil.TAG, "Send live location error: $code - $message") log.debug("Send live location error: $code - $message")
when (messageType) { when (messageType) {
TelegramHelper.MESSAGE_TYPE_TEXT -> shareInfo.pendingTdLibText-- TelegramHelper.MESSAGE_TYPE_TEXT -> shareInfo.pendingTdLibText--
TelegramHelper.MESSAGE_TYPE_MAP -> { TelegramHelper.MESSAGE_TYPE_MAP -> {
@ -388,26 +331,8 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
const val USED_BY_MY_LOCATION: Int = 1 const val USED_BY_MY_LOCATION: Int = 1
const val USED_BY_USERS_LOCATIONS: Int = 2 const val USED_BY_USERS_LOCATIONS: Int = 2
const val USAGE_INTENT = "SERVICE_USED_BY" const val USAGE_INTENT = "SERVICE_USED_BY"
const val USAGE_OFF_INTERVAL = "SERVICE_OFF_INTERVAL"
const val SEND_LOCATION_INTERVAL = "SEND_LOCATION_INTERVAL" const val SEND_LOCATION_INTERVAL = "SEND_LOCATION_INTERVAL"
const val OFF_INTERVAL_THRESHOLD: Long = 30000L
private var lockStatic: PowerManager.WakeLock? = null
@Synchronized
fun getLock(context: Context): PowerManager.WakeLock {
var lockStatic = lockStatic
return if (lockStatic == null) {
val mgr = context.getSystemService(Context.POWER_SERVICE) as PowerManager
lockStatic = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "OsmandServiceLock")
this.lockStatic = lockStatic
lockStatic
} else {
lockStatic
}
}
fun isUsedByMyLocation(usedBy: Int): Boolean { fun isUsedByMyLocation(usedBy: Int): Boolean {
return (usedBy and USED_BY_MY_LOCATION) > 0 return (usedBy and USED_BY_MY_LOCATION) > 0
} }
@ -416,14 +341,6 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
return (usedBy and USED_BY_USERS_LOCATIONS) > 0 return (usedBy and USED_BY_USERS_LOCATIONS) > 0
} }
fun isOffIntervalDepended(usedBy: Int): Boolean {
return isUsedByMyLocation(usedBy)
}
fun normalizeOffInterval(interval: Long): Long {
return if (interval < OFF_INTERVAL_THRESHOLD) 0 else interval
}
fun convertLocation(l: Location?): net.osmand.Location? { fun convertLocation(l: Location?): net.osmand.Location? {
if (l == null) { if (l == null) {
return null return null

View file

@ -505,6 +505,8 @@ class TelegramSettings(private val app: TelegramApplication) {
val currentTimeMillis = System.currentTimeMillis() val currentTimeMillis = System.currentTimeMillis()
val currentTime = currentTimeMillis / 1000 val currentTime = currentTimeMillis / 1000
statusChangeTime = currentTimeMillis statusChangeTime = currentTimeMillis
val gpsEnabled = false
/*
val lm = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager val lm = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val gpsEnabled = try { val gpsEnabled = try {
if (lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) { if (lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
@ -518,6 +520,7 @@ class TelegramSettings(private val app: TelegramApplication) {
} catch (ex: Exception) { } catch (ex: Exception) {
false false
} }
*/
var initializing = false var initializing = false
var sendChatsErrors = false var sendChatsErrors = false

View file

@ -21,7 +21,6 @@ import android.widget.*
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.* import net.osmand.telegram.*
@ -42,7 +41,7 @@ private const val SUGGESTED = 2
private const val SHARE_LOCATION_CHAT = 1 private const val SHARE_LOCATION_CHAT = 1
private const val DEFAULT_CHAT = 0 private const val DEFAULT_CHAT = 0
private const val ADAPTER_UPDATE_INTERVAL_MIL = 5 * 1000L // 5 sec private const val ADAPTER_UPDATE_INTERVAL_MS = 5 * 1000L // 5 sec
class MyLocationTabFragment : Fragment(), TelegramListener { class MyLocationTabFragment : Fragment(), TelegramListener {
@ -380,7 +379,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
updateContent() updateContent()
startHandler() startHandler()
} }
}, ADAPTER_UPDATE_INTERVAL_MIL) }, ADAPTER_UPDATE_INTERVAL_MS)
} }
private fun animateStartSharingBtn(show: Boolean) { private fun animateStartSharingBtn(show: Boolean) {

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:minHeight="@dimen/bottom_sheet_selected_item_title_height"
android:orientation="vertical"
android:paddingStart="@dimen/content_padding"
android:paddingLeft="@dimen/content_padding"
android:paddingTop="@dimen/wikilink_bottom_sheet_padding"
android:paddingEnd="@dimen/content_padding"
android:paddingRight="@dimen/content_padding">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/subHeaderPadding"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:minHeight="@dimen/default_title_line_height"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/default_list_text_size"
osmand:lineHeight="@dimen/default_title_line_height"
osmand:typeface="@string/font_roboto_medium"
tools:text="Some title" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:minHeight="@dimen/default_desc_line_height"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:lineHeight="@dimen/default_desc_line_height"
osmand:typeface="@string/font_roboto_regular"
tools:text="Some description" />
</LinearLayout>

View file

@ -14,10 +14,11 @@
android:orientation="horizontal"> android:orientation="horizontal">
<LinearLayout <LinearLayout
android:id="@+id/main_item_part" android:id="@+id/basic_item_body"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackground"
android:paddingLeft="@dimen/content_padding" android:paddingLeft="@dimen/content_padding"
android:paddingStart="@dimen/content_padding" android:paddingStart="@dimen/content_padding"
android:paddingRight="0dp" android:paddingRight="0dp"
@ -79,22 +80,24 @@
<View <View
android:layout_width="1dp" android:layout_width="1dp"
android:layout_height="36dp" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/content_padding"
android:layout_marginBottom="@dimen/content_padding"
android:background="?attr/divider_color_basic"/> android:background="?attr/divider_color_basic"/>
<LinearLayout <LinearLayout
android:id="@+id/eng_button" android:id="@+id/end_button"
android:paddingLeft="@dimen/content_padding" android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding" android:paddingRight="@dimen/content_padding"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/end_button_icon"
android:layout_width="@dimen/standard_icon_size" android:layout_width="@dimen/standard_icon_size"
android:layout_height="@dimen/standard_icon_size" android:layout_height="@dimen/standard_icon_size"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
app:srcCompat="@drawable/ic_action_settings" app:srcCompat="@drawable/ic_action_settings" />
android:tint="?attr/default_icon_color" />
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:osmand="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/card_row_min_height"
android:background="?attr/list_background_color"
android:gravity="center_vertical"
android:minHeight="@dimen/bottom_sheet_cancel_button_height_small"
android:orientation="horizontal">
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layout_weight="1"
android:paddingLeft="@dimen/content_padding"
android:paddingRight="@dimen/content_padding"
android:text="@string/select_folder"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular" />
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/select_folder_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:drawableRight="@drawable/ic_action_markers_list"
android:drawablePadding="@dimen/content_padding_small_half"
android:drawableTint="?attr/active_color_basic"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingStart="@dimen/content_padding_half"
android:paddingLeft="@dimen/content_padding_half"
android:paddingEnd="@dimen/content_padding_half"
android:paddingRight="@dimen/content_padding_half"
android:text="@string/shared_string_list"
android:textColor="?attr/color_dialog_buttons"
osmand:typeface="@string/font_roboto_medium" />
</LinearLayout>

View file

@ -275,7 +275,7 @@
<string name="modify_transparency">Nastavte průhlednost (0 - průhledný, 255 - neprůhledný)</string> <string name="modify_transparency">Nastavte průhlednost (0 - průhledný, 255 - neprůhledný)</string>
<string name="layer_underlay">Podkladová mapa…</string> <string name="layer_underlay">Podkladová mapa…</string>
<string name="map_underlay">Podkresová mapa</string> <string name="map_underlay">Podkresová mapa</string>
<string name="map_underlay_descr">Vyberte podkladovou mapu.</string> <string name="map_underlay_descr">Vyberte podkladovou mapu</string>
<string name="layer_overlay">Překryvná mapa…</string> <string name="layer_overlay">Překryvná mapa…</string>
<string name="shared_string_none">Žádná</string> <string name="shared_string_none">Žádná</string>
<string name="map_overlay">Překryvná mapa</string> <string name="map_overlay">Překryvná mapa</string>
@ -309,7 +309,9 @@
<string name="context_menu_item_share_location">Sdílet polohu</string> <string name="context_menu_item_share_location">Sdílet polohu</string>
<string name="add_waypoint_dialog_added">GPX bod na trase \'\'{0}\'\' byl přidán</string> <string name="add_waypoint_dialog_added">GPX bod na trase \'\'{0}\'\' byl přidán</string>
<string name="add_waypoint_dialog_title">Přidat bod na zaznamenávanou GPX trasu</string> <string name="add_waypoint_dialog_title">Přidat bod na zaznamenávanou GPX trasu</string>
<string name="osmand_routing_experimental">Off-line navigace je experimentální a funguje jen pro větší vzdálenosti než 20 km. Navigace je dočasně přepnuta na on-line CloudMade.</string> <string name="osmand_routing_experimental">Off-line navigace je experimentální a funguje jen pro větší vzdálenosti než 20 km.
\n
\nNavigace je dočasně přepnuta na online službu CloudMade.</string>
<string name="specified_dir_doesnt_exist">Nepodařilo se najít zadaný adresář.</string> <string name="specified_dir_doesnt_exist">Nepodařilo se najít zadaný adresář.</string>
<string name="application_dir">Adresář pro data</string> <string name="application_dir">Adresář pro data</string>
<string name="gps_status_app_not_found">Aplikace pro zobrazení stavu GPS není nainstalovaná. Hledat v obchodě?</string> <string name="gps_status_app_not_found">Aplikace pro zobrazení stavu GPS není nainstalovaná. Hledat v obchodě?</string>
@ -440,7 +442,7 @@
<string name="map_orientation_landscape">Na šířku</string> <string name="map_orientation_landscape">Na šířku</string>
<string name="map_screen_orientation">Orientace obrazovky</string> <string name="map_screen_orientation">Orientace obrazovky</string>
<string name="map_screen_orientation_descr">Orientace obrazovky: na výšku, na šířku, podle zařízení.</string> <string name="map_screen_orientation_descr">Orientace obrazovky: na výšku, na šířku, podle zařízení.</string>
<string name="opening_hours_not_supported">Formát otevírací doby nemůže být změněn</string> <string name="opening_hours_not_supported">Formát otevírací doby nemůže být změněn.</string>
<string name="add_new_rule">Přidat nové pravidlo</string> <string name="add_new_rule">Přidat nové pravidlo</string>
<string name="transport_Routes">Cesty</string> <string name="transport_Routes">Cesty</string>
<string name="transport_Stop">Zastávka</string> <string name="transport_Stop">Zastávka</string>
@ -459,8 +461,8 @@
<string name="show_transport_over_map">Zobrazit zastávky</string> <string name="show_transport_over_map">Zobrazit zastávky</string>
<string name="hello">Navigační aplikace OsmAnd</string> <string name="hello">Navigační aplikace OsmAnd</string>
<string name="update_poi_success">Data POI byla aktualizována ({0} načteno)</string> <string name="update_poi_success">Data POI byla aktualizována ({0} načteno)</string>
<string name="update_poi_error_local">Nelze aktualizovat lokální seznam POI</string> <string name="update_poi_error_local">Nepodařilo se aktualizovat lokální seznam POI.</string>
<string name="update_poi_error_loading">Nelze načíst data ze serveru</string> <string name="update_poi_error_loading">Nepodařilo se načíst data ze serveru.</string>
<string name="update_poi_no_offline_poi_index">Pro tuto oblast nejsou dostupné žádné off-line POI</string> <string name="update_poi_no_offline_poi_index">Pro tuto oblast nejsou dostupné žádné off-line POI</string>
<string name="update_poi_is_not_available_for_zoom">Větší přiblížení vám umožní aktualizovat POI</string> <string name="update_poi_is_not_available_for_zoom">Větší přiblížení vám umožní aktualizovat POI</string>
<string name="context_menu_item_update_poi">Aktualizovat POI</string> <string name="context_menu_item_update_poi">Aktualizovat POI</string>
@ -482,11 +484,11 @@
<string name="search_offline_address">Hledání off-line</string> <string name="search_offline_address">Hledání off-line</string>
<string name="search_online_address">Hledat on-line</string> <string name="search_online_address">Hledat on-line</string>
<string name="max_level_download_tile">Maximální zvětšení</string> <string name="max_level_download_tile">Maximální zvětšení</string>
<string name="max_level_download_tile_descr">Nelze procházet dlaždice online map pro přiblížení nad tuto úroveň.</string> <string name="max_level_download_tile_descr">Nepoužívat online mapy pro přiblížení nad tuto úroveň.</string>
<string name="route_general_information">Celková vzdálenost %1$s, doba cesty %2$d h %3$d min.</string> <string name="route_general_information">Celková vzdálenost %1$s, doba cesty %2$d h %3$d min.</string>
<string name="router_service_descr">Vyberte online nebo offline navigační službu.</string> <string name="router_service_descr">Online nebo offline navigační služba.</string>
<string name="router_service">Navigace</string> <string name="router_service">Navigace</string>
<string name="sd_dir_not_accessible">Úložný datový adresář na paměťové kartě není dostupný!</string> <string name="sd_dir_not_accessible">Úložný adresář na paměťové kartě není dostupný!</string>
<string name="download_question">Stáhnout {0} - {1} ?</string> <string name="download_question">Stáhnout {0} - {1} ?</string>
<string name="download_question_exist">Offline data pro {0} již existují ({1}). Aktualizovat je ({2})\?</string> <string name="download_question_exist">Offline data pro {0} již existují ({1}). Aktualizovat je ({2})\?</string>
<string name="address">Adresa</string> <string name="address">Adresa</string>
@ -500,7 +502,7 @@
<string name="only_show">Pouze zobrazit</string> <string name="only_show">Pouze zobrazit</string>
<string name="follow">Spustit navádění</string> <string name="follow">Spustit navádění</string>
<string name="recalculate_route_to_your_location">Způsob dopravy:</string> <string name="recalculate_route_to_your_location">Způsob dopravy:</string>
<string name="mark_final_location_first">Prosím zadejte nejprve cíl</string> <string name="mark_final_location_first">Prosím nejprve zadejte cíl</string>
<string name="get_directions">Navigace</string> <string name="get_directions">Navigace</string>
<string name="opening_hours">Otevírací doba</string> <string name="opening_hours">Otevírací doba</string>
<string name="opening_changeset">Otevírání sady změn…</string> <string name="opening_changeset">Otevírání sady změn…</string>
@ -515,14 +517,14 @@
<string name="loading_streets">Načítání ulic…</string> <string name="loading_streets">Načítání ulic…</string>
<string name="loading_cities">Načítání měst…</string> <string name="loading_cities">Načítání měst…</string>
<string name="poi">POI</string> <string name="poi">POI</string>
<string name="error_occurred_saving_gpx">Nelze uložit soubor GPX</string> <string name="error_occurred_saving_gpx">Nepodařilo se uložit soubor GPX.</string>
<string name="error_calculating_route">Nelze vypočítat trasu</string> <string name="error_calculating_route">Nepodařilo se vypočítat trasu.</string>
<string name="error_calculating_route_occured">Nelze vypočítat trasu</string> <string name="error_calculating_route_occured">Nepodařilo se vypočítat trasu.</string>
<string name="empty_route_calculated">Vypočtená trasa je prázdná</string> <string name="empty_route_calculated">Vypočítaná trasa je prázdná.</string>
<string name="new_route_calculated_dist">Vypočtena nová trasa, vzdálenost</string> <string name="new_route_calculated_dist">Vypočtena nová trasa, vzdálenost</string>
<string name="arrived_at_destination">Dorazili jste do cíle</string> <string name="arrived_at_destination">Dorazili jste do cíle.</string>
<string name="invalid_locations">Neplatné souřadnice</string> <string name="invalid_locations">Neplatné souřadnice</string>
<string name="go_back_to_osmand">Vrátit se k mapě OsmAnd</string> <string name="go_back_to_osmand">Vrátit se k mapě</string>
<string name="shared_string_close">Zavřít</string> <string name="shared_string_close">Zavřít</string>
<string name="loading_data">Načítání dat…</string> <string name="loading_data">Načítání dat…</string>
<string name="reading_indexes">Načítání místních dat…</string> <string name="reading_indexes">Načítání místních dat…</string>
@ -559,15 +561,15 @@
<string name="show_view_angle">Zorné pole</string> <string name="show_view_angle">Zorné pole</string>
<string name="map_view_3d_descr">Zapnout 3D pohled na mapu.</string> <string name="map_view_3d_descr">Zapnout 3D pohled na mapu.</string>
<string name="map_view_3d">3D Mapa</string> <string name="map_view_3d">3D Mapa</string>
<string name="show_poi_over_map_description">Zobrazit naposledy vybrané POI jako vrstvu na mapě.</string> <string name="show_poi_over_map_description">Zobrazit naposledy použité POI jako vrstvu.</string>
<string name="show_poi_over_map">Zobrazit vrstvu POI</string> <string name="show_poi_over_map">Zobrazit vrstvu POI</string>
<string name="map_tile_source_descr">Zvolte zdroj on-line dlaždic nebo dlaždic v mezipaměti.</string> <string name="map_tile_source_descr">Zvolte zdroj online mapových dlaždic nebo dlaždic v mezipaměti.</string>
<string name="map_tile_source">Zdroj rastrových map</string> <string name="map_tile_source">Zdroj rastrových map</string>
<string name="map_source">Zdroj map</string> <string name="map_source">Zdroj map</string>
<string name="use_internet">Používat Internet</string> <string name="use_internet">Používat Internet</string>
<string name="show_location">Zobrazit mou polohu</string> <string name="show_location">Zobrazit mou polohu</string>
<string name="show_gps_coordinates_text">Zobrazit GPS souřadnice na mapě</string> <string name="show_gps_coordinates_text">Zobrazit GPS souřadnice na mapě</string>
<string name="use_internet_to_download_tile">Stahovat chybějící části mapy z internetu</string> <string name="use_internet_to_download_tile">Stáhnout chybějící mapové dlaždice</string>
<string name="app_description">Navigační aplikace</string> <string name="app_description">Navigační aplikace</string>
<string name="shared_string_exit">Konec</string> <string name="shared_string_exit">Konec</string>
<string name="search_button">Hledat</string> <string name="search_button">Hledat</string>
@ -586,11 +588,11 @@
<string name="app_mode_pedestrian">Chůze</string> <string name="app_mode_pedestrian">Chůze</string>
<string name="position_on_map_center">Uprostřed</string> <string name="position_on_map_center">Uprostřed</string>
<string name="position_on_map_bottom">Dole</string> <string name="position_on_map_bottom">Dole</string>
<string name="navigate_point_top_text">Zadejte zem. šířku a délku ve vybraném formátu (D - stupně, M - minuty, S - vteřiny)</string> <string name="navigate_point_top_text">Zadejte zeměpisnou šířku a délku ve vybraném formátu (D - stupně, M - minuty, S - vteřiny)</string>
<string name="navigate_point_latitude">Šířka</string> <string name="navigate_point_latitude">Šířka</string>
<string name="navigate_point_longitude">Délka</string> <string name="navigate_point_longitude">Délka</string>
<string name="shared_string_show_on_map">Zobrazit na mapě</string> <string name="shared_string_show_on_map">Zobrazit na mapě</string>
<string name="search_address_top_text">Zvolit adresu</string> <string name="search_address_top_text">Adresa</string>
<string name="search_address_region">Oblast</string> <string name="search_address_region">Oblast</string>
<string name="search_address_city">Město</string> <string name="search_address_city">Město</string>
<string name="search_address_street">Ulice</string> <string name="search_address_street">Ulice</string>
@ -621,7 +623,7 @@
<string name="poi_remove_confirm_template">Vymazat {0} (komentář) \?</string> <string name="poi_remove_confirm_template">Vymazat {0} (komentář) \?</string>
<string name="poi_remove_title">Vymazat POI</string> <string name="poi_remove_title">Vymazat POI</string>
<string name="shared_string_delete">Vymazat</string> <string name="shared_string_delete">Vymazat</string>
<string name="poi_remove_success">POI byl vymazán</string> <string name="poi_remove_success">Smazáno</string>
<string name="poi_action_add">Přidání</string> <string name="poi_action_add">Přidání</string>
<string name="poi_action_change">Změna</string> <string name="poi_action_change">Změna</string>
<string name="poi_action_succeded_template">Akce {0} provedena.</string> <string name="poi_action_succeded_template">Akce {0} provedena.</string>
@ -636,9 +638,9 @@
<string name="shared_string_clear">Vyčistit</string> <string name="shared_string_clear">Vyčistit</string>
<string name="filter_current_poiButton">Filtr</string> <string name="filter_current_poiButton">Filtr</string>
<string name="edit_filter_save_as_menu_item">Uložit jako</string> <string name="edit_filter_save_as_menu_item">Uložit jako</string>
<string name="edit_filter_delete_dialog_title">Smazat vybraný filtr?</string> <string name="edit_filter_delete_dialog_title">Smazat tento filtr\?</string>
<string name="edit_filter_delete_message">Filtr %1$s byl smazán</string> <string name="edit_filter_delete_message">Filtr \'%1$s\' byl smazán</string>
<string name="edit_filter_create_message">Filtr %1$s byl vytvořen</string> <string name="edit_filter_create_message">Filtr \'%1$s\' byl vytvořen</string>
<string name="offline_navigation_not_available">Off-line navigace OsmAnd je dočasně nedostupná.</string> <string name="offline_navigation_not_available">Off-line navigace OsmAnd je dočasně nedostupná.</string>
<string name="left_side_navigation">Levostranný provoz</string> <string name="left_side_navigation">Levostranný provoz</string>
<string name="left_side_navigation_descr">Pro země, kde se jezdí po levé straně cesty.</string> <string name="left_side_navigation_descr">Pro země, kde se jezdí po levé straně cesty.</string>
@ -672,8 +674,8 @@
<string name="favourites_edit_dialog_category">Kategorie</string> <string name="favourites_edit_dialog_category">Kategorie</string>
<string name="local_index_descr_title">Správce mapových souborů.</string> <string name="local_index_descr_title">Správce mapových souborů.</string>
<string name="navigate_point_format_D">DDD.DDDDD</string> <string name="navigate_point_format_D">DDD.DDDDD</string>
<string name="navigate_point_format_DM">DDD MM.MMMMM</string> <string name="navigate_point_format_DM">DDD MM.MMM</string>
<string name="navigate_point_format_DMS">DDD MM SS.SSSSS</string> <string name="navigate_point_format_DMS">DDD MM SS.S</string>
<string name="local_index_routing_data">Navigační data</string> <string name="local_index_routing_data">Navigační data</string>
<string name="navigate_point_format">Formát</string> <string name="navigate_point_format">Formát</string>
<string name="poi_search_desc">Hledat body zájmu (POI)</string> <string name="poi_search_desc">Hledat body zájmu (POI)</string>
@ -1022,15 +1024,15 @@
<string name="speak_traffic_warnings">Dopravní varování</string> <string name="speak_traffic_warnings">Dopravní varování</string>
<string name="osb_author_dialog_password">Heslo OSM (volitelné)</string> <string name="osb_author_dialog_password">Heslo OSM (volitelné)</string>
<string name="av_camera_focus">Typ ostření fotoaparátu</string> <string name="av_camera_focus">Typ ostření fotoaparátu</string>
<string name="av_camera_focus_descr">Zvolte způsob ostření vestavěného fotoaparátu.</string> <string name="av_camera_focus_descr">Režim zaostřování fotoaparátu:</string>
<string name="av_camera_focus_auto">Automatické ostření</string> <string name="av_camera_focus_auto">Automatické ostření</string>
<string name="av_camera_focus_hiperfocal">Fix-focus</string> <string name="av_camera_focus_hiperfocal">Fix-focus</string>
<string name="av_camera_focus_edof">Fix-focus</string> <string name="av_camera_focus_edof">Fix-focus</string>
<string name="av_camera_focus_infinity">Ostření na nekonečno</string> <string name="av_camera_focus_infinity">Ostření je nastaveno na nekonečno</string>
<string name="av_camera_focus_macro">Makro</string> <string name="av_camera_focus_macro">Makro</string>
<string name="av_camera_focus_continuous">Kamera ostří souvisle</string> <string name="av_camera_focus_continuous">Kamera ostří souvisle</string>
<string name="av_photo_play_sound">Přehrát zvuk závěrky</string> <string name="av_photo_play_sound">Přehrát zvuk závěrky</string>
<string name="av_photo_play_sound_descr">Zvolte zda při vyfotografování snímku má být přehrán zvuk závěrky.</string> <string name="av_photo_play_sound_descr">Nastavit zvuk či ticho při pořízení snímku.</string>
<string name="new_directions_point_dialog">Mezicíle jsou již nastaveny.</string> <string name="new_directions_point_dialog">Mezicíle jsou již nastaveny.</string>
<string name="context_menu_item_directions_to">Navigovat sem</string> <string name="context_menu_item_directions_to">Navigovat sem</string>
<string name="context_menu_item_directions_from">Navigovat odsud</string> <string name="context_menu_item_directions_from">Navigovat odsud</string>
@ -1229,8 +1231,7 @@
<string name="save_as_favorites_points">Uložit jako skupinu Oblíbených</string> <string name="save_as_favorites_points">Uložit jako skupinu Oblíbených</string>
<string name="select_destination_and_intermediate_points">Určete cíle</string> <string name="select_destination_and_intermediate_points">Určete cíle</string>
<string name="layer_amenity_label">Překryvné popisky bodů</string> <string name="layer_amenity_label">Překryvné popisky bodů</string>
<string name="create_poi_link_to_osm_doc"> <string name="create_poi_link_to_osm_doc"><u>On-line OSM</u> klasifikace map s obrázky.</string>
<u>On-line OSM</u> klasifikace map s obrázky.</string>
<string name="show_zoom_buttons_navigation_descr">Zobrazit tlačítka lupy během navigace.</string> <string name="show_zoom_buttons_navigation_descr">Zobrazit tlačítka lupy během navigace.</string>
<string name="show_zoom_buttons_navigation">Zobrazit tlačítka lupy</string> <string name="show_zoom_buttons_navigation">Zobrazit tlačítka lupy</string>
<string name="sort_by_distance">Třídit podle vzdálenosti</string> <string name="sort_by_distance">Třídit podle vzdálenosti</string>
@ -1287,7 +1288,7 @@
<string name="arrival_distance_factor_late">Později</string> <string name="arrival_distance_factor_late">Později</string>
<string name="arrival_distance_factor_at_last">V posledních metrech</string> <string name="arrival_distance_factor_at_last">V posledních metrech</string>
<string name="av_camera_pic_size">Velikost fotografie</string> <string name="av_camera_pic_size">Velikost fotografie</string>
<string name="av_camera_pic_size_descr">Vyberte velikost obrázku interního fotoaparátu.</string> <string name="av_camera_pic_size_descr">Nastavte velikost obrázku fotoaparátu</string>
<string name="shared_string_more">Více…</string> <string name="shared_string_more">Více…</string>
<string name="rendering_value_browse_map_name">Standardní</string> <string name="rendering_value_browse_map_name">Standardní</string>
<string name="rendering_value_car_name">Automobil</string> <string name="rendering_value_car_name">Automobil</string>
@ -1442,12 +1443,12 @@
<string name="shared_string_export">Exportovat</string> <string name="shared_string_export">Exportovat</string>
<string name="shared_string_enable">Povolit</string> <string name="shared_string_enable">Povolit</string>
<string name="shared_string_disable">Zakázat</string> <string name="shared_string_disable">Zakázat</string>
<string name="plugin_install_needs_network">Pro instalaci tohoto modulu je potřeba internetové připojení.</string> <string name="plugin_install_needs_network">Pro instalaci tohoto modulu musíte být online.</string>
<string name="get_plugin">Získat</string> <string name="get_plugin">Získat</string>
<string name="route_points">Body cesty</string> <string name="route_points">Body cesty</string>
<string name="simulate_your_location">Simulovat polohu</string> <string name="simulate_your_location">Simulovat polohu</string>
<string name="short_location_on_map">Šířka %1$s <string name="short_location_on_map">Šířka %1$s
Délka %2$s</string> \nDélka %2$s</string>
<string name="routing_settings_2">Nastavení navigace</string> <string name="routing_settings_2">Nastavení navigace</string>
<string name="general_settings_2">Všeobecná nastavení</string> <string name="general_settings_2">Všeobecná nastavení</string>
<string name="shared_string_ellipsis"></string> <string name="shared_string_ellipsis"></string>
@ -1589,11 +1590,11 @@ Délka %2$s</string>
<string name="add_opening_hours">Přidat otevírací dobu</string> <string name="add_opening_hours">Přidat otevírací dobu</string>
<string name="contact_info">Kontaktní informace</string> <string name="contact_info">Kontaktní informace</string>
<string name="do_you_like_osmand">Máte rádi OsmAnd?</string> <string name="do_you_like_osmand">Máte rádi OsmAnd?</string>
<string name="we_really_care_about_your_opinion">Záleží nám na vašem názoru, zpětná vazba je pro nás důležitá.</string> <string name="we_really_care_about_your_opinion">Oceníme váš názor a zpětnou vazbu.</string>
<string name="rate_this_app">Ohodnoťte tuto aplikaci</string> <string name="rate_this_app">Ohodnoťte tuto aplikaci</string>
<string name="rate_this_app_long">Ohodnoťte prosím aplikaci OsmAnd na Google Play</string> <string name="rate_this_app_long">Ohodnoťte prosím aplikaci OsmAnd na Google Play</string>
<string name="user_hates_app_get_feedback">Řekněte nám proč.</string> <string name="user_hates_app_get_feedback">Řekněte nám proč.</string>
<string name="user_hates_app_get_feedback_long">Sdělte nám prosím, co byste chtěli v této aplikaci změnit.</string> <string name="user_hates_app_get_feedback_long">Sdělte nám prosím své návrhy.</string>
<string name="shared_string_skip">Přeskočit</string> <string name="shared_string_skip">Přeskočit</string>
<string name="app_name_osmand">OsmAnd</string> <string name="app_name_osmand">OsmAnd</string>
<string name="plugin_settings">Moduly</string> <string name="plugin_settings">Moduly</string>
@ -1612,7 +1613,7 @@ Délka %2$s</string>
<string name="shared_string_card_was_hidden">Karta byla skryta</string> <string name="shared_string_card_was_hidden">Karta byla skryta</string>
<string name="shared_string_undo">Zpět</string> <string name="shared_string_undo">Zpět</string>
<string name="offline_maps_and_navigation">Off-line mapy <string name="offline_maps_and_navigation">Off-line mapy
&amp; navigace</string> \n&amp; navigace</string>
<string name="commit_poi">Odeslat POI</string> <string name="commit_poi">Odeslat POI</string>
<string name="tab_title_basic">Základní</string> <string name="tab_title_basic">Základní</string>
<string name="tab_title_advanced">Rozšířené</string> <string name="tab_title_advanced">Rozšířené</string>
@ -1836,7 +1837,7 @@ Délka %2$s</string>
<string name="weekly">Jednou týdně</string> <string name="weekly">Jednou týdně</string>
<string name="morning">Ráno</string> <string name="morning">Ráno</string>
<string name="night">V noci</string> <string name="night">V noci</string>
<string name="select_month_and_country">Vyberte měsíc a krajinu</string> <string name="select_month_and_country">Měsíc a země:</string>
<string name="shared_string_remove">Odstranit</string> <string name="shared_string_remove">Odstranit</string>
<string name="rendering_attr_hideIcons_name">Ikony POI</string> <string name="rendering_attr_hideIcons_name">Ikony POI</string>
<string name="shared_string_select">Vybrat</string> <string name="shared_string_select">Vybrat</string>
@ -1918,7 +1919,7 @@ Délka %2$s</string>
<string name="consider_turning_polygons_off">Doporučujeme vypnout vykreslování polygonů.</string> <string name="consider_turning_polygons_off">Doporučujeme vypnout vykreslování polygonů.</string>
<string name="rendering_attr_showMtbRoutes_name">Zobrazit trasy pro horská kola</string> <string name="rendering_attr_showMtbRoutes_name">Zobrazit trasy pro horská kola</string>
<string name="storage_permission_restart_is_required">Aplikace nyní smí zapisovat na externí úložiště, ale je potřeba její restart.</string> <string name="storage_permission_restart_is_required">Aplikace nyní smí zapisovat na externí úložiště, ale je potřeba její restart.</string>
<string name="file_name_containes_illegal_char">Název souboru obsahuje nepovolené znaky</string> <string name="file_name_containes_illegal_char">Nepovolený znak v názvu souboru</string>
<string name="access_no_destination">Modul usnadnění: Cíl není nastaven</string> <string name="access_no_destination">Modul usnadnění: Cíl není nastaven</string>
<string name="map_widget_magnetic_bearing">Magnetické směrování</string> <string name="map_widget_magnetic_bearing">Magnetické směrování</string>
<string name="map_widget_bearing">Relativní směrování</string> <string name="map_widget_bearing">Relativní směrování</string>
@ -2112,20 +2113,20 @@ Délka %2$s</string>
<string name="quick_actions_delete_text">Opravdu chcete odstranit akci \"%s\"?</string> <string name="quick_actions_delete_text">Opravdu chcete odstranit akci \"%s\"?</string>
<string name="quick_favorites_show_favorites_dialog">Zobrazit dialog Oblíbených</string> <string name="quick_favorites_show_favorites_dialog">Zobrazit dialog Oblíbených</string>
<string name="quick_favorites_name_preset">Přednastavení názvu</string> <string name="quick_favorites_name_preset">Přednastavení názvu</string>
<string name="quick_action_add_marker_descr">Klepnutím na tlačítko akce se přidá mapová značka do středu obrazovky.</string> <string name="quick_action_add_marker_descr">Tlačítko pro přidání mapové značky do středu obrazovky.</string>
<string name="quick_action_add_gpx_descr">Klepnutím na tlačítko akce se přidá GPX bod trasy na místo ve středu obrazovky.</string> <string name="quick_action_add_gpx_descr">Tlačítko pro přidání bodu GPX trasy do středu obrazovky.</string>
<string name="quick_action_take_audio_note_descr">Klepnutím na tlačítko akce se přidá audio záznam na místo ve středu obrazovky.</string> <string name="quick_action_take_audio_note_descr">Tlačítko pro přidání zvukové poznámky do středu obrazovky.</string>
<string name="quick_action_take_video_note_descr">Klepnutím na tlačítko akce se přidá video záznam na místo ve středu obrazovky.</string> <string name="quick_action_take_video_note_descr">Tlačítko pro přidání video poznámky do středu obrazovky.</string>
<string name="quick_action_take_photo_note_descr">Klepnutím na tlačítko akce se přidá foto záznam na místo ve středu obrazovky.</string> <string name="quick_action_take_photo_note_descr">Tlačítko pro přidání foto poznámky do středu obrazovky.</string>
<string name="quick_action_add_osm_bug_descr">Klepnutím na tlačítko akce se přidá OSM poznámka na místo ve středu obrazovky.</string> <string name="quick_action_add_osm_bug_descr">Tlačítko pro přidání OSM poznámky do středu obrazovky.</string>
<string name="quick_action_add_poi_descr">Klepnutím na tlačítko akce se přidá POI na místo ve středu obrazovky.</string> <string name="quick_action_add_poi_descr">Tlačítko pro přidání bodu zájmu do středu obrazovky.</string>
<string name="quick_action_navigation_voice_descr">Klepnutím na tlačítko akce se vypnou nebo zapnou hlasové pokyny během navigace.</string> <string name="quick_action_navigation_voice_descr">Tlačítko pro vypnutí nebo zapnutí hlasových pokynů během navigace.</string>
<string name="quick_action_add_parking_descr">Klepnutím na tlačítko akce se přidá parkovací místo do středu obrazovky.</string> <string name="quick_action_add_parking_descr">Tlačítko pro přidání místa zaparkování do středu obrazovky.</string>
<string name="favorite_empty_place_name">Místo</string> <string name="favorite_empty_place_name">Místo</string>
<string name="quick_action_duplicates">Zadaný název rychlé akce je již použit a byl změněn na %1$s z důvodu duplicity.</string> <string name="quick_action_duplicates">Rychlá akce byla přejmenována na %1$s z důvodu duplicity.</string>
<string name="quick_action_duplicate">Duplicitní název rychlé akce</string> <string name="quick_action_duplicate">Duplicitní název rychlé akce</string>
<string name="quick_action_showhide_favorites_descr">Klepnutím na tlačítko akce se zobrazí nebo skryjí Oblíbená místa na mapě.</string> <string name="quick_action_showhide_favorites_descr">Tlačítko pro zobrazení nebo skrytí Oblíbených míst na mapě.</string>
<string name="quick_action_showhide_poi_descr">Klepnutím na tlačítko akce se zobrazí nebo skryjí POI na mapě.</string> <string name="quick_action_showhide_poi_descr">Tlačítko pro zobrazení nebo skrytí bodů zájmu na mapě.</string>
<string name="quick_action_showhide_favorites_title">Zobrazit/skrýt Oblíbená místa</string> <string name="quick_action_showhide_favorites_title">Zobrazit/skrýt Oblíbená místa</string>
<string name="quick_action_favorites_show">Zobrazit oblíbená místa</string> <string name="quick_action_favorites_show">Zobrazit oblíbená místa</string>
<string name="quick_action_favorites_hide">Skrýt Oblíbená místa</string> <string name="quick_action_favorites_hide">Skrýt Oblíbená místa</string>
@ -2139,7 +2140,7 @@ Délka %2$s</string>
<string name="quick_action_fav_name_descr">Ponecháte-li prázdné, bude použita adresa nebo název místa.</string> <string name="quick_action_fav_name_descr">Ponecháte-li prázdné, bude použita adresa nebo název místa.</string>
<string name="quick_action_bug_descr">Tato zpráva je zahrnuta v poli komentáře.</string> <string name="quick_action_bug_descr">Tato zpráva je zahrnuta v poli komentáře.</string>
<string name="quick_action_bug_message">Zpráva</string> <string name="quick_action_bug_message">Zpráva</string>
<string name="quick_action_category_descr">Vyberte kategorii pro uložení Oblíbeného místa.</string> <string name="quick_action_category_descr">Kategorie pro uložení Oblíbeného místa:</string>
<string name="quick_action_gpx_category_descr">Vyberte volitelnou kategorii.</string> <string name="quick_action_gpx_category_descr">Vyberte volitelnou kategorii.</string>
<string name="quick_action_poi_list">Seznam POI</string> <string name="quick_action_poi_list">Seznam POI</string>
<string name="quick_action_sh_poi_descr">Přidat jednu nebo více kategorií POI pro zobrazení na mapě.</string> <string name="quick_action_sh_poi_descr">Přidat jednu nebo více kategorií POI pro zobrazení na mapě.</string>
@ -2155,23 +2156,23 @@ Délka %2$s</string>
<string name="quick_action_map_source">Změnit zdroj mapy</string> <string name="quick_action_map_source">Změnit zdroj mapy</string>
<string name="quick_action_map_source_title">Mapové zdroje</string> <string name="quick_action_map_source_title">Mapové zdroje</string>
<string name="quick_action_map_source_action">Přidat zdroj mapy</string> <string name="quick_action_map_source_action">Přidat zdroj mapy</string>
<string name="quick_action_map_source_switch">Zdroj mapy se změnil na \"%s\".</string> <string name="quick_action_map_source_switch">Zdroj mapy změněn na \"%s\".</string>
<string name="quick_action_btn_tutorial_title">Změnit polohu tlačítka</string> <string name="quick_action_btn_tutorial_title">Změnit polohu tlačítka</string>
<string name="quick_action_btn_tutorial_descr">Podržení a potáhnutí tlačítka změní jeho umístění na obrazovce.</string> <string name="quick_action_btn_tutorial_descr">Podržení a potáhnutí tlačítka změní jeho umístění na obrazovce.</string>
<string name="shared_string_action_name">Název akce</string> <string name="shared_string_action_name">Název akce</string>
<string name="navigate_point_olc_info_invalid">Neplatný OLC <string name="navigate_point_olc_info_invalid">Neplatný OLC
</string> \n</string>
<string name="navigate_point_olc_info_short">Krátké OLC <string name="navigate_point_olc_info_short">Krátké OLC
Prosím uveďte úplný kód</string> \nProsím zadejte úplný kód</string>
<string name="navigate_point_olc_info_area">Platné plné OLC <string name="navigate_point_olc_info_area">Platné plné OLC
Zobrazená oblast: %1$s x %2$s</string> \nOdpovídá oblasti: %1$s x %2$s</string>
<string name="favorite_autofill_toast_text">" je uložen do "</string> <string name="favorite_autofill_toast_text">" uložený do "</string>
<string name="quick_action_map_overlay_switch">Překryvná mapa se změnila na \"%s\".</string> <string name="quick_action_map_overlay_switch">Překryvná mapa změněna na \"%s\".</string>
<string name="quick_action_map_underlay_switch">Mapa podkladu se změnila na \"%s\".</string> <string name="quick_action_map_underlay_switch">Podkladová mapa změněna na \"%s\".</string>
<string name="auto_split_recording_title">Při pauze automaticky rozdělovat nahrávky</string> <string name="auto_split_recording_title">Při pauze automaticky rozdělovat nahrávky</string>
<string name="auto_split_recording_descr">Začít nový úsek po 6minutové pauze, novou trasu po 2hodinové pauze nebo nový soubor po delší pauze, pokud se změnilo datum.</string> <string name="auto_split_recording_descr">Začít nový úsek po 6minutové pauze, novou trasu po 2hodinové pauze nebo nový soubor po delší pauze, pokud se změnilo datum.</string>
<string name="quick_action_interim_dialog">Zobrazit dočasné okno</string> <string name="quick_action_interim_dialog">Zobrazit dočasné okno</string>
<string name="quick_action_page_list_descr">Aktivace tlačítka akce posune seznam na další stranu.</string> <string name="quick_action_page_list_descr">Tlačítko pro posunutí seznamu na další stranu.</string>
<string name="rendering_attr_depthContours_description">Zobrazit hloubkové vrstevnice a body.</string> <string name="rendering_attr_depthContours_description">Zobrazit hloubkové vrstevnice a body.</string>
<string name="rendering_attr_depthContours_name">Námořní hloubkové vrstevnice</string> <string name="rendering_attr_depthContours_name">Námořní hloubkové vrstevnice</string>
<string name="navigate_point_olc">Open Location Code (OLC)</string> <string name="navigate_point_olc">Open Location Code (OLC)</string>
@ -2337,10 +2338,10 @@ Zobrazená oblast: %1$s x %2$s</string>
<string name="measurement_tool">Měřit vzdálenost</string> <string name="measurement_tool">Měřit vzdálenost</string>
<string name="live_monitoring_max_interval_to_send">Časový zásobník pro online sledování</string> <string name="live_monitoring_max_interval_to_send">Časový zásobník pro online sledování</string>
<string name="live_monitoring_max_interval_to_send_desrc">Zadejte čas pro podržení pozic k odeslání, pokud není připojení</string> <string name="live_monitoring_max_interval_to_send_desrc">Zadejte čas pro podržení pozic k odeslání, pokud není připojení</string>
<string name="mappilary_no_internet_desc">Pro zobrazení fotografií z Mapillary potřebujete internetové připojení.</string> <string name="mappilary_no_internet_desc">Fotografie z Mapillary jsou dostupné pouze online.</string>
<string name="retry">Zkusit znovu</string> <string name="retry">Zkusit znovu</string>
<string name="add_waypoint">Přidat trasový bod</string> <string name="add_waypoint">Přidat trasový bod</string>
<string name="save_gpx_waypoint">Uložit trasový bod GPX</string> <string name="save_gpx_waypoint">Uložit bod GPX trasy</string>
<string name="save_route_point">Uložit bod trasy</string> <string name="save_route_point">Uložit bod trasy</string>
<string name="waypoint_one">Trasový bod 1</string> <string name="waypoint_one">Trasový bod 1</string>
<string name="route_point_one">Bod trasy 1</string> <string name="route_point_one">Bod trasy 1</string>
@ -2564,7 +2565,8 @@ Zobrazená oblast: %1$s x %2$s</string>
\n• Svoji polohu můžete sdílet se svými přáteli \n• Svoji polohu můžete sdílet se svými přáteli
\n• Důležitá místa si můžete uložit do složky \'Moje místa\' \n• Důležitá místa si můžete uložit do složky \'Moje místa\'
\n• Můžete si vybrat, jak se na mapě budou zobrazovat názvy: V angličtině, v místním jazyce nebo ve fonetickém přepisu \n• Můžete si vybrat, jak se na mapě budou zobrazovat názvy: V angličtině, v místním jazyce nebo ve fonetickém přepisu
\n• Zobrazuje specializované online dlaždice, satelitní mapy (z Bingu), různé překryvné informace jako navigační GPX trasy a další vrstvy s volitelnou průhledností</string> \n• Zobrazuje specializované online dlaždice, satelitní mapy (z Bingu), různé překryvné informace jako navigační GPX trasy a další vrstvy s volitelnou průhledností
\n</string>
<string name="osmand_extended_description_part4">Lyžování <string name="osmand_extended_description_part4">Lyžování
\nOsmAnd plugin Lyžařský mapový pohled zobrazuje lyžařské trasy podle stupně obtížnosti a také další informace jako polohy vleků a dalších zařízení.</string> \nOsmAnd plugin Lyžařský mapový pohled zobrazuje lyžařské trasy podle stupně obtížnosti a také další informace jako polohy vleků a dalších zařízení.</string>
<string name="osmand_extended_description_part5">Cyklistika <string name="osmand_extended_description_part5">Cyklistika
@ -2578,11 +2580,13 @@ Zobrazená oblast: %1$s x %2$s</string>
\n• Wikipedie ve vašem zvoleném jazyce vám poskytne mnoho informací při procházce městem \n• Wikipedie ve vašem zvoleném jazyce vám poskytne mnoho informací při procházce městem
\n• Při pohybu v neznámém městě vám pomůže zobrazování názvů zastávek a tras veřejné dopravy (autobusy, tramvaje, vlaky) \n• Při pohybu v neznámém městě vám pomůže zobrazování názvů zastávek a tras veřejné dopravy (autobusy, tramvaje, vlaky)
\n• GPS navigace v režimu Pěší vytvoří trasu cest vhodných pro chodce \n• GPS navigace v režimu Pěší vytvoří trasu cest vhodných pro chodce
\n• Můžete otevřít GPX trasu a následovat ji nebo zaznamenat a sdílet svou vlastní</string> \n• Můžete otevřít GPX trasu a následovat ji nebo zaznamenat a sdílet svou vlastní
\n</string>
<string name="osmand_extended_description_part7">Přispějte do OSM <string name="osmand_extended_description_part7">Přispějte do OSM
\n• Hlaste chyby v datech \n• Hlaste chyby v datech
\n•Nahrávejte GPX trasy do OSM přímo z aplikace \n•Nahrávejte GPX trasy do OSM přímo z aplikace
\n• Přidávejte body zájmu a rovnou je nahrávejte do OSM (nebo později, pokud jste zrovna offline)</string> \n• Přidávejte body zájmu a rovnou je nahrávejte do OSM (nebo později, pokud jste zrovna offline)
\n</string>
<string name="osmand_extended_description_part8">OsmAnd je aktivně vyvíjený open source software. Do aplikace může každý přispět hlášením chyb, vylepšováním překladů nebo programováním nových funkcí. Projekt je také závislý na finančních příspěvcích pro vývoj a testování nových funkcí. <string name="osmand_extended_description_part8">OsmAnd je aktivně vyvíjený open source software. Do aplikace může každý přispět hlášením chyb, vylepšováním překladů nebo programováním nových funkcí. Projekt je také závislý na finančních příspěvcích pro vývoj a testování nových funkcí.
\n Přibližné pokrytí a kvalita map: \n Přibližné pokrytí a kvalita map:
\n • Západní Evropa: **** \n • Západní Evropa: ****
@ -2609,7 +2613,8 @@ Zobrazená oblast: %1$s x %2$s</string>
\n• Volitelné navádění do jízdních pruhů, zobrazení názvů ulic a předpokládaného čas příjezdu \n• Volitelné navádění do jízdních pruhů, zobrazení názvů ulic a předpokládaného čas příjezdu
\n• Podporuje mezicíle v itineráři \n• Podporuje mezicíle v itineráři
\n• Automatické přepočítání trasy při odchýlení \n• Automatické přepočítání trasy při odchýlení
\n• Hledání míst podle adresy, typu (např. restaurace, hotel, čerpací stanice, muzeum) či podle geografických souřadnic</string> \n• Hledání míst podle adresy, typu (např. restaurace, hotel, čerpací stanice, muzeum) či podle geografických souřadnic
\n</string>
<string name="osmand_plus_extended_description_part3">Prohlížení mapy <string name="osmand_plus_extended_description_part3">Prohlížení mapy
\n • Zobrazení vaší pozice a orientace \n • Zobrazení vaší pozice a orientace
\n • Volitelné otáčení podle směru vašeho pohybu (nebo podle kompasu) \n • Volitelné otáčení podle směru vašeho pohybu (nebo podle kompasu)
@ -2630,7 +2635,8 @@ Zobrazená oblast: %1$s x %2$s</string>
\n• Volitelné automatické přepínání denního a nočního režimu \n• Volitelné automatické přepínání denního a nočního režimu
\n• Volitelné zobrazení rychlostních limitů s upozorněním při překročení \n• Volitelné zobrazení rychlostních limitů s upozorněním při překročení
\n• Volitelné přiblížení mapy v závislosti na rychlosti \n• Volitelné přiblížení mapy v závislosti na rychlosti
\n• Sdílení polohy s přáteli</string> \n• Sdílení polohy s přáteli
\n</string>
<string name="osmand_plus_extended_description_part6">Funkce pro cyklisty a pěší <string name="osmand_plus_extended_description_part6">Funkce pro cyklisty a pěší
\n• Zobrazení pěších, turistických a cyklistických tras, skvělé pro venkovní aktivity \n• Zobrazení pěších, turistických a cyklistických tras, skvělé pro venkovní aktivity
\n• Speciální navigační režim a zobrazení pro cyklisty a chodce \n• Speciální navigační režim a zobrazení pro cyklisty a chodce
@ -2784,7 +2790,7 @@ Zobrazená oblast: %1$s x %2$s</string>
<string name="wikivoyage_travel_guide_descr">Průvodce Wikivoyage Vám ukáže nejzajímavější místa planety, přímo v aplikaci OsmAnd a bez nutnosti připojení k internetu.</string> <string name="wikivoyage_travel_guide_descr">Průvodce Wikivoyage Vám ukáže nejzajímavější místa planety, přímo v aplikaci OsmAnd a bez nutnosti připojení k internetu.</string>
<string name="access_intermediate_arrival_time">Čas příjezdu do mezicíle</string> <string name="access_intermediate_arrival_time">Čas příjezdu do mezicíle</string>
<string name="map_widget_intermediate_time">Do průjezdního bodu</string> <string name="map_widget_intermediate_time">Do průjezdního bodu</string>
<string name="test_voice_desrc">Klepněte na tlačítko pro vyslechnutí odpovídajícího hlasového pokynu, aby jste zjistili chybné nebo chybějící pokyny.</string> <string name="test_voice_desrc">Klepněte na tlačítko pro vyslechnutí odpovídajícího hlasového pokynu, abyste zjistili, zda chybí nebo je chybný</string>
<string name="nautical_render_descr">Pro námořní navigaci. Obsahuje bóje, majáky, plavební cesty, mořské cesty a značky, přístavy, námořní služby a hloubkové vrstevnice.</string> <string name="nautical_render_descr">Pro námořní navigaci. Obsahuje bóje, majáky, plavební cesty, mořské cesty a značky, přístavy, námořní služby a hloubkové vrstevnice.</string>
<string name="ski_map_render_descr">Pro lyžování. Obsahuje sjezdovky, lyžařské vleky, běžkařské trasy atd. Nedůležité objekty jsou na mapě méně výrazné.</string> <string name="ski_map_render_descr">Pro lyžování. Obsahuje sjezdovky, lyžařské vleky, běžkařské trasy atd. Nedůležité objekty jsou na mapě méně výrazné.</string>
<string name="welcome_to_open_beta">Vítejte v otevřené beta verzi</string> <string name="welcome_to_open_beta">Vítejte v otevřené beta verzi</string>
@ -2843,7 +2849,7 @@ Zobrazená oblast: %1$s x %2$s</string>
<string name="run_full_osmand_msg">Používáte mapu {0}, která běží na aplikaci OsmAnd. Chcete spustit plnou verzi OsmAnd\?</string> <string name="run_full_osmand_msg">Používáte mapu {0}, která běží na aplikaci OsmAnd. Chcete spustit plnou verzi OsmAnd\?</string>
<string name="run_full_osmand_header">Spustit OsmAnd\?</string> <string name="run_full_osmand_header">Spustit OsmAnd\?</string>
<string name="lang_gn_py">Guaranština</string> <string name="lang_gn_py">Guaranština</string>
<string name="quick_action_switch_day_night_descr">Klepnutím na tlačítko akce se přepíná mezi denním a nočním režimem OsmAnd</string> <string name="quick_action_switch_day_night_descr">Tlačítko pro přepínání mezi denním a nočním režimem v OsmAnd.</string>
<string name="quick_action_switch_day_mode">Denní režim</string> <string name="quick_action_switch_day_mode">Denní režim</string>
<string name="quick_action_switch_night_mode">Noční režim</string> <string name="quick_action_switch_night_mode">Noční režim</string>
<string name="quick_action_day_night_switch_mode">Přepnout denní/noční režim</string> <string name="quick_action_day_night_switch_mode">Přepnout denní/noční režim</string>
@ -2886,7 +2892,7 @@ Zobrazená oblast: %1$s x %2$s</string>
<string name="exit_at">Výjezd na</string> <string name="exit_at">Výjezd na</string>
<string name="shared_string_swap">Vyměnit</string> <string name="shared_string_swap">Vyměnit</string>
<string name="quick_action_show_hide_gpx_tracks">Zobrazit/skrýt stopy</string> <string name="quick_action_show_hide_gpx_tracks">Zobrazit/skrýt stopy</string>
<string name="quick_action_show_hide_gpx_tracks_descr">Tlačítko pro zobrazení nebo skrytí vybraných stop na mapě</string> <string name="quick_action_show_hide_gpx_tracks_descr">Tlačítko pro zobrazení nebo skrytí vybraných stop na mapě.</string>
<string name="quick_action_gpx_tracks_hide">Skrýt stopy</string> <string name="quick_action_gpx_tracks_hide">Skrýt stopy</string>
<string name="quick_action_gpx_tracks_show">Zobrazit stopy</string> <string name="quick_action_gpx_tracks_show">Zobrazit stopy</string>
<string name="transfers_size">%1$d přenosů</string> <string name="transfers_size">%1$d přenosů</string>
@ -2965,8 +2971,8 @@ Zobrazená oblast: %1$s x %2$s</string>
\n \n
\n • Odstraněné analytické moduly Facebook a Firebase z verze zdarma (OsmAnd+ je neobsahoval) \n • Odstraněné analytické moduly Facebook a Firebase z verze zdarma (OsmAnd+ je neobsahoval)
\n "</string> \n "</string>
<string name="routing_attr_avoid_sett_name">Vyhnout se kostkám</string> <string name="routing_attr_avoid_sett_name">Žádné dlažební kostky</string>
<string name="routing_attr_avoid_sett_description">Vyhnout se kočičím hlavám a dlažebním kostkám</string> <string name="routing_attr_avoid_sett_description">Vyhne se dlažebním kostkám</string>
<string name="shared_string_degrees">Stupně</string> <string name="shared_string_degrees">Stupně</string>
<string name="shared_string_milliradians">Miliradiány</string> <string name="shared_string_milliradians">Miliradiány</string>
<string name="angular_measeurement">Úhlová jednotka</string> <string name="angular_measeurement">Úhlová jednotka</string>
@ -3068,10 +3074,10 @@ Zobrazená oblast: %1$s x %2$s</string>
<string name="application_profiles_descr">Zvolte profily, které mají být viditelné v aplikaci.</string> <string name="application_profiles_descr">Zvolte profily, které mají být viditelné v aplikaci.</string>
<string name="application_profiles">Profily aplikace</string> <string name="application_profiles">Profily aplikace</string>
<string name="zoom_by_wunderlinq">Použít WunderLINQ pro ovládání</string> <string name="zoom_by_wunderlinq">Použít WunderLINQ pro ovládání</string>
<string name="quick_action_need_to_add_item_to_list">Musíte přidat alespoň jednu položku v nastavení Rychlé akce</string> <string name="quick_action_need_to_add_item_to_list">Přidejte alespoň jednu položku do seznamu \'Rychlá akce\'</string>
<string name="routing_attr_piste_type_downhill_name">Alpské/sjezdové lyžování</string> <string name="routing_attr_piste_type_downhill_name">Alpské a sjezdové lyžování</string>
<string name="routing_attr_piste_type_downhill_description">Svahy pro alpské nebo sjezdové lyžování a přístup k lyžařským vlekům.</string> <string name="routing_attr_piste_type_downhill_description">Svahy pro alpské nebo sjezdové lyžování a přístup k lyžařským vlekům.</string>
<string name="routing_attr_piste_type_nordic_name">Běžecké a severské lyžování</string> <string name="routing_attr_piste_type_nordic_name">Běžecké a klasické lyžování</string>
<string name="routing_attr_piste_type_nordic_description">Trasy pro severské nebo běžecké lyžování.</string> <string name="routing_attr_piste_type_nordic_description">Trasy pro severské nebo běžecké lyžování.</string>
<string name="routing_attr_piste_type_skitour_name">Lyžařské okruhy</string> <string name="routing_attr_piste_type_skitour_name">Lyžařské okruhy</string>
<string name="routing_attr_piste_type_skitour_description">Trasy pro lyžařské okruhy.</string> <string name="routing_attr_piste_type_skitour_description">Trasy pro lyžařské okruhy.</string>
@ -3556,7 +3562,7 @@ Zobrazená oblast: %1$s x %2$s</string>
<string name="shared_string_gpx_files">Stopy</string> <string name="shared_string_gpx_files">Stopy</string>
<string name="layer_gpx_layer">Stopy</string> <string name="layer_gpx_layer">Stopy</string>
<string name="show_gpx">Stopy</string> <string name="show_gpx">Stopy</string>
<string name="save_track_to_gpx_globally">"Ukládat trasu do GPX souboru"</string> <string name="save_track_to_gpx_globally">Ukládat trasu do GPX souboru</string>
<string name="shared_string_gpx_route">Trasa ze stopy</string> <string name="shared_string_gpx_route">Trasa ze stopy</string>
<string name="empty_state_my_tracks">Přidat soubory stop</string> <string name="empty_state_my_tracks">Přidat soubory stop</string>
<string name="simplified_track">Zjednodušená trasa</string> <string name="simplified_track">Zjednodušená trasa</string>
@ -4014,4 +4020,27 @@ Zobrazená oblast: %1$s x %2$s</string>
<string name="routing_attr_allow_streams_name">Povolit říčky a stoky</string> <string name="routing_attr_allow_streams_name">Povolit říčky a stoky</string>
<string name="routing_attr_allow_intermittent_description">Povolit občasné vodní cesty</string> <string name="routing_attr_allow_intermittent_description">Povolit občasné vodní cesty</string>
<string name="routing_attr_allow_intermittent_name">Povolit občasné vodní cesty</string> <string name="routing_attr_allow_intermittent_name">Povolit občasné vodní cesty</string>
<string name="voice_prompts_timetable">Časy hlasových pokynů</string>
<string name="add_online_routing_engine">Přidat online navigační službu</string>
<string name="edit_online_routing_engine">Upravit online navigační službu</string>
<string name="shared_string_subtype">Podtyp</string>
<string name="shared_string_vehicle">Vozidlo</string>
<string name="shared_string_api_key">API klíč</string>
<string name="shared_string_server_url">URL serveru</string>
<string name="shared_string_enter_param">Zadejte parametr</string>
<string name="keep_it_empty_if_not">Pokud ne, nechte prázdné</string>
<string name="online_routing_example_hint">Adresa se všemi parametry bude vypadat takto:</string>
<string name="test_route_calculation">Vyzkoušet výpočet trasy</string>
<string name="routing_engine_vehicle_type_driving">Řízení vozidla</string>
<string name="routing_engine_vehicle_type_foot">Chůze</string>
<string name="routing_engine_vehicle_type_bike">Kolo</string>
<string name="routing_engine_vehicle_type_car">Auto</string>
<string name="message_error_recheck_parameters">Chyba, zkontrolujte parametry</string>
<string name="copy_address">Kopírovat adresu</string>
<string name="online_routing_engine">Online navigační služba</string>
<string name="online_routing_engines">Online navigační služby</string>
<string name="shared_string_folders">Složky</string>
<string name="select_folder">Zvolte složku</string>
<string name="select_folder_descr">Zvolte složku nebo vytvořte novou</string>
<string name="shared_string_empty">Prázdné</string>
</resources> </resources>

View file

@ -108,7 +108,7 @@
<string name="poi_cave_entrance">Höhleneingang</string> <string name="poi_cave_entrance">Höhleneingang</string>
<string name="poi_cemetery">Friedhof</string> <string name="poi_cemetery">Friedhof</string>
<string name="poi_chalet">Ferienhaus</string> <string name="poi_chalet">Ferienhaus</string>
<string name="poi_electricity_combined_charging">Ladestation</string> <string name="poi_electricity_combined_charging">Ladestation;Ladestation für elektische Fahrzeuge;Aufladestation</string>
<string name="poi_chemist">Drogerie</string> <string name="poi_chemist">Drogerie</string>
<string name="poi_chess">Schach</string> <string name="poi_chess">Schach</string>
<string name="poi_clothes_children">Kinderbekleidungsgeschäft</string> <string name="poi_clothes_children">Kinderbekleidungsgeschäft</string>

View file

@ -738,7 +738,7 @@
<string name="osm_settings">OpenStreetMap-Bearbeitung</string> <string name="osm_settings">OpenStreetMap-Bearbeitung</string>
<string name="additional_settings">Weitere Einstellungen</string> <string name="additional_settings">Weitere Einstellungen</string>
<string name="shared_string_settings">Einstellungen</string> <string name="shared_string_settings">Einstellungen</string>
<string name="update_tile">Karte aktualisieren</string> <string name="update_tile">Aktualisieren</string>
<string name="reload_tile">Kartenteile aktualisieren</string> <string name="reload_tile">Kartenteile aktualisieren</string>
<string name="mark_point">Ziel</string> <string name="mark_point">Ziel</string>
<string name="shared_string_add_to_favorites">Zu \'Favoriten\' hinzufügen</string> <string name="shared_string_add_to_favorites">Zu \'Favoriten\' hinzufügen</string>
@ -4047,4 +4047,11 @@
<string name="shared_string_subtype">Subtyp</string> <string name="shared_string_subtype">Subtyp</string>
<string name="keep_it_empty_if_not">Leer lassen, wenn kein API-Schlüssel vorhanden</string> <string name="keep_it_empty_if_not">Leer lassen, wenn kein API-Schlüssel vorhanden</string>
<string name="copy_address">Adresse kopieren</string> <string name="copy_address">Adresse kopieren</string>
<string name="routing_engine_vehicle_type_driving">Fahren</string>
<string name="online_routing_engine">Online Navigationssystem</string>
<string name="online_routing_engines">Online Navigationssysteme</string>
<string name="shared_string_folders">Ordner</string>
<string name="select_folder">Ordner auswählen</string>
<string name="select_folder_descr">Ordner auswählen oder neuen hinzufügen</string>
<string name="shared_string_empty">Leer</string>
</resources> </resources>

View file

@ -4044,4 +4044,10 @@
<string name="routing_engine_vehicle_type_car">Aŭtomobilo</string> <string name="routing_engine_vehicle_type_car">Aŭtomobilo</string>
<string name="message_error_recheck_parameters">Eraro, rekontrolu parametrojn</string> <string name="message_error_recheck_parameters">Eraro, rekontrolu parametrojn</string>
<string name="copy_address">Kopii adreson</string> <string name="copy_address">Kopii adreson</string>
<string name="online_routing_engine">Enreta navigilo</string>
<string name="online_routing_engines">Enretaj navigiloj</string>
<string name="shared_string_folders">Dosierujoj</string>
<string name="select_folder">Elekti dosierujon</string>
<string name="select_folder_descr">Elekti dosierujon aŭ krei novan</string>
<string name="shared_string_empty">Malplena</string>
</resources> </resources>

View file

@ -4049,4 +4049,6 @@
<string name="routing_engine_vehicle_type_car">Automóvil</string> <string name="routing_engine_vehicle_type_car">Automóvil</string>
<string name="message_error_recheck_parameters">Error, vuelve a comprobar los parámetros</string> <string name="message_error_recheck_parameters">Error, vuelve a comprobar los parámetros</string>
<string name="copy_address">Copiar dirección</string> <string name="copy_address">Copiar dirección</string>
<string name="online_routing_engine">Motor de navegación en línea</string>
<string name="online_routing_engines">Motores de navegación en línea</string>
</resources> </resources>

View file

@ -512,7 +512,7 @@
<string name="poi_canadian_football">Football canadien</string> <string name="poi_canadian_football">Football canadien</string>
<string name="poi_cycling">Cyclisme</string> <string name="poi_cycling">Cyclisme</string>
<string name="poi_shooting">Tir</string> <string name="poi_shooting">Tir</string>
<string name="poi_physiotherapist">Psychothérapeute</string> <string name="poi_physiotherapist">Kinésithérapeute</string>
<string name="poi_occupational_therapist">Ergothérapeute</string> <string name="poi_occupational_therapist">Ergothérapeute</string>
<string name="poi_place_farm">Ferme</string> <string name="poi_place_farm">Ferme</string>
<string name="poi_place_allotments">Jardins familiaux</string> <string name="poi_place_allotments">Jardins familiaux</string>

View file

@ -4023,4 +4023,10 @@
<string name="edit_online_routing_engine">Modifier le moteur de routage en ligne</string> <string name="edit_online_routing_engine">Modifier le moteur de routage en ligne</string>
<string name="online_routing_example_hint">L\'URL avec tous les paramètres sera de la forme :</string> <string name="online_routing_example_hint">L\'URL avec tous les paramètres sera de la forme :</string>
<string name="copy_address">Copier l\'adresse</string> <string name="copy_address">Copier l\'adresse</string>
<string name="online_routing_engine">Moteur de routage en ligne</string>
<string name="online_routing_engines">Moteurs de routage en ligne</string>
<string name="shared_string_folders">Dossiers</string>
<string name="select_folder">Sélectionnez le dossier</string>
<string name="select_folder_descr">Sélectionnez un dossier ou créez-en un nouveau</string>
<string name="shared_string_empty">Vide</string>
</resources> </resources>

View file

@ -3958,7 +3958,7 @@ Lon %2$s</string>
<string name="navigate_point_mgrs">MGRS</string> <string name="navigate_point_mgrs">MGRS</string>
<string name="navigate_point_format_mgrs">MGRS</string> <string name="navigate_point_format_mgrs">MGRS</string>
<string name="mgrs_format_descr">O OsmAnd emprega MGRS, que é semellante ó formato UTM NATO.</string> <string name="mgrs_format_descr">O OsmAnd emprega MGRS, que é semellante ó formato UTM NATO.</string>
<string name="message_need_calculate_route_before_show_graph">%1$s datos dispoñíbeis só nas estrada, necesitas calcular unha ruta empregando \"Ruta entre puntos\" para obtela.</string> <string name="message_need_calculate_route_before_show_graph">Os datos de %1$s só dispoñíbeis nas estradas, calcula unha ruta empregando \"Ruta entre puntos\" para ver gráficas.</string>
<string name="message_graph_will_be_available_after_recalculation">O gráfico estará dispoñíbel após o recálculo da ruta.</string> <string name="message_graph_will_be_available_after_recalculation">O gráfico estará dispoñíbel após o recálculo da ruta.</string>
<string name="shared_string_local_maps">Mapas locais</string> <string name="shared_string_local_maps">Mapas locais</string>
<string name="app_mode_gap">Salto</string> <string name="app_mode_gap">Salto</string>
@ -4050,4 +4050,27 @@ Lon %2$s</string>
<string name="routing_attr_allow_streams_name">Permitir regatos e cunetas</string> <string name="routing_attr_allow_streams_name">Permitir regatos e cunetas</string>
<string name="routing_attr_allow_intermittent_description">Permitir canles de auga intermitentes</string> <string name="routing_attr_allow_intermittent_description">Permitir canles de auga intermitentes</string>
<string name="routing_attr_allow_intermittent_name">Permitir canles de auga intermitentes</string> <string name="routing_attr_allow_intermittent_name">Permitir canles de auga intermitentes</string>
<string name="add_online_routing_engine">Engadir motor de navegación en liña</string>
<string name="edit_online_routing_engine">Editar motor de navegación en liña</string>
<string name="shared_string_subtype">Subtipo</string>
<string name="shared_string_vehicle">Vehículo</string>
<string name="shared_string_api_key">Chave da API</string>
<string name="shared_string_server_url">URL do servidor</string>
<string name="shared_string_enter_param">Insire parámetro</string>
<string name="keep_it_empty_if_not">Mantenlo baleiro se non</string>
<string name="online_routing_example_hint">A URL con todos os parámetros será así:</string>
<string name="test_route_calculation">Cálculo da ruta de proba</string>
<string name="routing_engine_vehicle_type_driving">Conducindo</string>
<string name="routing_engine_vehicle_type_foot"></string>
<string name="routing_engine_vehicle_type_bike">Bicicleta</string>
<string name="routing_engine_vehicle_type_car">Coche</string>
<string name="message_error_recheck_parameters">Erro, verifica novamente os parámetros</string>
<string name="copy_address">Copiar enderezo</string>
<string name="voice_prompts_timetable">Horarios dos avisos por voz</string>
<string name="online_routing_engine">Motor de navegación en liña</string>
<string name="online_routing_engines">Motores de navegación en liña</string>
<string name="shared_string_folders">Cartafoles</string>
<string name="select_folder_descr">Selecionar cartafol ou engadir un novo</string>
<string name="select_folder">Seleccionar cartafol</string>
<string name="shared_string_empty">Baleiro</string>
</resources> </resources>

View file

@ -4036,4 +4036,10 @@
<string name="test_route_calculation">Útvonaltervezés kipróbálása</string> <string name="test_route_calculation">Útvonaltervezés kipróbálása</string>
<string name="message_error_recheck_parameters">Hiba, ellenőrizze újra a paramétereket</string> <string name="message_error_recheck_parameters">Hiba, ellenőrizze újra a paramétereket</string>
<string name="copy_address">Cím másolása</string> <string name="copy_address">Cím másolása</string>
<string name="online_routing_engine">Online útvonaltervező</string>
<string name="online_routing_engines">Online útvonaltervezők</string>
<string name="shared_string_folders">Mappák</string>
<string name="select_folder">Mappa kijelölése</string>
<string name="select_folder_descr">Mappa kijelölése vagy új hozzáadása</string>
<string name="shared_string_empty">Üres</string>
</resources> </resources>

View file

@ -286,7 +286,7 @@
<string name="poi_fuel_biogas">Lífgas</string> <string name="poi_fuel_biogas">Lífgas</string>
<string name="poi_fuel_lh2">Fljótandi vetni</string> <string name="poi_fuel_lh2">Fljótandi vetni</string>
<string name="poi_fuel_electricity">Rafmagn</string> <string name="poi_fuel_electricity">Rafmagn</string>
<string name="poi_electricity_combined_charging">Hleðslustöð</string> <string name="poi_electricity_combined_charging">Hleðslustöð;Hleðslustöð fyrir rafknúin farartæki;EV-hleðslustöð;Hleðslustæði;Rafhleðslustæði;Þjónustustöð fyrir rafknúin farartæki</string>
<string name="poi_compressed_air">Loftdæla</string> <string name="poi_compressed_air">Loftdæla</string>
<string name="poi_motorcycle_parking">Mótorhjólastæði</string> <string name="poi_motorcycle_parking">Mótorhjólastæði</string>
<string name="poi_parking_entrance">Aðgangur að bílastæði</string> <string name="poi_parking_entrance">Aðgangur að bílastæði</string>
@ -3883,4 +3883,8 @@
<string name="poi_lavoir">Þvottaþró (þvottaaðstaða)</string> <string name="poi_lavoir">Þvottaþró (þvottaaðstaða)</string>
<string name="poi_water_source_river">Á</string> <string name="poi_water_source_river">Á</string>
<string name="poi_lifeguard_base">Bækistöð öryggisvarða</string> <string name="poi_lifeguard_base">Bækistöð öryggisvarða</string>
<string name="poi_wildlife_crossing_bat_tunnel">Göng fyrir leðurblökur</string>
<string name="poi_wildlife_crossing_bat_bridge">Brú fyrir leðurblökur</string>
<string name="poi_wildlife_crossing">Þverun villtra dýra</string>
<string name="poi_swimming_area">Sundsvæði</string>
</resources> </resources>

View file

@ -4029,4 +4029,29 @@
<string name="profile_type_user_string">Notandasnið</string> <string name="profile_type_user_string">Notandasnið</string>
<string name="reverse_all_points">Snúa við öllum punktum</string> <string name="reverse_all_points">Snúa við öllum punktum</string>
<string name="profile_type_osmand_string">OsmAnd-snið</string> <string name="profile_type_osmand_string">OsmAnd-snið</string>
<string name="voice_prompts_timetable">Tímasetning raddskipana</string>
<string name="profile_by_default_description">Veldu sniðið sem verður notað þegar forritið er ræst.</string>
<string name="shared_string_last_used">Síðast notað</string>
<string name="routing_attr_prefer_hiking_routes_description">Velja frekar gönguleiðir</string>
<string name="routing_attr_prefer_hiking_routes_name">Velja frekar gönguleiðir</string>
<string name="routing_attr_allow_streams_description">Leyfa læki og afföll</string>
<string name="routing_attr_allow_streams_name">Leyfa læki og afföll</string>
<string name="routing_attr_allow_intermittent_description">Leyfa tímabundnar vatnaleiðir</string>
<string name="routing_attr_allow_intermittent_name">Leyfa tímabundnar vatnaleiðir</string>
<string name="add_online_routing_engine">Bæta við leiðagerð af netinu</string>
<string name="edit_online_routing_engine">Breyta nettengdri leiðagerð</string>
<string name="shared_string_subtype">Undirtegund</string>
<string name="shared_string_vehicle">Farartæki</string>
<string name="shared_string_api_key">API-lykill</string>
<string name="shared_string_server_url">Vefslóð netþjóns</string>
<string name="shared_string_enter_param">Setja inn breytu</string>
<string name="keep_it_empty_if_not">Halda þessu auðu ef ekki</string>
<string name="online_routing_example_hint">Vefslóð með öllum breytum mun líta svona út:</string>
<string name="test_route_calculation">Útreikningur prufuleiðar</string>
<string name="routing_engine_vehicle_type_driving">Akstur</string>
<string name="routing_engine_vehicle_type_foot">Gangandi</string>
<string name="routing_engine_vehicle_type_bike">Hjólandi</string>
<string name="routing_engine_vehicle_type_car">Bíll</string>
<string name="message_error_recheck_parameters">Villa, yfirfarðu breytur</string>
<string name="copy_address">Afrita heimilisfang</string>
</resources> </resources>

View file

@ -4048,4 +4048,10 @@
<string name="routing_engine_vehicle_type_car">מכונית</string> <string name="routing_engine_vehicle_type_car">מכונית</string>
<string name="message_error_recheck_parameters">שגיאה, נא לבדוק את המשתנים מחדש</string> <string name="message_error_recheck_parameters">שגיאה, נא לבדוק את המשתנים מחדש</string>
<string name="copy_address">העתקת כתובת</string> <string name="copy_address">העתקת כתובת</string>
<string name="online_routing_engine">מנוע ניווט מקוון</string>
<string name="online_routing_engines">מנועי ניווט מקוונים</string>
<string name="shared_string_folders">תיקיות</string>
<string name="select_folder">בחירת תקינה</string>
<string name="select_folder_descr">נא לבחור תיקייה או להוסיף אחת חדשה</string>
<string name="shared_string_empty">ריק</string>
</resources> </resources>

View file

@ -3940,4 +3940,30 @@
<string name="routing_attr_allow_streams_description">Beken en sloten gebruiken</string> <string name="routing_attr_allow_streams_description">Beken en sloten gebruiken</string>
<string name="routing_attr_allow_streams_name">Beken en sloten gebruiken</string> <string name="routing_attr_allow_streams_name">Beken en sloten gebruiken</string>
<string name="routing_attr_allow_intermittent_description">Waterwegen die periodiek water voeren gebruiken</string> <string name="routing_attr_allow_intermittent_description">Waterwegen die periodiek water voeren gebruiken</string>
<string name="select_folder">Selecteer een map</string>
<string name="select_folder_descr">Selecteer een map of maak een nieuwe</string>
<string name="shared_string_empty">Leeg</string>
<string name="select_groups_for_import">Selecteer de groepen om deze te importeren.</string>
<string name="select_items_for_import">Selecteer de items om deze te importeren.</string>
<string name="add_to_mapillary">Voeg toe aan Mapillary</string>
<string name="add_to_opr">Voeg toe aan OpenPlaceReviews</string>
<string name="use_dev_url">Gebruik dev.openstreetmap.org</string>
<string name="profile_by_default_description">Selecteer het profiel dat zal worden gebruikt bij het starten van de applicatie.</string>
<string name="add_online_routing_engine">Online routeplanner toevoegen</string>
<string name="edit_online_routing_engine">Bewerk online routeplanner</string>
<string name="shared_string_subtype">Subtype</string>
<string name="shared_string_vehicle">Voertuig</string>
<string name="shared_string_api_key">API-sleutel</string>
<string name="shared_string_server_url">Server-URL</string>
<string name="online_routing_example_hint">De URL met alle parameters ziet er als volgt uit:</string>
<string name="test_route_calculation">Test routeberekening</string>
<string name="routing_engine_vehicle_type_driving">Rijden</string>
<string name="routing_engine_vehicle_type_foot">Te voet</string>
<string name="routing_engine_vehicle_type_bike">Fiets</string>
<string name="routing_engine_vehicle_type_car">Auto</string>
<string name="message_error_recheck_parameters">Fout, controleer parameters opnieuw</string>
<string name="copy_address">Kopieer adres</string>
<string name="online_routing_engine">Online routeplanningssysteem</string>
<string name="online_routing_engines">Online routeplanningssystemen</string>
<string name="shared_string_folders">Mappen</string>
</resources> </resources>

View file

@ -4038,4 +4038,11 @@
<string name="routing_engine_vehicle_type_bike">Bicicleta</string> <string name="routing_engine_vehicle_type_bike">Bicicleta</string>
<string name="routing_engine_vehicle_type_car">Carro</string> <string name="routing_engine_vehicle_type_car">Carro</string>
<string name="message_error_recheck_parameters">Erro, verifique novamente os parâmetros</string> <string name="message_error_recheck_parameters">Erro, verifique novamente os parâmetros</string>
<string name="copy_address">Copiar endereço</string>
<string name="online_routing_engine">Motor de encaminhamento online</string>
<string name="online_routing_engines">Mecanismos de roteamento online</string>
<string name="shared_string_folders">Pastas</string>
<string name="select_folder">Selecione a pasta</string>
<string name="select_folder_descr">Selecione a pasta ou adicione uma nova</string>
<string name="shared_string_empty">Vazio</string>
</resources> </resources>

View file

@ -3879,4 +3879,7 @@
<string name="poi_water_source_tap">Torneira</string> <string name="poi_water_source_tap">Torneira</string>
<string name="poi_water_source_water_works">Estação de tratamento de água</string> <string name="poi_water_source_water_works">Estação de tratamento de água</string>
<string name="poi_ranger_station">Posto de guarda florestal</string> <string name="poi_ranger_station">Posto de guarda florestal</string>
<string name="poi_wildlife_crossing_bat_tunnel">Túnel de morcegos</string>
<string name="poi_wildlife_crossing_bat_bridge">Ponte de morcegos</string>
<string name="poi_swimming_area">Área de natação</string>
</resources> </resources>

View file

@ -4005,4 +4005,14 @@
<string name="shared_string_last_used">Последний раз использовалось</string> <string name="shared_string_last_used">Последний раз использовалось</string>
<string name="routing_attr_allow_intermittent_description">Разрешить прерывистые водные пути</string> <string name="routing_attr_allow_intermittent_description">Разрешить прерывистые водные пути</string>
<string name="routing_attr_allow_intermittent_name">Разрешить прерывистые водные пути</string> <string name="routing_attr_allow_intermittent_name">Разрешить прерывистые водные пути</string>
<string name="release_3_9">• Добавлена возможность экспорта и импорта всех данных, включая настройки, ресурсы, мои места.
\n
\n• Планирование маршрута: графики для сегментов трека с маршрутом, добавлена возможность создавать и редактировать несколько сегментов трека.
\n
\n• Добавлен OAuth метод аутентификации для OpenStreetMap, улучшен интерфейс диалоговых OSM.
\n
\n • Поддержка пользовательских цветов для избранного и путевых точек трека.
\n
\n</string>
<string name="copy_address">Скопировать адрес</string>
</resources> </resources>

View file

@ -4025,4 +4025,26 @@
<string name="routing_attr_allow_intermittent_description">Permite caminos de abba intermitentes</string> <string name="routing_attr_allow_intermittent_description">Permite caminos de abba intermitentes</string>
<string name="routing_attr_allow_intermittent_name">Permite caminos de abba intermitentes</string> <string name="routing_attr_allow_intermittent_name">Permite caminos de abba intermitentes</string>
<string name="voice_prompts_timetable">Nùmeru de annùntzios vocales</string> <string name="voice_prompts_timetable">Nùmeru de annùntzios vocales</string>
<string name="add_online_routing_engine">Annanghe unu motore de càrculu de s\'àndala in lìnia</string>
<string name="edit_online_routing_engine">Modìfica su motore de càrculu de s\'àndala in lìnia</string>
<string name="shared_string_subtype">Suta-casta</string>
<string name="shared_string_vehicle">Veìculu</string>
<string name="shared_string_api_key">Crae de s\'API</string>
<string name="shared_string_server_url">URL de su serbidore</string>
<string name="shared_string_enter_param">Inserta su paràmetru</string>
<string name="keep_it_empty_if_not">Si nono, mantene·lu bòidu</string>
<string name="online_routing_example_hint">S\'URL cun totu sos paràmetros at a apàrrere gasi:</string>
<string name="test_route_calculation">Proa a carculare un\'àndala</string>
<string name="routing_engine_vehicle_type_driving">In vetura</string>
<string name="routing_engine_vehicle_type_foot">A pee</string>
<string name="routing_engine_vehicle_type_bike">Bitzicleta</string>
<string name="routing_engine_vehicle_type_car">Màchina</string>
<string name="message_error_recheck_parameters">Errore, torra a verificare sos paràmetros</string>
<string name="copy_address">Còpia s\'indiritzu</string>
<string name="online_routing_engine">Motore de càrculu in lìnia</string>
<string name="online_routing_engines">Motores de càrculu in lìnia</string>
<string name="shared_string_folders">Cartellas</string>
<string name="select_folder">Ischerta sa cartella</string>
<string name="select_folder_descr">Ischerta una cartella o crea·nde una noa</string>
<string name="shared_string_empty">Bòidu</string>
</resources> </resources>

View file

@ -4043,4 +4043,7 @@
<string name="routing_engine_vehicle_type_bike">Bicykel</string> <string name="routing_engine_vehicle_type_bike">Bicykel</string>
<string name="routing_engine_vehicle_type_car">Auto</string> <string name="routing_engine_vehicle_type_car">Auto</string>
<string name="message_error_recheck_parameters">Chyba, skontrolujte parametre</string> <string name="message_error_recheck_parameters">Chyba, skontrolujte parametre</string>
<string name="copy_address">Kopírovať adresu</string>
<string name="online_routing_engine">Online navigačná služba</string>
<string name="online_routing_engines">Online navigačné služby</string>
</resources> </resources>

View file

@ -3999,4 +3999,10 @@
<string name="routing_engine_vehicle_type_car">Araba</string> <string name="routing_engine_vehicle_type_car">Araba</string>
<string name="message_error_recheck_parameters">Hata, parametreleri tekrar gözden geçirin</string> <string name="message_error_recheck_parameters">Hata, parametreleri tekrar gözden geçirin</string>
<string name="copy_address">Adresi kopyala</string> <string name="copy_address">Adresi kopyala</string>
<string name="online_routing_engine">Çevrim içi yönlendirme motoru</string>
<string name="online_routing_engines">Çevrim içi yönlendirme motorları</string>
<string name="shared_string_folders">Klasörler</string>
<string name="select_folder">Klasör seç</string>
<string name="select_folder_descr">Klasör seçin veya yeni bir tane ekleyin</string>
<string name="shared_string_empty">Boş</string>
</resources> </resources>

View file

@ -4039,4 +4039,11 @@
<string name="routing_engine_vehicle_type_bike">Велосипед</string> <string name="routing_engine_vehicle_type_bike">Велосипед</string>
<string name="routing_engine_vehicle_type_car">Автомобіль</string> <string name="routing_engine_vehicle_type_car">Автомобіль</string>
<string name="message_error_recheck_parameters">Помилка, повторно перевірте параметри</string> <string name="message_error_recheck_parameters">Помилка, повторно перевірте параметри</string>
<string name="copy_address">Копіювати адресу</string>
<string name="online_routing_engine">Мережний рушій маршрутизації</string>
<string name="online_routing_engines">Мережні рушії маршрутизації</string>
<string name="shared_string_folders">Теки</string>
<string name="select_folder">Вибрати теку</string>
<string name="select_folder_descr">Виберіть теку або додайте нову</string>
<string name="shared_string_empty">Порожньо</string>
</resources> </resources>

View file

@ -4039,4 +4039,10 @@
<string name="routing_engine_vehicle_type_car">汽車</string> <string name="routing_engine_vehicle_type_car">汽車</string>
<string name="message_error_recheck_parameters">錯誤,重新檢查參數</string> <string name="message_error_recheck_parameters">錯誤,重新檢查參數</string>
<string name="copy_address">複製地址</string> <string name="copy_address">複製地址</string>
<string name="online_routing_engine">線上路線計算引擎</string>
<string name="online_routing_engines">線上路線計算引擎</string>
<string name="shared_string_folders">資料夾</string>
<string name="select_folder">選取資料夾</string>
<string name="select_folder_descr">選取資料夾或新增</string>
<string name="shared_string_empty"></string>
</resources> </resources>

View file

@ -12,6 +12,10 @@
--> -->
<string name="shared_string_empty">Empty</string>
<string name="select_folder_descr">Select folder or add new one</string>
<string name="select_folder">Select folder</string>
<string name="shared_string_folders">Folders</string>
<string name="online_routing_engines">Online routing engines</string> <string name="online_routing_engines">Online routing engines</string>
<string name="online_routing_engine">Online routing engine</string> <string name="online_routing_engine">Online routing engine</string>
<string name="copy_address">Copy address</string> <string name="copy_address">Copy address</string>

View file

@ -1,27 +1,24 @@
package net.osmand; package net.osmand;
import android.app.Activity;
import android.content.DialogInterface;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.Metadata;
import net.osmand.plus.GpxSelectionHelper; import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.SQLiteTileSource; import net.osmand.plus.dialogs.RenameFileBottomSheet;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class FileUtils { public class FileUtils {
@ -29,70 +26,11 @@ public class FileUtils {
public static final Pattern ILLEGAL_FILE_NAME_CHARACTERS = Pattern.compile("[?:\"*|/<>]"); public static final Pattern ILLEGAL_FILE_NAME_CHARACTERS = Pattern.compile("[?:\"*|/<>]");
public static final Pattern ILLEGAL_PATH_NAME_CHARACTERS = Pattern.compile("[?:\"*|<>]"); public static final Pattern ILLEGAL_PATH_NAME_CHARACTERS = Pattern.compile("[?:\"*|<>]");
public static void renameFile(Activity a, final File f, final RenameCallback callback) { public static void renameFile(@NonNull FragmentActivity activity, @NonNull File file,
final WeakReference<Activity> weakActivity = new WeakReference<>(a); @Nullable Fragment target, boolean usedOnMap) {
AlertDialog.Builder b = new AlertDialog.Builder(a); if (file.exists()) {
if (f.exists()) { FragmentManager fragmentManager = activity.getSupportFragmentManager();
int xt = f.getName().lastIndexOf('.'); RenameFileBottomSheet.showInstance(fragmentManager, target, file, usedOnMap);
final String ext = xt == -1 ? "" : f.getName().substring(xt);
final String originalName = xt == -1 ? f.getName() : f.getName().substring(0, xt);
final EditText editText = new EditText(a);
editText.setText(originalName);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
Editable text = editText.getText();
if (text.length() >= 1) {
Activity activity = weakActivity.get();
if (ILLEGAL_FILE_NAME_CHARACTERS.matcher(text).find() && activity != null) {
editText.setError(activity.getString(R.string.file_name_containes_illegal_char));
}
}
}
});
b.setTitle(R.string.shared_string_rename);
int leftPadding = AndroidUtils.dpToPx(a, 24f);
int topPadding = AndroidUtils.dpToPx(a, 4f);
b.setView(editText, leftPadding, topPadding, leftPadding, topPadding);
// Behaviour will be overwritten later;
b.setPositiveButton(R.string.shared_string_save, null);
b.setNegativeButton(R.string.shared_string_cancel, null);
final AlertDialog alertDialog = b.create();
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Activity activity = weakActivity.get();
if (activity != null) {
OsmandApplication app = (OsmandApplication) activity.getApplication();
if (ext.equals(SQLiteTileSource.EXT)) {
if (renameSQLiteFile(app, f, editText.getText().toString() + ext,
callback) != null) {
alertDialog.dismiss();
}
} else {
if (renameGpxFile(app, f, editText.getText().toString() + ext,
false, callback) != null) {
alertDialog.dismiss();
}
}
}
}
});
}
});
alertDialog.show();
} }
} }
@ -106,7 +44,7 @@ public class FileUtils {
dest.getParentFile().mkdirs(); dest.getParentFile().mkdirs();
} }
if (source.renameTo(dest)) { if (source.renameTo(dest)) {
final String[] suffixes = new String[]{"-journal", "-wal", "-shm"}; final String[] suffixes = new String[] {"-journal", "-wal", "-shm"};
for (String s : suffixes) { for (String s : suffixes) {
File file = new File(ctx.getDatabasePath(source + s).toString()); File file = new File(ctx.getDatabasePath(source + s).toString());
if (file.exists()) { if (file.exists()) {
@ -140,6 +78,26 @@ public class FileUtils {
return res; return res;
} }
public static File renameFile(@NonNull OsmandApplication app, @NonNull File source,
@NonNull String newName, boolean dirAllowed, RenameCallback callback) {
File dest = checkRenamePossibility(app, source, newName, dirAllowed);
if (dest == null) {
return null;
}
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
File res = source.renameTo(dest) ? dest : null;
if (res != null) {
if (callback != null) {
callback.renamedTo(res);
}
} else {
Toast.makeText(app, R.string.file_can_not_be_renamed, Toast.LENGTH_LONG).show();
}
return res;
}
public static File renameGpxFile(@NonNull OsmandApplication app, @NonNull File src, @NonNull File dest) { public static File renameGpxFile(@NonNull OsmandApplication app, @NonNull File src, @NonNull File dest) {
if (!dest.getParentFile().exists()) { if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs(); dest.getParentFile().mkdirs();

View file

@ -63,8 +63,8 @@ import net.osmand.plus.measurementtool.MeasurementToolFragment;
import net.osmand.plus.measurementtool.StartPlanRouteBottomSheet; import net.osmand.plus.measurementtool.StartPlanRouteBottomSheet;
import net.osmand.plus.monitoring.OsmandMonitoringPlugin; import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.osmedit.dialogs.DismissRouteBottomSheetFragment; import net.osmand.plus.osmedit.dialogs.DismissRouteBottomSheetFragment;
import net.osmand.plus.profiles.ProfileDataObject;
import net.osmand.plus.profiles.ProfileDataUtils; import net.osmand.plus.profiles.ProfileDataUtils;
import net.osmand.plus.profiles.RoutingProfileDataObject;
import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu; import net.osmand.plus.routepreparationmenu.MapRouteInfoMenu;
import net.osmand.plus.routepreparationmenu.WaypointsFragment; import net.osmand.plus.routepreparationmenu.WaypointsFragment;
import net.osmand.plus.routing.RouteProvider.GPXRouteParamsBuilder; import net.osmand.plus.routing.RouteProvider.GPXRouteParamsBuilder;
@ -737,7 +737,7 @@ public class MapActivityActions implements DialogProvider {
String modeDescription; String modeDescription;
Map<String, RoutingProfileDataObject> profilesObjects = ProfileDataUtils.getRoutingProfiles(app); Map<String, ProfileDataObject> profilesObjects = ProfileDataUtils.getRoutingProfiles(app);
for (final ApplicationMode appMode : activeModes) { for (final ApplicationMode appMode : activeModes) {
if (appMode.isCustomProfile()) { if (appMode.isCustomProfile()) {
modeDescription = getProfileDescription(app, appMode, profilesObjects, getString(R.string.profile_type_user_string)); modeDescription = getProfileDescription(app, appMode, profilesObjects, getString(R.string.profile_type_user_string));
@ -1046,7 +1046,7 @@ public class MapActivityActions implements DialogProvider {
//switch profile button //switch profile button
ApplicationMode currentMode = app.getSettings().APPLICATION_MODE.get(); ApplicationMode currentMode = app.getSettings().APPLICATION_MODE.get();
String modeDescription; String modeDescription;
Map<String, RoutingProfileDataObject> profilesObjects = ProfileDataUtils.getRoutingProfiles(app); Map<String, ProfileDataObject> profilesObjects = ProfileDataUtils.getRoutingProfiles(app);
if (currentMode.isCustomProfile()) { if (currentMode.isCustomProfile()) {
modeDescription = getProfileDescription(app, currentMode, profilesObjects, getString(R.string.profile_type_user_string)); modeDescription = getProfileDescription(app, currentMode, profilesObjects, getString(R.string.profile_type_user_string));
} else { } else {
@ -1087,12 +1087,12 @@ public class MapActivityActions implements DialogProvider {
} }
private String getProfileDescription(OsmandApplication app, ApplicationMode mode, private String getProfileDescription(OsmandApplication app, ApplicationMode mode,
Map<String, RoutingProfileDataObject> profilesObjects, String defaultDescription) { Map<String, ProfileDataObject> profilesObjects, String defaultDescription) {
String description = defaultDescription; String description = defaultDescription;
String routingProfileKey = mode.getRoutingProfile(); String routingProfileKey = mode.getRoutingProfile();
if (!Algorithms.isEmpty(routingProfileKey)) { if (!Algorithms.isEmpty(routingProfileKey)) {
RoutingProfileDataObject profileDataObject = profilesObjects.get(routingProfileKey); ProfileDataObject profileDataObject = profilesObjects.get(routingProfileKey);
if (profileDataObject != null) { if (profileDataObject != null) {
description = String.format(app.getString(R.string.profile_type_descr_string), description = String.format(app.getString(R.string.profile_type_descr_string),
Algorithms.capitalizeFirstLetterAndLowercase(profileDataObject.getName())); Algorithms.capitalizeFirstLetterAndLowercase(profileDataObject.getName()));

View file

@ -5,6 +5,7 @@ import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.PersistableBundle;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
@ -45,6 +46,7 @@ import net.osmand.plus.settings.backend.OsmAndAppCustomization;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.track.TrackDisplayHelper; import net.osmand.plus.track.TrackDisplayHelper;
import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint; import net.osmand.plus.views.AddGpxPointBottomSheetHelper.NewGpxPoint;
import net.osmand.util.Algorithms;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@ -83,10 +85,35 @@ public class TrackActivity extends TabActivity {
return; return;
} }
displayHelper = new TrackDisplayHelper(app); displayHelper = new TrackDisplayHelper(app);
if (intent.hasExtra(TRACK_FILE_NAME)) { if (savedInstanceState != null) {
String path = savedInstanceState.getString(TRACK_FILE_NAME);
if (!Algorithms.isEmpty(path)) {
displayHelper.setFile(new File(path));
}
} else if (intent.hasExtra(TRACK_FILE_NAME)) {
displayHelper.setFile(new File(intent.getStringExtra(TRACK_FILE_NAME))); displayHelper.setFile(new File(intent.getStringExtra(TRACK_FILE_NAME)));
} }
setupActionBar();
if (intent.hasExtra(OPEN_POINTS_TAB)
|| (savedInstanceState != null && savedInstanceState.getBoolean(OPEN_POINTS_TAB, false))) {
openPointsTab = true;
}
if (intent.hasExtra(OPEN_TRACKS_LIST)) {
openTracksList = true;
}
setContentView(R.layout.track_content);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {
File file = getFile();
outState.putString(TRACK_FILE_NAME, file != null ? file.getAbsolutePath() : null);
outState.putBoolean(CURRENT_RECORDING, file == null);
super.onSaveInstanceState(outState, outPersistentState);
}
public void setupActionBar() {
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
if (getFile() != null) { if (getFile() != null) {
@ -97,14 +124,6 @@ public class TrackActivity extends TabActivity {
} }
actionBar.setElevation(AndroidUtils.dpToPx(app, 4f)); actionBar.setElevation(AndroidUtils.dpToPx(app, 4f));
} }
if (intent.hasExtra(OPEN_POINTS_TAB)
|| (savedInstanceState != null && savedInstanceState.getBoolean(OPEN_POINTS_TAB, false))) {
openPointsTab = true;
}
if (intent.hasExtra(OPEN_TRACKS_LIST)) {
openTracksList = true;
}
setContentView(R.layout.track_content);
} }
public TrackDisplayHelper getDisplayHelper() { public TrackDisplayHelper getDisplayHelper() {

View file

@ -0,0 +1,168 @@
package net.osmand.plus.dialogs;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import net.osmand.AndroidUtils;
import net.osmand.FileUtils.RenameCallback;
import net.osmand.IndexConstants;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.SQLiteTileSource;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.base.MenuBottomSheetDialogFragment;
import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import java.io.File;
import static net.osmand.FileUtils.ILLEGAL_FILE_NAME_CHARACTERS;
import static net.osmand.FileUtils.renameFile;
import static net.osmand.FileUtils.renameGpxFile;
import static net.osmand.FileUtils.renameSQLiteFile;
public class RenameFileBottomSheet extends MenuBottomSheetDialogFragment {
private static final Log LOG = PlatformUtil.getLog(RenameFileBottomSheet.class);
private static final String TAG = RenameFileBottomSheet.class.getName();
private static final String SOURCE_FILE_NAME_KEY = "source_file_name_key";
private static final String SELECTED_FILE_NAME_KEY = "selected_file_name_key";
private OsmandApplication app;
private TextInputEditText editText;
private TextInputLayout nameTextBox;
private File file;
private String selectedFileName;
@Override
public void createMenuItems(Bundle savedInstanceState) {
app = requiredMyApplication();
if (savedInstanceState != null) {
String path = savedInstanceState.getString(SOURCE_FILE_NAME_KEY);
if (!Algorithms.isEmpty(path)) {
file = new File(path);
}
selectedFileName = savedInstanceState.getString(SELECTED_FILE_NAME_KEY);
}
items.add(new TitleItem(getString(R.string.shared_string_rename)));
View view = UiUtilities.getInflater(app, nightMode).inflate(R.layout.track_name_edit_text, null);
nameTextBox = view.findViewById(R.id.name_text_box);
nameTextBox.setBoxBackgroundColorResource(nightMode ? R.color.list_background_color_dark : R.color.activity_background_color_light);
nameTextBox.setHint(AndroidUtils.addColon(app, R.string.shared_string_name));
ColorStateList colorStateList = ColorStateList.valueOf(ContextCompat
.getColor(app, nightMode ? R.color.text_color_secondary_dark : R.color.text_color_secondary_light));
nameTextBox.setDefaultHintTextColor(colorStateList);
editText = view.findViewById(R.id.name_edit_text);
editText.setText(selectedFileName != null ? selectedFileName : Algorithms.getFileNameWithoutExtension(file));
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
updateFileName(s.toString());
}
});
BaseBottomSheetItem editFolderName = new BaseBottomSheetItem.Builder()
.setCustomView(view)
.create();
items.add(editFolderName);
}
private void updateFileName(String name) {
if (!Algorithms.isEmpty(name) && ILLEGAL_FILE_NAME_CHARACTERS.matcher(name).find()) {
nameTextBox.setError(getString(R.string.file_name_containes_illegal_char));
} else {
selectedFileName = name;
nameTextBox.setError(null);
}
updateBottomButtons();
}
@Override
protected boolean isRightBottomButtonEnabled() {
return nameTextBox.getError() == null;
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString(SOURCE_FILE_NAME_KEY, file.getAbsolutePath());
outState.putString(SELECTED_FILE_NAME_KEY, selectedFileName);
super.onSaveInstanceState(outState);
}
@Override
protected void onRightBottomButtonClick() {
FragmentActivity activity = getActivity();
if (activity != null) {
AndroidUtils.hideSoftKeyboard(activity, editText);
}
File dest;
int index = file.getName().lastIndexOf('.');
String ext = index == -1 ? "" : file.getName().substring(index);
if (SQLiteTileSource.EXT.equals(ext)) {
dest = renameSQLiteFile(app, file, selectedFileName + ext, null);
} else if (IndexConstants.GPX_FILE_EXT.equals(ext)) {
dest = renameGpxFile(app, file, selectedFileName + ext, false, null);
} else {
dest = renameFile(app, file, selectedFileName + ext, false, null);
}
if (dest != null) {
Fragment fragment = getTargetFragment();
if (fragment instanceof RenameCallback) {
RenameCallback listener = (RenameCallback) fragment;
listener.renamedTo(dest);
}
dismiss();
}
}
@Override
protected int getDismissButtonTextId() {
return R.string.shared_string_cancel;
}
@Override
protected int getRightBottomButtonTextId() {
return R.string.shared_string_save;
}
public static void showInstance(@NonNull FragmentManager fragmentManager, @Nullable Fragment target,
@NonNull File file, boolean usedOnMap) {
if (file.exists() && !fragmentManager.isStateSaved()
&& fragmentManager.findFragmentByTag(RenameFileBottomSheet.TAG) == null) {
RenameFileBottomSheet fragment = new RenameFileBottomSheet();
fragment.file = file;
fragment.setUsedOnMap(usedOnMap);
fragment.setTargetFragment(target, 0);
fragment.show(fragmentManager, RenameFileBottomSheet.TAG);
}
}
}

View file

@ -29,6 +29,7 @@ import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.view.MenuItemCompat; import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.FragmentActivity;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.Collator; import net.osmand.Collator;
@ -74,7 +75,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
public class LocalIndexesFragment extends OsmandExpandableListFragment implements DownloadEvents, public class LocalIndexesFragment extends OsmandExpandableListFragment implements DownloadEvents,
OnMapSourceUpdateListener { OnMapSourceUpdateListener, RenameCallback {
private LoadLocalIndexTask asyncLoader; private LoadLocalIndexTask asyncLoader;
private Map<String, IndexItem> filesToUpdate = new HashMap<>(); private Map<String, IndexItem> filesToUpdate = new HashMap<>();
private LocalIndexesAdapter listAdapter; private LocalIndexesAdapter listAdapter;
@ -141,13 +142,11 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement
private boolean performBasicOperation(int resId, final LocalIndexInfo info) { private boolean performBasicOperation(int resId, final LocalIndexInfo info) {
if (resId == R.string.shared_string_rename) { if (resId == R.string.shared_string_rename) {
FileUtils.renameFile(getActivity(), new File(info.getPathToData()), new RenameCallback() { FragmentActivity activity = getActivity();
if (activity != null) {
@Override File file = new File(info.getPathToData());
public void renamedTo(File file) { FileUtils.renameFile(activity, file, this, false);
getDownloadActivity().reloadLocalIndexes();
} }
});
} else if (resId == R.string.clear_tile_data) { } else if (resId == R.string.clear_tile_data) {
AlertDialog.Builder confirm = new AlertDialog.Builder(getActivity()); AlertDialog.Builder confirm = new AlertDialog.Builder(getActivity());
confirm.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() { confirm.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() {
@ -188,7 +187,19 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement
@Override @Override
public void onMapSourceUpdated() { public void onMapSourceUpdated() {
getDownloadActivity().reloadLocalIndexes(); reloadLocalIndexes();
}
@Override
public void renamedTo(File file) {
reloadLocalIndexes();
}
private void reloadLocalIndexes() {
DownloadActivity activity = getDownloadActivity();
if (activity != null) {
activity.reloadLocalIndexes();
}
} }
public class LoadLocalIndexTask extends AsyncTask<Void, LocalIndexInfo, List<LocalIndexInfo>> public class LoadLocalIndexTask extends AsyncTask<Void, LocalIndexInfo, List<LocalIndexInfo>>
@ -666,7 +677,7 @@ public class LocalIndexesFragment extends OsmandExpandableListFragment implement
public void localOptionsMenu(final int itemId) { public void localOptionsMenu(final int itemId) {
if (itemId == R.string.shared_string_refresh) { if (itemId == R.string.shared_string_refresh) {
getDownloadActivity().reloadLocalIndexes(); reloadLocalIndexes();
} else if (itemId == R.string.shared_string_delete) { } else if (itemId == R.string.shared_string_delete) {
openSelectionMode(itemId, R.drawable.ic_action_delete_dark, openSelectionMode(itemId, R.drawable.ic_action_delete_dark,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {

View file

@ -93,18 +93,20 @@ public class AmenityMenuBuilder extends MenuBuilder {
protected void buildNearestPoiRow(View view) { protected void buildNearestPoiRow(View view) {
} }
private void buildRow(View view, int iconId, String text, String textPrefix, private void buildRow(View view, int iconId, String text, String textPrefix, String socialMediaUrl,
boolean collapsable, final CollapsableView collapsableView, boolean collapsable, final CollapsableView collapsableView,
int textColor, boolean isWiki, boolean isText, boolean needLinks, int textColor, boolean isWiki, boolean isText, boolean needLinks,
boolean isPhoneNumber, boolean isUrl, boolean matchWidthDivider, int textLinesLimit) { boolean isPhoneNumber, boolean isUrl, boolean matchWidthDivider, int textLinesLimit) {
buildRow(view, iconId == 0 ? null : getRowIcon(iconId), text, textPrefix, collapsable, collapsableView, textColor, buildRow(view, iconId == 0 ? null : getRowIcon(iconId), text, textPrefix, socialMediaUrl,
collapsable, collapsableView, textColor,
isWiki, isText, needLinks, isPhoneNumber, isUrl, matchWidthDivider, textLinesLimit); isWiki, isText, needLinks, isPhoneNumber, isUrl, matchWidthDivider, textLinesLimit);
} }
protected void buildRow(final View view, Drawable icon, final String text, final String textPrefix, protected void buildRow(final View view, Drawable icon, final String text, final String textPrefix,
boolean collapsable, final CollapsableView collapsableView, final String socialMediaUrl, boolean collapsable,
int textColor, boolean isWiki, boolean isText, boolean needLinks, final CollapsableView collapsableView, int textColor, boolean isWiki,
boolean isPhoneNumber, boolean isUrl, boolean matchWidthDivider, int textLinesLimit) { boolean isText, boolean needLinks, boolean isPhoneNumber, boolean isUrl,
boolean matchWidthDivider, int textLinesLimit) {
if (!isFirstRow()) { if (!isFirstRow()) {
buildRowDivider(view); buildRowDivider(view);
@ -312,8 +314,9 @@ public class AmenityMenuBuilder extends MenuBuilder {
WikipediaArticleWikiLinkFragment.showInstance(mapActivity.getSupportFragmentManager(), text); WikipediaArticleWikiLinkFragment.showInstance(mapActivity.getSupportFragmentManager(), text);
} }
} else { } else {
String uri = socialMediaUrl == null ? text : socialMediaUrl;
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(text)); intent.setData(Uri.parse(uri));
v.getContext().startActivity(intent); v.getContext().startActivity(intent);
} }
} }
@ -366,6 +369,7 @@ public class AmenityMenuBuilder extends MenuBuilder {
continue; continue;
} }
String socialMediaUrl = null;
String textPrefix = ""; String textPrefix = "";
CollapsableView collapsableView = null; CollapsableView collapsableView = null;
boolean collapsable = false; boolean collapsable = false;
@ -397,6 +401,11 @@ public class AmenityMenuBuilder extends MenuBuilder {
if (vl.startsWith("http://") || vl.startsWith("https://") || vl.startsWith("HTTP://") || vl.startsWith("HTTPS://")) { if (vl.startsWith("http://") || vl.startsWith("https://") || vl.startsWith("HTTP://") || vl.startsWith("HTTPS://")) {
isUrl = true; isUrl = true;
} else {
socialMediaUrl = getSocialMediaUrl(key, vl);
if (socialMediaUrl != null) {
isUrl = true;
}
} }
if (pType != null && !pType.isText()) { if (pType != null && !pType.isText()) {
@ -557,16 +566,16 @@ public class AmenityMenuBuilder extends MenuBuilder {
AmenityInfoRow row; AmenityInfoRow row;
if (isDescription) { if (isDescription) {
row = new AmenityInfoRow(key, R.drawable.ic_action_note_dark, textPrefix, row = new AmenityInfoRow(key, R.drawable.ic_action_note_dark, textPrefix,
vl, collapsable, collapsableView, 0, false, true, vl, null, collapsable, collapsableView, 0, false,
true, 0, "", false, false, matchWidthDivider, 0); true, true, 0, "", false, false, matchWidthDivider, 0);
} else if (icon != null) { } else if (icon != null) {
row = new AmenityInfoRow(key, icon, textPrefix, vl, collapsable, collapsableView, row = new AmenityInfoRow(key, icon, textPrefix, vl, socialMediaUrl, collapsable,
textColor, isWiki, isText, needLinks, poiTypeOrder, poiTypeKeyName, collapsableView, textColor, isWiki, isText, needLinks, poiTypeOrder,
isPhoneNumber, isUrl, matchWidthDivider, 0); poiTypeKeyName, isPhoneNumber, isUrl, matchWidthDivider, 0);
} else { } else {
row = new AmenityInfoRow(key, iconId, textPrefix, vl, collapsable, collapsableView, row = new AmenityInfoRow(key, iconId, textPrefix, vl, socialMediaUrl, collapsable,
textColor, isWiki, isText, needLinks, poiTypeOrder, poiTypeKeyName, collapsableView, textColor, isWiki, isText, needLinks, poiTypeOrder,
isPhoneNumber, isUrl, matchWidthDivider, 0); poiTypeKeyName, isPhoneNumber, isUrl, matchWidthDivider, 0);
} }
if (isDescription) { if (isDescription) {
descriptions.add(row); descriptions.add(row);
@ -613,8 +622,11 @@ public class AmenityMenuBuilder extends MenuBuilder {
} }
boolean cuisineOrDish = categoryName.equals(Amenity.CUISINE) || categoryName.equals(Amenity.DISH); boolean cuisineOrDish = categoryName.equals(Amenity.CUISINE) || categoryName.equals(Amenity.DISH);
CollapsableView collapsableView = getPoiTypeCollapsableView(view.getContext(), true, categoryTypes, true, cuisineOrDish ? cuisineRow : null); CollapsableView collapsableView = getPoiTypeCollapsableView(view.getContext(), true, categoryTypes, true, cuisineOrDish ? cuisineRow : null);
infoRows.add(new AmenityInfoRow(poiAdditionalCategoryName, icon, pType.getPoiAdditionalCategoryTranslation(), sb.toString(), true, collapsableView, infoRows.add(new AmenityInfoRow(poiAdditionalCategoryName, icon,
0, false, false, false, pType.getOrder(), pType.getKeyName(), false, false, false, 1)); pType.getPoiAdditionalCategoryTranslation(), sb.toString(), null,
true, collapsableView, 0, false, false,
false, pType.getOrder(), pType.getKeyName(), false,
false, false, 1));
} }
} }
@ -629,8 +641,10 @@ public class AmenityMenuBuilder extends MenuBuilder {
} }
sb.append(pt.getTranslation()); sb.append(pt.getTranslation());
} }
infoRows.add(new AmenityInfoRow(poiCategory.getKeyName(), icon, poiCategory.getTranslation(), sb.toString(), true, collapsableView, infoRows.add(new AmenityInfoRow(poiCategory.getKeyName(), icon,
0, false, false, false, 40, poiCategory.getKeyName(), false, false, false, 1)); poiCategory.getTranslation(), sb.toString(), null, true,
collapsableView, 0, false, false, false, 40,
poiCategory.getKeyName(), false, false, false, 1));
} }
Collections.sort(infoRows, new Comparator<AmenityInfoRow>() { Collections.sort(infoRows, new Comparator<AmenityInfoRow>() {
@ -670,7 +684,8 @@ public class AmenityMenuBuilder extends MenuBuilder {
if (processNearestWiki() && nearestWiki.size() > 0) { if (processNearestWiki() && nearestWiki.size() > 0) {
AmenityInfoRow wikiInfo = new AmenityInfoRow( AmenityInfoRow wikiInfo = new AmenityInfoRow(
"nearest_wiki", R.drawable.ic_plugin_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size() + ")", true, "nearest_wiki", R.drawable.ic_plugin_wikipedia, null, app.getString(R.string.wiki_around) + " (" + nearestWiki.size() + ")",
null, true,
getCollapsableView(view.getContext(), true, nearestWiki), getCollapsableView(view.getContext(), true, nearestWiki),
0, false, false, false, 1000, null, false, false, false, 0); 0, false, false, false, 1000, null, false, false, false, 0);
buildAmenityRow(view, wikiInfo); buildAmenityRow(view, wikiInfo);
@ -678,7 +693,8 @@ public class AmenityMenuBuilder extends MenuBuilder {
if (processNearestPoi() && nearestPoi.size() > 0) { if (processNearestPoi() && nearestPoi.size() > 0) {
AmenityInfoRow poiInfo = new AmenityInfoRow( AmenityInfoRow poiInfo = new AmenityInfoRow(
"nearest_poi", AmenityMenuController.getRightIconId(amenity), null, app.getString(R.string.speak_poi) + " (" + nearestPoi.size() + ")", true, "nearest_poi", AmenityMenuController.getRightIconId(amenity), null, app.getString(R.string.speak_poi) + " (" + nearestPoi.size() + ")",
null, true,
getCollapsableView(view.getContext(), true, nearestPoi), getCollapsableView(view.getContext(), true, nearestPoi),
0, false, false, false, 1000, null, false, false, false, 0); 0, false, false, false, 1000, null, false, false, false, 0);
buildAmenityRow(view, poiInfo); buildAmenityRow(view, poiInfo);
@ -783,12 +799,14 @@ public class AmenityMenuBuilder extends MenuBuilder {
public void buildAmenityRow(View view, AmenityInfoRow info) { public void buildAmenityRow(View view, AmenityInfoRow info) {
if (info.icon != null) { if (info.icon != null) {
buildRow(view, info.icon, info.text, info.textPrefix, info.collapsable, info.collapsableView, buildRow(view, info.icon, info.text, info.textPrefix, info.socialMediaUrl,
info.textColor, info.isWiki, info.isText, info.needLinks, info.isPhoneNumber, info.collapsable, info.collapsableView, info.textColor, info.isWiki, info.isText,
info.needLinks, info.isPhoneNumber,
info.isUrl, info.matchWidthDivider, info.textLinesLimit); info.isUrl, info.matchWidthDivider, info.textLinesLimit);
} else { } else {
buildRow(view, info.iconId, info.text, info.textPrefix, info.collapsable, info.collapsableView, buildRow(view, info.iconId, info.text, info.textPrefix, info.socialMediaUrl,
info.textColor, info.isWiki, info.isText, info.needLinks, info.isPhoneNumber, info.collapsable, info.collapsableView, info.textColor, info.isWiki, info.isText,
info.needLinks, info.isPhoneNumber,
info.isUrl, info.matchWidthDivider, info.textLinesLimit); info.isUrl, info.matchWidthDivider, info.textLinesLimit);
} }
} }
@ -892,12 +910,45 @@ public class AmenityMenuBuilder extends MenuBuilder {
return new CollapsableView(view, this, collapsed); return new CollapsableView(view, this, collapsed);
} }
private String getSocialMediaUrl(String key, String value) {
// Remove leading and closing slashes
StringBuilder sb = new StringBuilder(value.trim());
if (sb.charAt(0) == '/') {
sb.deleteCharAt(0);
}
int lastIdx = sb.length() - 1;
if (sb.charAt(lastIdx) == '/') {
sb.deleteCharAt(lastIdx);
}
// It cannot be username
if (sb.indexOf("/") != -1) {
return "https://" + value;
}
Map<String, String> urls = new HashMap<>(7);
urls.put("facebook", "https://facebook.com/%s");
urls.put("vk", "https://vk.com/%s");
urls.put("instagram", "https://instagram.com/%s");
urls.put("twitter", "https://twitter.com/%s");
urls.put("ok", "https://ok.ru/%s");
urls.put("telegram", "https://t.me/%s");
urls.put("flickr", "https://flickr.com/%s");
if (urls.containsKey(key)) {
return String.format(urls.get(key), value);
} else {
return null;
}
}
private static class AmenityInfoRow { private static class AmenityInfoRow {
private String key; private String key;
private Drawable icon; private Drawable icon;
private int iconId; private int iconId;
private String textPrefix; private String textPrefix;
private String text; private String text;
private String socialMediaUrl;
private CollapsableView collapsableView; private CollapsableView collapsableView;
private boolean collapsable; private boolean collapsable;
private int textColor; private int textColor;
@ -912,14 +963,16 @@ public class AmenityMenuBuilder extends MenuBuilder {
private int textLinesLimit; private int textLinesLimit;
public AmenityInfoRow(String key, Drawable icon, String textPrefix, String text, public AmenityInfoRow(String key, Drawable icon, String textPrefix, String text,
boolean collapsable, CollapsableView collapsableView, String socialMediaUrl, boolean collapsable,
int textColor, boolean isWiki, boolean isText, boolean needLinks, CollapsableView collapsableView, int textColor, boolean isWiki,
int order, String name, boolean isPhoneNumber, boolean isUrl, boolean isText, boolean needLinks, int order, String name,
boolean isPhoneNumber, boolean isUrl,
boolean matchWidthDivider, int textLinesLimit) { boolean matchWidthDivider, int textLinesLimit) {
this.key = key; this.key = key;
this.icon = icon; this.icon = icon;
this.textPrefix = textPrefix; this.textPrefix = textPrefix;
this.text = text; this.text = text;
this.socialMediaUrl = socialMediaUrl;
this.collapsable = collapsable; this.collapsable = collapsable;
this.collapsableView = collapsableView; this.collapsableView = collapsableView;
this.textColor = textColor; this.textColor = textColor;
@ -935,14 +988,16 @@ public class AmenityMenuBuilder extends MenuBuilder {
} }
public AmenityInfoRow(String key, int iconId, String textPrefix, String text, public AmenityInfoRow(String key, int iconId, String textPrefix, String text,
boolean collapsable, CollapsableView collapsableView, String socialMediaUrl, boolean collapsable,
int textColor, boolean isWiki, boolean isText, boolean needLinks, CollapsableView collapsableView, int textColor, boolean isWiki,
int order, String name, boolean isPhoneNumber, boolean isUrl, boolean isText, boolean needLinks, int order, String name,
boolean isPhoneNumber, boolean isUrl,
boolean matchWidthDivider, int textLinesLimit) { boolean matchWidthDivider, int textLinesLimit) {
this.key = key; this.key = key;
this.iconId = iconId; this.iconId = iconId;
this.textPrefix = textPrefix; this.textPrefix = textPrefix;
this.text = text; this.text = text;
this.socialMediaUrl = socialMediaUrl;
this.collapsable = collapsable; this.collapsable = collapsable;
this.collapsableView = collapsableView; this.collapsableView = collapsableView;
this.textColor = textColor; this.textColor = textColor;

View file

@ -8,6 +8,7 @@ import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
@ -16,13 +17,16 @@ import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.GPXUtilities; import net.osmand.GPXUtilities;
import net.osmand.GPXUtilities.GPXFile;
import net.osmand.IndexConstants; import net.osmand.IndexConstants;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
@ -35,12 +39,17 @@ import net.osmand.plus.base.bottomsheetmenu.HorizontalRecyclerBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.DividerSpaceItem; import net.osmand.plus.base.bottomsheetmenu.simpleitems.DividerSpaceItem;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
import net.osmand.plus.measurementtool.adapter.FolderListAdapter; import net.osmand.plus.measurementtool.adapter.FolderListAdapter;
import net.osmand.plus.myplaces.MoveGpxFileBottomSheet;
import net.osmand.plus.myplaces.MoveGpxFileBottomSheet.OnTrackFileMoveListener;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class SaveAsNewTrackBottomSheetDialogFragment extends MenuBottomSheetDialogFragment { public class SaveAsNewTrackBottomSheetDialogFragment extends MenuBottomSheetDialogFragment implements OnTrackFileMoveListener {
public static final String TAG = SaveAsNewTrackBottomSheetDialogFragment.class.getSimpleName(); public static final String TAG = SaveAsNewTrackBottomSheetDialogFragment.class.getSimpleName();
private static final Log LOG = PlatformUtil.getLog(SaveAsNewTrackBottomSheetDialogFragment.class); private static final Log LOG = PlatformUtil.getLog(SaveAsNewTrackBottomSheetDialogFragment.class);
@ -52,19 +61,24 @@ public class SaveAsNewTrackBottomSheetDialogFragment extends MenuBottomSheetDial
public static final String SOURCE_FOLDER_NAME_KEY = "source_folder_name_key"; public static final String SOURCE_FOLDER_NAME_KEY = "source_folder_name_key";
public static final String SHOW_SIMPLIFIED_BUTTON_KEY = "show_simplified_button_key"; public static final String SHOW_SIMPLIFIED_BUTTON_KEY = "show_simplified_button_key";
private boolean showOnMap; private OsmandApplication app;
private boolean simplifiedTrack;
private FolderListAdapter adapter;
private TextInputLayout nameTextBox;
private RecyclerView recyclerView;
private String fileName; private String fileName;
private String sourceFileName; private String sourceFileName;
private String sourceFolderName; private String sourceFolderName;
private String folderName; private String folderName;
private boolean showOnMap;
private boolean simplifiedTrack;
private boolean rightButtonEnabled = true; private boolean rightButtonEnabled = true;
private boolean showSimplifiedButton = true; private boolean showSimplifiedButton = true;
private TextInputLayout nameTextBox;
@Override @Override
public void createMenuItems(Bundle savedInstanceState) { public void createMenuItems(Bundle savedInstanceState) {
OsmandApplication app = getMyApplication(); app = getMyApplication();
if (app == null) { if (app == null) {
return; return;
} }
@ -118,12 +132,30 @@ public class SaveAsNewTrackBottomSheetDialogFragment extends MenuBottomSheetDial
items.add(new DividerSpaceItem(app, contentPaddingSmall)); items.add(new DividerSpaceItem(app, contentPaddingSmall));
FolderListAdapter adapter = new FolderListAdapter(app, nightMode, folderName); View selectFolderView = View.inflate(UiUtilities.getThemedContext(app, nightMode), R.layout.select_folder_row, null);
selectFolderView.findViewById(R.id.select_folder_button).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FragmentActivity activity = getActivity();
if (activity != null) {
File dest = getFile(app, folderName, fileName);
MoveGpxFileBottomSheet.showInstance(activity.getSupportFragmentManager(),
SaveAsNewTrackBottomSheetDialogFragment.this, dest.getAbsolutePath(), usedOnMap);
}
}
});
BaseBottomSheetItem selectFolderItem = new BaseBottomSheetItem.Builder()
.setCustomView(selectFolderView)
.create();
items.add(selectFolderItem);
adapter = new FolderListAdapter(app, nightMode, folderName);
adapter.setFolders(getFolders());
if (adapter.getItemCount() > 0) { if (adapter.getItemCount() > 0) {
adapter.setListener(createFolderSelectListener()); adapter.setListener(createFolderSelectListener());
View view = View.inflate(UiUtilities.getThemedContext(app, nightMode), R.layout.bottom_sheet_item_recyclerview, View view = View.inflate(UiUtilities.getThemedContext(app, nightMode), R.layout.bottom_sheet_item_recyclerview,
null); null);
View recyclerView = view.findViewById(R.id.recycler_view); recyclerView = view.findViewById(R.id.recycler_view);
recyclerView.setPadding(contentPaddingHalf, 0, contentPaddingHalf, 0); recyclerView.setPadding(contentPaddingHalf, 0, contentPaddingHalf, 0);
BaseBottomSheetItem scrollItem = new HorizontalRecyclerBottomSheetItem.Builder() BaseBottomSheetItem scrollItem = new HorizontalRecyclerBottomSheetItem.Builder()
.setAdapter(adapter) .setAdapter(adapter)
@ -191,7 +223,7 @@ public class SaveAsNewTrackBottomSheetDialogFragment extends MenuBottomSheetDial
GradientDrawable background = (GradientDrawable) AppCompatResources.getDrawable(app, GradientDrawable background = (GradientDrawable) AppCompatResources.getDrawable(app,
R.drawable.bg_select_group_button_outline); R.drawable.bg_select_group_button_outline);
if (background != null) { if (background != null) {
int highlightColor = ContextCompat.getColor(app,nightMode ? int highlightColor = ContextCompat.getColor(app, nightMode ?
R.color.list_background_color_dark : R.color.activity_background_color_light); R.color.list_background_color_dark : R.color.activity_background_color_light);
int strokedColor = AndroidUtils.getColorFromAttr(UiUtilities.getThemedContext(app, nightMode), int strokedColor = AndroidUtils.getColorFromAttr(UiUtilities.getThemedContext(app, nightMode),
R.attr.stroked_buttons_and_links_outline); R.attr.stroked_buttons_and_links_outline);
@ -284,7 +316,7 @@ public class SaveAsNewTrackBottomSheetDialogFragment extends MenuBottomSheetDial
} }
} }
} }
GPXUtilities.GPXFile gpxFile = GPXUtilities.loadGPXFile(dest); GPXFile gpxFile = GPXUtilities.loadGPXFile(dest);
if (gpxFile.error != null) { if (gpxFile.error != null) {
return; return;
} }
@ -331,6 +363,38 @@ public class SaveAsNewTrackBottomSheetDialogFragment extends MenuBottomSheetDial
return false; return false;
} }
private List<String> getFolders() {
List<File> dirs = new ArrayList<>();
File gpxDir = app.getAppPath(IndexConstants.GPX_INDEX_DIR);
dirs.add(gpxDir);
Algorithms.collectDirs(gpxDir, dirs);
List<String> dirItems = new ArrayList<>();
for (File dir : dirs) {
dirItems.add(dir.getName());
}
return dirItems;
}
@Override
public void onFileMove(@NonNull File src, @NonNull File dest) {
File destFolder = dest.getParentFile();
if (destFolder != null) {
folderName = destFolder.getName();
boolean newFolder = destFolder.mkdirs();
List<String> folders = getFolders();
if (newFolder) {
adapter.setFolders(folders);
}
adapter.setSelectedFolderName(folderName);
adapter.notifyDataSetChanged();
int position = folders.indexOf(folderName);
if (position != -1) {
recyclerView.scrollToPosition(position);
}
}
}
@Override @Override
protected int getBgColorId() { protected int getBgColorId() {
return nightMode ? R.color.activity_background_color_dark : R.color.list_background_color_light; return nightMode ? R.color.activity_background_color_dark : R.color.list_background_color_light;

View file

@ -15,14 +15,11 @@ import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.IndexConstants;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
public class FolderListAdapter extends RecyclerView.Adapter<FolderListAdapter.GroupsViewHolder> { public class FolderListAdapter extends RecyclerView.Adapter<FolderListAdapter.GroupsViewHolder> {
@ -38,24 +35,15 @@ public class FolderListAdapter extends RecyclerView.Adapter<FolderListAdapter.Gr
this.app = app; this.app = app;
this.nightMode = nightMode; this.nightMode = nightMode;
selectedItemName = folderName; selectedItemName = folderName;
fillGroups();
} }
private void fillGroups() { public void setSelectedFolderName(String folderName) {
this.selectedItemName = folderName;
}
public void setFolders(List<String> folders) {
items.clear(); items.clear();
items.addAll(getFolders()); items.addAll(folders);
}
private Collection<? extends String> getFolders() {
List<File> dirs = new ArrayList<>();
File gpxDir = app.getAppPath(IndexConstants.GPX_INDEX_DIR);
dirs.add(gpxDir);
Algorithms.collectDirs(gpxDir, dirs);
List<String> dirItems = new ArrayList<>();
for (File dir : dirs) {
dirItems.add(dir.getName());
}
return dirItems;
} }
@NonNull @NonNull

View file

@ -0,0 +1,158 @@
package net.osmand.plus.myplaces;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import net.osmand.AndroidUtils;
import net.osmand.IndexConstants;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.base.MenuBottomSheetDialogFragment;
import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import java.io.File;
import static net.osmand.FileUtils.ILLEGAL_PATH_NAME_CHARACTERS;
public class AddNewTrackFolderBottomSheet extends MenuBottomSheetDialogFragment {
public static final String TAG = AddNewTrackFolderBottomSheet.class.getName();
private static final Log LOG = PlatformUtil.getLog(AddNewTrackFolderBottomSheet.class);
private static final String FOLDER_NAME_KEY = "folder_name_key";
private OsmandApplication app;
private TextInputEditText editText;
private TextInputLayout nameTextBox;
private String folderName;
private boolean rightButtonEnabled = true;
@Override
public void createMenuItems(Bundle savedInstanceState) {
app = requiredMyApplication();
if (savedInstanceState != null) {
folderName = savedInstanceState.getString(FOLDER_NAME_KEY);
} else if (Algorithms.isEmpty(folderName)) {
folderName = app.getAppPath(IndexConstants.GPX_INDEX_DIR).getName();
}
items.add(new TitleItem(getString(R.string.add_new_folder)));
View view = UiUtilities.getInflater(app, nightMode).inflate(R.layout.track_name_edit_text, null);
nameTextBox = view.findViewById(R.id.name_text_box);
nameTextBox.setBoxBackgroundColorResource(nightMode ? R.color.list_background_color_dark : R.color.activity_background_color_light);
nameTextBox.setHint(AndroidUtils.addColon(app, R.string.shared_string_name));
ColorStateList colorStateList = ColorStateList.valueOf(ContextCompat
.getColor(app, nightMode ? R.color.text_color_secondary_dark : R.color.text_color_secondary_light));
nameTextBox.setDefaultHintTextColor(colorStateList);
editText = view.findViewById(R.id.name_edit_text);
editText.setText(folderName);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
updateFileNameFromEditText(s.toString());
}
});
BaseBottomSheetItem editFolderName = new BaseBottomSheetItem.Builder()
.setCustomView(view)
.create();
items.add(editFolderName);
}
@Override
protected boolean isRightBottomButtonEnabled() {
return rightButtonEnabled;
}
private void updateFileNameFromEditText(String name) {
rightButtonEnabled = false;
if (ILLEGAL_PATH_NAME_CHARACTERS.matcher(name).find()) {
nameTextBox.setError(getString(R.string.file_name_containes_illegal_char));
} else if (Algorithms.isEmpty(name.trim())) {
nameTextBox.setError(getString(R.string.empty_filename));
} else {
File destFolder = new File(app.getAppPath(IndexConstants.GPX_INDEX_DIR), name);
if (destFolder.exists()) {
nameTextBox.setError(getString(R.string.file_with_name_already_exist));
} else {
nameTextBox.setError(null);
folderName = name;
rightButtonEnabled = true;
}
}
updateBottomButtons();
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString(FOLDER_NAME_KEY, folderName);
super.onSaveInstanceState(outState);
}
@Override
protected void onRightBottomButtonClick() {
AndroidUtils.hideSoftKeyboard(requireActivity(), editText);
Fragment fragment = getTargetFragment();
if (fragment instanceof OnTrackFolderAddListener) {
OnTrackFolderAddListener listener = (OnTrackFolderAddListener) fragment;
listener.onTrackFolderAdd(folderName);
}
dismiss();
}
@Override
protected int getDismissButtonTextId() {
return R.string.shared_string_cancel;
}
@Override
protected int getRightBottomButtonTextId() {
return R.string.shared_string_add;
}
public interface OnTrackFolderAddListener {
void onTrackFolderAdd(String folderName);
}
public static void showInstance(@NonNull FragmentManager fragmentManager, @Nullable Fragment target,
@NonNull String folderName, boolean usedOnMap) {
try {
if (!fragmentManager.isStateSaved() && fragmentManager.findFragmentByTag(AddNewTrackFolderBottomSheet.TAG) == null) {
AddNewTrackFolderBottomSheet fragment = new AddNewTrackFolderBottomSheet();
fragment.folderName = folderName;
fragment.setUsedOnMap(usedOnMap);
fragment.setTargetFragment(target, 0);
fragment.show(fragmentManager, AddNewTrackFolderBottomSheet.TAG);
}
} catch (RuntimeException e) {
LOG.error("showInstance", e);
}
}
}

View file

@ -42,6 +42,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode; import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.Collator; import net.osmand.Collator;
@ -81,11 +82,12 @@ import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
import net.osmand.plus.helpers.enums.TracksSortByMode; import net.osmand.plus.helpers.enums.TracksSortByMode;
import net.osmand.plus.mapmarkers.CoordinateInputDialogFragment; import net.osmand.plus.mapmarkers.CoordinateInputDialogFragment;
import net.osmand.plus.monitoring.OsmandMonitoringPlugin; import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.myplaces.MoveGpxFileBottomSheet.OnTrackFileMoveListener;
import net.osmand.plus.osmedit.OsmEditingPlugin; import net.osmand.plus.osmedit.OsmEditingPlugin;
import net.osmand.plus.osmedit.oauth.OsmOAuthHelper.OsmAuthorizationListener; import net.osmand.plus.osmedit.oauth.OsmOAuthHelper.OsmAuthorizationListener;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.widgets.popup.PopUpMenuItem;
import net.osmand.plus.widgets.popup.PopUpMenuHelper; import net.osmand.plus.widgets.popup.PopUpMenuHelper;
import net.osmand.plus.widgets.popup.PopUpMenuItem;
import java.io.File; import java.io.File;
import java.text.DateFormat; import java.text.DateFormat;
@ -105,6 +107,7 @@ import java.util.regex.Pattern;
import static net.osmand.plus.GpxSelectionHelper.CURRENT_TRACK; import static net.osmand.plus.GpxSelectionHelper.CURRENT_TRACK;
import static net.osmand.plus.myplaces.FavoritesActivity.GPX_TAB; import static net.osmand.plus.myplaces.FavoritesActivity.GPX_TAB;
import static net.osmand.plus.myplaces.FavoritesActivity.OPEN_GPX_REQUEST;
import static net.osmand.plus.myplaces.FavoritesActivity.TAB_ID; import static net.osmand.plus.myplaces.FavoritesActivity.TAB_ID;
import static net.osmand.util.Algorithms.capitalizeFirstLetter; import static net.osmand.util.Algorithms.capitalizeFirstLetter;
import static net.osmand.util.Algorithms.collectDirs; import static net.osmand.util.Algorithms.collectDirs;
@ -113,9 +116,8 @@ import static net.osmand.util.Algorithms.objectEquals;
import static net.osmand.util.Algorithms.removeAllFiles; import static net.osmand.util.Algorithms.removeAllFiles;
public class AvailableGPXFragment extends OsmandExpandableListFragment implements public class AvailableGPXFragment extends OsmandExpandableListFragment implements
FavoritesFragmentStateHolder, OsmAuthorizationListener { FavoritesFragmentStateHolder, OsmAuthorizationListener, OnTrackFileMoveListener, RenameCallback {
public static final Pattern ILLEGAL_PATH_NAME_CHARACTERS = Pattern.compile("[?:\"*|<>]");
public static final int SEARCH_ID = -1; public static final int SEARCH_ID = -1;
// public static final int ACTION_ID = 0; // public static final int ACTION_ID = 0;
// protected static final int DELETE_ACTION_ID = 1; // protected static final int DELETE_ACTION_ID = 1;
@ -426,7 +428,7 @@ public class AvailableGPXFragment extends OsmandExpandableListFragment implement
newIntent.putExtra(TrackActivity.TRACK_FILE_NAME, f.getAbsolutePath()); newIntent.putExtra(TrackActivity.TRACK_FILE_NAME, f.getAbsolutePath());
} }
newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
a.startActivity(newIntent); a.startActivityForResult(newIntent, OPEN_GPX_REQUEST);
} }
public void reloadTracks() { public void reloadTracks() {
@ -434,6 +436,10 @@ public class AvailableGPXFragment extends OsmandExpandableListFragment implement
asyncLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getActivity()); asyncLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getActivity());
} }
public void resetTracksLoader() {
asyncLoader = null;
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, @NonNull MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, @NonNull MenuInflater inflater) {
menu.clear(); menu.clear();
@ -790,129 +796,10 @@ public class AvailableGPXFragment extends OsmandExpandableListFragment implement
} }
private void moveGpx(final GpxInfo info) { private void moveGpx(final GpxInfo info) {
FragmentActivity activity = getActivity();
final ContextMenuAdapter menuAdapter = new ContextMenuAdapter(app); if (activity != null) {
ContextMenuItem.ItemBuilder itemBuilder = new ContextMenuItem.ItemBuilder(); MoveGpxFileBottomSheet.showInstance(activity.getSupportFragmentManager(), this, info.file.getAbsolutePath(), false);
final List<File> dirs = new ArrayList<>();
collectDirs(app.getAppPath(IndexConstants.GPX_INDEX_DIR), dirs, info.file.getParentFile());
if (!info.file.getParentFile().equals(app.getAppPath(IndexConstants.GPX_INDEX_DIR))) {
dirs.add(0, app.getAppPath(IndexConstants.GPX_INDEX_DIR));
} }
String gpxDir = app.getAppPath(IndexConstants.GPX_INDEX_DIR).getPath();
int i = 0;
for (File dir : dirs) {
String dirName = dir.getPath();
if (dirName.startsWith(gpxDir)) {
if (dirName.length() == gpxDir.length()) {
dirName = dir.getName();
} else {
dirName = dirName.substring(gpxDir.length() + 1);
}
}
menuAdapter.addItem(itemBuilder.setTitle(capitalizeFirstLetter(dirName))
.setIcon(R.drawable.ic_action_folder_stroke).setTag(i).createItem());
i++;
}
menuAdapter.addItem(itemBuilder.setTitleId(R.string.add_new_folder, app)
.setIcon(R.drawable.ic_zoom_in).setTag(-1).createItem());
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final ArrayAdapter<ContextMenuItem> listAdapter =
menuAdapter.createListAdapter(getActivity(), app.getSettings().isLightContent());
builder.setTitle(R.string.select_gpx_folder);
builder.setAdapter(listAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ContextMenuItem item = menuAdapter.getItem(which);
int index = item.getTag();
if (index == -1) {
Activity a = getActivity();
AlertDialog.Builder b = new AlertDialog.Builder(a);
b.setTitle(R.string.add_new_folder);
final EditText editText = new EditText(a);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
Editable text = editText.getText();
if (text.length() >= 1) {
if (ILLEGAL_PATH_NAME_CHARACTERS.matcher(text).find()) {
editText.setError(app.getString(R.string.file_name_containes_illegal_char));
}
}
}
});
int leftPadding = AndroidUtils.dpToPx(a, 24f);
int topPadding = AndroidUtils.dpToPx(a, 4f);
b.setView(editText, leftPadding, topPadding, leftPadding, topPadding);
// Behaviour will be overwritten later;
b.setPositiveButton(R.string.shared_string_ok, null);
b.setNegativeButton(R.string.shared_string_cancel, null);
final AlertDialog alertDialog = b.create();
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
String newName = editText.getText().toString();
if (ILLEGAL_PATH_NAME_CHARACTERS.matcher(newName).find()) {
Toast.makeText(app, R.string.file_name_containes_illegal_char,
Toast.LENGTH_LONG).show();
return;
}
File destFolder = new File(app.getAppPath(IndexConstants.GPX_INDEX_DIR), newName);
if (destFolder.exists()) {
Toast.makeText(app, R.string.file_with_name_already_exists,
Toast.LENGTH_LONG).show();
return;
} else if (destFolder.mkdirs()) {
File dest = new File(destFolder, info.fileName);
if (info.file.renameTo(dest)) {
app.getGpxDbHelper().rename(info.file, dest);
asyncLoader = new LoadGpxTask();
asyncLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getActivity());
} else {
Toast.makeText(app, R.string.file_can_not_be_moved, Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(app, R.string.file_can_not_be_moved, Toast.LENGTH_LONG).show();
}
alertDialog.dismiss();
}
});
}
});
alertDialog.show();
} else {
File dir = dirs.get(index);
File dest = new File(dir, info.file.getName());
if (dest.exists()) {
Toast.makeText(app, R.string.file_with_name_already_exists, Toast.LENGTH_LONG).show();
} else {
if (info.file.renameTo(dest)) {
app.getGpxDbHelper().rename(info.file, dest);
asyncLoader = new LoadGpxTask();
asyncLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getActivity());
} else {
Toast.makeText(app, R.string.file_can_not_be_moved, Toast.LENGTH_LONG).show();
}
}
}
}
});
builder.setNegativeButton(R.string.shared_string_cancel, null);
builder.create().show();
} }
@Override @Override
@ -938,6 +825,26 @@ public class AvailableGPXFragment extends OsmandExpandableListFragment implement
app.startActivity(intent); app.startActivity(intent);
} }
@Override
public void onFileMove(@NonNull File src, @NonNull File dest) {
File destFolder = dest.getParentFile();
if (destFolder != null && !destFolder.exists() && !destFolder.mkdirs()) {
app.showToastMessage(R.string.file_can_not_be_moved);
} else if (dest.exists()) {
app.showToastMessage(R.string.file_with_name_already_exists);
} else if (src.renameTo(dest)) {
app.getGpxDbHelper().rename(src, dest);
asyncLoader = new LoadGpxTask();
asyncLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getActivity());
} else {
app.showToastMessage(R.string.file_can_not_be_moved);
}
}
public void renamedTo(File file) {
reloadTracks();
}
public class LoadGpxTask extends AsyncTask<Activity, GpxInfo, List<GpxInfo>> { public class LoadGpxTask extends AsyncTask<Activity, GpxInfo, List<GpxInfo>> {
private List<GpxInfo> result; private List<GpxInfo> result;
@ -1543,13 +1450,10 @@ public class AvailableGPXFragment extends OsmandExpandableListFragment implement
.setOnClickListener(new View.OnClickListener() { .setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
FileUtils.renameFile(getActivity(), gpxInfo.file, new RenameCallback() { FragmentActivity activity = getActivity();
@Override if (activity != null) {
public void renamedTo(File file) { FileUtils.renameFile(activity, gpxInfo.file, AvailableGPXFragment.this, false);
asyncLoader = new LoadGpxTask();
asyncLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getActivity());
} }
});
} }
}) })
.create() .create()
@ -1711,20 +1615,7 @@ public class AvailableGPXFragment extends OsmandExpandableListFragment implement
GpxInfo item = allGpxAdapter.getChild(groupPosition, childPosition); GpxInfo item = allGpxAdapter.getChild(groupPosition, childPosition);
if (!selectionMode) { if (!selectionMode) {
Intent newIntent = new Intent(getActivity(), getMyApplication().getAppCustomization().getTrackActivity()); openTrack(getActivity(), item.file);
// causes wrong position caching: newIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
if (item.currentlyRecordingTrack) {
newIntent.putExtra(TrackActivity.CURRENT_RECORDING, true);
} else {
newIntent.putExtra(TrackActivity.TRACK_FILE_NAME, item.file.getAbsolutePath());
}
newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(newIntent);
// item.setExpanded(!item.isExpanded());
// if (item.isExpanded()) {
// descriptionLoader = new LoadLocalIndexDescriptionTask();
// descriptionLoader.execute(item);
// }
} else { } else {
if (!selectedItems.contains(item)) { if (!selectedItems.contains(item)) {
selectedItems.add(item); selectedItems.add(item);

View file

@ -42,6 +42,8 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.osmand.plus.myplaces.TrackSegmentFragment.TRACK_DELETED_KEY;
/** /**
* *
*/ */
@ -50,6 +52,7 @@ public class FavoritesActivity extends TabActivity {
private static final int OPEN_GPX_DOCUMENT_REQUEST = 1006; private static final int OPEN_GPX_DOCUMENT_REQUEST = 1006;
private static final int IMPORT_FAVOURITES_REQUEST = 1007; private static final int IMPORT_FAVOURITES_REQUEST = 1007;
protected static final int OPEN_GPX_REQUEST = 1008;
public static final String TAB_ID = "selected_tab_id"; public static final String TAB_ID = "selected_tab_id";
@ -152,6 +155,13 @@ public class FavoritesActivity extends TabActivity {
if (data != null && data.getData() != null) { if (data != null && data.getData() != null) {
importHelper.handleGpxOrFavouritesImport(data.getData()); importHelper.handleGpxOrFavouritesImport(data.getData());
} }
} else if (requestCode == OPEN_GPX_REQUEST && resultCode == Activity.RESULT_OK) {
if (data != null && data.getBooleanExtra(TRACK_DELETED_KEY, false)) {
AvailableGPXFragment gpxFragment = getGpxFragment();
if (gpxFragment != null) {
gpxFragment.resetTracksLoader();
}
}
} else { } else {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }

View file

@ -0,0 +1,183 @@
package net.osmand.plus.myplaces;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import net.osmand.IndexConstants;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.UiUtilities;
import net.osmand.plus.base.MenuBottomSheetDialogFragment;
import net.osmand.plus.base.bottomsheetmenu.BaseBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription;
import net.osmand.plus.base.bottomsheetmenu.SimpleBottomSheetItem;
import net.osmand.plus.base.bottomsheetmenu.simpleitems.DividerItem;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.myplaces.AddNewTrackFolderBottomSheet.OnTrackFolderAddListener;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static net.osmand.util.Algorithms.capitalizeFirstLetter;
import static net.osmand.util.Algorithms.collectDirs;
public class MoveGpxFileBottomSheet extends MenuBottomSheetDialogFragment implements OnTrackFolderAddListener {
public static final String TAG = MoveGpxFileBottomSheet.class.getSimpleName();
private static final Log LOG = PlatformUtil.getLog(MoveGpxFileBottomSheet.class);
private static final String FILE_PATH_KEY = "file_path_key";
private OsmandApplication app;
private String filePath;
@Override
public void createMenuItems(Bundle savedInstanceState) {
app = requiredMyApplication();
if (savedInstanceState != null) {
filePath = savedInstanceState.getString(FILE_PATH_KEY);
}
if (filePath == null) {
return;
}
final File file = new File(filePath);
final File fileDir = file.getParentFile();
BaseBottomSheetItem titleItem = new BottomSheetItemWithDescription.Builder()
.setDescription(getString(R.string.select_folder_descr))
.setTitle(getString(R.string.shared_string_folders))
.setLayoutId(R.layout.bottom_sheet_item_title_with_description)
.create();
items.add(titleItem);
View addNewFolderView = UiUtilities.getInflater(app, nightMode).inflate(R.layout.bottom_sheet_item_with_descr_64dp, null);
addNewFolderView.setMinimumHeight(getResources().getDimensionPixelSize(R.dimen.bottom_sheet_list_item_height));
AndroidUiHelper.updateVisibility(addNewFolderView.findViewById(R.id.description), false);
BaseBottomSheetItem addNewFolderItem = new SimpleBottomSheetItem.Builder()
.setTitle(getString(R.string.add_new_folder))
.setIcon(getActiveIcon(R.drawable.ic_action_folder_add))
.setLayoutId(R.layout.bottom_sheet_item_with_descr_64dp)
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FragmentActivity activity = getActivity();
if (activity != null) {
AddNewTrackFolderBottomSheet.showInstance(activity.getSupportFragmentManager(),
MoveGpxFileBottomSheet.this, fileDir.getName(), usedOnMap);
}
}
})
.setCustomView(addNewFolderView)
.create();
items.add(addNewFolderItem);
DividerItem dividerItem = new DividerItem(app);
dividerItem.setMargins(0, 0, 0, 0);
items.add(dividerItem);
final List<File> dirs = new ArrayList<>();
collectDirs(app.getAppPath(IndexConstants.GPX_INDEX_DIR), dirs, fileDir);
if (!Algorithms.objectEquals(fileDir, app.getAppPath(IndexConstants.GPX_INDEX_DIR))) {
dirs.add(0, app.getAppPath(IndexConstants.GPX_INDEX_DIR));
}
String gpxDir = app.getAppPath(IndexConstants.GPX_INDEX_DIR).getPath();
for (final File dir : dirs) {
String dirName = dir.getPath();
if (dirName.startsWith(gpxDir)) {
if (dirName.length() == gpxDir.length()) {
dirName = dir.getName();
} else {
dirName = dirName.substring(gpxDir.length() + 1);
}
}
String description;
List<File> files = collectFiles(dir);
if (Algorithms.isEmpty(files)) {
description = getString(R.string.shared_string_empty);
} else {
description = String.valueOf(files.size());
}
final BaseBottomSheetItem[] folderItem = new BaseBottomSheetItem[1];
folderItem[0] = new BottomSheetItemWithDescription.Builder()
.setDescription(description)
.setTitle(capitalizeFirstLetter(dirName))
.setIcon(getActiveIcon(R.drawable.ic_action_folder))
.setLayoutId(R.layout.bottom_sheet_item_with_descr_64dp)
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Fragment fragment = getTargetFragment();
if (fragment instanceof OnTrackFileMoveListener) {
OnTrackFileMoveListener listener = (OnTrackFileMoveListener) fragment;
listener.onFileMove(file, new File(dir, file.getName()));
}
dismiss();
}
})
.setTag(dir)
.create();
items.add(folderItem[0]);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(FILE_PATH_KEY, filePath);
}
@Override
public void onTrackFolderAdd(String folderName) {
Fragment fragment = getTargetFragment();
if (fragment instanceof OnTrackFileMoveListener) {
File file = new File(filePath);
File destFolder = new File(app.getAppPath(IndexConstants.GPX_INDEX_DIR), folderName);
OnTrackFileMoveListener listener = (OnTrackFileMoveListener) fragment;
listener.onFileMove(file, new File(destFolder, file.getName()));
}
dismiss();
}
public List<File> collectFiles(File parentDir) {
List<File> files = new ArrayList<>();
File[] listFiles = parentDir.listFiles();
if (listFiles != null) {
for (File file : listFiles) {
if (!file.isDirectory()) {
files.add(file);
}
}
}
return files;
}
public static void showInstance(@NonNull FragmentManager fragmentManager, @Nullable Fragment target,
@NonNull String filePath, boolean usedOnMap) {
try {
if (!fragmentManager.isStateSaved() && fragmentManager.findFragmentByTag(MoveGpxFileBottomSheet.TAG) == null) {
MoveGpxFileBottomSheet fragment = new MoveGpxFileBottomSheet();
fragment.filePath = filePath;
fragment.setUsedOnMap(usedOnMap);
fragment.setTargetFragment(target, 0);
fragment.show(fragmentManager, MoveGpxFileBottomSheet.TAG);
}
} catch (RuntimeException e) {
LOG.error("showInstance", e);
}
}
public interface OnTrackFileMoveListener {
void onFileMove(@NonNull File src, @NonNull File dest);
}
}

View file

@ -1,6 +1,8 @@
package net.osmand.plus.myplaces; package net.osmand.plus.myplaces;
import android.app.Activity;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -23,6 +25,8 @@ import androidx.fragment.app.FragmentManager;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.FileUtils;
import net.osmand.FileUtils.RenameCallback;
import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.GPXFile;
import net.osmand.GPXUtilities.Track; import net.osmand.GPXUtilities.Track;
import net.osmand.GPXUtilities.TrkSegment; import net.osmand.GPXUtilities.TrkSegment;
@ -53,7 +57,10 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class TrackSegmentFragment extends OsmAndListFragment implements TrackBitmapDrawerListener, SegmentActionsListener { public class TrackSegmentFragment extends OsmAndListFragment implements TrackBitmapDrawerListener,
SegmentActionsListener, RenameCallback {
public static final String TRACK_DELETED_KEY = "track_deleted_key";
private OsmandApplication app; private OsmandApplication app;
private TrackDisplayHelper displayHelper; private TrackDisplayHelper displayHelper;
@ -134,8 +141,40 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
} }
}); });
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
MenuItem renameItem = menu.add(R.string.shared_string_rename)
.setIcon(app.getUIUtilities().getIcon((R.drawable.ic_action_edit_dark)))
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
GPXFile gpx = displayHelper.getGpx();
FragmentActivity activity = getActivity();
if (activity != null && gpx != null) {
FileUtils.renameFile(activity, new File(gpx.path), TrackSegmentFragment.this, false);
} }
if (gpxFile.showCurrentTrack) { return true;
}
});
renameItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
MenuItem deleteItem = menu.add(R.string.shared_string_delete)
.setIcon(app.getUIUtilities().getIcon((R.drawable.ic_action_delete_dark)))
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
GPXFile gpx = displayHelper.getGpx();
FragmentActivity activity = getActivity();
if (activity != null && gpx != null) {
if (FileUtils.removeGpxFile(app, new File((gpx.path)))) {
Intent intent = new Intent();
intent.putExtra(TRACK_DELETED_KEY, true);
activity.setResult(Activity.RESULT_OK, intent);
activity.onBackPressed();
}
}
return true;
}
});
deleteItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
} else if (gpxFile.showCurrentTrack) {
MenuItem item = menu.add(R.string.shared_string_refresh).setIcon(R.drawable.ic_action_refresh_dark) MenuItem item = menu.add(R.string.shared_string_refresh).setIcon(R.drawable.ic_action_refresh_dark)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override @Override
@ -415,4 +454,14 @@ public class TrackSegmentFragment extends OsmAndListFragment implements TrackBit
} }
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@Override
public void renamedTo(File file) {
displayHelper.setFile(file);
TrackActivity activity = getTrackActivity();
if (activity != null) {
activity.setupActionBar();
activity.loadGpx();
}
}
} }

View file

@ -0,0 +1,24 @@
package net.osmand.plus.onlinerouting;
public enum EngineType {
GRAPHHOPPER("Graphhopper", "https://graphhopper.com/api/1/route"),
OSRM("OSRM", "https://router.project-osrm.org/route/v1/"),
ORS("Openroute Service", "https://api.openrouteservice.org/v2/directions/");
private String title;
private String standardUrl;
EngineType(String title, String standardUrl) {
this.title = title;
this.standardUrl = standardUrl;
}
public String getTitle() {
return title;
}
public String getStandardUrl() {
return standardUrl;
}
}

View file

@ -15,32 +15,32 @@ public class OnlineRoutingEngine {
public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_"; public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_";
public enum EngineParameterType { public enum EngineParameter {
CUSTOM_SERVER_URL,
CUSTOM_NAME, CUSTOM_NAME,
CUSTOM_URL,
API_KEY API_KEY
} }
private String stringKey; private String stringKey;
private ServerType serverType; private EngineType type;
private String vehicleKey; private String vehicleKey;
private Map<String, String> params = new HashMap<>(); private Map<String, String> params = new HashMap<>();
public OnlineRoutingEngine(@NonNull String stringKey, public OnlineRoutingEngine(@NonNull String stringKey,
@NonNull ServerType serverType, @NonNull EngineType type,
@NonNull String vehicleKey, @NonNull String vehicleKey,
@Nullable Map<String, String> params) { @Nullable Map<String, String> params) {
this(stringKey, serverType, vehicleKey); this(stringKey, type, vehicleKey);
if (!Algorithms.isEmpty(params)) { if (!Algorithms.isEmpty(params)) {
this.params.putAll(params); this.params.putAll(params);
} }
} }
public OnlineRoutingEngine(@NonNull String stringKey, public OnlineRoutingEngine(@NonNull String stringKey,
@NonNull ServerType serverType, @NonNull EngineType type,
@NonNull String vehicleKey) { @NonNull String vehicleKey) {
this.stringKey = stringKey; this.stringKey = stringKey;
this.serverType = serverType; this.type = type;
this.vehicleKey = vehicleKey; this.vehicleKey = vehicleKey;
} }
@ -48,8 +48,16 @@ public class OnlineRoutingEngine {
return stringKey; return stringKey;
} }
public ServerType getServerType() { public EngineType getType() {
return serverType; return type;
}
public String getBaseUrl() {
String customUrl = getParameter(EngineParameter.CUSTOM_URL);
if (Algorithms.isEmpty(customUrl)) {
return type.getStandardUrl();
}
return customUrl;
} }
public String getVehicleKey() { public String getVehicleKey() {
@ -60,25 +68,16 @@ public class OnlineRoutingEngine {
return params; return params;
} }
public String getBaseUrl() { public String getParameter(EngineParameter paramKey) {
String customServerUrl = getParameter(EngineParameterType.CUSTOM_SERVER_URL); return params.get(paramKey.name());
if (!Algorithms.isEmpty(customServerUrl)) {
return customServerUrl;
} else {
return serverType.getBaseUrl();
}
} }
public String getParameter(EngineParameterType paramType) { public void putParameter(EngineParameter paramKey, String paramValue) {
return params.get(paramType.name()); params.put(paramKey.name(), paramValue);
}
public void putParameter(EngineParameterType paramType, String paramValue) {
params.put(paramType.name(), paramValue);
} }
public String getName(@NonNull Context ctx) { public String getName(@NonNull Context ctx) {
String customName = getParameter(EngineParameterType.CUSTOM_NAME); String customName = getParameter(EngineParameter.CUSTOM_NAME);
if (customName != null) { if (customName != null) {
return customName; return customName;
} else { } else {
@ -87,21 +86,21 @@ public class OnlineRoutingEngine {
} }
private String getStandardName(@NonNull Context ctx) { private String getStandardName(@NonNull Context ctx) {
return getStandardName(ctx, serverType, vehicleKey); return getStandardName(ctx, type, vehicleKey);
} }
public static String getStandardName(@NonNull Context ctx, public static String getStandardName(@NonNull Context ctx,
@NonNull ServerType serverType, @NonNull EngineType type,
@NonNull String vehicleKey) { @NonNull String vehicleKey) {
String vehicleTitle = VehicleType.toHumanString(ctx, vehicleKey); String vehicleTitle = VehicleType.toHumanString(ctx, vehicleKey);
String pattern = ctx.getString(R.string.ltr_or_rtl_combine_via_dash); String pattern = ctx.getString(R.string.ltr_or_rtl_combine_via_dash);
return String.format(pattern, serverType.getTitle(), vehicleTitle); return String.format(pattern, type.getTitle(), vehicleTitle);
} }
public static OnlineRoutingEngine createNewEngine(@NonNull ServerType serverType, public static OnlineRoutingEngine createNewEngine(@NonNull EngineType type,
@NonNull String vehicleKey, @NonNull String vehicleKey,
@Nullable Map<String, String> params) { @Nullable Map<String, String> params) {
return new OnlineRoutingEngine(generateKey(), serverType, vehicleKey, params); return new OnlineRoutingEngine(generateKey(), type, vehicleKey, params);
} }
private static String generateKey() { private static String generateKey() {

View file

@ -30,7 +30,7 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.base.BaseOsmAndFragment; import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem;
import net.osmand.plus.onlinerouting.OnlineRoutingCard.OnTextChangedListener; import net.osmand.plus.onlinerouting.OnlineRoutingCard.OnTextChangedListener;
import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameterType; import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter;
import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -62,7 +62,7 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
private View view; private View view;
private ViewGroup segmentsContainer; private ViewGroup segmentsContainer;
private OnlineRoutingCard nameCard; private OnlineRoutingCard nameCard;
private OnlineRoutingCard serverCard; private OnlineRoutingCard typeCard;
private OnlineRoutingCard vehicleCard; private OnlineRoutingCard vehicleCard;
private OnlineRoutingCard apiKeyCard; private OnlineRoutingCard apiKeyCard;
private OnlineRoutingCard exampleCard; private OnlineRoutingCard exampleCard;
@ -143,7 +143,7 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
setupToolbar((Toolbar) view.findViewById(R.id.toolbar)); setupToolbar((Toolbar) view.findViewById(R.id.toolbar));
setupNameCard(); setupNameCard();
setupServerCard(); setupTypeCard();
setupVehicleCard(); setupVehicleCard();
setupApiKeyCard(); setupApiKeyCard();
setupExampleCard(); setupExampleCard();
@ -152,7 +152,7 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
setupButtons(); setupButtons();
updateCardViews(nameCard, serverCard, vehicleCard, exampleCard); updateCardViews(nameCard, typeCard, vehicleCard, exampleCard);
return view; return view;
} }
@ -174,38 +174,39 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
segmentsContainer.addView(nameCard.getView()); segmentsContainer.addView(nameCard.getView());
} }
private void setupServerCard() { private void setupTypeCard() {
serverCard = new OnlineRoutingCard(mapActivity, isNightMode()); typeCard = new OnlineRoutingCard(mapActivity, isNightMode());
serverCard.build(mapActivity); typeCard.build(mapActivity);
serverCard.setHeaderTitle(getString(R.string.shared_string_type)); typeCard.setHeaderTitle(getString(R.string.shared_string_type));
List<HorizontalSelectionItem> serverItems = new ArrayList<>(); List<HorizontalSelectionItem> serverItems = new ArrayList<>();
for (ServerType server : ServerType.values()) { for (EngineType server : EngineType.values()) {
serverItems.add(new HorizontalSelectionItem(server.getTitle(), server)); serverItems.add(new HorizontalSelectionItem(server.getTitle(), server));
} }
serverCard.setSelectionMenu(serverItems, engine.serverType.getTitle(), typeCard.setSelectionMenu(serverItems, engine.type.getTitle(),
new CallbackWithObject<HorizontalSelectionItem>() { new CallbackWithObject<HorizontalSelectionItem>() {
@Override @Override
public boolean processResult(HorizontalSelectionItem result) { public boolean processResult(HorizontalSelectionItem result) {
ServerType server = (ServerType) result.getObject(); EngineType type = (EngineType) result.getObject();
if (engine.serverType != server) { if (engine.type != type) {
engine.serverType = server; engine.type = type;
updateCardViews(nameCard, serverCard, exampleCard); updateCardViews(nameCard, typeCard, exampleCard);
return true; return true;
} }
return false; return false;
} }
}); });
serverCard.setOnTextChangedListener(new OnTextChangedListener() { typeCard.setOnTextChangedListener(new OnTextChangedListener() {
@Override @Override
public void onTextChanged(boolean editedByUser, String text) { public void onTextChanged(boolean editedByUser, String text) {
if (editedByUser) { if (editedByUser) {
engine.customServerUrl = text; engine.customServerUrl = text;
updateCardViews(exampleCard);
} }
} }
}); });
serverCard.setFieldBoxLabelText(getString(R.string.shared_string_server_url)); typeCard.setFieldBoxLabelText(getString(R.string.shared_string_server_url));
serverCard.showDivider(); typeCard.showDivider();
segmentsContainer.addView(serverCard.getView()); segmentsContainer.addView(typeCard.getView());
} }
private void setupVehicleCard() { private void setupVehicleCard() {
@ -342,22 +343,28 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
for (BaseCard card : cardsToUpdate) { for (BaseCard card : cardsToUpdate) {
if (nameCard.equals(card)) { if (nameCard.equals(card)) {
if (Algorithms.isEmpty(engine.customName)) { if (Algorithms.isEmpty(engine.customName)) {
String name = OnlineRoutingEngine.getStandardName(app, engine.serverType, engine.getVehicleKey()); String name;
if (Algorithms.isEmpty(engine.getVehicleKey())) {
name = engine.type.getTitle();
} else {
name = OnlineRoutingEngine.getStandardName(app, engine.type, engine.getVehicleKey());
}
nameCard.setEditedText(name); nameCard.setEditedText(name);
} }
} else if (serverCard.equals(card)) { } else if (typeCard.equals(card)) {
serverCard.setHeaderSubtitle(engine.serverType.getTitle()); typeCard.setHeaderSubtitle(engine.type.getTitle());
serverCard.setEditedText(engine.getBaseUrl()); typeCard.setEditedText(engine.getBaseUrl());
if (engine.serverType == ServerType.GRAPHHOPER) { if (engine.type == EngineType.GRAPHHOPPER || engine.type == EngineType.ORS) {
apiKeyCard.show(); apiKeyCard.show();
} else { } else {
apiKeyCard.hide(); apiKeyCard.hide();
} }
} else if (vehicleCard.equals(card)) { } else if (vehicleCard.equals(card)) {
vehicleCard.setHeaderSubtitle(engine.vehicleType.getTitle(app)); VehicleType vt = VehicleType.getVehicleByKey(engine.getVehicleKey());
if (engine.vehicleType == VehicleType.CUSTOM) { vehicleCard.setHeaderSubtitle(vt.getTitle(app));
if (vt == VehicleType.CUSTOM) {
vehicleCard.showFieldBox(); vehicleCard.showFieldBox();
vehicleCard.setEditedText(engine.getVehicleKey()); vehicleCard.setEditedText(engine.getVehicleKey());
} else { } else {
@ -400,15 +407,15 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
private void saveChanges() { private void saveChanges() {
OnlineRoutingEngine engineToSave; OnlineRoutingEngine engineToSave;
if (isEditingMode()) { if (isEditingMode()) {
engineToSave = new OnlineRoutingEngine(editedEngineKey, engine.serverType, engine.getVehicleKey()); engineToSave = new OnlineRoutingEngine(editedEngineKey, engine.type, engine.getVehicleKey());
} else { } else {
engineToSave = OnlineRoutingEngine.createNewEngine(engine.serverType, engine.getVehicleKey(), null); engineToSave = OnlineRoutingEngine.createNewEngine(engine.type, engine.getVehicleKey(), null);
} }
engineToSave.putParameter(EngineParameterType.CUSTOM_SERVER_URL, engine.customServerUrl); engineToSave.putParameter(EngineParameter.CUSTOM_NAME, engine.customName);
engineToSave.putParameter(EngineParameterType.CUSTOM_NAME, engine.customName); engineToSave.putParameter(EngineParameter.CUSTOM_URL, engine.customServerUrl);
if (engine.serverType == ServerType.GRAPHHOPER) { if (engine.type == EngineType.GRAPHHOPPER || engine.type == EngineType.ORS) {
engineToSave.putParameter(EngineParameterType.API_KEY, engine.apiKey); engineToSave.putParameter(EngineParameter.API_KEY, engine.apiKey);
} }
helper.saveEngine(engineToSave); helper.saveEngine(engineToSave);
@ -419,30 +426,18 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
} }
private String getTestUrl() { private String getTestUrl() {
String baseUrl = engine.serverType.getBaseUrl(); List<LatLon> path = new ArrayList<>();
String vehicle = engine.getVehicleKey(); path.add(selectedLocation.getCityCenterLatLon());
path.add(selectedLocation.getCityAirportLatLon());
LatLon startPoint = selectedLocation.getCityCenterLatLon(); OnlineRoutingEngine tmpEngine =
LatLon endPoint = selectedLocation.getCityAirportLatLon(); OnlineRoutingEngine.createNewEngine(engine.type, engine.getVehicleKey(), null);
tmpEngine.putParameter(EngineParameter.CUSTOM_URL, engine.customServerUrl);
if (engine.serverType == ServerType.GRAPHHOPER) { tmpEngine.putParameter(EngineParameter.API_KEY, engine.apiKey);
return baseUrl + "?" + "point=" + startPoint.getLatitude() return helper.createFullUrl(tmpEngine, path);
+ "," + startPoint.getLongitude()
+ "&" + "point=" + endPoint.getLatitude()
+ "," + endPoint.getLongitude()
+ "&" + "vehicle=" + vehicle
+ (!Algorithms.isEmpty(engine.apiKey) ? ("&" + "key=" + engine.apiKey) : "");
} else {
return baseUrl + vehicle + "/" + startPoint.getLatitude()
+ "," + startPoint.getLongitude()
+ ";" + endPoint.getLatitude()
+ "," + endPoint.getLongitude()
+ "?" + "geometries=geojson";
}
} }
private void testEngineWork() { private void testEngineWork() {
final ServerType server = engine.serverType; final EngineType type = engine.type;
final ExampleLocation location = selectedLocation; final ExampleLocation location = selectedLocation;
AndroidNetworkUtils.sendRequestAsync(app, exampleCard.getEditedText(), null, AndroidNetworkUtils.sendRequestAsync(app, exampleCard.getEditedText(), null,
null, false, false, new OnRequestResultListener() { null, false, false, new OnRequestResultListener() {
@ -453,10 +448,12 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
try { try {
JSONObject obj = new JSONObject(response); JSONObject obj = new JSONObject(response);
if (server == ServerType.GRAPHHOPER) { if (type == EngineType.GRAPHHOPPER) {
resultOk = obj.has("paths"); resultOk = obj.has("paths");
} else if (server == ServerType.OSRM) { } else if (type == EngineType.OSRM) {
resultOk = obj.has("routes"); resultOk = obj.has("routes");
} else if (type == EngineType.ORS) {
resultOk = obj.has("features");
} }
} catch (JSONException e) { } catch (JSONException e) {
@ -494,7 +491,7 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
private void saveState(Bundle outState) { private void saveState(Bundle outState) {
outState.putString(ENGINE_NAME_KEY, engine.customName); outState.putString(ENGINE_NAME_KEY, engine.customName);
outState.putString(ENGINE_SERVER_KEY, engine.serverType.name()); outState.putString(ENGINE_SERVER_KEY, engine.type.name());
outState.putString(ENGINE_SERVER_URL_KEY, engine.customServerUrl); outState.putString(ENGINE_SERVER_URL_KEY, engine.customServerUrl);
outState.putString(ENGINE_VEHICLE_TYPE_KEY, engine.vehicleType.name()); outState.putString(ENGINE_VEHICLE_TYPE_KEY, engine.vehicleType.name());
outState.putString(ENGINE_CUSTOM_VEHICLE_KEY, engine.customVehicleKey); outState.putString(ENGINE_CUSTOM_VEHICLE_KEY, engine.customVehicleKey);
@ -508,7 +505,7 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
private void restoreState(Bundle savedState) { private void restoreState(Bundle savedState) {
engine.customName = savedState.getString(ENGINE_NAME_KEY); engine.customName = savedState.getString(ENGINE_NAME_KEY);
engine.serverType = ServerType.valueOf(savedState.getString(ENGINE_SERVER_KEY)); engine.type = EngineType.valueOf(savedState.getString(ENGINE_SERVER_KEY));
engine.customServerUrl = savedState.getString(ENGINE_SERVER_URL_KEY); engine.customServerUrl = savedState.getString(ENGINE_SERVER_URL_KEY);
engine.vehicleType = VehicleType.valueOf(savedState.getString(ENGINE_VEHICLE_TYPE_KEY)); engine.vehicleType = VehicleType.valueOf(savedState.getString(ENGINE_VEHICLE_TYPE_KEY));
engine.customVehicleKey = savedState.getString(ENGINE_CUSTOM_VEHICLE_KEY); engine.customVehicleKey = savedState.getString(ENGINE_CUSTOM_VEHICLE_KEY);
@ -519,16 +516,15 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
} }
private void initState() { private void initState() {
engine.serverType = ServerType.values()[0]; engine.type = EngineType.values()[0];
engine.vehicleType = VehicleType.values()[0]; engine.vehicleType = VehicleType.values()[0];
selectedLocation = ExampleLocation.values()[0]; selectedLocation = ExampleLocation.values()[0];
if (isEditingMode()) { if (isEditingMode()) {
OnlineRoutingEngine editedEngine = helper.getEngineByKey(editedEngineKey); OnlineRoutingEngine editedEngine = helper.getEngineByKey(editedEngineKey);
if (editedEngine != null) { if (editedEngine != null) {
engine.customName = editedEngine.getParameter(EngineParameterType.CUSTOM_NAME); engine.customName = editedEngine.getParameter(EngineParameter.CUSTOM_NAME);
engine.serverType = editedEngine.getServerType(); engine.type = editedEngine.getType();
engine.customServerUrl = editedEngine.getParameter(EngineParameterType.CUSTOM_SERVER_URL);
String vehicleKey = editedEngine.getVehicleKey(); String vehicleKey = editedEngine.getVehicleKey();
if (vehicleKey != null) { if (vehicleKey != null) {
VehicleType vehicleType = VehicleType.getVehicleByKey(vehicleKey); VehicleType vehicleType = VehicleType.getVehicleByKey(vehicleKey);
@ -537,7 +533,7 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
} }
engine.vehicleType = vehicleType; engine.vehicleType = vehicleType;
} }
engine.apiKey = editedEngine.getParameter(EngineParameterType.API_KEY); engine.apiKey = editedEngine.getParameter(EngineParameter.API_KEY);
} }
} }
} }
@ -583,7 +579,7 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
private static class OnlineRoutingEngineObject { private static class OnlineRoutingEngineObject {
private String customName; private String customName;
private ServerType serverType; private EngineType type;
private String customServerUrl; private String customServerUrl;
private VehicleType vehicleType; private VehicleType vehicleType;
private String customVehicleKey; private String customVehicleKey;
@ -596,15 +592,18 @@ public class OnlineRoutingEngineFragment extends BaseOsmAndFragment {
return vehicleType.getKey(); return vehicleType.getKey();
} }
public String getBaseUrl() {
return customServerUrl != null ? customServerUrl : serverType.getBaseUrl();
}
public String getName(Context ctx) { public String getName(Context ctx) {
if (customName != null) { if (customName != null) {
return customName; return customName;
} }
return OnlineRoutingEngine.getStandardName(ctx, serverType, getVehicleKey()); return OnlineRoutingEngine.getStandardName(ctx, type, getVehicleKey());
}
public String getBaseUrl() {
if (Algorithms.isEmpty(customServerUrl)) {
return type.getStandardUrl();
}
return customServerUrl;
} }
} }
} }

View file

@ -6,18 +6,28 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.data.LatLon;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.Version;
import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import net.osmand.util.GeoPolylineParserUtil;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.net.URLConnection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -27,8 +37,7 @@ public class OnlineRoutingHelper {
private OsmandApplication app; private OsmandApplication app;
private OsmandSettings settings; private OsmandSettings settings;
private List<OnlineRoutingEngine> cachedEngines; private Map<String, OnlineRoutingEngine> cachedEngines;
private Map<String, OnlineRoutingEngine> cachedEnginesMap;
public OnlineRoutingHelper(OsmandApplication app) { public OnlineRoutingHelper(OsmandApplication app) {
this.app = app; this.app = app;
@ -38,23 +47,121 @@ public class OnlineRoutingHelper {
@NonNull @NonNull
public List<OnlineRoutingEngine> getEngines() { public List<OnlineRoutingEngine> getEngines() {
return cachedEngines; return new ArrayList<>(cachedEngines.values());
} }
public OnlineRoutingEngine getEngineByKey(String stringKey) { public OnlineRoutingEngine getEngineByKey(String stringKey) {
return cachedEnginesMap.get(stringKey); return cachedEngines.get(stringKey);
}
public List<LatLon> calculateRouteOnline(@NonNull OnlineRoutingEngine engine,
@NonNull List<LatLon> path) throws IOException, JSONException {
String fullUrl = createFullUrl(engine, path);
String content = makeRequest(fullUrl);
return parseResponse(engine, content);
}
public String createFullUrl(OnlineRoutingEngine engine, List<LatLon> path) {
StringBuilder sb = new StringBuilder(engine.getBaseUrl());
String vehicle = engine.getVehicleKey();
String apiKey = engine.getParameter(EngineParameter.API_KEY);
switch (engine.getType()) {
case GRAPHHOPPER:
sb.append("?");
for (LatLon point : path) {
sb.append("point=")
.append(point.getLatitude())
.append(',')
.append(point.getLongitude())
.append('&');
}
sb.append("vehicle=").append(vehicle);
if (!Algorithms.isEmpty(apiKey)) {
sb.append('&').append("key=").append(apiKey);
}
break;
case OSRM:
sb.append(vehicle).append('/');
for (int i = 0; i < path.size(); i++) {
LatLon point = path.get(i);
sb.append(point.getLongitude()).append(',').append(point.getLatitude());
if (i < path.size() - 1) {
sb.append(';');
}
}
break;
case ORS:
if (path.size() > 1) {
sb.append("driving-car").append('?'); // todo only for testing
if (!Algorithms.isEmpty(apiKey)) {
sb.append("api_key=").append(apiKey);
}
LatLon start = path.get(0);
LatLon end = path.get(path.size() - 1);
sb.append('&').append("start=")
.append(start.getLatitude()).append(',').append(start.getLongitude());
sb.append('&').append("end=")
.append(end.getLatitude()).append(',').append(end.getLongitude());
}
break;
}
return sb.toString();
}
private List<LatLon> parseResponse(OnlineRoutingEngine engine, String content) throws JSONException {
JSONObject obj = new JSONObject(content);
switch (engine.getType()) {
case GRAPHHOPPER:
return GeoPolylineParserUtil.parse(
obj.getJSONArray("paths").getJSONObject(0).getString("points"),
GeoPolylineParserUtil.PRECISION_5);
case OSRM:
return GeoPolylineParserUtil.parse(
obj.getJSONArray("routes").getJSONObject(0).getString("geometry"),
GeoPolylineParserUtil.PRECISION_5);
case ORS:
JSONArray array = obj.getJSONArray("features").getJSONObject(0)
.getJSONObject("geometry").getJSONArray("coordinates");
List<LatLon> track = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
JSONArray point = array.getJSONArray(i);
double lat = Double.parseDouble(point.getString(0));
double lon = Double.parseDouble(point.getString(1));
track.add(new LatLon(lat, lon));
}
return track;
}
return new ArrayList<>();
}
private String makeRequest(String url) throws IOException {
URLConnection connection = NetworkUtils.getHttpURLConnection(url);
connection.setRequestProperty("User-Agent", Version.getFullVersion(app));
StringBuilder content = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String s;
while ((s = reader.readLine()) != null) {
content.append(s);
}
try {
reader.close();
} catch (IOException ignored) {
}
return content.toString();
} }
public void saveEngine(@NonNull OnlineRoutingEngine engine) { public void saveEngine(@NonNull OnlineRoutingEngine engine) {
String stringKey = engine.getStringKey(); String stringKey = engine.getStringKey();
OnlineRoutingEngine existedEngine = cachedEnginesMap.get(stringKey); cachedEngines.put(stringKey, engine);
if (existedEngine != null) {
int index = cachedEngines.indexOf(existedEngine);
cachedEngines.set(index, engine);
} else {
cachedEngines.add(engine);
}
cachedEnginesMap.put(stringKey, engine);
saveToSettings(); saveToSettings();
} }
@ -67,19 +174,18 @@ public class OnlineRoutingHelper {
public void deleteEngine(@NonNull OnlineRoutingEngine engine) { public void deleteEngine(@NonNull OnlineRoutingEngine engine) {
String stringKey = engine.getStringKey(); String stringKey = engine.getStringKey();
if (cachedEnginesMap.containsKey(stringKey)) { if (cachedEngines.containsKey(stringKey)) {
OnlineRoutingEngine existedEngine = cachedEnginesMap.remove(stringKey); cachedEngines.remove(stringKey);
cachedEngines.remove(existedEngine);
saveToSettings(); saveToSettings();
} }
} }
private void loadFromSettings() { private void loadFromSettings() {
cachedEngines = readFromSettings(); Map<String, OnlineRoutingEngine> cachedEngines = new LinkedHashMap<>();
cachedEnginesMap = new HashMap<>(); for (OnlineRoutingEngine engine : readFromSettings()) {
for (OnlineRoutingEngine engine : cachedEngines) { cachedEngines.put(engine.getStringKey(), engine);
cachedEnginesMap.put(engine.getStringKey(), engine);
} }
this.cachedEngines = cachedEngines;
} }
@NonNull @NonNull
@ -101,7 +207,7 @@ public class OnlineRoutingHelper {
if (!Algorithms.isEmpty(cachedEngines)) { if (!Algorithms.isEmpty(cachedEngines)) {
try { try {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
writeToJson(json, cachedEngines); writeToJson(json, getEngines());
settings.ONLINE_ROUTING_ENGINES.set(json.toString()); settings.ONLINE_ROUTING_ENGINES.set(json.toString());
} catch (JSONException e) { } catch (JSONException e) {
LOG.debug("Error when writing engines to JSON ", e); LOG.debug("Error when writing engines to JSON ", e);
@ -123,10 +229,10 @@ public class OnlineRoutingHelper {
JSONObject object = itemsJson.getJSONObject(i); JSONObject object = itemsJson.getJSONObject(i);
String key = object.getString("key"); String key = object.getString("key");
String vehicleKey = object.getString("vehicle"); String vehicleKey = object.getString("vehicle");
ServerType serverType = ServerType.valueOf(object.getString("serverType")); EngineType engineType = EngineType.valueOf(object.getString("type"));
String paramsString = object.getString("params"); String paramsString = object.getString("params");
HashMap<String, String> params = gson.fromJson(paramsString, type); HashMap<String, String> params = gson.fromJson(paramsString, type);
engines.add(new OnlineRoutingEngine(key, serverType, vehicleKey, params)); engines.add(new OnlineRoutingEngine(key, engineType, vehicleKey, params));
} }
} }
@ -138,7 +244,7 @@ public class OnlineRoutingHelper {
for (OnlineRoutingEngine engine : engines) { for (OnlineRoutingEngine engine : engines) {
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
jsonObject.put("key", engine.getStringKey()); jsonObject.put("key", engine.getStringKey());
jsonObject.put("serverType", engine.getServerType().name()); jsonObject.put("type", engine.getType().name());
jsonObject.put("vehicle", engine.getVehicleKey()); jsonObject.put("vehicle", engine.getVehicleKey());
jsonObject.put("params", gson.toJson(engine.getParams(), type)); jsonObject.put("params", gson.toJson(engine.getParams(), type));
jsonArray.put(jsonObject); jsonArray.put(jsonObject);

View file

@ -1,22 +0,0 @@
package net.osmand.plus.onlinerouting;
public enum ServerType {
GRAPHHOPER("Graphhoper", "https://graphhopper.com/api/1/route"),
OSRM("OSRM", "https://zlzk.biz/route/v1/");
ServerType(String title, String baseUrl) {
this.title = title;
this.baseUrl = baseUrl;
}
private String title;
private String baseUrl;
public String getTitle() {
return title;
}
public String getBaseUrl() {
return baseUrl;
}
}

View file

@ -352,11 +352,11 @@ public class PoiUIFilter implements SearchPoiTypeFilter, Comparable<PoiUIFilter>
} }
}; };
} }
StringBuilder nmFilter = new StringBuilder();
String[] items = filter.split(" "); String[] items = filter.split(" ");
boolean allTime = false; boolean allTime = false;
boolean open = false; boolean open = false;
List<PoiType> poiAdditionalsFilter = null; List<PoiType> poiAdditionalsFilter = null;
List<String> unknownFilters = null;
for (String s : items) { for (String s : items) {
s = s.trim(); s = s.trim();
if (!Algorithms.isEmpty(s)) { if (!Algorithms.isEmpty(s)) {
@ -373,110 +373,179 @@ public class PoiUIFilter implements SearchPoiTypeFilter, Comparable<PoiUIFilter>
poiAdditionalsFilter.add(pt); poiAdditionalsFilter.add(pt);
} }
} else { } else {
nmFilter.append(s).append(" "); if (unknownFilters == null) {
unknownFilters = new ArrayList<>();
}
unknownFilters.add(s);
} }
} }
} }
return getNameFilterInternal(nmFilter, allTime, open, poiAdditionalsFilter); return getNameFilterInternal(unknownFilters, allTime, open, poiAdditionalsFilter);
} }
private AmenityNameFilter getNameFilterInternal(StringBuilder nmFilter, private AmenityNameFilter getNameFilterInternal(
final boolean allTime, final boolean open, final List<PoiType> poiAdditionals) { final List<String> unknownFilters, final boolean shouldBeAllTime,
final CollatorStringMatcher sm = nmFilter.length() > 0 ? final boolean shouldBeOpened, final List<PoiType> selectedFilters
new CollatorStringMatcher(nmFilter.toString().trim(), StringMatcherMode.CHECK_CONTAINS) : null; ) {
return new AmenityNameFilter() { return new AmenityNameFilter() {
@Override @Override
public boolean accept(Amenity a) { public boolean accept(Amenity amenity) {
if (sm != null) { if (shouldBeAllTime) {
List<String> names = OsmAndFormatter.getPoiStringsWithoutType(a, if (!"24/7".equalsIgnoreCase(amenity.getOpeningHours()) &&
app.getSettings().MAP_PREFERRED_LOCALE.get(), app.getSettings().MAP_TRANSLITERATE_NAMES.get()); !"Mo-Su 00:00-24:00".equalsIgnoreCase(amenity.getOpeningHours())) {
boolean match = false;
for (String name : names) {
if (sm.matches(name)) {
match = true;
break;
}
}
if (!match) {
return false; return false;
} }
} }
if (poiAdditionals != null) {
Map<PoiType, PoiType> textPoiAdditionalsMap = new HashMap<>();
Map<String, List<PoiType>> poiAdditionalCategoriesMap = new HashMap<>();
for (PoiType pt : poiAdditionals) {
String category = pt.getPoiAdditionalCategory();
List<PoiType> types = poiAdditionalCategoriesMap.get(category);
if (types == null) {
types = new ArrayList<>();
poiAdditionalCategoriesMap.put(category, types);
}
types.add(pt);
String osmTag = pt.getOsmTag(); if (shouldBeOpened && !isOpened(amenity)) {
if (osmTag.length() < pt.getKeyName().length()) {
PoiType textPoiType = poiTypes.getTextPoiAdditionalByKey(osmTag);
if (textPoiType != null) {
textPoiAdditionalsMap.put(pt, textPoiType);
}
}
}
for (List<PoiType> types : poiAdditionalCategoriesMap.values()) {
boolean acceptedAnyInCategory = false;
for (PoiType p : types) {
String inf = a.getAdditionalInfo(p.getKeyName());
if (inf != null) {
acceptedAnyInCategory = true;
break;
} else {
PoiType textPoiType = textPoiAdditionalsMap.get(p);
if (textPoiType != null) {
inf = a.getAdditionalInfo(textPoiType.getKeyName());
if (!Algorithms.isEmpty(inf)) {
String[] items = inf.split(";");
String val = p.getOsmValue().trim().toLowerCase();
for (String item : items) {
if (item.trim().toLowerCase().equals(val)) {
acceptedAnyInCategory = true;
break;
}
}
if (acceptedAnyInCategory) {
break;
}
}
}
}
}
if (!acceptedAnyInCategory) {
return false; return false;
} }
}
} String nameFilter = extractNameFilter(amenity, unknownFilters);
if (allTime) { if (!matchesAnyAmenityName(amenity, nameFilter)) {
if (!"24/7".equalsIgnoreCase(a.getOpeningHours()) && !"Mo-Su 00:00-24:00".equalsIgnoreCase(a.getOpeningHours())) {
return false; return false;
} }
}
if (open) { if (!acceptedAnyFilterOfEachCategory(amenity, selectedFilters)) {
OpeningHours rs = OpeningHoursParser.parseOpenedHours(a.getOpeningHours());
if (rs != null) {
Calendar inst = Calendar.getInstance();
inst.setTimeInMillis(System.currentTimeMillis());
boolean work = rs.isOpenedForTime(inst);
if (!work) {
return false; return false;
} }
} else {
return false;
}
}
return true; return true;
} }
}; };
} }
private boolean isOpened(Amenity amenity) {
OpeningHours openedHours = OpeningHoursParser.parseOpenedHours(amenity.getOpeningHours());
if (openedHours == null) {
return false;
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
return openedHours.isOpenedForTime(calendar);
}
private String extractNameFilter(Amenity amenity, List<String> unknownFilters) {
if (unknownFilters == null) {
return "";
}
StringBuilder nameFilter = new StringBuilder();
for (String filter : unknownFilters) {
String formattedFilter = filter.replace(':', '_').toLowerCase();
if (amenity.getAdditionalInfo(formattedFilter) == null) {
nameFilter.append(filter).append(" ");
}
}
return nameFilter.toString();
}
private boolean matchesAnyAmenityName(Amenity amenity, String nameFilter) {
if (nameFilter.length() == 0) {
return true;
}
final CollatorStringMatcher sm =
new CollatorStringMatcher(nameFilter.trim(), StringMatcherMode.CHECK_CONTAINS);
List<String> names = OsmAndFormatter.getPoiStringsWithoutType(
amenity, app.getSettings().MAP_PREFERRED_LOCALE.get(),
app.getSettings().MAP_TRANSLITERATE_NAMES.get());
for (String name : names) {
if (sm.matches(name)) {
return true;
}
}
return false;
}
private boolean acceptedAnyFilterOfEachCategory(Amenity amenity, List<PoiType> selectedFilters) {
if (selectedFilters == null) {
return true;
}
Map<String, List<PoiType>> filterCategories = new HashMap<>();
Map<PoiType, PoiType> textFilters = new HashMap<>();
fillFilterCategories(selectedFilters, filterCategories, textFilters);
for (List<PoiType> category : filterCategories.values()) {
if (!acceptedAnyFilterOfCategory(amenity, category, textFilters)) {
return false;
}
}
return true;
}
private void fillFilterCategories(
List<PoiType> selectedFilters,
Map<String, List<PoiType>> filterCategories, Map<PoiType, PoiType> textFilters
) {
for (PoiType filter : selectedFilters) {
String category = filter.getPoiAdditionalCategory();
List<PoiType> filtersOfCategory = filterCategories.get(category);
if (filtersOfCategory == null) {
filtersOfCategory = new ArrayList<>();
filterCategories.put(category, filtersOfCategory);
}
filtersOfCategory.add(filter);
String osmTag = filter.getOsmTag();
if (osmTag.length() < filter.getKeyName().length()) {
PoiType textFilter = poiTypes.getTextPoiAdditionalByKey(osmTag);
if (textFilter != null) {
textFilters.put(filter, textFilter);
}
}
}
}
private boolean acceptedAnyFilterOfCategory(
Amenity amenity, List<PoiType> category, Map<PoiType, PoiType> textFilters) {
for (PoiType filter : category) {
if (acceptedFilter(amenity, filter, textFilters)) {
return true;
}
}
return false;
}
private boolean acceptedFilter(
Amenity amenity, PoiType filter, Map<PoiType, PoiType> textFilterCategories
) {
String filterValue = amenity.getAdditionalInfo(filter.getKeyName());
if (filterValue != null) {
return true;
}
PoiType textPoiType = textFilterCategories.get(filter);
if (textPoiType == null) {
return false;
}
filterValue = amenity.getAdditionalInfo(textPoiType.getKeyName());
if (Algorithms.isEmpty(filterValue)) {
return false;
}
String[] items = filterValue.split(";");
String val = filter.getOsmValue().trim().toLowerCase();
for (String item : items) {
if (item.trim().toLowerCase().equals(val)) {
return true;
}
}
return false;
}
public String getNameToken24H() { public String getNameToken24H() {
return app.getString(R.string.shared_string_is_open_24_7).replace(' ', '_').toLowerCase(); return app.getString(R.string.shared_string_is_open_24_7).replace(' ', '_').toLowerCase();
} }

View file

@ -22,6 +22,7 @@ import java.util.Map;
public class ProfileDataUtils { public class ProfileDataUtils {
public static final String OSMAND_NAVIGATION = "osmand_navigation"; public static final String OSMAND_NAVIGATION = "osmand_navigation";
public static final String ONLINE_NAVIGATION = "online_navigation";
public static List<ProfileDataObject> getDataObjects(OsmandApplication app, public static List<ProfileDataObject> getDataObjects(OsmandApplication app,
List<ApplicationMode> appModes) { List<ApplicationMode> appModes) {
@ -48,9 +49,9 @@ public class ProfileDataUtils {
return description; return description;
} }
public static List<RoutingProfileDataObject> getSortedRoutingProfiles(OsmandApplication app) { public static List<ProfileDataObject> getSortedRoutingProfiles(OsmandApplication app) {
List<RoutingProfileDataObject> result = new ArrayList<>(); List<ProfileDataObject> result = new ArrayList<>();
Map<String, List<RoutingProfileDataObject>> routingProfilesByFileNames = getRoutingProfilesByFileNames(app); Map<String, List<ProfileDataObject>> routingProfilesByFileNames = getRoutingProfilesByFileNames(app);
List<String> fileNames = new ArrayList<>(routingProfilesByFileNames.keySet()); List<String> fileNames = new ArrayList<>(routingProfilesByFileNames.keySet());
Collections.sort(fileNames, new Comparator<String>() { Collections.sort(fileNames, new Comparator<String>() {
@Override @Override
@ -59,7 +60,7 @@ public class ProfileDataUtils {
} }
}); });
for (String fileName : fileNames) { for (String fileName : fileNames) {
List<RoutingProfileDataObject> routingProfilesFromFile = routingProfilesByFileNames.get(fileName); List<ProfileDataObject> routingProfilesFromFile = routingProfilesByFileNames.get(fileName);
if (routingProfilesFromFile != null) { if (routingProfilesFromFile != null) {
Collections.sort(routingProfilesFromFile); Collections.sort(routingProfilesFromFile);
result.addAll(routingProfilesFromFile); result.addAll(routingProfilesFromFile);
@ -77,14 +78,20 @@ public class ProfileDataUtils {
return objects; return objects;
} }
public static Map<String, List<RoutingProfileDataObject>> getRoutingProfilesByFileNames(OsmandApplication app) { public static Map<String, List<ProfileDataObject>> getRoutingProfilesByFileNames(OsmandApplication app) {
Map<String, List<RoutingProfileDataObject>> result = new HashMap<>(); Map<String, List<ProfileDataObject>> result = new HashMap<>();
for (final RoutingProfileDataObject profile : getRoutingProfiles(app).values()) { for (final ProfileDataObject profile : getRoutingProfiles(app).values()) {
String fileName = profile.getFileName() != null ? profile.getFileName() : OSMAND_NAVIGATION; String fileName = null;
if (profile instanceof RoutingProfileDataObject) {
fileName = ((RoutingProfileDataObject) profile).getFileName();
} else if (profile instanceof OnlineRoutingEngineDataObject) {
fileName = ONLINE_NAVIGATION;
}
fileName = fileName != null ? fileName : OSMAND_NAVIGATION;
if (result.containsKey(fileName)) { if (result.containsKey(fileName)) {
result.get(fileName).add(profile); result.get(fileName).add(profile);
} else { } else {
result.put(fileName, new ArrayList<RoutingProfileDataObject>() { result.put(fileName, new ArrayList<ProfileDataObject>() {
{ add(profile); } { add(profile); }
}); });
} }
@ -92,8 +99,8 @@ public class ProfileDataUtils {
return result; return result;
} }
public static Map<String, RoutingProfileDataObject> getRoutingProfiles(OsmandApplication context) { public static Map<String, ProfileDataObject> getRoutingProfiles(OsmandApplication context) {
Map<String, RoutingProfileDataObject> profilesObjects = new HashMap<>(); Map<String, ProfileDataObject> profilesObjects = new HashMap<>();
profilesObjects.put(RoutingProfilesResources.STRAIGHT_LINE_MODE.name(), new RoutingProfileDataObject( profilesObjects.put(RoutingProfilesResources.STRAIGHT_LINE_MODE.name(), new RoutingProfileDataObject(
RoutingProfilesResources.STRAIGHT_LINE_MODE.name(), RoutingProfilesResources.STRAIGHT_LINE_MODE.name(),
context.getString(RoutingProfilesResources.STRAIGHT_LINE_MODE.getStringRes()), context.getString(RoutingProfilesResources.STRAIGHT_LINE_MODE.getStringRes()),
@ -119,11 +126,14 @@ public class ProfileDataUtils {
for (RoutingConfiguration.Builder builder : context.getAllRoutingConfigs()) { for (RoutingConfiguration.Builder builder : context.getAllRoutingConfigs()) {
collectRoutingProfilesFromConfig(context, builder, profilesObjects, disabledRouterNames); collectRoutingProfilesFromConfig(context, builder, profilesObjects, disabledRouterNames);
} }
for (OnlineRoutingEngineDataObject onlineEngine : getOnlineRoutingProfiles(context)) {
profilesObjects.put(onlineEngine.getStringKey(), onlineEngine);
}
return profilesObjects; return profilesObjects;
} }
private static void collectRoutingProfilesFromConfig(OsmandApplication app, RoutingConfiguration.Builder builder, private static void collectRoutingProfilesFromConfig(OsmandApplication app, RoutingConfiguration.Builder builder,
Map<String, RoutingProfileDataObject> profilesObjects, List<String> disabledRouterNames) { Map<String, ProfileDataObject> profilesObjects, List<String> disabledRouterNames) {
for (Map.Entry<String, GeneralRouter> entry : builder.getAllRouters().entrySet()) { for (Map.Entry<String, GeneralRouter> entry : builder.getAllRouters().entrySet()) {
String routerKey = entry.getKey(); String routerKey = entry.getKey();
GeneralRouter router = entry.getValue(); GeneralRouter router = entry.getValue();

View file

@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
@ -58,7 +59,7 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
public final static String PROFILE_KEY_ARG = "profile_key_arg"; public final static String PROFILE_KEY_ARG = "profile_key_arg";
public final static String USE_LAST_PROFILE_ARG = "use_last_profile_arg"; public final static String USE_LAST_PROFILE_ARG = "use_last_profile_arg";
public final static String IS_PROFILE_IMPORTED_ARG = "is_profile_imported_arg"; public final static String PROFILES_LIST_UPDATED_ARG = "is_profiles_list_updated";
private DialogMode dialogMode; private DialogMode dialogMode;
private final List<ProfileDataObject> profiles = new ArrayList<>(); private final List<ProfileDataObject> profiles = new ArrayList<>();
@ -130,7 +131,7 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
} }
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(PROFILE_KEY_ARG, item.getName()); args.putString(PROFILE_KEY_ARG, item.getName());
args.putBoolean(IS_PROFILE_IMPORTED_ARG, true); args.putBoolean(PROFILES_LIST_UPDATED_ARG, true);
listener.onSelectedType(args); listener.onSelectedType(args);
dismiss(); dismiss();
break; break;
@ -234,9 +235,10 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
int activeColorResId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light; int activeColorResId = nightMode ? R.color.active_color_primary_dark : R.color.active_color_primary_light;
int iconDefaultColorResId = nightMode ? R.color.icon_color_default_dark : R.color.icon_color_default_light; int iconDefaultColorResId = nightMode ? R.color.icon_color_default_dark : R.color.icon_color_default_light;
final boolean onlineRoutingProfile = profile instanceof OnlineRoutingEngineDataObject;
View itemView = UiUtilities.getInflater(getContext(), nightMode).inflate( View itemView = UiUtilities.getInflater(getContext(), nightMode).inflate(
profile instanceof OnlineRoutingEngineDataObject ? onlineRoutingProfile ?
R.layout.bottom_sheet_item_with_descr_radio_and_icon_btn : R.layout.bottom_sheet_item_with_descr_radio_and_icon_btn :
R.layout.bottom_sheet_item_with_descr_and_radio_btn, null); R.layout.bottom_sheet_item_with_descr_and_radio_btn, null);
TextView tvTitle = itemView.findViewById(R.id.title); TextView tvTitle = itemView.findViewById(R.id.title);
@ -262,28 +264,52 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
UiUtilities.setupCompoundButton(compoundButton, nightMode, UiUtilities.CompoundButtonType.GLOBAL); UiUtilities.setupCompoundButton(compoundButton, nightMode, UiUtilities.CompoundButtonType.GLOBAL);
bottomDivider.setVisibility(showBottomDivider ? View.VISIBLE : View.INVISIBLE); bottomDivider.setVisibility(showBottomDivider ? View.VISIBLE : View.INVISIBLE);
items.add(new BaseBottomSheetItem.Builder() BaseBottomSheetItem.Builder builder =
.setCustomView(itemView) new BaseBottomSheetItem.Builder().setCustomView(itemView);
.setOnClickListener(new OnClickListener() {
OnClickListener listener = new OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(PROFILE_KEY_ARG, profile.getStringKey()); args.putString(PROFILE_KEY_ARG, profile.getStringKey());
args.putBoolean(PROFILES_LIST_UPDATED_ARG, onlineRoutingProfile);
Fragment target = getTargetFragment(); Fragment target = getTargetFragment();
if (target instanceof OnSelectProfileCallback) { if (target instanceof OnSelectProfileCallback) {
if (profile instanceof OnlineRoutingEngineDataObject) { ((OnSelectProfileCallback) target).onProfileSelected(args);
}
dismiss();
}
};
if (onlineRoutingProfile) {
View basePart = itemView.findViewById(R.id.basic_item_body);
View endBtn = itemView.findViewById(R.id.end_button);
ImageView ivEndBtnIcon = itemView.findViewById(R.id.end_button_icon);
Drawable drawable = getIcon(R.drawable.ic_action_settings,
nightMode ?
R.color.route_info_control_icon_color_dark :
R.color.route_info_control_icon_color_light);
if (Build.VERSION.SDK_INT >= 21) {
Drawable activeDrawable = getIcon(R.drawable.ic_action_settings, activeColorResId);
drawable = AndroidUtils.createPressedStateListDrawable(drawable, activeDrawable);
}
ivEndBtnIcon.setImageDrawable(drawable);
basePart.setOnClickListener(listener);
endBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (getActivity() != null) { if (getActivity() != null) {
OnlineRoutingEngineFragment.showInstance(getActivity(), getAppMode(), profile.getStringKey()); OnlineRoutingEngineFragment.showInstance(getActivity(), getAppMode(), profile.getStringKey());
} }
dismiss(); dismiss();
}
});
} else { } else {
((OnSelectProfileCallback) target).onProfileSelected(args); builder.setOnClickListener(listener);
} }
} items.add(builder.create());
dismiss();
}
})
.create());
} }
private void addCheckableItem(int titleId, private void addCheckableItem(int titleId,
@ -363,7 +389,6 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet {
case NAVIGATION_PROFILE: case NAVIGATION_PROFILE:
profiles.addAll(ProfileDataUtils.getSortedRoutingProfiles(app)); profiles.addAll(ProfileDataUtils.getSortedRoutingProfiles(app));
profiles.addAll(ProfileDataUtils.getOnlineRoutingProfiles(app));
break; break;
case DEFAULT_PROFILE: case DEFAULT_PROFILE:

View file

@ -22,6 +22,8 @@ import net.osmand.data.LocationPoint;
import net.osmand.data.WptLocationPoint; import net.osmand.data.WptLocationPoint;
import net.osmand.osm.io.NetworkUtils; import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.onlinerouting.OnlineRoutingEngine;
import net.osmand.plus.onlinerouting.OnlineRoutingHelper;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.R; import net.osmand.plus.R;
@ -91,7 +93,8 @@ public class RouteProvider {
OSMAND("OsmAnd (offline)"), OSMAND("OsmAnd (offline)"),
BROUTER("BRouter (offline)"), BROUTER("BRouter (offline)"),
STRAIGHT("Straight line"), STRAIGHT("Straight line"),
DIRECT_TO("Direct To"); DIRECT_TO("Direct To"),
ONLINE("Online engine");
private final String name; private final String name;
@ -363,10 +366,8 @@ public class RouteProvider {
res = findVectorMapsRoute(params, calcGPXRoute); res = findVectorMapsRoute(params, calcGPXRoute);
} else if (params.mode.getRouteService() == RouteService.BROUTER) { } else if (params.mode.getRouteService() == RouteService.BROUTER) {
res = findBROUTERRoute(params); res = findBROUTERRoute(params);
// } else if (params.type == RouteService.ORS) { } else if (params.mode.getRouteService() == RouteService.ONLINE) {
// res = findORSRoute(params); res = findOnlineRoute(params);
// } else if (params.type == RouteService.OSRM) {
// res = findOSRMRoute(params);
} else if (params.mode.getRouteService() == RouteService.STRAIGHT || } else if (params.mode.getRouteService() == RouteService.STRAIGHT ||
params.mode.getRouteService() == RouteService.DIRECT_TO) { params.mode.getRouteService() == RouteService.DIRECT_TO) {
res = findStraightRoute(params); res = findStraightRoute(params);
@ -383,6 +384,8 @@ public class RouteProvider {
log.error("Failed to find route ", e); //$NON-NLS-1$ log.error("Failed to find route ", e); //$NON-NLS-1$
} catch (SAXException e) { } catch (SAXException e) {
log.error("Failed to find route ", e); //$NON-NLS-1$ log.error("Failed to find route ", e); //$NON-NLS-1$
} catch (JSONException e) {
log.error("Failed to find route ", e); //$NON-NLS-1$
} }
} }
return new RouteCalculationResult(null); return new RouteCalculationResult(null);
@ -1204,56 +1207,12 @@ public class RouteProvider {
return exporter.exportRoute(); return exporter.exportRoute();
} }
private void appendOSRMLoc(StringBuilder uri, LatLon il) { private RouteCalculationResult findOnlineRoute(RouteCalculationParams params) throws IOException, JSONException {
uri.append(";").append(il.getLongitude()); OnlineRoutingHelper helper = params.ctx.getOnlineRoutingHelper();
uri.append(",").append(il.getLatitude()); String stringKey = params.mode.getRoutingProfile();
} List<LatLon> route = helper.calculateRouteOnline(helper.getEngineByKey(stringKey), getFullPathFromParams(params));
if (!route.isEmpty()) {
protected RouteCalculationResult findOSRMRoute(RouteCalculationParams params) List<Location> res = new ArrayList<>();
throws MalformedURLException, IOException, JSONException {
// http://router.project-osrm.org/route/v1/driving/4.83,52.28;4.95,52.28
List<Location> res = new ArrayList<Location>();
StringBuilder uri = new StringBuilder();
// possibly hide that API key because it is privacy of osmand
// A6421860EBB04234AB5EF2D049F2CD8F key is compromised
String scheme = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
scheme = "https";
} else {
scheme = "http";
}
uri.append(scheme + "://router.project-osrm.org/route/v1/driving/"); //$NON-NLS-1$
uri.append(String.valueOf(params.start.getLongitude()));
uri.append(",").append(String.valueOf(params.start.getLatitude()));
if(params.intermediates != null && params.intermediates.size() > 0) {
for(LatLon il : params.intermediates) {
appendOSRMLoc(uri, il);
}
}
appendOSRMLoc(uri, params.end);
// to get more waypoints, add overview=full option
// uri.append("?overview=full")
log.info("URL route " + uri);
URLConnection connection = NetworkUtils.getHttpURLConnection(uri.toString());
connection.setRequestProperty("User-Agent", Version.getFullVersion(params.ctx));
StringBuilder content = new StringBuilder();
BufferedReader rs = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String s;
while((s = rs.readLine()) != null) {
content.append(s);
}
JSONObject obj = new JSONObject(content.toString());
try {
rs.close();
} catch(IOException e){
}
List<LatLon> route = GeoPolylineParserUtil.parse(obj.getJSONArray("routes").getJSONObject(0).getString("geometry"),
GeoPolylineParserUtil.PRECISION_5);
if (route.isEmpty()) {
return new RouteCalculationResult("Route is empty");
}
for (LatLon pt : route) { for (LatLon pt : route) {
WptPt wpt = new WptPt(); WptPt wpt = new WptPt();
wpt.lat = pt.getLatitude(); wpt.lat = pt.getLatitude();
@ -1262,6 +1221,19 @@ public class RouteProvider {
} }
params.intermediates = null; params.intermediates = null;
return new RouteCalculationResult(res, null, params, null, true); return new RouteCalculationResult(res, null, params, null, true);
} else {
return new RouteCalculationResult("Route is empty");
}
}
private static List<LatLon> getFullPathFromParams(RouteCalculationParams params) {
List<LatLon> points = new ArrayList<>();
points.add(new LatLon(params.start.getLatitude(), params.start.getLongitude()));
if (Algorithms.isEmpty(params.intermediates)) {
points.addAll(params.intermediates);
}
points.add(params.end);
return points;
} }
protected RouteCalculationResult findBROUTERRoute(RouteCalculationParams params) throws MalformedURLException, protected RouteCalculationResult findBROUTERRoute(RouteCalculationParams params) throws MalformedURLException,

View file

@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.onlinerouting.OnlineRoutingEngine; import net.osmand.plus.onlinerouting.OnlineRoutingEngine;
import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameterType; import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter;
import net.osmand.plus.onlinerouting.OnlineRoutingHelper; import net.osmand.plus.onlinerouting.OnlineRoutingHelper;
import org.json.JSONException; import org.json.JSONException;
@ -93,8 +93,8 @@ public class OnlineRoutingSettingsItem extends CollectionSettingsItem<OnlineRout
int number = 0; int number = 0;
while (true) { while (true) {
number++; number++;
OnlineRoutingEngine renamedItem = OnlineRoutingEngine.createNewEngine(item.getServerType(), item.getVehicleKey(), item.getParams()); OnlineRoutingEngine renamedItem = OnlineRoutingEngine.createNewEngine(item.getType(), item.getVehicleKey(), item.getParams());
renamedItem.putParameter(EngineParameterType.CUSTOM_NAME, renamedItem.getName(app) + "_" + number); renamedItem.putParameter(EngineParameter.CUSTOM_NAME, renamedItem.getName(app) + "_" + number);
if (!isDuplicate(renamedItem)) { if (!isDuplicate(renamedItem)) {
return renamedItem; return renamedItem;
} }

View file

@ -2,7 +2,6 @@ package net.osmand.plus.settings.bottomsheets;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
@ -255,7 +254,7 @@ public class ElevationDateBottomSheet extends MenuBottomSheetDialogFragment {
fragment.appMode = appMode; fragment.appMode = appMode;
fragment.setUsedOnMap(usedOnMap); fragment.setUsedOnMap(usedOnMap);
fragment.setTargetFragment(target, 0); fragment.setTargetFragment(target, 0);
fragment.show(fm, ScreenTimeoutBottomSheet.TAG); fragment.show(fm, ElevationDateBottomSheet.TAG);
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
LOG.error("showInstance", e); LOG.error("showInstance", e);

View file

@ -35,7 +35,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import static net.osmand.plus.importfiles.ImportHelper.ImportType.SETTINGS; import static net.osmand.plus.importfiles.ImportHelper.ImportType.SETTINGS;
import static net.osmand.plus.profiles.SelectProfileBottomSheet.IS_PROFILE_IMPORTED_ARG; import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILES_LIST_UPDATED_ARG;
import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILE_KEY_ARG; import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILE_KEY_ARG;
public class MainSettingsFragment extends BaseSettingsFragment implements OnSelectProfileCallback{ public class MainSettingsFragment extends BaseSettingsFragment implements OnSelectProfileCallback{
@ -229,7 +229,7 @@ public class MainSettingsFragment extends BaseSettingsFragment implements OnSele
FragmentManager fragmentManager = activity.getSupportFragmentManager(); FragmentManager fragmentManager = activity.getSupportFragmentManager();
if (fragmentManager != null) { if (fragmentManager != null) {
String profileKey = args.getString(PROFILE_KEY_ARG); String profileKey = args.getString(PROFILE_KEY_ARG);
boolean imported = args.getBoolean(IS_PROFILE_IMPORTED_ARG); boolean imported = args.getBoolean(PROFILES_LIST_UPDATED_ARG);
ProfileAppearanceFragment.showInstance(activity, SettingsScreenType.PROFILE_APPEARANCE, ProfileAppearanceFragment.showInstance(activity, SettingsScreenType.PROFILE_APPEARANCE,
profileKey, imported); profileKey, imported);
} }

View file

@ -10,13 +10,14 @@ import androidx.preference.Preference;
import androidx.preference.SwitchPreferenceCompat; import androidx.preference.SwitchPreferenceCompat;
import net.osmand.plus.measurementtool.MeasurementToolFragment; import net.osmand.plus.measurementtool.MeasurementToolFragment;
import net.osmand.plus.profiles.ProfileDataObject;
import net.osmand.plus.profiles.ProfileDataUtils; import net.osmand.plus.profiles.ProfileDataUtils;
import net.osmand.plus.routepreparationmenu.RouteOptionsBottomSheet; import net.osmand.plus.routepreparationmenu.RouteOptionsBottomSheet;
import net.osmand.plus.routepreparationmenu.RouteOptionsBottomSheet.DialogMode; import net.osmand.plus.routepreparationmenu.RouteOptionsBottomSheet.DialogMode;
import net.osmand.plus.routing.RouteProvider.RouteService;
import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.profiles.RoutingProfileDataObject;
import net.osmand.plus.profiles.RoutingProfileDataObject.RoutingProfilesResources; import net.osmand.plus.profiles.RoutingProfileDataObject.RoutingProfilesResources;
import net.osmand.plus.profiles.SelectProfileBottomSheet; import net.osmand.plus.profiles.SelectProfileBottomSheet;
import net.osmand.plus.profiles.SelectProfileBottomSheet.OnSelectProfileCallback; import net.osmand.plus.profiles.SelectProfileBottomSheet.OnSelectProfileCallback;
@ -26,7 +27,8 @@ import net.osmand.util.Algorithms;
import java.util.Map; import java.util.Map;
import static net.osmand.plus.profiles.SelectProfileBottomSheet.IS_PROFILE_IMPORTED_ARG; import static net.osmand.plus.onlinerouting.OnlineRoutingEngine.ONLINE_ROUTING_ENGINE_PREFIX;
import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILES_LIST_UPDATED_ARG;
import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILE_KEY_ARG; import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILE_KEY_ARG;
import static net.osmand.plus.routepreparationmenu.RouteOptionsBottomSheet.DIALOG_MODE_KEY; import static net.osmand.plus.routepreparationmenu.RouteOptionsBottomSheet.DIALOG_MODE_KEY;
@ -35,13 +37,13 @@ public class NavigationFragment extends BaseSettingsFragment implements OnSelect
public static final String TAG = NavigationFragment.class.getSimpleName(); public static final String TAG = NavigationFragment.class.getSimpleName();
public static final String NAVIGATION_TYPE = "navigation_type"; public static final String NAVIGATION_TYPE = "navigation_type";
private Map<String, RoutingProfileDataObject> routingProfileDataObjects; private Map<String, ProfileDataObject> routingProfileDataObjects;
private Preference navigationType; private Preference navigationType;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
routingProfileDataObjects = ProfileDataUtils.getRoutingProfiles(app); updateRoutingProfilesDataObjects();
setupOnBackPressedCallback(); setupOnBackPressedCallback();
} }
@ -90,7 +92,7 @@ public class NavigationFragment extends BaseSettingsFragment implements OnSelect
private void setupNavigationTypePref() { private void setupNavigationTypePref() {
String routingProfileKey = getSelectedAppMode().getRoutingProfile(); String routingProfileKey = getSelectedAppMode().getRoutingProfile();
if (!Algorithms.isEmpty(routingProfileKey)) { if (!Algorithms.isEmpty(routingProfileKey)) {
RoutingProfileDataObject routingProfileDataObject = routingProfileDataObjects.get(routingProfileKey); ProfileDataObject routingProfileDataObject = routingProfileDataObjects.get(routingProfileKey);
if (routingProfileDataObject != null) { if (routingProfileDataObject != null) {
navigationType.setSummary(routingProfileDataObject.getName()); navigationType.setSummary(routingProfileDataObject.getName());
navigationType.setIcon(getActiveIcon(routingProfileDataObject.getIconRes())); navigationType.setIcon(getActiveIcon(routingProfileDataObject.getIconRes()));
@ -132,12 +134,16 @@ public class NavigationFragment extends BaseSettingsFragment implements OnSelect
return false; return false;
} }
private void updateRoutingProfilesDataObjects() {
routingProfileDataObjects = ProfileDataUtils.getRoutingProfiles(app);
}
void updateRoutingProfile(String profileKey) { void updateRoutingProfile(String profileKey) {
RoutingProfileDataObject selectedRoutingProfileDataObject = routingProfileDataObjects.get(profileKey); ProfileDataObject selectedRoutingProfileDataObject = routingProfileDataObjects.get(profileKey);
if (profileKey == null || selectedRoutingProfileDataObject == null) { if (profileKey == null || selectedRoutingProfileDataObject == null) {
return; return;
} }
for (Map.Entry<String, RoutingProfileDataObject> rp : routingProfileDataObjects.entrySet()) { for (Map.Entry<String, ProfileDataObject> rp : routingProfileDataObjects.entrySet()) {
boolean selected = profileKey.equals(rp.getKey()); boolean selected = profileKey.equals(rp.getKey());
rp.getValue().setSelected(selected); rp.getValue().setSelected(selected);
} }
@ -152,6 +158,8 @@ public class NavigationFragment extends BaseSettingsFragment implements OnSelect
routeService = RouteProvider.RouteService.DIRECT_TO; routeService = RouteProvider.RouteService.DIRECT_TO;
} else if (profileKey.equals(RoutingProfilesResources.BROUTER_MODE.name())) { } else if (profileKey.equals(RoutingProfilesResources.BROUTER_MODE.name())) {
routeService = RouteProvider.RouteService.BROUTER; routeService = RouteProvider.RouteService.BROUTER;
} else if (profileKey.startsWith(ONLINE_ROUTING_ENGINE_PREFIX)) {
routeService = RouteService.ONLINE;
} else { } else {
routeService = RouteProvider.RouteService.OSMAND; routeService = RouteProvider.RouteService.OSMAND;
} }
@ -174,8 +182,8 @@ public class NavigationFragment extends BaseSettingsFragment implements OnSelect
@Override @Override
public void onProfileSelected(Bundle args) { public void onProfileSelected(Bundle args) {
if (args.getBoolean(IS_PROFILE_IMPORTED_ARG)) { if (args.getBoolean(PROFILES_LIST_UPDATED_ARG)) {
routingProfileDataObjects = ProfileDataUtils.getRoutingProfiles(app); updateRoutingProfilesDataObjects();
} }
updateRoutingProfile(args.getString(PROFILE_KEY_ARG)); updateRoutingProfile(args.getString(PROFILE_KEY_ARG));
} }

View file

@ -66,7 +66,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.DRAWER_SETTINGS_ID; import static net.osmand.aidlapi.OsmAndCustomizationConstants.DRAWER_SETTINGS_ID;
import static net.osmand.plus.profiles.SelectProfileBottomSheet.IS_PROFILE_IMPORTED_ARG; import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILES_LIST_UPDATED_ARG;
import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILE_KEY_ARG; import static net.osmand.plus.profiles.SelectProfileBottomSheet.PROFILE_KEY_ARG;
public class ProfileAppearanceFragment extends BaseSettingsFragment implements OnSelectProfileCallback { public class ProfileAppearanceFragment extends BaseSettingsFragment implements OnSelectProfileCallback {
@ -934,7 +934,7 @@ public class ProfileAppearanceFragment extends BaseSettingsFragment implements O
@Override @Override
public void onProfileSelected(Bundle args) { public void onProfileSelected(Bundle args) {
String profileKey = args.getString(PROFILE_KEY_ARG); String profileKey = args.getString(PROFILE_KEY_ARG);
boolean imported = args.getBoolean(IS_PROFILE_IMPORTED_ARG); boolean imported = args.getBoolean(PROFILES_LIST_UPDATED_ARG);
updateParentProfile(profileKey, imported); updateParentProfile(profileKey, imported);
} }

View file

@ -492,7 +492,7 @@ public class TravelObfHelper implements TravelHelper {
@Override @Override
public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect, @NonNull final String lang) { public TravelArticle getArticleByTitle(@NonNull final String title, @NonNull QuadRect rect, @NonNull final String lang) {
TravelArticle article = null; TravelArticle article = null;
List<Amenity> amenities = null; final List<Amenity> amenities = new ArrayList<>();
int x = 0; int x = 0;
int y = 0; int y = 0;
int left = 0; int left = 0;
@ -510,8 +510,25 @@ public class TravelObfHelper implements TravelHelper {
for (BinaryMapIndexReader reader : getReaders()) { for (BinaryMapIndexReader reader : getReaders()) {
try { try {
SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest( SearchRequest<Amenity> req = BinaryMapIndexReader.buildSearchPoiRequest(
x, y, title, left, right, top, bottom, getSearchRouteArticleFilter(), null, null); x, y, title, left, right, top, bottom, getSearchRouteArticleFilter(),
amenities = reader.searchPoiByName(req); new ResultMatcher<Amenity>() {
boolean done = false;
@Override
public boolean publish(Amenity amenity) {
if (Algorithms.stringsEqual(title, Algorithms.emptyIfNull(amenity.getName(lang)))) {
amenities.add(amenity);
done = true;
}
return false;
}
@Override
public boolean isCancelled() {
return done;
}
}, null);
reader.searchPoiByName(req);
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage()); LOG.error(e.getMessage());
} }