Merge branch 'master' of ssh://github.com/osmandapp/Osmand into startup_time
This commit is contained in:
commit
a38c012415
19 changed files with 958 additions and 790 deletions
|
@ -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)) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3837,4 +3837,5 @@
|
|||
|
||||
<string name="poi_hill">Kopec</string>
|
||||
|
||||
</resources>
|
||||
<string name="poi_atm">Bankomat</string>
|
||||
</resources>
|
||||
|
|
|
@ -3964,4 +3964,5 @@
|
|||
|
||||
<string name="poi_hill">Bakke</string>
|
||||
|
||||
</resources>
|
||||
<string name="poi_atm">Pengeautomat</string>
|
||||
</resources>
|
||||
|
|
|
@ -3889,4 +3889,5 @@
|
|||
|
||||
<string name="poi_hill">Monteto</string>
|
||||
|
||||
</resources>
|
||||
<string name="poi_atm">Bankaŭtomato</string>
|
||||
</resources>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3898,4 +3898,5 @@
|
|||
|
||||
<string name="poi_hill">Montigru</string>
|
||||
|
||||
</resources>
|
||||
<string name="poi_atm">Bancomat</string>
|
||||
</resources>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue