Merge branch 'master' of ssh://github.com/osmandapp/Osmand into startup_time

This commit is contained in:
Alex Sytnyk 2018-10-16 10:46:43 +03:00
commit a38c012415
19 changed files with 958 additions and 790 deletions

View file

@ -647,8 +647,7 @@ public class OpeningHoursParser {
/**
* represents the list on which day it is open.
*/
private boolean[][] dayMonths = new boolean[12][31];
private boolean hasDayMonths = false;
private boolean[][] dayMonths = null;
/**
* lists of equal size representing the start and end times
@ -696,13 +695,15 @@ public class OpeningHoursParser {
/**
* @return the day months of the rule
*/
public boolean[][] getDayMonths() {
return dayMonths;
}
public boolean[] getDayMonths(int month) {
if (dayMonths == null) {
dayMonths = new boolean[12][31];
}
return dayMonths[month];
}
public boolean hasDayMonths() {
return dayMonths != null;
}
/**
* return an array representing the months of the rule
@ -988,7 +989,7 @@ public class OpeningHoursParser {
break;
}
}
boolean allDays = !hasDayMonths;
boolean allDays = !hasDayMonths();
if (!allDays) {
boolean dash = false;
boolean first = true;
@ -1406,16 +1407,16 @@ public class OpeningHoursParser {
int i = cal.get(Calendar.DAY_OF_WEEK);
int day = (i + 5) % 7;
int previous = (day + 6) % 7;
boolean thisDay = hasDays || hasDayMonths;
if (thisDay && hasDayMonths) {
boolean thisDay = hasDays || hasDayMonths();
if (thisDay && hasDayMonths()) {
thisDay = dayMonths[month][dmonth];
}
if (thisDay && hasDays) {
thisDay = days[day];
}
// potential error for Dec 31 12:00-01:00
boolean previousDay = hasDays || hasDayMonths;
if (previousDay && hasDayMonths && dmonth > 0) {
boolean previousDay = hasDays || hasDayMonths();
if (previousDay && hasDayMonths() && dmonth > 0) {
previousDay = dayMonths[month][dmonth - 1];
}
if (previousDay && hasDays) {
@ -1848,10 +1849,6 @@ public class OpeningHoursParser {
}
if (!presentTokens.contains(TokenType.TOKEN_MONTH)) {
Arrays.fill(basic.getMonths(), true);
} else {
if (presentTokens.contains(TokenType.TOKEN_DAY_MONTH)) {
basic.hasDayMonths = true;
}
}
if (!presentTokens.contains(TokenType.TOKEN_DAY_WEEK) && !presentTokens.contains(TokenType.TOKEN_HOLIDAY) &&
!presentTokens.contains(TokenType.TOKEN_DAY_MONTH)) {

View file

@ -82,7 +82,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
fun stopSharingLocation() {
settings.stopSharingLocationToChats()
shareLocationHelper.stopSharingLocation()
telegramHelper.stopSendingLiveLocationMessages()
telegramHelper.stopSendingLiveLocationMessages(settings.getChatsShareInfo())
}
fun isOsmAndInstalled() = AndroidUtils.isAppInstalled(this, settings.appToConnectPackage)

View file

@ -13,18 +13,25 @@ import android.os.*
import android.util.Log
import android.widget.Toast
import net.osmand.PlatformUtil
import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener
import net.osmand.telegram.helpers.TelegramHelper.TelegramIncomingMessagesListener
import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidUtils
import org.drinkless.td.libcore.telegram.TdApi
import java.util.*
class TelegramService : Service(), LocationListener, TelegramIncomingMessagesListener {
private const val UPDATE_LIVE_MESSAGES_INTERVAL_MS = 10000L // 10 sec
class TelegramService : Service(), LocationListener, TelegramIncomingMessagesListener,
TelegramOutgoingMessagesListener {
private fun app() = application as TelegramApplication
private val binder = LocationServiceBinder()
private var shouldCleanupResources: Boolean = false
private var updateShareInfoHandler: Handler? = null
private var mHandlerThread = HandlerThread("SharingServiceThread")
var handler: Handler? = null
private set
var usedBy = 0
@ -43,6 +50,12 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
class LocationServiceBinder : Binder()
override fun onCreate() {
super.onCreate()
mHandlerThread.start()
updateShareInfoHandler = Handler(mHandlerThread.looper)
}
override fun onBind(intent: Intent): IBinder? {
return binder
}
@ -77,9 +90,11 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
app.telegramService = this
app.telegramHelper.addIncomingMessagesListener(this)
app.telegramHelper.addOutgoingMessagesListener(this)
if (isUsedByMyLocation(usedBy)) {
initLocationUpdates()
startShareInfoUpdates()
}
if (isUsedByUsersLocations(usedBy)) {
app.telegramHelper.startLiveMessagesUpdates(app.settings.sendMyLocInterval)
@ -107,7 +122,9 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
val app = app()
app.telegramHelper.stopLiveMessagesUpdates()
app.telegramHelper.removeIncomingMessagesListener(this)
app.telegramHelper.removeOutgoingMessagesListener(this)
app.telegramService = null
mHandlerThread.quit()
usedBy = 0
@ -159,6 +176,15 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
}
}
private fun startShareInfoUpdates() {
updateShareInfoHandler?.postDelayed({
if (isUsedByMyLocation(usedBy)) {
app().shareLocationHelper.updateSendLiveMessages()
startShareInfoUpdates()
}
}, UPDATE_LIVE_MESSAGES_INTERVAL_MS)
}
@SuppressLint("MissingPermission")
private fun getFirstTimeRunDefaultLocation(): net.osmand.Location? {
val app = app()
@ -260,6 +286,16 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
app().showLocationHelper.startUpdateMessagesTask()
}
override fun onUpdateMessages(messages: List<TdApi.Message>) {
messages.forEach {
app().settings.updateShareInfo(it)
}
}
override fun onDeleteMessages(chatId: Long, messages: List<Long>) {
app().settings.onDeleteLiveMessages(chatId, messages)
}
companion object {
const val USED_BY_MY_LOCATION: Int = 1

View file

@ -3,12 +3,19 @@ package net.osmand.telegram
import android.content.Context
import android.support.annotation.DrawableRes
import android.support.annotation.StringRes
import net.osmand.data.LatLon
import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.OsmandFormatter.MetricsConstants
import net.osmand.telegram.utils.OsmandFormatter.SpeedConstants
import org.drinkless.td.libcore.telegram.TdApi
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
val ADDITIONAL_ACTIVE_TIME_VALUES_SEC = listOf(15 * 60L, 30 * 60L, 60 * 60L, 180 * 60L)
private val SEND_MY_LOC_VALUES_SEC =
listOf(1L, 2L, 3L, 5L, 10L, 15L, 30L, 60L, 90L, 2 * 60L, 3 * 60L, 5 * 60L)
@ -26,7 +33,6 @@ private val LOC_HISTORY_VALUES_SEC = listOf(
12 * 60 * 60L,
24 * 60 * 60L
)
private val MESSAGE_ADD_ACTIVE_TIME_VALUES_SEC = listOf(15 * 60L, 30 * 60L, 60 * 60L, 180 * 60L)
private const val SEND_MY_LOC_DEFAULT_INDEX = 6
private const val STALE_LOC_DEFAULT_INDEX = 4
@ -54,14 +60,11 @@ private const val TITLES_REPLACED_WITH_IDS = "changed_to_chat_id"
private const val LIVE_NOW_SORT_TYPE_KEY = "live_now_sort_type"
private const val SHARE_CHATS_INFO_KEY = "share_chats_info"
class TelegramSettings(private val app: TelegramApplication) {
private var chatLivePeriods = mutableMapOf<Long, Long>()
private var chatShareLocStartSec = mutableMapOf<Long, Long>()
private var chatShareAddActiveTime = mutableMapOf<Long, Long>()
private var shareLocationChats: Set<Long> = emptySet()
private var shareChatsInfo = mutableMapOf<Long, ShareChatInfo>()
private var hiddenOnMapChats: Set<Long> = emptySet()
var shareDevicesIds = mutableMapOf<String, String>()
@ -86,32 +89,20 @@ class TelegramSettings(private val app: TelegramApplication) {
read()
}
fun hasAnyChatToShareLocation() = shareLocationChats.isNotEmpty()
fun hasAnyChatToShareLocation() = shareChatsInfo.isNotEmpty()
fun isSharingLocationToChat(chatId: Long) = shareLocationChats.contains(chatId)
fun isSharingLocationToChat(chatId: Long) = shareChatsInfo.contains(chatId)
fun hasAnyChatToShowOnMap() = !hiddenOnMapChats.containsAll(getLiveNowChats())
fun isShowingChatOnMap(chatId: Long) = !hiddenOnMapChats.contains(chatId)
fun removeNonexistingChats(presentChatIds: List<Long>) {
val shareLocationChats = shareLocationChats.toMutableList()
shareLocationChats.intersect(presentChatIds)
this.shareLocationChats = shareLocationChats.toHashSet()
val hiddenChats = hiddenOnMapChats.toMutableList()
hiddenChats.intersect(presentChatIds)
hiddenOnMapChats = hiddenChats.toHashSet()
chatLivePeriods = chatLivePeriods.filter { (key, _) ->
presentChatIds.contains(key)
}.toMutableMap()
chatShareAddActiveTime = chatShareAddActiveTime.filter { (key, _) ->
presentChatIds.contains(key)
}.toMutableMap()
chatShareLocStartSec = chatShareLocStartSec.filter { (key, _) ->
shareChatsInfo = shareChatsInfo.filter { (key, _) ->
presentChatIds.contains(key)
}.toMutableMap()
}
@ -120,25 +111,31 @@ class TelegramSettings(private val app: TelegramApplication) {
chatId: Long,
share: Boolean,
livePeriod: Long = DEFAULT_VISIBLE_TIME_SECONDS,
addActiveTime: Long = MESSAGE_ADD_ACTIVE_TIME_VALUES_SEC[0]
addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
) {
val shareLocationChats = shareLocationChats.toMutableList()
if (share) {
val lp: Long = when {
livePeriod < TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC -> TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()
else -> livePeriod
}
chatLivePeriods[chatId] = lp
chatShareLocStartSec[chatId] = (System.currentTimeMillis() / 1000)
chatShareAddActiveTime[chatId] = addActiveTime
shareLocationChats.add(chatId)
} else {
shareLocationChats.remove(chatId)
chatLivePeriods.remove(chatId)
chatShareLocStartSec.remove(chatId)
chatShareAddActiveTime.remove(chatId)
var shareChatInfo = shareChatsInfo[chatId]
if (shareChatInfo == null) {
shareChatInfo = ShareChatInfo()
}
val currentTime = System.currentTimeMillis() / 1000
shareChatInfo.start = currentTime
if (shareChatInfo.livePeriod == -1L) {
shareChatInfo.livePeriod = lp
}
shareChatInfo.userSetLivePeriod = lp
shareChatInfo.userSetLivePeriodStart = currentTime
shareChatInfo.currentMessageLimit = currentTime +
Math.min(lp, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong())
shareChatInfo.additionalActiveTime = addActiveTime
shareChatsInfo[chatId] = shareChatInfo
} else {
shareChatsInfo.remove(chatId)
}
this.shareLocationChats = shareLocationChats.toHashSet()
}
fun updateShareDevicesIds(list: List<DeviceBot>) {
@ -148,54 +145,21 @@ class TelegramSettings(private val app: TelegramApplication) {
}
}
fun getChatLivePeriod(chatId: Long) = chatLivePeriods[chatId]
fun getChatLivePeriod(chatId: Long) = shareChatsInfo[chatId]?.livePeriod
fun getChatAddActiveTime(chatId: Long) = chatShareAddActiveTime[chatId] ?: MESSAGE_ADD_ACTIVE_TIME_VALUES_SEC[0]
fun getChatNextAddActiveTime(chatId: Long): Long {
return if (chatShareAddActiveTime.containsKey(chatId)) {
var index = MESSAGE_ADD_ACTIVE_TIME_VALUES_SEC.indexOf(chatShareAddActiveTime[chatId])
if (MESSAGE_ADD_ACTIVE_TIME_VALUES_SEC.lastIndex > index) {
MESSAGE_ADD_ACTIVE_TIME_VALUES_SEC[++index]
} else {
MESSAGE_ADD_ACTIVE_TIME_VALUES_SEC[index]
}
} else {
MESSAGE_ADD_ACTIVE_TIME_VALUES_SEC[0]
}
}
fun getChatLivePeriods(): Map<Long, Long> {
return chatLivePeriods.filter {
getChatLiveMessageExpireTime(it.key) > 0
}
}
fun getChatShareLocStartSec(chatId: Long) = chatShareLocStartSec[chatId]
fun getChatsShareInfo() = shareChatsInfo
fun getChatLiveMessageExpireTime(chatId: Long): Long {
val startTime = getChatShareLocStartSec(chatId)
val livePeriod = getChatLivePeriod(chatId)
return if (startTime != null && livePeriod != null) {
livePeriod - ((System.currentTimeMillis() / 1000) - startTime)
val shareInfo = shareChatsInfo[chatId]
return if (shareInfo != null) {
shareInfo.userSetLivePeriod - ((System.currentTimeMillis() / 1000) - shareInfo.start)
} else {
0
}
}
fun updateChatShareLocStartSec(chatId: Long, startTime: Long) {
chatShareLocStartSec[chatId] = startTime
}
fun updateChatAddActiveTime(chatId: Long, newTime: Long) {
chatShareAddActiveTime[chatId] = newTime
}
fun stopSharingLocationToChats() {
this.shareLocationChats = emptySet()
this.chatLivePeriods.clear()
this.chatShareLocStartSec.clear()
this.chatShareAddActiveTime.clear()
shareChatsInfo.clear()
}
fun showChatOnMap(chatId: Long, show: Boolean) {
@ -208,7 +172,7 @@ class TelegramSettings(private val app: TelegramApplication) {
hiddenOnMapChats = hiddenChats.toHashSet()
}
fun getShareLocationChats() = ArrayList(shareLocationChats)
fun getShareLocationChats() = shareChatsInfo.keys
fun getShowOnMapChats() = getLiveNowChats().minus(hiddenOnMapChats)
@ -225,17 +189,27 @@ class TelegramSettings(private val app: TelegramApplication) {
app.osmandAidlHelper.reconnectOsmand()
}
fun updateShareInfo(message: TdApi.Message) {
val shareChatInfo = shareChatsInfo[message.chatId]
val content = message.content
if (shareChatInfo != null && content is TdApi.MessageLocation) {
shareChatInfo.currentMessageId = message.id
shareChatInfo.lastSuccessfulLocation = LatLon(content.location.latitude, content.location.longitude)
shareChatInfo.lastSuccessfulSendTime = Math.max(message.editDate, message.date).toLong()
}
}
fun onDeleteLiveMessages(chatId: Long, messages: List<Long>) {
val currentMessageId = shareChatsInfo[chatId]?.currentMessageId
if (messages.contains(currentMessageId)) {
shareChatsInfo[chatId]?.currentMessageId = -1
}
}
fun save() {
val prefs = app.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE)
val edit = prefs.edit()
val shareLocationChatsSet = mutableSetOf<String>()
val shareLocationChats = ArrayList(shareLocationChats)
for (chatId in shareLocationChats) {
shareLocationChatsSet.add(chatId.toString())
}
edit.putStringSet(SHARE_LOCATION_CHATS_KEY, shareLocationChatsSet)
val hiddenChatsSet = mutableSetOf<String>()
val hiddenChats = ArrayList(hiddenOnMapChats)
for (chatId in hiddenChats) {
@ -256,19 +230,30 @@ class TelegramSettings(private val app: TelegramApplication) {
edit.putString(LIVE_NOW_SORT_TYPE_KEY, liveNowSortType.name)
try {
val jArray = JSONArray()
shareChatsInfo.forEach { (chatId, chatInfo) ->
val obj = JSONObject()
obj.put(ShareChatInfo.CHAT_ID_KEY, chatId)
obj.put(ShareChatInfo.START_KEY, chatInfo.start)
obj.put(ShareChatInfo.LIVE_PERIOD_KEY, chatInfo.livePeriod)
obj.put(ShareChatInfo.LIMIT_KEY, chatInfo.currentMessageLimit)
obj.put(ShareChatInfo.CURRENT_MESSAGE_ID_KEY, chatInfo.currentMessageId)
obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY, chatInfo.userSetLivePeriod)
obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY, chatInfo.userSetLivePeriodStart)
jArray.put(obj)
}
edit.putString(SHARE_CHATS_INFO_KEY, jArray.toString())
} catch (e: JSONException) {
e.printStackTrace()
}
edit.apply()
}
fun read() {
val prefs = app.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE)
val shareLocationChats = mutableSetOf<Long>()
val shareLocationChatsSet = prefs.getStringSet(SHARE_LOCATION_CHATS_KEY, mutableSetOf())
for (chatId in shareLocationChatsSet) {
shareLocationChats.add(chatId.toLong())
}
this.shareLocationChats = shareLocationChats
val hiddenChats = mutableSetOf<Long>()
val hiddenChatsSet = prefs.getStringSet(HIDDEN_ON_MAP_CHATS_KEY, mutableSetOf())
for (chatId in hiddenChatsSet) {
@ -283,6 +268,12 @@ class TelegramSettings(private val app: TelegramApplication) {
prefs.getString(SPEED_CONSTANTS_KEY, SpeedConstants.KILOMETERS_PER_HOUR.name)
)
try {
parseShareChatsInfo(JSONArray(prefs.getString(SHARE_CHATS_INFO_KEY, "")))
} catch (e: JSONException) {
e.printStackTrace()
}
val sendMyLocDef = SEND_MY_LOC_VALUES_SEC[SEND_MY_LOC_DEFAULT_INDEX]
sendMyLocInterval = prefs.getLong(SEND_MY_LOC_INTERVAL_KEY, sendMyLocDef)
val staleLocDef = STALE_LOC_VALUES_SEC[STALE_LOC_DEFAULT_INDEX]
@ -299,6 +290,22 @@ class TelegramSettings(private val app: TelegramApplication) {
)
}
private fun parseShareChatsInfo(json: JSONArray) {
for (i in 0 until json.length()) {
val obj = json.getJSONObject(i)
val shareInfo = ShareChatInfo().apply {
chatId = obj.optLong(ShareChatInfo.CHAT_ID_KEY)
start = obj.optLong(ShareChatInfo.START_KEY)
livePeriod = obj.optLong(ShareChatInfo.LIVE_PERIOD_KEY)
currentMessageLimit = obj.optLong(ShareChatInfo.LIMIT_KEY)
currentMessageId = obj.optLong(ShareChatInfo.CURRENT_MESSAGE_ID_KEY)
userSetLivePeriod = obj.optLong(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY)
userSetLivePeriodStart = obj.optLong(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY)
}
shareChatsInfo[shareInfo.chatId] = shareInfo
}
}
private fun getLiveNowChats() = app.telegramHelper.getMessagesByChatIds(locHistoryTime).keys
private fun updatePrefs() {
@ -454,4 +461,39 @@ class TelegramSettings(private val app: TelegramApplication) {
var externalId: String = ""
var data: String = ""
}
class ShareChatInfo {
var chatId = -1L
var start = -1L
var livePeriod = -1L
var currentMessageLimit = -1L
var currentMessageId = -1L
var userSetLivePeriod = -1L
var userSetLivePeriodStart = -1L
var lastSuccessfulLocation: LatLon? = null
var lastSuccessfulSendTime = -1L
var shouldDeletePreviousMessage = false
var additionalActiveTime = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
fun getNextAdditionalActiveTime(): Long {
var index = ADDITIONAL_ACTIVE_TIME_VALUES_SEC.indexOf(additionalActiveTime)
return if (ADDITIONAL_ACTIVE_TIME_VALUES_SEC.lastIndex > index) {
ADDITIONAL_ACTIVE_TIME_VALUES_SEC[++index]
} else {
ADDITIONAL_ACTIVE_TIME_VALUES_SEC[index]
}
}
companion object {
internal const val CHAT_ID_KEY = "chatId"
internal const val START_KEY = "start"
internal const val LIVE_PERIOD_KEY = "livePeriod"
internal const val LIMIT_KEY = "limit"
internal const val CURRENT_MESSAGE_ID_KEY = "currentMessageId"
internal const val USER_SET_LIVE_PERIOD_KEY = "userSetLivePeriod"
internal const val USER_SET_LIVE_PERIOD_START_KEY = "userSetLivePeriodStart"
}
}
}

View file

@ -1,12 +1,17 @@
package net.osmand.telegram.helpers
import net.osmand.Location
import net.osmand.PlatformUtil
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidNetworkUtils
private const val USER_SET_LIVE_PERIOD_DELAY_MS = 5000 // 5 sec
class ShareLocationHelper(private val app: TelegramApplication) {
private val log = PlatformUtil.getLog(ShareLocationHelper::class.java)
var sharingLocation: Boolean = false
private set
@ -38,37 +43,58 @@ class ShareLocationHelper(private val app: TelegramApplication) {
fun updateLocation(location: Location?) {
lastLocation = location
if (location != null && app.isInternetConnectionAvailable) {
val chatLivePeriods = app.settings.getChatLivePeriods()
val updatedLivePeriods = mutableMapOf<Long, Long>()
if (chatLivePeriods.isNotEmpty()) {
chatLivePeriods.forEach { (chatId, livePeriod) ->
if (livePeriod > TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC) {
val startTime = app.settings.getChatShareLocStartSec(chatId)
val currTime = (System.currentTimeMillis() / 1000)
if (startTime != null && startTime + TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC < currTime) {
app.settings.shareLocationToChat(chatId, true, livePeriod - TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC)
} else if (startTime != null) {
updatedLivePeriods[chatId] = TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()
}
} else {
updatedLivePeriods[chatId] = livePeriod
}
}
if (location != null) {
val chatsShareInfo = app.settings.getChatsShareInfo()
if (chatsShareInfo.isNotEmpty()) {
val user = app.telegramHelper.getCurrentUser()
val sharingMode = app.settings.currentSharingMode
if (user != null && sharingMode == user.id.toString()) {
app.telegramHelper.sendLiveLocationMessage(updatedLivePeriods, location.latitude, location.longitude)
app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, location.latitude, location.longitude)
} else if (sharingMode.isNotEmpty()) {
val url = "https://live.osmand.net/device/$sharingMode/send?lat=${location.latitude}&lon=${location.longitude}"
AndroidNetworkUtils.sendRequestAsync(url, null)
}
}
lastLocationMessageSentTime = System.currentTimeMillis()
}
}
refreshNotification()
}
fun updateSendLiveMessages() {
log.info("updateSendLiveMessages")
app.settings.getChatsShareInfo().forEach { chatId, shareInfo ->
val currentTime = System.currentTimeMillis() / 1000
when {
app.settings.getChatLiveMessageExpireTime(chatId) <= 0 ->
app.settings.shareLocationToChat(chatId, false)
currentTime > shareInfo.currentMessageLimit -> {
shareInfo.apply {
val newLivePeriod =
if (livePeriod > TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC) {
livePeriod - TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC
} else {
livePeriod
}
livePeriod = newLivePeriod
shouldDeletePreviousMessage = true
currentMessageLimit = currentTime + Math.min(
newLivePeriod, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong())
}
}
shareInfo.userSetLivePeriod != shareInfo.livePeriod
&& (shareInfo.userSetLivePeriodStart + USER_SET_LIVE_PERIOD_DELAY_MS) > currentTime -> {
shareInfo.apply {
shouldDeletePreviousMessage = true
livePeriod = shareInfo.userSetLivePeriod
currentMessageLimit = currentTime + Math.min(
livePeriod, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()
)
}
}
}
}
}
fun startSharingLocation() {
if (!sharingLocation) {
sharingLocation = true

View file

@ -2,6 +2,7 @@ package net.osmand.telegram.helpers
import android.text.TextUtils
import net.osmand.PlatformUtil
import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.*
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_DIR
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_EXT
@ -70,9 +71,6 @@ class TelegramHelper private constructor() {
private val chats = ConcurrentHashMap<Long, TdApi.Chat>()
private val chatList = TreeSet<OrderedChat>()
private val chatLiveMessages = ConcurrentHashMap<Long, TdApi.Message>()
private val pausedLiveChatIds = mutableListOf<Long>()
private val downloadChatFilesMap = ConcurrentHashMap<String, TdApi.Chat>()
private val downloadUserFilesMap = ConcurrentHashMap<String, TdApi.User>()
@ -105,6 +103,7 @@ class TelegramHelper private constructor() {
var listener: TelegramListener? = null
private val incomingMessagesListeners = HashSet<TelegramIncomingMessagesListener>()
private val outgoingMessagesListeners = HashSet<TelegramOutgoingMessagesListener>()
private val fullInfoUpdatesListeners = HashSet<FullInfoUpdatesListener>()
fun addIncomingMessagesListener(listener: TelegramIncomingMessagesListener) {
@ -115,6 +114,14 @@ class TelegramHelper private constructor() {
incomingMessagesListeners.remove(listener)
}
fun addOutgoingMessagesListener(listener: TelegramOutgoingMessagesListener) {
outgoingMessagesListeners.add(listener)
}
fun removeOutgoingMessagesListener(listener: TelegramOutgoingMessagesListener) {
outgoingMessagesListeners.remove(listener)
}
fun addFullInfoUpdatesListener(listener: FullInfoUpdatesListener) {
fullInfoUpdatesListeners.add(listener)
}
@ -147,8 +154,6 @@ class TelegramHelper private constructor() {
fun getMessages() = usersLocationMessages.values.toList()
fun getChatLiveMessages() = chatLiveMessages
fun getMessagesByChatIds(messageExpTime: Long): Map<Long, List<TdApi.Message>> {
val res = mutableMapOf<Long, MutableList<TdApi.Message>>()
for (message in usersLocationMessages.values) {
@ -238,6 +243,11 @@ class TelegramHelper private constructor() {
fun updateLocationMessages()
}
interface TelegramOutgoingMessagesListener {
fun onUpdateMessages(messages: List<TdApi.Message>)
fun onDeleteMessages(chatId: Long, messages: List<Long>)
}
interface FullInfoUpdatesListener {
fun onBasicGroupFullInfoUpdated(groupId: Int, info: TdApi.BasicGroupFullInfo)
fun onSupergroupFullInfoUpdated(groupId: Int, info: TdApi.SupergroupFullInfo)
@ -570,58 +580,34 @@ class TelegramHelper private constructor() {
* @latitude Latitude of the location
* @longitude Longitude of the location
*/
fun sendLiveLocationMessage(chatLivePeriods: Map<Long, Long>, latitude: Double, longitude: Double): Boolean {
fun sendLiveLocationMessage(chatsShareInfo:Map<Long, TelegramSettings.ShareChatInfo>, latitude: Double, longitude: Double): Boolean {
if (!requestingActiveLiveLocationMessages && haveAuthorization) {
if (needRefreshActiveLiveLocationMessages) {
getActiveLiveLocationMessages {
sendLiveLocationImpl(chatLivePeriods, latitude, longitude)
sendLiveLocationImpl(chatsShareInfo, latitude, longitude)
}
needRefreshActiveLiveLocationMessages = false
} else {
sendLiveLocationImpl(chatLivePeriods, latitude, longitude)
sendLiveLocationImpl(chatsShareInfo, latitude, longitude)
}
return true
}
return false
}
fun stopSendingLiveLocationToChat(chatId: Long) {
val msgId = chatLiveMessages[chatId]?.id
if (msgId != null && msgId != 0L) {
fun stopSendingLiveLocationToChat(chatId: Long, msgId: Long) {
if (msgId != -1L) {
client?.send(
TdApi.EditMessageLiveLocation(chatId, msgId, null, null),
liveLocationMessageUpdatesHandler
)
}
chatLiveMessages.remove(chatId)
needRefreshActiveLiveLocationMessages = true
}
fun pauseSendingLiveLocationToChat(chatId: Long) {
synchronized(pausedLiveChatIds) {
pausedLiveChatIds.add(chatId)
}
}
fun resumeSendingLiveLocationToChat(chatId: Long) {
synchronized(pausedLiveChatIds) {
pausedLiveChatIds.remove(chatId)
}
}
fun deleteLiveLocationMessage(chatId: Long) {
val msgId = chatLiveMessages[chatId]?.id
if (msgId != null && msgId != 0L) {
val array = LongArray(1)
array[0] = msgId
client?.send(TdApi.DeleteMessages(chatId, array, true), UpdatesHandler())
}
needRefreshActiveLiveLocationMessages = true
}
fun stopSendingLiveLocationMessages() {
chatLiveMessages.forEach { (chatId, _ )->
stopSendingLiveLocationToChat(chatId)
fun stopSendingLiveLocationMessages(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>) {
chatsShareInfo.forEach { (chatId, chatInfo) ->
stopSendingLiveLocationToChat(chatId, chatInfo.currentMessageId)
}
}
@ -638,11 +624,9 @@ class TelegramHelper private constructor() {
}
TdApi.Messages.CONSTRUCTOR -> {
val messages = (obj as TdApi.Messages).messages
chatLiveMessages.clear()
if (messages.isNotEmpty()) {
for (msg in messages) {
val chatId = msg.chatId
chatLiveMessages[chatId] = msg
outgoingMessagesListeners.forEach {
it.onUpdateMessages(messages.asList())
}
}
onComplete?.invoke()
@ -653,28 +637,63 @@ class TelegramHelper private constructor() {
}
}
private fun sendLiveLocationImpl(chatLivePeriods: Map<Long, Long>, latitude: Double, longitude: Double) {
private fun recreateLiveLocationMessage(chatId: Long, msgId: Long, content: TdApi.InputMessageLocation) {
if (msgId != -1L) {
log.info("recreateLiveLocationMessage - $msgId")
val array = LongArray(1)
array[0] = msgId
client?.send(TdApi.DeleteMessages(chatId, array, true)) { obj ->
when (obj.constructor) {
TdApi.Ok.CONSTRUCTOR -> {
sendNewLiveLocationMessage(chatId, content)
}
TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error
if (error.code != IGNORED_ERROR_CODE) {
needRefreshActiveLiveLocationMessages = true
listener?.onSendLiveLocationError(error.code, error.message)
}
}
}
}
}
needRefreshActiveLiveLocationMessages = true
}
private fun sendNewLiveLocationMessage(chatId: Long, content: TdApi.InputMessageLocation) {
needRefreshActiveLiveLocationMessages = true
log.info("sendNewLiveLocationMessage")
client?.send(
TdApi.SendMessage(chatId, 0, false, true, null, content),
liveLocationMessageUpdatesHandler
)
}
private fun sendLiveLocationImpl(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, latitude: Double, longitude: Double) {
val location = TdApi.Location(latitude, longitude)
chatLivePeriods.forEach { (chatId, livePeriod) ->
synchronized(pausedLiveChatIds) {
if (pausedLiveChatIds.contains(chatId)) {
return@forEach
chatsShareInfo.forEach { (chatId, shareInfo) ->
val livePeriod =
if (shareInfo.currentMessageLimit > (shareInfo.start + MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC)) {
MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC
} else {
shareInfo.livePeriod.toInt()
}
}
val content = TdApi.InputMessageLocation(location, livePeriod.toInt())
val msgId = chatLiveMessages[chatId]?.id
if (msgId != null) {
if (msgId != 0L) {
val content = TdApi.InputMessageLocation(location, livePeriod)
val msgId = shareInfo.currentMessageId
if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousMessage) {
recreateLiveLocationMessage(chatId, msgId, content)
shareInfo.shouldDeletePreviousMessage = false
shareInfo.currentMessageId = -1
} else {
log.info("EditMessageLiveLocation - $msgId")
client?.send(
TdApi.EditMessageLiveLocation(chatId, msgId, null, location),
liveLocationMessageUpdatesHandler
)
}
} else {
client?.send(
TdApi.SendMessage(chatId, 0, false, true, null, content),
liveLocationMessageUpdatesHandler
)
sendNewLiveLocationMessage(chatId, content)
}
}
}
@ -742,12 +761,14 @@ class TelegramHelper private constructor() {
listener?.onSendLiveLocationError(error.code, error.message)
}
}
else -> {
TdApi.Message.CONSTRUCTOR -> {
if (obj is TdApi.Message) {
when (obj.sendingState?.constructor) {
TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
if (obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) {
needRefreshActiveLiveLocationMessages = true
listener?.onSendLiveLocationError(-1, "Live location message ${obj.id} failed to send")
} else {
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj))
}
}
}
@ -1199,23 +1220,18 @@ class TelegramHelper private constructor() {
TdApi.UpdateMessageSendFailed.CONSTRUCTOR -> {
needRefreshActiveLiveLocationMessages = true
}
TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR -> {
val updateMessageSendSucceeded = obj as TdApi.UpdateMessageSendSucceeded
val message = updateMessageSendSucceeded.message
chatLiveMessages[message.chatId] = message
}
TdApi.UpdateDeleteMessages.CONSTRUCTOR -> {
val updateDeleteMessages = obj as TdApi.UpdateDeleteMessages
if (updateDeleteMessages.isPermanent) {
val chatId = updateDeleteMessages.chatId
val deletedMessages = mutableListOf<TdApi.Message>()
for (messageId in updateDeleteMessages.messageIds) {
if (chatLiveMessages[chatId]?.id == messageId) {
chatLiveMessages.remove(chatId)
}
usersLocationMessages.remove(messageId)
?.also { deletedMessages.add(it) }
}
outgoingMessagesListeners.forEach {
it.onDeleteMessages(chatId, updateDeleteMessages.messageIds.toList())
}
if (deletedMessages.isNotEmpty()) {
incomingMessagesListeners.forEach {
it.onDeleteChatLocationMessages(chatId, deletedMessages)

View file

@ -15,6 +15,7 @@ import android.support.v7.widget.RecyclerView
import android.view.*
import android.view.animation.LinearInterpolator
import android.widget.*
import net.osmand.telegram.ADDITIONAL_ACTIVE_TIME_VALUES_SEC
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper
@ -300,24 +301,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
val updateAdapter = Handler()
updateAdapter.postDelayed({
if (updateEnable) {
if (sharingMode) {
updateExistingLiveMessages()
val iterator = adapter.chats.iterator()
while (iterator.hasNext()) {
val chat = iterator.next()
if (settings.getChatLiveMessageExpireTime(chat.id) <= 0) {
settings.shareLocationToChat(chat.id, false)
iterator.remove()
}
}
if (adapter.chats.isNotEmpty()) {
adapter.chats = sortAdapterItems(adapter.chats)
adapter.notifyDataSetChanged()
} else {
sharingMode = false
updateContent()
}
}
startHandler()
}
}, ADAPTER_UPDATE_INTERVAL_MIL)
@ -436,15 +420,8 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
for (chatId in chatList) {
val chat = telegramHelper.getChat(chatId)
if (chat != null) {
if (settings.isSharingLocationToChat(chatId)) {
if (sharingMode) {
val message = telegramHelper.getChatLiveMessages()[chat.id]
if (message != null) {
// settings.updateChatShareLocStartSec(chatId, message.date.toLong())
}
} else {
if (!sharingMode && settings.isSharingLocationToChat(chatId)) {
continue
}
} else if (telegramHelper.isPrivateChat(chat)) {
if ((chat.type as TdApi.ChatTypePrivate).userId == currentUser?.id) {
continue
@ -460,20 +437,6 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
}
}
private fun updateExistingLiveMessages() {
telegramHelper.getChatLiveMessages().values.forEach {
if (settings.isSharingLocationToChat(it.chatId)
&& (settings.getChatShareLocStartSec(it.chatId) == null || settings.getChatLivePeriod(it.chatId) == null)) {
settings.shareLocationToChat(it.chatId, true, (it.content as TdApi.MessageLocation).livePeriod.toLong())
// settings.updateChatShareLocStartSec(it.chatId, it.date.toLong())
}
}
sharingMode = settings.hasAnyChatToShareLocation()
if (!shareLocationHelper.sharingLocation && sharingMode && AndroidUtils.isLocationPermissionAvailable(app)) {
shareLocationHelper.startSharingLocation()
}
}
private fun sortAdapterItems(list: MutableList<TdApi.Chat>): MutableList<TdApi.Chat> {
list.sortWith(Comparator<TdApi.Chat> { o1, o2 -> o1.title.compareTo(o2.title) })
return list
@ -516,6 +479,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
val lastItem = position == itemCount - 1
val placeholderId = if (telegramHelper.isGroup(chat)) R.drawable.img_group_picture else R.drawable.img_user_picture
val live = settings.isSharingLocationToChat(chat.id)
val shareInfo = settings.getChatsShareInfo()[chat.id]
TelegramUiHelper.setupPhoto(app, holder.icon, chat.photo?.small?.local?.path, placeholderId, false)
holder.title?.text = chat.title
@ -556,14 +520,17 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
isChecked = live
setOnCheckedChangeListener { _, isChecked ->
if (!isChecked) {
val currentMessageId = shareInfo?.currentMessageId
settings.shareLocationToChat(chat.id, false)
telegramHelper.stopSendingLiveLocationToChat(chat.id)
if (currentMessageId != null) {
telegramHelper.stopSendingLiveLocationToChat(chat.id, currentMessageId)
}
removeItem(chat)
}
}
}
val duration = settings.getChatLivePeriod(chat.id)
val duration = shareInfo?.userSetLivePeriod
if (duration != null && duration > 0) {
holder.descriptionDuration?.text = OsmandFormatter.getFormattedDuration(context!!, duration)
holder.description?.apply {
@ -575,18 +542,13 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
val expiresIn = settings.getChatLiveMessageExpireTime(chat.id)
holder.textInArea?.apply {
val time = shareInfo?.additionalActiveTime ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
visibility = View.VISIBLE
text = "${getText(R.string.plus)} ${OsmandFormatter.getFormattedDuration(
context!!, settings.getChatAddActiveTime(chat.id))}"
text = "${getText(R.string.plus)} ${OsmandFormatter.getFormattedDuration(context!!, time)}"
setOnClickListener {
val chatNextAddTime = settings.getChatNextAddActiveTime(chat.id)
val newLivePeriod = settings.getChatLiveMessageExpireTime(chat.id) + settings.getChatAddActiveTime(chat.id)
settings.shareLocationToChat(chat.id, true, newLivePeriod, chatNextAddTime)
if (app.isInternetConnectionAvailable) {
telegramHelper.pauseSendingLiveLocationToChat(chat.id)
telegramHelper.deleteLiveLocationMessage(chat.id)
telegramHelper.resumeSendingLiveLocationToChat(chat.id)
}
val newLivePeriod = settings.getChatLiveMessageExpireTime(chat.id) + (shareInfo?.additionalActiveTime ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0])
val nextAdditionalActiveTime = shareInfo?.getNextAdditionalActiveTime() ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[1]
settings.shareLocationToChat(chat.id, true, newLivePeriod, nextAdditionalActiveTime)
notifyItemChanged(position)
}
}

View file

@ -3837,4 +3837,5 @@
<string name="poi_hill">Kopec</string>
</resources>
<string name="poi_atm">Bankomat</string>
</resources>

View file

@ -3964,4 +3964,5 @@
<string name="poi_hill">Bakke</string>
</resources>
<string name="poi_atm">Pengeautomat</string>
</resources>

View file

@ -3889,4 +3889,5 @@
<string name="poi_hill">Monteto</string>
</resources>
<string name="poi_atm">Bankaŭtomato</string>
</resources>

View file

@ -107,7 +107,7 @@
<string name="poi_ses_station">ایستگاه SES</string>
<string name="poi_emergency_access_point">نقطه دسترسی اضطراری</string>
<string name="poi_ford">فورد</string>
<string name="poi_ford">گدار</string>
<string name="poi_mountain_pass">ایستگاه کوهستانی</string>
<string name="poi_gate">دروازه</string>
<string name="poi_city_wall">دیوار شهرستان</string>
@ -116,9 +116,9 @@
<string name="poi_border_control">کنترل مرزی</string>
<string name="poi_traffic_calming_bump">سرعت‌گیر</string>
<string name="poi_traffic_calming_hump">سرعتکاه قوسی</string>
<string name="poi_traffic_calming_cushion">سرعتکاه صفحه‌ای</string>
<string name="poi_traffic_calming_cushion">بالشتک سرعتکاه</string>
<string name="poi_traffic_calming_chicane">پیچانه</string>
<string name="poi_traffic_calming_rumble_strip">زبرهٔ نواری</string>
<string name="poi_traffic_calming_rumble_strip">نوارهٔ لرزاننده</string>
<string name="poi_traffic_calming_table">سرعتکاه تخت</string>
<string name="poi_traffic_calming_choker">نقطهٔ گازانبری</string>
<string name="poi_traffic_signals">چراغ راهنما</string>

View file

@ -2641,7 +2641,7 @@ représentant la zone : %1$s x %2$s</string>
<string name="type_postcode">Saisissez le code postal</string>
<string name="nearest_cities">Villes les plus proches</string>
<string name="select_city">Sélectionner la ville</string>
<string name="select_postcode">Sélectionner le code postal</string>
<string name="select_postcode">Recherche par code postal</string>
<string name="type_address">Saisissez une adresse</string>
<string name="select_street">Sélectionner la rue</string>
<string name="shared_string_in_name">dans %1$s</string>
@ -3183,7 +3183,7 @@ représentant la zone : %1$s x %2$s</string>
<string name="rendering_value_black_name">Noir</string>
<string name="more_transport_on_stop_hint">Il y a d\'autres moyens de transport à cet arrêt.</string>
<string name="search_street">Rechercher une rue</string>
<string name="start_search_from_city">Démarrer la recherche à partir de la ville</string>
<string name="start_search_from_city">Recherche par nom de ville</string>
<string name="shared_string_restore">Restaurer</string>
<string name="keep_passed_markers_descr">Les marques visitées resteront affichées sur la carte, que les marques appartiennent à un groupe de favoris ou qu\'il s\'agisse de points de passage GPX. Ces marques ne disparaîtront que lorsque le groupe ou la trace sera désactivé.</string>
<string name="keep_passed_markers">Afficher sur la carte les marques visitées</string>

File diff suppressed because it is too large Load diff

View file

@ -1333,4 +1333,5 @@
<string name="poi_lighting_tower">Lysmast</string>
<string name="poi_checkpoint_type_code">Kode</string>
<string name="poi_checkpoint_type_notebook">Notatbok</string>
<string name="poi_ford">Vadested</string>
</resources>

View file

@ -473,7 +473,7 @@
<string name="rendering_value_pink_name">rosa</string>
<string name="rendering_value_brown_name">marrom</string>
<string name="rendering_value_brown_name">Marrom</string>
@ -2944,4 +2944,11 @@ Pôr do Sol: %2$s</string>
<string name="increase_search_radius_to">Aumentar o raio de pesquisa para %1$s</string>
<string name="send_search_query">Enviar consulta de pesquisa?</string>
<string name="ask_for_location_permission">Por favor, dê permissão ao OsmAnd para ver a sua localização para continuar.</string>
<string name="markers_remove_dialog_msg">Apagar marcador de mapa \'%s\'\?</string>
<string name="edit_map_marker">Editar marcador de mapa</string>
<string name="third_party_application">Aplicativo de terceiros</string>
<string name="search_street">Pesquisar rua</string>
<string name="start_search_from_city">Iniciar pesquisa a partir da cidade</string>
<string name="shared_string_restore">Restaurar</string>
<string name="rendering_value_black_name">Preto</string>
</resources>

View file

@ -372,11 +372,11 @@
<string name="native_rendering">Быстрый рендеринг</string>
<string name="test_voice_prompts">Тест голосовых подсказок</string>
<string name="switch_to_raster_map_to_see">Для этой местности отсутствует векторная карта. Загрузите ее с помощью меню \"Скачать карты\" или переключитесь на плагин \"Онлайн карты\".</string>
<string name="send_files_to_osm">Отправить GPX файлы в OSM?</string>
<string name="send_files_to_osm">Отправить файлы GPX в OSM\?</string>
<string name="gpx_visibility_txt">Видимость</string>
<string name="gpx_tags_txt">Теги</string>
<string name="gpx_description_txt">Описание</string>
<string name="validate_gpx_upload_name_pwd">Пожалуйста, укажите имя и пароль пользователя OSM для выгрузки GPX файлов.</string>
<string name="validate_gpx_upload_name_pwd">Пожалуйста, укажите имя и пароль пользователя OSM для выгрузки файлов GPX.</string>
<string name="default_buttons_support">Поддержка</string>
<string name="support_new_features">Поддержать разработку новых функций</string>
<string name="show_ruler_level">Показывать масштаб</string>
@ -398,7 +398,7 @@
<string name="live_monitoring_interval">Интервал онлайн слежения</string>
<string name="live_monitoring_url_descr">Укажите веб-адрес со следующими параметрами: lat={0}, lon={1}, timestamp={2}, hdop={3}, altitude={4}, speed={5}, bearing={6}.</string>
<string name="live_monitoring_url">Web адрес онлайн слежения</string>
<string name="gpx_monitoring_disabled_warn">Записать трек можно с помощью GPX виджета или выбрав \"Запись поездки\" в Настройках.</string>
<string name="gpx_monitoring_disabled_warn">Записать трек можно с помощью виджета GPX или выбрав \"Запись поездки\" в Настройках.</string>
<string name="show_current_gpx_title">Показывать текущий путь</string>
<string name="free_version_message">Бесплатная версия OsmAnd ограничена %1$s загрузками (добавлениями или обновлениями карт) и не поддерживает геостатьи из Википедии.</string>
<string name="free_version_title">Бесплатная версия</string>
@ -437,7 +437,7 @@
<string name="auto_follow_route_navigation">Автовозврат карты только при следовании по маршруту</string>
<string name="auto_follow_route_navigation_descr">Возвращать карту в текущее положение только при следовании по маршруту.</string>
<string name="auto_follow_location_enabled">Привязка карты к местоположению включена.</string>
<string name="shared_string_gpx_route">GPX маршрут</string>
<string name="shared_string_gpx_route">"Маршрут GPX"</string>
<string name="poi_query_by_name_matches_categories">Некоторые категории POI соответствуют запросу, можете использовать их для создания фильтра:</string>
<string name="data_to_search_poi_not_available">Локальные данные для поиска POI по имени не найдены.</string>
<string name="poi_filter_by_name">Поиск по имени</string>
@ -458,7 +458,7 @@
<string name="route_successfully_saved_at">Маршрут успешно сохранен в \"%1$s\".</string>
<string name="filename_input">Имя файла: </string>
<string name="file_with_name_already_exist">Файл с таким именем уже существует.</string>
<string name="local_index_upload_gpx_description">Выгрузить GPX файлы в сообщество OSM. Они будут использованы для улучшения карты.</string>
<string name="local_index_upload_gpx_description">Выгрузить файлы GPX в сообщество OSM. Они будут использованы для улучшения карты.</string>
<string name="local_index_items_uploaded">%1$d из %2$d объект(ов) успешно загружены.</string>
<string name="local_index_mi_upload_gpx">Отправить в OSM</string>
<string name="show_more_map_detail">Детализированная карта</string>
@ -554,9 +554,9 @@
<string name="send_location_email_pattern">Чтобы увидеть местоположение следуйте ссылке %1$s или android ссылке %2$s</string>
<string name="send_location">Отправить местоположение</string>
<string name="context_menu_item_share_location">Поделиться местоположением</string>
<string name="add_waypoint_dialog_added">Точка GPX \"{0}\" была успешно добавлена</string>
<string name="add_waypoint_dialog_title">Добавить точку к записанному треку</string>
<string name="context_menu_item_add_waypoint">Добавить точку к треку</string>
<string name="add_waypoint_dialog_added">"Путевая точка GPX \"{0}\" добавлена"</string>
<string name="add_waypoint_dialog_title">Добавить путевую точку к записанному треку GPX</string>
<string name="context_menu_item_add_waypoint">Добавить путевую точку GPX</string>
<string name="amenity_type_administrative">Административное</string>
<string name="amenity_type_barrier">Препятствие</string>
<string name="amenity_type_education">Образование</string>
@ -657,7 +657,7 @@
<string name="no_vector_map_loaded">Векторные карты не загружены в приложение</string>
<string name="gpx_files_not_found">Файлы GPX не найдены в папке с треками</string>
<string name="layer_gpx_layer">Трек GPX…</string>
<string name="layer_gpx_layer">Файлы GPX…</string>
<string name="error_reading_gpx">Не удалось прочитать данные GPX</string>
<string name="vector_data">Локальные векторные карты</string>
@ -670,7 +670,7 @@
<string name="rotate_map_to_bearing">Ориентация карты</string>
<string name="show_route">Показать маршрут</string>
<string name="fav_imported_sucessfully">Избранные точки успешно импортированы</string>
<string name="fav_file_to_load_not_found">Файл GPX, содержащий точки, не был найден в {0}</string>
<string name="fav_file_to_load_not_found">Файл GPX, содержащий избранные точки, не был найден в {0}</string>
<string name="fav_saved_sucessfully">Избранные точки сохранены в {0}</string>
<string name="no_fav_to_save">Нет избранных точек для сохранения</string>
<string name="error_occurred_loading_gpx">Не удалось загрузить GPX</string>
@ -813,7 +813,7 @@
<string name="loading_streets">Загружаются улицы…</string>
<string name="loading_cities">Загружаются города…</string>
<string name="poi">POI</string>
<string name="error_occurred_saving_gpx">Не удалось сохранить GPX трек</string>
<string name="error_occurred_saving_gpx">Не удалось сохранить файл GPX</string>
<string name="error_calculating_route">Не удалось рассчитать маршрут</string>
<string name="error_calculating_route_occured">Не удалось рассчитать маршрут</string>
<string name="empty_route_calculated">Невозможно построить маршрут</string>
@ -824,7 +824,7 @@
<string name="loading_data">Загрузка данных…</string>
<string name="reading_indexes">Чтение локальных данных…</string>
<string name="previous_run_crashed">Приложение завершилось некорректно. Лог-файл в {0}. Сообщите разработчику об ошибке и приложите лог-файл.</string>
<string name="saving_gpx_tracks">Сохранение треков GPX на SD-карту</string>
<string name="saving_gpx_tracks">Сохранение файла GPX</string>
<string name="finished_task">Окончен</string>
<string name="use_online_routing_descr">Использовать Интернет-сервис для прокладки маршрута.</string>
<string name="use_online_routing">Онлайн маршрутизация</string>
@ -1278,10 +1278,10 @@
<string name="available_downloads_left">Доступно %1$d файлов для скачивания</string>
<string name="files_limit">осталось %1$d файлов</string>
<string name="wait_current_task_finished">Пожалуйста, подождите, пока завершится текущая операция</string>
<string name="distance_measurement_load_gpx">Открыть существующий GPX трек</string>
<string name="distance_measurement_load_gpx">Открыть существующий файл GPX</string>
<string name="distance_measurement_finish_subtrack">Начать новый подтрек</string>
<string name="gpx_file_name">Имя файла GPX</string>
<string name="gpx_saved_sucessfully">Файл GPX успешно сохранен в {0}</string>
<string name="gpx_saved_sucessfully">Файл GPX сохранен в {0}</string>
<string name="osmand_distance_planning_plugin_name">Инструмент расчета дистанции и планирования</string>
<string name="intermediate_items_sort_return">Промежуточные пункты выстроены в оптимальный маршрут от текущего местоположения до пункта назначения.</string>
<string name="local_osm_changes_backup_failed">Не удалось выполнить резервное копирование изменений OSM</string>
@ -1526,7 +1526,7 @@
<string name="osmo_connect_menu">Соединение</string>
<string name="import_file_favourites">Сохранить данные как файл GPX или импортировать путевые точки в избранное?</string>
<string name="import_file_favourites">Сохранить данные как файл GPX или импортировать путевые точки в избранные\?</string>
<string name="rendering_value_pink_name">Розовый</string>
@ -1545,7 +1545,7 @@
<string name="gpx_file_is_empty">Трек GPX пуст</string>
<string name="gpx_file_is_empty">Пустой файл GPX</string>
@ -1588,7 +1588,7 @@
<string name="sort_by_distance">Сортировать по дистанции</string>
<string name="sort_by_name">Сортировать по имени</string>
<string name="osmo_edit_color">Цвет метки</string>
<string name="none_selected_gpx">Нет выбранных файлов GPX. Используйте длительное нажатие для выбора доступного трека.</string>
<string name="none_selected_gpx">Нет выбранных файлов GPX. Выберите один путем длительного нажатия.</string>
<string name="local_index_select_gpx_file">Выбрать для отображения</string>
<string name="rendering_attr_hideBuildings_name">Строения</string>
@ -2049,7 +2049,7 @@
<string name="favourites">Избранные</string>
<string name="saved_at_time">Успешно сохранен в: %1$s</string>
<string name="poi_deleted_localy">POI будут удалены после того как вы загрузите ваши изменения</string>
<string name="show_gpx">Показать GPX</string>
<string name="show_gpx">Показать данные GPX</string>
<string name="poi_action_delete">удалить</string>
<string name="recent_places">Недавние места</string>
<string name="count_of_lines">Количество строк</string>
@ -2391,7 +2391,7 @@
<string name="quick_action_add_poi">Добавить POI</string>
<string name="quick_action_map_style">Изменить стиль карты</string>
<string name="quick_action_map_style_switch">Стиль карты был изменен на \"%s\".</string>
<string name="quick_action_add_gpx">Добавить точку GPX</string>
<string name="quick_action_add_gpx">Добавить путевую точку GPX</string>
<string name="quick_action_add_parking">Добавить место парковки</string>
<string name="quick_action_new_action">Добавить действие</string>
<string name="quick_action_edit_action">Редактировать действие</string>
@ -2650,7 +2650,7 @@
<string name="navigate_point_olc">Открытый код местоположения (OLC)</string>
<string name="osmand_extended_description_part1">OsmAnd (OSM Automated Navigation Directions) - картографическая и навигационная программа с доступом к свободным, мировым и высококачественным данным OpenStreetMap (OSM).
\n
\nПолучайте наслаждение от голосовой и визуальной навигации, просматривайте точки интереса (англ. POI, points of interest), создавайте и управляйте GPX треками, используйте визуализацию контурных линий и данных высот (через плагин), переключайтесь между режимами автомобиль, велосипед и пешеход, редактируйте OSM данные и многое другое.</string>
\nПолучайте наслаждение от голосовой и визуальной навигации, просматривайте точки интереса (англ. POI, points of interest), создавайте и управляйте треками GPX, используйте визуализацию контурных линий и данных высот (через плагин), переключайтесь между режимами автомобиль, велосипед и пешеход, редактируйте OSM данные и многое другое.</string>
<string name="osmand_extended_description_part2">GPS-навигация
\n• Вы можете выбрать между автономным режимом (без платы за роуминг) и через Интернет (быстрее)
\n• Пошаговые голосовые подсказки (записанные или синтезированные голоса) доставят вас к месту назначения
@ -2669,27 +2669,27 @@
\n• Делитесь своим расположением, чтобы друзья смогли найти вас
\n• Сохраняет ваши самые важные места в избранных
\n• Позволяет вам выбрать как отображать названия на карте: на английском, местным или с фонетическим написанием
\n• Отображает специальные онлайн тайлы, спутниковые снимки (с Bing), различные метки, как туристические/навигационные GPX треки и дополнительные слои с настраиваемой прозрачностью</string>
\n• Отображает специальные онлайн тайлы, спутниковые снимки (с Bing), различные метки, как туристические/навигационные треки GPX и дополнительные слои с настраиваемой прозрачностью</string>
<string name="osmand_extended_description_part4">Катание на лыжах
\n• OsmAnd плагин лыжные карты позволяет видеть лыжные трассы с уровнем сложности и некоторой дополнительной информацией, как расположение подъемников и других объектов.</string>
<string name="osmand_extended_description_part5">Езда на велосипеде
\n• Поиск велосипедных дорожек на карте
\n• GPS навигация в велосипедном режиме строит маршрут используя велосипедные дорожки
\n• Просмотр вашей скорости и высоты над уровнем моря
\n• Опция GPX записи позволяет вам записывать ваше путешествие и делиться им
\n• Опция записи GPX позволяет вам записывать ваше путешествие и делиться им
\n• Через приложение вы можете включить отображение контурных линий и затемнение рельефа</string>
<string name="osmand_extended_description_part6">Прогулки, походы, экскурсии
\n• Карта показывает пешеходные и треккинговые тропы
\n• Википедия на предпочитаемом вами языке может многое рассказать вам во время экскурсии по городу
\n• Остановки общественного транспорта (автобус, трамвай, поезд), включая названия маршрутов, помогут ориентироваться в новом городе
\n• GPS навигация в пешеходном режиме высчитывает маршрут, используя пешеходные тропы
\n• Загрузите и следуйте по GPX маршруту или запишите и поделитесь своим собственным</string>
\n• Загрузите и следуйте по маршруту GPX или запишите и поделитесь своим собственным</string>
<string name="osmand_extended_description_part7">Внесение вклада в OSM
\n• Сообщения об ошибках
\n• Загрузка GPX треков в OSM напрямую из программы
\n• Загрузка треков GPX в OSM напрямую из программы
\n• Добавление точек интереса (POI) и загрузка их в OSM (или позже, если оффлайн)</string>
<string name="osmand_plus_extended_description_part1">"OsmAnd+ (OSM Automated Navigation Directions) - картографическая и навигационная программа с доступом к свободным, мировым и высококачественным данным OpenStreetMap (OSM).
\n Получайте наслаждение от голосовой и визуальной навигации, просматривайте точки интереса (англ. POI, points of interest), создавайте и управляйте GPX треками, используйте визуализацию контурных линий и данных высот, переключайтесь между режимами автомобиль, велосипед и пешеход, редактируйте OSM данные и многое другое.
\n Получайте наслаждение от голосовой и визуальной навигации, просматривайте точки интереса (англ. POI, points of interest), создавайте и управляйте треками GPX, используйте визуализацию контурных линий и данных высот, переключайтесь между режимами автомобиль, велосипед и пешеход, редактируйте OSM данные и многое другое.
\n
\n OsmAnd + - платная версия программы. При приобретении, вы поддержите проект, финансируете разработку новых возможностей и получите последние обновления.
\n
@ -2706,7 +2706,7 @@
\n• Возможность ориентирования карты по компасу или по направлению вашего движения
\n• Сохранение ваших самых важных мест в качестве избранных
\n• Отображение POI (точек интереса) вокруг вас
\n• Отображение специализированных онлайн тайлов, спутниковые снимки (от Bing), различные наложения, такие как туристические/навигационные GPX треки и дополнительные слои с настраиваемой прозрачностью
\n• Отображение специализированных онлайн тайлов, спутниковые снимки (от Bing), различные наложения, такие как туристические/навигационные треки GPX и дополнительные слои с настраиваемой прозрачностью
\n• Возможность отображения географических названий на английском, местном или фонетическом написании</string>
<string name="quick_action_item_screen">Экран %d</string>
<string name="quick_favorites_show_favorites_dialog">Показать диалог избранных</string>
@ -2716,17 +2716,17 @@
<string name="add_route_points">Добавить точки маршрута</string>
<string name="add_waypoint">Добавить путевую точку</string>
<string name="add_line">Добавить линию</string>
<string name="save_gpx_waypoint">Сохранить GPX путевую точку</string>
<string name="save_gpx_waypoint">Сохранить путевую точку GPX</string>
<string name="save_route_point">Сохранить точку маршрута</string>
<string name="waypoint_one">Путевая точка 1</string>
<string name="route_point_one">Точка маршрута 1</string>
<string name="empty_state_my_tracks">Добавить и записать треки</string>
<string name="empty_state_my_tracks_desc">Запись или импорт треков для просмотра.</string>
<string name="empty_state_my_tracks">Добавить файлы GPX</string>
<string name="empty_state_my_tracks_desc">Импорт файлов GPX, или запись треков.</string>
<string name="empty_state_favourites">Добавить избранные</string>
<string name="empty_state_favourites_desc">Добавить избранные на карту или импортировать из файла.</string>
<string name="import_track">Импортировать трек</string>
<string name="import_track">Импортировать файл GPX</string>
<string name="move_point">Переместить точку</string>
<string name="add_segment_to_the_track">Добавить в GPX трек</string>
<string name="add_segment_to_the_track">Добавить в файл GPX</string>
<string name="osmand_plus_extended_description_part4">Используйте данные OSM и Википедии
\n• Высококачественная информация из лучших совместных проектов мира
\n• Данные OSM доступны по каждой стране или региону
@ -2743,7 +2743,7 @@
\n• Просмотр пешеходных, туристических и велосипедных дорожек, прекрасно подходит для активного отдыха
\n• Специальный режимы маршрутизации и отображения для велосипедистов и пешеходов
\n• Опционально остановки общественного транспорта (автобус, трамвай, поезд), включая названия маршрутов
\n• Возможность записи путешествие в локальный GPX файл или Интернет-сервис
\n• Возможность записи путешествие в локальный файл GPX или Интернет-сервис
\n• Возможность отображения скорости и высоты
\n• Отображение контурных линий и затенения высот (через дополнительный плагин)</string>
<string name="osmand_plus_extended_description_part7">Непосредственный вклад в OSM

View file

@ -3898,4 +3898,5 @@
<string name="poi_hill">Montigru</string>
</resources>
<string name="poi_atm">Bancomat</string>
</resources>

View file

@ -2490,4 +2490,33 @@
<string name="poi_stone_type_conciliation_cross">Тип: крст помирења</string>
<string name="poi_stone_type_coat_of_arms">Тип: грб</string>
<string name="poi_atm">АТМ</string>
<string name="poi_material_wood">Материјал: дрво</string>
<string name="poi_material_metal">Материјал: метал</string>
<string name="poi_material_reinforced_concrete">Материјал: ојачани бетон</string>
<string name="poi_material_concrete">Материјал: бетон</string>
<string name="poi_material_steel">Материјал: челик</string>
<string name="poi_material_stone">Материјал: камен</string>
<string name="poi_material_masonry">Материјал: зидарски</string>
<string name="poi_material_brick">Материјал: цигла</string>
<string name="poi_material_plastic">Материјал: пластика</string>
<string name="poi_material_sandstone">Материјал: камени песак</string>
<string name="poi_material_granite_stone">Материјал: гранитни камен</string>
<string name="poi_material_metal_wood">Материјал: метал, дрво</string>
<string name="poi_material_glass">Материјал: стакло</string>
<string name="poi_material_bronze">Материјал: бронза</string>
<string name="poi_material_earth">Материјал: земља</string>
<string name="poi_material_composite">Материјал: композит</string>
<string name="poi_material_limestone">Материјал: кречњак</string>
<string name="poi_material_marble">Материјал: мермер</string>
<string name="poi_material_aluminium">Материјал: алуминијум</string>
<string name="poi_site_type_megalith">Мегалит</string>
<string name="poi_site_type_tumulus">Хумка</string>
<string name="poi_site_type_fortification">Утврђење</string>
<string name="poi_site_type_settlement">Насеље</string>
<string name="poi_site_type_city">Велеград</string>
<string name="poi_site_type_hut_circle">Круг кућишта</string>
<string name="poi_site_type_petroglyph">Петроглиф</string>
<string name="poi_site_type_earthwork">Земљана баријера</string>
</resources>

View file

@ -2733,7 +2733,7 @@ Vänligen tillhandahåll fullständig kod</string>
<string name="quick_action_add_destination_desc">Knackning på åtgärdsknappen kommer att lägga till en destination från skärmens mitt. Tidigare inställda destinationen blir den sista mellanliggande destinationen.</string>
<string name="quick_action_replace_destination_desc">Knackning på åtgärdsknappen kommer att ersätta destinationen med skärmens mittläge.</string>
<string name="quick_action_add_first_intermediate_desc">Knackning på åtgärdsknappen kommer att lägga en första mellanliggande punkt från skärmens mittpunkt.</string>
<string name="subscribe_email_desc">Prenumerera på vår e-postlista om apprabatter och få 3 fler kartnedladdningar!</string>
<string name="subscribe_email_desc">Anmäl dig till vår e-postlista om apprabatter och få 3 extra kartnedladdningar!</string>
<string name="depth_contour_descr">Kartor som innehåller havsdjupskonturer och nautiska punkter.</string>
<string name="sea_depth_thanks">Tack för att du köpt nautiska djupkonturer!</string>
<string name="index_item_depth_contours_osmand_ext">Nautiska djupkonturer</string>