Merge pull request #6475 from osmandapp/TelegramDataBase

Telegram database refactoring
This commit is contained in:
Alexey 2019-01-31 13:19:18 +03:00 committed by GitHub
commit 3a61240951
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1577 additions and 1221 deletions

View file

@ -135,7 +135,7 @@ dependencies {
implementation project(path: ':OsmAnd-java', configuration: 'android') implementation project(path: ':OsmAnd-java', configuration: 'android')
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation( "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version") { implementation( "org.jetbrains.kotlin:kotlin-stdlib:1.2.71") {
exclude group: 'org.jetbrains', module: 'annotations' exclude group: 'org.jetbrains', module: 'annotations'
} }
implementation 'com.android.support:appcompat-v7:28.0.0-rc01' implementation 'com.android.support:appcompat-v7:28.0.0-rc01'

View file

@ -96,7 +96,7 @@
android:layout_height="1dp" android:layout_height="1dp"
android:background="@color/app_bar_divider" /> android:background="@color/app_bar_divider" />
<LinearLayout <FrameLayout
android:id="@+id/date_row" android:id="@+id/date_row"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/list_header_height" android:layout_height="@dimen/list_header_height"
@ -106,13 +106,12 @@
android:paddingRight="@dimen/content_padding_standard"> android:paddingRight="@dimen/content_padding_standard">
<net.osmand.telegram.ui.views.TextViewEx <net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/date_start_btn" android:id="@+id/date_btn"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="@dimen/dialog_button_height" android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical" android:layout_gravity="center"
android:layout_marginEnd="@dimen/content_padding_half" android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half" android:layout_marginRight="@dimen/content_padding_half"
android:layout_weight="1"
android:background="@drawable/btn_round_border" android:background="@drawable/btn_round_border"
android:drawablePadding="@dimen/content_padding_half" android:drawablePadding="@dimen/content_padding_half"
android:ellipsize="end" android:ellipsize="end"
@ -120,38 +119,12 @@
android:maxLines="1" android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding" android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding" android:paddingRight="@dimen/image_button_padding"
android:text="@string/start_date" android:text="@string/shared_string_date"
android:textColor="?attr/ctrl_active_color" android:textColor="?attr/ctrl_active_color"
android:textSize="@dimen/text_button_text_size" android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium" /> app:typeface="@string/font_roboto_medium" />
<View </FrameLayout>
android:layout_width="16dp"
android:layout_height="1dp"
android:layout_gravity="center_vertical"
android:background="@color/app_bar_divider" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/date_end_btn"
android:layout_width="0dp"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/content_padding_half"
android:layout_marginStart="@dimen/content_padding_half"
android:layout_weight="1"
android:background="@drawable/btn_round_border"
android:drawablePadding="@dimen/content_padding_half"
android:ellipsize="end"
android:gravity="start|center_vertical"
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:text="@string/end_date"
android:textColor="?attr/ctrl_active_color"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>

View file

@ -171,6 +171,63 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/gps_points"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_regular"
android:text="@string/gps_points" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/gps_points_collected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_mono_bold" />
<net.osmand.telegram.ui.views.TextViewEx
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_regular"
android:text="@string/shared_string_collected" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/gps_points_sent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_mono_bold" />
<net.osmand.telegram.ui.views.TextViewEx
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/hint_text_size"
android:text="@string/shared_string_sent"
app:typeface="@string/font_roboto_regular" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<Switch <Switch

View file

@ -1,4 +1,9 @@
<resources> <resources>
<string name="points_size">%1$d points</string>
<string name="shared_string_date">Date</string>
<string name="shared_string_collected">Collected</string>
<string name="gps_points">Gps points</string>
<string name="shared_string_sent">Sent</string>
<string name="monitoring_is_enabled">Monitoring is enabled</string> <string name="monitoring_is_enabled">Monitoring is enabled</string>
<string name="monitoring_is_disabled">Monitoring is disabled</string> <string name="monitoring_is_disabled">Monitoring is disabled</string>
<string name="time_on_the_move">time on the move</string> <string name="time_on_the_move">time on the move</string>

View file

@ -24,8 +24,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
lateinit var notificationHelper: NotificationHelper private set lateinit var notificationHelper: NotificationHelper private set
lateinit var osmandAidlHelper: OsmandAidlHelper private set lateinit var osmandAidlHelper: OsmandAidlHelper private set
lateinit var locationProvider: TelegramLocationProvider private set lateinit var locationProvider: TelegramLocationProvider private set
lateinit var messagesDbHelper: MessagesDbHelper private set lateinit var locationMessages: LocationMessages private set
lateinit var savingTracksDbHelper: SavingTracksDbHelper private set
var telegramService: TelegramService? = null var telegramService: TelegramService? = null
@ -68,8 +67,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
showLocationHelper = ShowLocationHelper(this) showLocationHelper = ShowLocationHelper(this)
notificationHelper = NotificationHelper(this) notificationHelper = NotificationHelper(this)
locationProvider = TelegramLocationProvider(this) locationProvider = TelegramLocationProvider(this)
messagesDbHelper = MessagesDbHelper(this) locationMessages = LocationMessages(this)
savingTracksDbHelper = SavingTracksDbHelper(this)
if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) { if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) {
shareLocationHelper.startSharingLocation() shareLocationHelper.startSharingLocation()
@ -96,6 +94,13 @@ class TelegramApplication : Application(), OsmandHelperListener {
return ni != null && ni.type == ConnectivityManager.TYPE_WIFI return ni != null && ni.type == ConnectivityManager.TYPE_WIFI
} }
val isMobileConnected: Boolean
get() {
val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val ni = mgr.activeNetworkInfo
return ni != null && ni.type == ConnectivityManager.TYPE_MOBILE
}
private val isInternetConnected: Boolean private val isInternetConnected: Boolean
get() { get() {
val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

View file

@ -13,10 +13,11 @@ import android.os.*
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener
import net.osmand.telegram.helpers.TelegramHelper.TelegramIncomingMessagesListener import net.osmand.telegram.helpers.TelegramHelper.TelegramIncomingMessagesListener
import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener
import net.osmand.telegram.notifications.TelegramNotification.NotificationType import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandLocationUtils
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
import java.util.* import java.util.*
@ -274,6 +275,9 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
override fun onReceiveChatLocationMessages(chatId: Long, vararg messages: TdApi.Message) { override fun onReceiveChatLocationMessages(chatId: Long, vararg messages: TdApi.Message) {
app().showLocationHelper.startShowMessagesTask(chatId, *messages) app().showLocationHelper.startShowMessagesTask(chatId, *messages)
messages.forEach {
app().locationMessages.addNewLocationMessage(it)
}
} }
override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) { override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {
@ -287,6 +291,10 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
override fun onUpdateMessages(messages: List<TdApi.Message>) { override fun onUpdateMessages(messages: List<TdApi.Message>) {
messages.forEach { messages.forEach {
app().settings.updateShareInfo(it) app().settings.updateShareInfo(it)
app().shareLocationHelper.checkAndSendBufferMessagesToChat(it.chatId)
if (it.sendingState == null && (it.content is TdApi.MessageLocation || it.content is TdApi.MessageText)) {
app().locationMessages.addNewLocationMessage(it)
}
} }
} }

View file

@ -7,6 +7,7 @@ import android.support.annotation.DrawableRes
import android.support.annotation.StringRes import android.support.annotation.StringRes
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import net.osmand.PlatformUtil
import net.osmand.telegram.helpers.OsmandAidlHelper import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.TelegramHelper import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
@ -87,6 +88,8 @@ private const val GPS_UPDATE_EXPIRED_TIME = 60 * 3L // 3 minutes
class TelegramSettings(private val app: TelegramApplication) { class TelegramSettings(private val app: TelegramApplication) {
private val log = PlatformUtil.getLog(TelegramSettings::class.java)
private var shareChatsInfo = ConcurrentHashMap<Long, ShareChatInfo>() private var shareChatsInfo = ConcurrentHashMap<Long, ShareChatInfo>()
private var hiddenOnMapChats: Set<Long> = emptySet() private var hiddenOnMapChats: Set<Long> = emptySet()
private var shareDevices: Set<DeviceBot> = emptySet() private var shareDevices: Set<DeviceBot> = emptySet()
@ -242,21 +245,56 @@ class TelegramSettings(private val app: TelegramApplication) {
} }
fun updateShareInfo(message: TdApi.Message) { fun updateShareInfo(message: TdApi.Message) {
val shareChatInfo = shareChatsInfo[message.chatId] val shareInfo = shareChatsInfo[message.chatId]
val content = message.content val content = message.content
if (shareChatInfo != null) { if (shareInfo != null) {
when (content) { when (content) {
is TdApi.MessageLocation -> { is TdApi.MessageLocation -> {
shareChatInfo.currentMapMessageId = message.id val state = message.sendingState
shareChatInfo.pendingMapMessage = false if (state != null) {
if (state.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR) {
shareInfo.pendingMapMessage = true
log.debug("updateShareInfo MAP ${message.id} MessageSendingStatePending")
shareInfo.oldMapMessageId = message.id
} else if (state.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) {
shareInfo.hasSharingError = true
shareInfo.pendingMapMessage = false
log.debug("updateShareInfo MAP ${message.id} MessageSendingStateFailed")
}
} else {
shareInfo.currentMapMessageId = message.id
shareInfo.pendingMapMessage = false
shareInfo.pendingTdLib--
shareInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L
if (shareTypeValue == SHARE_TYPE_MAP) {
shareInfo.sentMessages++
}
log.debug("updateShareInfo MAP ${message.id} SUCCESS pendingTdLib: ${shareInfo.pendingTdLib}")
}
} }
is TdApi.MessageText -> { is TdApi.MessageText -> {
shareChatInfo.currentTextMessageId = message.id val state = message.sendingState
shareChatInfo.updateTextMessageId++ if (state != null) {
shareChatInfo.pendingTextMessage = false if (state.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR) {
log.debug("updateShareInfo TEXT ${message.id} MessageSendingStatePending")
shareInfo.pendingTextMessage = true
shareInfo.oldTextMessageId = message.id
} else if (state.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) {
log.debug("updateShareInfo TEXT ${message.id} MessageSendingStateFailed")
shareInfo.hasSharingError = true
shareInfo.pendingTextMessage = false
}
} else {
shareInfo.currentTextMessageId = message.id
shareInfo.updateTextMessageId++
shareInfo.pendingTextMessage = false
shareInfo.pendingTdLib--
shareInfo.sentMessages++
shareInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L
log.debug("updateShareInfo TEXT ${message.id} SUCCESS pendingTdLib: ${shareInfo.pendingTdLib}")
}
} }
} }
shareChatInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L
} }
} }
@ -540,6 +578,11 @@ class TelegramSettings(private val app: TelegramApplication) {
obj.put(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY, chatInfo.lastSuccessfulSendTimeMs) obj.put(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY, chatInfo.lastSuccessfulSendTimeMs)
obj.put(ShareChatInfo.LAST_SEND_MAP_TIME_KEY, chatInfo.lastSendMapMessageTime) obj.put(ShareChatInfo.LAST_SEND_MAP_TIME_KEY, chatInfo.lastSendMapMessageTime)
obj.put(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY, chatInfo.lastSendTextMessageTime) obj.put(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY, chatInfo.lastSendTextMessageTime)
obj.put(ShareChatInfo.PENDING_TEXT_MESSAGE_KEY, chatInfo.pendingTextMessage)
obj.put(ShareChatInfo.PENDING_MAP_MESSAGE_KEY, chatInfo.pendingMapMessage)
obj.put(ShareChatInfo.COLLECTED_MESSAGES_KEY, chatInfo.collectedMessages)
obj.put(ShareChatInfo.SENT_MESSAGES_KEY, chatInfo.sentMessages)
obj.put(ShareChatInfo.PENDING_TDLIB_KEY, chatInfo.pendingTdLib)
jArray.put(obj) jArray.put(obj)
} }
jArray jArray
@ -566,6 +609,11 @@ class TelegramSettings(private val app: TelegramApplication) {
lastSuccessfulSendTimeMs = obj.optLong(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY) lastSuccessfulSendTimeMs = obj.optLong(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY)
lastSendMapMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_MAP_TIME_KEY) lastSendMapMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_MAP_TIME_KEY)
lastSendTextMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY) lastSendTextMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY)
pendingTextMessage = obj.optBoolean(ShareChatInfo.PENDING_TEXT_MESSAGE_KEY)
pendingMapMessage = obj.optBoolean(ShareChatInfo.PENDING_MAP_MESSAGE_KEY)
collectedMessages = obj.optInt(ShareChatInfo.COLLECTED_MESSAGES_KEY)
sentMessages = obj.optInt(ShareChatInfo.SENT_MESSAGES_KEY)
pendingTdLib = obj.optInt(ShareChatInfo.PENDING_TDLIB_KEY)
} }
shareChatsInfo[shareInfo.chatId] = shareInfo shareChatsInfo[shareInfo.chatId] = shareInfo
} }
@ -845,12 +893,17 @@ class TelegramSettings(private val app: TelegramApplication) {
var updateTextMessageId = 1 var updateTextMessageId = 1
var currentMessageLimit = -1L var currentMessageLimit = -1L
var currentMapMessageId = -1L var currentMapMessageId = -1L
var oldMapMessageId = -1L
var currentTextMessageId = -1L var currentTextMessageId = -1L
var oldTextMessageId = -1L
var userSetLivePeriod = -1L var userSetLivePeriod = -1L
var userSetLivePeriodStart = -1L var userSetLivePeriodStart = -1L
var lastSuccessfulSendTimeMs = -1L var lastSuccessfulSendTimeMs = -1L
var lastSendTextMessageTime = -1 var lastSendTextMessageTime = -1
var lastSendMapMessageTime = -1 var lastSendMapMessageTime = -1
var collectedMessages = 0
var sentMessages = 0
var pendingTdLib = 0
var pendingTextMessage = false var pendingTextMessage = false
var pendingMapMessage = false var pendingMapMessage = false
var shouldSendViaBotMessage = false var shouldSendViaBotMessage = false
@ -887,6 +940,11 @@ class TelegramSettings(private val app: TelegramApplication) {
internal const val LAST_SUCCESSFUL_SEND_TIME_KEY = "lastSuccessfulSendTime" internal const val LAST_SUCCESSFUL_SEND_TIME_KEY = "lastSuccessfulSendTime"
internal const val LAST_SEND_MAP_TIME_KEY = "lastSendMapMessageTime" internal const val LAST_SEND_MAP_TIME_KEY = "lastSendMapMessageTime"
internal const val LAST_SEND_TEXT_TIME_KEY = "lastSendTextMessageTime" internal const val LAST_SEND_TEXT_TIME_KEY = "lastSendTextMessageTime"
internal const val PENDING_TEXT_MESSAGE_KEY = "pendingTextMessage"
internal const val PENDING_MAP_MESSAGE_KEY = "pendingMapMessage"
internal const val COLLECTED_MESSAGES_KEY = "collectedMessages"
internal const val SENT_MESSAGES_KEY = "sentMessages"
internal const val PENDING_TDLIB_KEY = "sentMessages"
} }
} }
} }

View file

@ -0,0 +1,347 @@
package net.osmand.telegram.helpers
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import net.osmand.PlatformUtil
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.utils.OsmandLocationUtils
import org.drinkless.td.libcore.telegram.TdApi
class LocationMessages(val app: TelegramApplication) {
private val log = PlatformUtil.getLog(LocationMessages::class.java)
// todo - bufferedMessages is for prepared/pending messages only. On app start we read prepared/pending messages to bufferedMessages. After status changed to sent/error - remove message from buffered.
private var bufferedMessages = emptyList<BufferMessage>()
private var lastMessages = emptyList<LocationMessage>()
private val dbHelper: SQLiteHelper
init {
dbHelper = SQLiteHelper(app)
readBufferedMessages()
}
fun getBufferedMessages(): List<BufferMessage> {
return bufferedMessages.sortedBy { it.time }
}
fun getBufferedMessagesForChat(chatId: Long): List<BufferMessage> {
return bufferedMessages.filter { it.chatId==chatId }.sortedBy { it.time }
}
// todo - read from db by date (Victor's suggestion - filter by one day only. Need to be changed in UI also.
fun getIngoingMessages(currentUserId: Int, start: Long, end: Long): List<LocationMessage> {
return dbHelper.getIngoingMessages(currentUserId, start, end)
}
fun getMessagesForUserInChat(userId: Int, chatId: Long, start: Long, end: Long): List<LocationMessage> {
return dbHelper.getMessagesForUserInChat(userId, chatId, start, end)
}
fun getMessagesForUser(userId: Int, start: Long, end: Long): List<LocationMessage> {
return dbHelper.getMessagesForUser(userId, start, end)
}
fun addBufferedMessage(message: BufferMessage) {
log.debug("addBufferedMessage $message")
val messages = mutableListOf(*this.bufferedMessages.toTypedArray())
messages.add(message)
this.bufferedMessages = messages
dbHelper.addBufferedMessage(message)
}
fun addNewLocationMessage(message: TdApi.Message) {
log.debug("addNewLocationMessage ${message.id}")
val type = OsmandLocationUtils.getMessageType(message, app.telegramHelper)
val previousMessage = lastMessages.firstOrNull { it.chatId == message.chatId && it.userId == message.senderUserId && it.type == type }
val locationMessage = OsmandLocationUtils.parseMessage(message, app.telegramHelper, previousMessage)
if (locationMessage != null) {
dbHelper.addLocationMessage(locationMessage)
val messages = mutableListOf(*this.lastMessages.toTypedArray())
messages.remove(previousMessage)
messages.add(locationMessage)
this.lastMessages = messages
}
}
fun clearBufferedMessages() {
log.debug("clearBufferedMessages")
dbHelper.clearBufferedMessages()
bufferedMessages = emptyList()
}
fun removeBufferedMessage(message: BufferMessage) {
log.debug("removeBufferedMessage $message")
val messages = mutableListOf(*this.bufferedMessages.toTypedArray())
messages.remove(message)
this.bufferedMessages = messages
dbHelper.removeBufferedMessage(message)
}
private fun readBufferedMessages() {
this.bufferedMessages = dbHelper.getBufferedMessages()
}
private fun readLastMessages() {
this.lastMessages = dbHelper.getLastMessages()
}
private class SQLiteHelper(context: Context) :
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(TIMELINE_TABLE_CREATE)
db.execSQL("CREATE INDEX $DATE_INDEX ON $TIMELINE_TABLE_NAME (\"$COL_TIME\" DESC);")
db.execSQL(BUFFER_TABLE_CREATE)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL(TIMELINE_TABLE_DELETE)
db.execSQL(BUFFER_TABLE_DELETE)
onCreate(db)
}
internal fun addBufferedMessage(message: BufferMessage) {
writableDatabase?.execSQL(BUFFER_TABLE_INSERT,
arrayOf(message.chatId, message.lat, message.lon, message.altitude, message.speed,
message.hdop, message.bearing, message.time, message.type))
}
internal fun addLocationMessage(message: LocationMessage) {
writableDatabase?.execSQL(TIMELINE_TABLE_INSERT,
arrayOf(message.userId, message.chatId, message.lat, message.lon, message.altitude, message.speed,
message.hdop, message.bearing, message.time, message.type, message.messageId, message.distanceFromPrev))
}
internal fun getMessagesForUser(userId: Int, start: Long, end: Long): List<LocationMessage> {
val res = arrayListOf<LocationMessage>()
readableDatabase?.rawQuery(
"$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_TIME ASC ",
arrayOf(userId.toString()))?.apply {
if (moveToFirst()) {
do {
res.add(readLocationMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun getPreviousMessage(userId: Int, chatId: Long): LocationMessage? {
var res:LocationMessage? = null
readableDatabase?.rawQuery(
"$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_CHAT_ID = ? ORDER BY $COL_TIME DESC LIMIT 1",
arrayOf(userId.toString(), chatId.toString()))?.apply {
if (moveToFirst()) {
res = readLocationMessage(this@apply)
}
close()
}
return res
}
internal fun getIngoingMessages(currentUserId: Int, start: Long, end: Long): List<LocationMessage> {
val res = arrayListOf<LocationMessage>()
readableDatabase?.rawQuery(
"$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID != ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_USER_ID ASC, $COL_CHAT_ID ASC, $COL_TIME ASC ",
arrayOf(currentUserId.toString()))?.apply {
if (moveToFirst()) {
do {
res.add(readLocationMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun getMessagesForUserInChat(userId: Int, chatId: Long, start: Long, end: Long): List<LocationMessage> {
val res = arrayListOf<LocationMessage>()
readableDatabase?.rawQuery(
"$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_CHAT_ID = ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_TIME ASC ",
arrayOf(userId.toString(), chatId.toString()))?.apply {
if (moveToFirst()) {
do {
res.add(readLocationMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun getBufferedMessages(): List<BufferMessage> {
val res = arrayListOf<BufferMessage>()
readableDatabase?.rawQuery(BUFFER_TABLE_SELECT, null)?.apply {
if (moveToFirst()) {
do {
res.add(readBufferMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun getLastMessages(): List<LocationMessage> {
val res = arrayListOf<LocationMessage>()
readableDatabase?.rawQuery(TIMELINE_TABLE_SELECT, null)?.apply {
if (moveToFirst()) {
do {
res.add(readLocationMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun readLocationMessage(cursor: Cursor): LocationMessage {
val userId = cursor.getInt(0)
val chatId = cursor.getLong(1)
val lat = cursor.getDouble(2)
val lon = cursor.getDouble(3)
val altitude = cursor.getDouble(4)
val speed = cursor.getDouble(5)
val hdop = cursor.getDouble(6)
val bearing = cursor.getDouble(7)
val date = cursor.getLong(8)
val type = cursor.getInt(9)
val messageId = cursor.getLong(10)
val distanceFromPrev = cursor.getDouble(11)
return LocationMessage(userId, chatId, lat, lon, altitude, speed, hdop, bearing, date, type, messageId, distanceFromPrev)
}
internal fun readBufferMessage(cursor: Cursor): BufferMessage {
val chatId = cursor.getLong(0)
val lat = cursor.getDouble(1)
val lon = cursor.getDouble(2)
val altitude = cursor.getDouble(3)
val speed = cursor.getDouble(4)
val hdop = cursor.getDouble(5)
val bearing = cursor.getDouble(6)
val date = cursor.getLong(7)
val type = cursor.getInt(8)
return BufferMessage(chatId, lat, lon, altitude, speed, hdop, bearing, date, type)
}
internal fun clearBufferedMessages() {
writableDatabase?.execSQL(BUFFER_TABLE_CLEAR)
}
internal fun removeBufferedMessage(message: BufferMessage) {
writableDatabase?.execSQL(
BUFFER_TABLE_REMOVE,
arrayOf(
message.chatId,
message.lat,
message.lon,
message.altitude,
message.speed,
message.hdop,
message.bearing,
message.time,
message.type
)
)
}
companion object {
private const val DATABASE_NAME = "location_messages"
private const val DATABASE_VERSION = 5
private const val TIMELINE_TABLE_NAME = "timeline"
private const val BUFFER_TABLE_NAME = "buffer"
private const val COL_USER_ID = "user_id"
private const val COL_CHAT_ID = "chat_id"
private const val COL_TIME = "time"
private const val COL_LAT = "lat"
private const val COL_LON = "lon"
private const val COL_ALTITUDE = "altitude"
private const val COL_SPEED = "speed"
private const val COL_HDOP = "hdop"
private const val COL_BEARING = "bearing"
private const val COL_TYPE = "type" // 0 = user map message, 1 = user text message, 2 = bot map message, 3 = bot text message
private const val COL_MESSAGE_ID = "message_id"
private const val COL_DISTANCE_FROM_PREV = "distance_from_prev"
private const val DATE_INDEX = "date_index"
// Timeline messages table
private const val TIMELINE_TABLE_INSERT =
("INSERT INTO $TIMELINE_TABLE_NAME ($COL_USER_ID, $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE, $COL_MESSAGE_ID, $COL_DISTANCE_FROM_PREV) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
private const val TIMELINE_TABLE_CREATE =
("CREATE TABLE IF NOT EXISTS $TIMELINE_TABLE_NAME ($COL_USER_ID long, $COL_CHAT_ID long,$COL_LAT double, $COL_LON double, $COL_ALTITUDE double, $COL_SPEED float, $COL_HDOP double, $COL_BEARING double, $COL_TIME long, $COL_TYPE int, $COL_MESSAGE_ID long, $COL_DISTANCE_FROM_PREV double )")
private const val TIMELINE_TABLE_SELECT =
"SELECT $COL_USER_ID, $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE, $COL_MESSAGE_ID, $COL_DISTANCE_FROM_PREV FROM $TIMELINE_TABLE_NAME"
private const val TIMELINE_TABLE_CLEAR = "DELETE FROM $TIMELINE_TABLE_NAME"
private const val TIMELINE_TABLE_DELETE = "DROP TABLE IF EXISTS $TIMELINE_TABLE_NAME"
// Buffer messages table
private const val BUFFER_TABLE_INSERT =
("INSERT INTO $BUFFER_TABLE_NAME ($COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
private const val BUFFER_TABLE_CREATE =
("CREATE TABLE IF NOT EXISTS $BUFFER_TABLE_NAME ($COL_CHAT_ID long, $COL_LAT double, $COL_LON double, $COL_ALTITUDE double, $COL_SPEED float, $COL_HDOP double, $COL_BEARING double, $COL_TIME long, $COL_TYPE int)")
private const val BUFFER_TABLE_SELECT =
"SELECT $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE FROM $BUFFER_TABLE_NAME"
private const val BUFFER_TABLE_CLEAR = "DELETE FROM $BUFFER_TABLE_NAME"
private const val BUFFER_TABLE_REMOVE = "DELETE FROM $BUFFER_TABLE_NAME WHERE $COL_CHAT_ID = ? AND $COL_LAT = ? AND $COL_LON = ? AND $COL_ALTITUDE = ? AND $COL_SPEED = ? AND $COL_HDOP = ? AND $COL_BEARING = ? AND $COL_TIME = ? AND $COL_TYPE = ?"
private const val BUFFER_TABLE_DELETE = "DROP TABLE IF EXISTS $BUFFER_TABLE_NAME"
}
}
data class LocationMessage(
val userId: Int,
val chatId: Long,
val lat: Double,
val lon: Double,
val altitude: Double,
val speed: Double,
val hdop: Double,
val bearing: Double,
val time: Long,
val type: Int,
val messageId: Long,
val distanceFromPrev: Double)
data class BufferMessage (
val chatId: Long,
val lat: Double,
val lon: Double,
val altitude: Double,
val speed: Double,
val hdop: Double,
val bearing: Double,
val time: Long,
val type: Int)
companion object {
const val TYPE_USER_MAP = 0
const val TYPE_USER_TEXT = 1
const val TYPE_USER_BOTH = 2
const val TYPE_BOT_MAP = 3
const val TYPE_BOT_TEXT = 4
const val TYPE_BOT_BOTH = 5
}
}

View file

@ -1,122 +0,0 @@
package net.osmand.telegram.helpers
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import net.osmand.telegram.TelegramApplication
import org.drinkless.td.libcore.telegram.TdApi
class MessagesDbHelper(val app: TelegramApplication) {
private val messages = HashSet<Message>()
private val sqliteHelper: SQLiteHelper
init {
sqliteHelper = SQLiteHelper(app)
sqliteHelper.getMessages().forEach {
app.telegramHelper.loadMessage(it.chatId, it.messageId)
}
app.telegramHelper.addIncomingMessagesListener(object :
TelegramHelper.TelegramIncomingMessagesListener {
override fun onReceiveChatLocationMessages(
chatId: Long, vararg messages: TdApi.Message
) {
messages.forEach { addMessage(chatId, it.id) }
}
override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {
messages.forEach { removeMessage(chatId, it.id) }
}
override fun updateLocationMessages() {}
})
}
fun saveMessages() {
clearMessages()
synchronized(messages) {
sqliteHelper.addMessages(messages)
}
}
fun clearMessages() {
sqliteHelper.clearMessages()
}
private fun addMessage(chatId: Long, messageId: Long) {
synchronized(messages) {
messages.add(Message(chatId, messageId))
}
}
private fun removeMessage(chatId: Long, messageId: Long) {
synchronized(messages) {
messages.remove(Message(chatId, messageId))
}
}
private class SQLiteHelper(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(MESSAGES_TABLE_CREATE)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL(MESSAGES_TABLE_DELETE)
onCreate(db)
}
internal fun addMessages(messages: Set<Message>) {
messages.forEach {
writableDatabase?.execSQL(MESSAGES_TABLE_INSERT, arrayOf(it.chatId, it.messageId))
}
}
internal fun getMessages(): Set<Message> {
val res = HashSet<Message>()
readableDatabase?.rawQuery(MESSAGES_TABLE_SELECT, null)?.apply {
if (moveToFirst()) {
do {
res.add(Message(getLong(0), getLong(1)))
} while (moveToNext())
}
close()
}
return res
}
internal fun clearMessages() {
writableDatabase?.execSQL(MESSAGES_TABLE_CLEAR)
}
companion object {
private const val DB_NAME = "messages.db"
private const val DB_VERSION = 1
private const val MESSAGES_TABLE_NAME = "messages"
private const val MESSAGES_COL_CHAT_ID = "chat_id"
private const val MESSAGES_COL_MESSAGE_ID = "message_id"
private const val MESSAGES_TABLE_CREATE =
"CREATE TABLE IF NOT EXISTS $MESSAGES_TABLE_NAME (" +
"$MESSAGES_COL_CHAT_ID LONG, " +
"$MESSAGES_COL_MESSAGE_ID LONG)"
private const val MESSAGES_TABLE_DELETE = "DROP TABLE IF EXISTS $MESSAGES_TABLE_NAME"
private const val MESSAGES_TABLE_SELECT =
"SELECT $MESSAGES_COL_CHAT_ID, $MESSAGES_COL_MESSAGE_ID FROM $MESSAGES_TABLE_NAME"
private const val MESSAGES_TABLE_CLEAR = "DELETE FROM $MESSAGES_TABLE_NAME"
private const val MESSAGES_TABLE_INSERT = "INSERT INTO $MESSAGES_TABLE_NAME (" +
"$MESSAGES_COL_CHAT_ID, $MESSAGES_COL_MESSAGE_ID) VALUES (?, ?)"
}
}
private data class Message(val chatId: Long, val messageId: Long)
}

View file

@ -1,476 +0,0 @@
package net.osmand.telegram.helpers;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import net.osmand.PlatformUtil;
import net.osmand.telegram.TelegramApplication;
import net.osmand.telegram.utils.GPXUtilities;
import net.osmand.telegram.utils.GPXUtilities.GPXFile;
import net.osmand.telegram.utils.GPXUtilities.Track;
import net.osmand.telegram.utils.GPXUtilities.TrkSegment;
import net.osmand.telegram.utils.GPXUtilities.WptPt;
import org.apache.commons.logging.Log;
import org.drinkless.td.libcore.telegram.TdApi;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class SavingTracksDbHelper extends SQLiteOpenHelper {
private final static String DATABASE_NAME = "tracks";
private final static int DATABASE_VERSION = 3;
private final static String TRACK_NAME = "track"; //$NON-NLS-1$
private final static String TRACK_COL_USER_ID = "user_id"; //$NON-NLS-1$
private final static String TRACK_COL_CHAT_ID = "chat_id"; //$NON-NLS-1$
private final static String TRACK_COL_DATE = "date"; //$NON-NLS-1$
private final static String TRACK_COL_LAT = "lat"; //$NON-NLS-1$
private final static String TRACK_COL_LON = "lon"; //$NON-NLS-1$
private final static String TRACK_COL_ALTITUDE = "altitude"; //$NON-NLS-1$
private final static String TRACK_COL_SPEED = "speed"; //$NON-NLS-1$
private final static String TRACK_COL_HDOP = "hdop"; //$NON-NLS-1$
private final static String TRACK_COL_TEXT_INFO = "text_info"; // 1 = true, 0 = false //$NON-NLS-1$
private final static String INSERT_SCRIPT = "INSERT INTO " + TRACK_NAME + " (" + TRACK_COL_USER_ID + ", " + TRACK_COL_CHAT_ID + ", " + TRACK_COL_LAT + ", " + TRACK_COL_LON + ", "
+ TRACK_COL_ALTITUDE + ", " + TRACK_COL_SPEED + ", " + TRACK_COL_HDOP + ", " + TRACK_COL_DATE + ", " + TRACK_COL_TEXT_INFO + ")"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
private final static String CREATE_SCRIPT = "CREATE TABLE " + TRACK_NAME + " (" + TRACK_COL_USER_ID + " long," + TRACK_COL_CHAT_ID + " long," + TRACK_COL_LAT + " double, " + TRACK_COL_LON + " double, " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$
+ TRACK_COL_ALTITUDE + " double, " + TRACK_COL_SPEED + " double, " //$NON-NLS-1$ //$NON-NLS-2$
+ TRACK_COL_HDOP + " double, " + TRACK_COL_DATE + " long, " + TRACK_COL_TEXT_INFO + " int )";
private final static Log log = PlatformUtil.getLog(SavingTracksDbHelper.class);
private final TelegramApplication app;
public SavingTracksDbHelper(TelegramApplication app) {
super(app, DATABASE_NAME, null, DATABASE_VERSION);
this.app = app;
app.getTelegramHelper().addIncomingMessagesListener(new TelegramHelper.TelegramIncomingMessagesListener() {
@Override
public void onReceiveChatLocationMessages(long chatId, @NotNull TdApi.Message... messages) {
for (TdApi.Message message : messages) {
updateLocationMessage(message);
}
}
@Override
public void onDeleteChatLocationMessages(long chatId, @NotNull List<? extends TdApi.Message> messages) {
}
@Override
public void updateLocationMessages() {
}
});
app.getTelegramHelper().addOutgoingMessagesListener(new TelegramHelper.TelegramOutgoingMessagesListener() {
@Override
public void onUpdateMessages(@NotNull List<? extends TdApi.Message> messages) {
for (TdApi.Message message : messages) {
updateLocationMessage(message);
}
}
@Override
public void onDeleteMessages(long chatId, @NotNull List<Long> messages) {
}
@Override
public void onSendLiveLocationError(int code, @NotNull String message) {
}
});
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_SCRIPT);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 3) {
db.execSQL("ALTER TABLE " + TRACK_NAME + " ADD " + TRACK_COL_TEXT_INFO + " int");
}
}
public void saveUserDataToGpx(SaveGpxListener listener, File dir, int userId, long chatId, long start, long end) {
GPXFile gpxFile = collectRecordedDataForUserAndChat(userId, chatId, start, end);
if (gpxFile != null && !gpxFile.isEmpty()) {
SaveGPXTrackToFileTask task = new SaveGPXTrackToFileTask(app, listener, gpxFile, dir, userId);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
public void saveGpx(SaveGpxListener listener, File dir, GPXFile gpxFile) {
if (gpxFile != null && !gpxFile.isEmpty()) {
SaveGPXTrackToFileTask task = new SaveGPXTrackToFileTask(app, listener, gpxFile, dir, 0);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private void updateLocationMessage(TdApi.Message message) {
log.debug(message);
TdApi.MessageContent content = message.content;
int senderId = app.getTelegramHelper().getSenderMessageId(message);
if (content instanceof TdApi.MessageLocation) {
long lastTextMessageUpdate = getLastTextTrackPointTimeForUser(message.senderUserId);
long currentTime = System.currentTimeMillis();
if (lastTextMessageUpdate == 0 || currentTime - lastTextMessageUpdate < 10 * 1000) {
log.debug("Add map message " + message.senderUserId);
TdApi.MessageLocation messageLocation = (TdApi.MessageLocation) content;
insertData(senderId, message.chatId, messageLocation.location.latitude,
messageLocation.location.longitude, 0.0, 0.0, 0.0,
Math.max(message.date, message.editDate), 0);
} else {
log.debug("Skip map message");
}
} else if (content instanceof TelegramHelper.MessageLocation) {
log.debug("Add text message " + message.senderUserId);
TelegramHelper.MessageLocation messageLocation = (TelegramHelper.MessageLocation) content;
insertData(senderId, message.chatId, messageLocation.getLat(), messageLocation.getLon(),
messageLocation.getAltitude(), messageLocation.getSpeed(), messageLocation.getHdop(),
messageLocation.getLastUpdated() * 1000L, 1);
}
}
private void insertData(int userId, long chatId, double lat, double lon, double alt, double speed, double hdop, long time, int textMessage) {
execWithClose(INSERT_SCRIPT, new Object[]{userId, chatId, lat, lon, alt, speed, hdop, time, textMessage});
}
private synchronized void execWithClose(String script, Object[] objects) {
SQLiteDatabase db = getWritableDatabase();
try {
if (db != null) {
db.execSQL(script, objects);
}
} catch (RuntimeException e) {
log.error(e.getMessage(), e);
} finally {
if (db != null) {
db.close();
}
}
}
private long getLastTextTrackPointTimeForUser(int userId) {
long res = 0;
try {
SQLiteDatabase db = getWritableDatabase();
if (db != null) {
try {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_USER_ID + " = ? AND "
+ TRACK_COL_TEXT_INFO + " = ?" + " ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId), String.valueOf(1)});
if (query.moveToFirst()) {
res = query.getLong(0);
}
query.close();
} finally {
db.close();
}
}
} catch (RuntimeException e) {
}
return res;
}
public GPXFile collectRecordedDataForUserAndChat(int userId, long chatId, long start, long end) {
GPXFile gpxFile = null;
SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) {
try {
gpxFile = collectDBTracksForUser(db, userId, chatId, start, end);
} finally {
db.close();
}
}
return gpxFile;
}
public GPXFile collectRecordedDataForUser(int userId, long chatId, long start, long end) {
GPXFile gpxFile = null;
SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) {
try {
if (chatId == 0) {
gpxFile = collectDBTracksForUser(db, userId, start, end);
} else {
gpxFile = collectDBTracksForUser(db, userId, chatId, start, end);
}
} finally {
db.close();
}
}
return gpxFile;
}
public ArrayList<GPXFile> collectRecordedDataForUsers(long start, long end, ArrayList<Integer> ignoredUsersIds) {
ArrayList<GPXFile> data = new ArrayList<>();
SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) {
try {
collectDBTracksForUsers(db, data, start, end, ignoredUsersIds);
} finally {
db.close();
}
}
return data;
}
private GPXFile collectDBTracksForUser(SQLiteDatabase db, int userId, long chatId, long start, long end) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_USER_ID + " = ?"
+ " AND " + TRACK_COL_CHAT_ID + " = ?" + " AND " + TRACK_COL_DATE + " BETWEEN " + start + " AND " + end
+ " ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId), String.valueOf(chatId)});
GPXFile gpxFile = null;
long previousTime = 0;
TrkSegment segment = null;
Track track = null;
if (query.moveToFirst()) {
gpxFile = new GPXFile();
gpxFile.chatId = chatId;
gpxFile.userId = userId;
do {
long time = query.getLong(7);
WptPt pt = new WptPt();
pt.userId = query.getInt(0);
pt.chatId = query.getLong(1);
pt.lat = query.getDouble(2);
pt.lon = query.getDouble(3);
pt.ele = query.getDouble(4);
pt.speed = query.getDouble(5);
pt.hdop = query.getDouble(6);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment.points.add(pt);
} else {
segment = new TrkSegment();
segment.points.add(pt);
track.segments.add(segment);
}
} else {
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
segment.points.add(pt);
gpxFile.tracks.add(track);
}
previousTime = time;
} while (query.moveToNext());
}
query.close();
return gpxFile;
}
private GPXFile collectDBTracksForUser(SQLiteDatabase db, int userId, long start, long end) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_USER_ID + " = ?"
+ " AND " + TRACK_COL_DATE + " BETWEEN " + start + " AND " + end
+ " ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId)});
GPXFile gpxFile = null;
long previousTime = 0;
TrkSegment segment = null;
Track track = null;
if (query.moveToFirst()) {
gpxFile = new GPXFile();
gpxFile.userId = userId;
do {
long time = query.getLong(7);
WptPt pt = new WptPt();
pt.userId = query.getInt(0);
pt.chatId = query.getLong(1);
pt.lat = query.getDouble(2);
pt.lon = query.getDouble(3);
pt.ele = query.getDouble(4);
pt.speed = query.getDouble(5);
pt.hdop = query.getDouble(6);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment.points.add(pt);
} else {
segment = new TrkSegment();
segment.points.add(pt);
track.segments.add(segment);
}
} else {
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
segment.points.add(pt);
gpxFile.tracks.add(track);
}
previousTime = time;
} while (query.moveToNext());
}
query.close();
return gpxFile;
}
private void collectDBTracksForUsers(SQLiteDatabase db, ArrayList<GPXFile> dataTracks, long start, long end, ArrayList<Integer> ignoredUsersIds) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_DATE
+ " BETWEEN " + start + " AND " + end + " ORDER BY " + TRACK_COL_USER_ID + " ASC, "
+ TRACK_COL_CHAT_ID + " ASC, " + TRACK_COL_DATE + " ASC ", null);
long previousTime = 0;
long previousChatId = 0;
int previousUserId = 0;
TrkSegment segment = null;
Track track = null;
GPXFile gpx = new GPXFile();
if (query.moveToFirst()) {
do {
int userId = query.getInt(0);
if (ignoredUsersIds.contains(userId)) {
continue;
}
int chatId = query.getInt(1);
long time = query.getLong(7);
if (previousUserId != userId || previousChatId != chatId) {
gpx = new GPXFile();
gpx.chatId = chatId;
gpx.userId = userId;
previousTime = 0;
track = null;
segment = null;
dataTracks.add(gpx);
}
WptPt pt = new WptPt();
pt.userId = userId;
pt.chatId = chatId;
pt.lat = query.getDouble(2);
pt.lon = query.getDouble(3);
pt.ele = query.getDouble(4);
pt.speed = query.getDouble(5);
pt.hdop = query.getDouble(6);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment.points.add(pt);
} else {
segment = new TrkSegment();
segment.points.add(pt);
track.segments.add(segment);
}
} else {
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
segment.points.add(pt);
gpx.tracks.add(track);
}
previousTime = time;
previousUserId = userId;
previousChatId = chatId;
} while (query.moveToNext());
}
query.close();
}
private static class SaveGPXTrackToFileTask extends AsyncTask<Void, Void, List<String>> {
private TelegramApplication app;
private SaveGpxListener listener;
private final GPXFile gpxFile;
private File dir;
private int userId;
SaveGPXTrackToFileTask(TelegramApplication app, SaveGpxListener listener, GPXFile gpxFile, File dir, int userId) {
this.gpxFile = gpxFile;
this.listener = listener;
this.app = app;
this.dir = dir;
this.userId = userId;
}
@Override
protected List<String> doInBackground(Void... params) {
List<String> warnings = new ArrayList<String>();
dir.mkdirs();
if (dir.getParentFile().canWrite()) {
if (dir.exists()) {
// save file
File fout = new File(dir, userId + ".gpx"); //$NON-NLS-1$
if (!gpxFile.isEmpty()) {
WptPt pt = gpxFile.findPointToShow();
TdApi.User user = app.getTelegramHelper().getUser(pt.userId);
String fileName;
if (user != null) {
fileName = TelegramUiHelper.INSTANCE.getUserName(user)
+ "_" + new SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(new Date(pt.time)); //$NON-NLS-1$
} else {
fileName = userId + "_" + new SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(new Date(pt.time)); //$NON-NLS-1$
}
fout = new File(dir, fileName + ".gpx"); //$NON-NLS-1$
int ind = 1;
while (fout.exists()) {
fout = new File(dir, fileName + "_" + (++ind) + ".gpx"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
String warn = GPXUtilities.writeGpxFile(fout, gpxFile, app);
if (warn != null) {
warnings.add(warn);
return warnings;
}
}
}
return warnings;
}
@Override
protected void onPostExecute(List<String> warnings) {
if (listener != null) {
if (warnings != null && warnings.isEmpty()) {
listener.onSavingGpxFinish(gpxFile.path);
} else {
listener.onSavingGpxError(warnings);
}
}
}
}
public interface SaveGpxListener {
void onSavingGpxFinish(String path);
void onSavingGpxError(List<String> warnings);
}
}

View file

@ -3,6 +3,7 @@ package net.osmand.telegram.helpers
import net.osmand.Location import net.osmand.Location
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.* import net.osmand.telegram.*
import net.osmand.telegram.helpers.LocationMessages.BufferMessage
import net.osmand.telegram.notifications.TelegramNotification.NotificationType import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidNetworkUtils import net.osmand.telegram.utils.AndroidNetworkUtils
import net.osmand.telegram.utils.BASE_URL import net.osmand.telegram.utils.BASE_URL
@ -48,36 +49,8 @@ class ShareLocationHelper(private val app: TelegramApplication) {
lastLocation = location lastLocation = location
if (location != null) { if (location != null) {
val chatsShareInfo = app.settings.getChatsShareInfo() if (app.settings.getChatsShareInfo().isNotEmpty()) {
if (chatsShareInfo.isNotEmpty()) { shareLocationMessages(location, app.telegramHelper.getCurrentUserId())
val latitude = location.latitude
val longitude = location.longitude
val user = app.telegramHelper.getCurrentUser()
val sharingMode = app.settings.currentSharingMode
if (user != null && sharingMode == user.id.toString()) {
when (app.settings.shareTypeValue) {
SHARE_TYPE_MAP -> app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, latitude, longitude)
SHARE_TYPE_TEXT -> app.telegramHelper.sendLiveLocationText(chatsShareInfo, location)
SHARE_TYPE_MAP_AND_TEXT -> {
app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, latitude, longitude)
app.telegramHelper.sendLiveLocationText(chatsShareInfo, location)
}
}
} else if (sharingMode.isNotEmpty()) {
val url = getDeviceSharingUrl(location,sharingMode)
AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false,
object : AndroidNetworkUtils.OnRequestResultListener {
override fun onResult(result: String?) {
updateShareInfoSuccessfulSendTime(result, chatsShareInfo)
val osmandBot = app.telegramHelper.getOsmandBot()
if (osmandBot != null) {
checkAndSendViaBotMessages(chatsShareInfo, TdApi.Location(latitude, longitude), osmandBot)
}
}
})
}
} }
lastLocationMessageSentTime = System.currentTimeMillis() lastLocationMessageSentTime = System.currentTimeMillis()
} }
@ -122,6 +95,37 @@ class ShareLocationHelper(private val app: TelegramApplication) {
} }
} }
fun checkAndSendBufferMessagesToChat(chatId: Long) {
val shareInfo = app.settings.getChatsShareInfo()[chatId]
if (shareInfo != null && shareInfo.pendingTdLib < 10) {
app.locationMessages.getBufferedMessagesForChat(shareInfo.chatId).forEach {
if (it.type == LocationMessages.TYPE_USER_TEXT && !shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
app.telegramHelper.editTextLocation(shareInfo, it)
app.locationMessages.removeBufferedMessage(it)
} else if (it.type == LocationMessages.TYPE_USER_MAP && !shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
app.telegramHelper.editMapLocation(shareInfo, it)
app.locationMessages.removeBufferedMessage(it)
} else if (it.type == LocationMessages.TYPE_USER_BOTH) {
var messageSent = false
if (!shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
app.telegramHelper.editMapLocation(shareInfo, it)
messageSent = true
}
if (!shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
app.telegramHelper.editTextLocation(shareInfo, it)
messageSent = true
}
if (messageSent) {
app.locationMessages.removeBufferedMessage(it)
}
}
if (shareInfo.pendingTdLib >= 10) {
return
}
}
}
}
fun startSharingLocation() { fun startSharingLocation() {
if (!sharingLocation) { if (!sharingLocation) {
sharingLocation = true sharingLocation = true
@ -129,6 +133,8 @@ class ShareLocationHelper(private val app: TelegramApplication) {
app.startMyLocationService() app.startMyLocationService()
refreshNotification() refreshNotification()
checkAndSendBufferMessages()
} else { } else {
app.forceUpdateMyLocation() app.forceUpdateMyLocation()
} }
@ -158,25 +164,193 @@ class ShareLocationHelper(private val app: TelegramApplication) {
refreshNotification() refreshNotification()
} }
private fun getDeviceSharingUrl(loc: Location, sharingMode: String): String { private fun checkAndSendBufferMessages(){
val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.latitude}&lon=${loc.longitude}" log.debug("checkAndSendBufferMessages")
app.settings.getChatsShareInfo().forEach loop@{ (chatId, shareInfo) ->
if (shareInfo.pendingTdLib < 10) {
app.locationMessages.getBufferedMessagesForChat(chatId).forEach {
if (it.type == LocationMessages.TYPE_USER_TEXT && !shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
app.telegramHelper.editTextLocation(shareInfo, it)
app.locationMessages.removeBufferedMessage(it)
} else if (it.type == LocationMessages.TYPE_USER_MAP && !shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
app.telegramHelper.editMapLocation(shareInfo, it)
app.locationMessages.removeBufferedMessage(it)
}
if (shareInfo.pendingTdLib >= 10) {
return@loop
}
}
}
}
}
private fun shareLocationMessages(location: Location, userId: Int) {
val chatsShareInfo = app.settings.getChatsShareInfo()
val latitude = location.latitude
val longitude = location.longitude
val sharingMode = app.settings.currentSharingMode
val isBot = sharingMode != userId.toString()
var bufferedMessagesFull = false
val type = when (app.settings.shareTypeValue) {
SHARE_TYPE_MAP -> {
if (isBot) LocationMessages.TYPE_BOT_MAP else LocationMessages.TYPE_USER_MAP
}
SHARE_TYPE_TEXT -> {
if (isBot) LocationMessages.TYPE_BOT_TEXT else LocationMessages.TYPE_USER_TEXT
}
SHARE_TYPE_MAP_AND_TEXT -> {
if (isBot) LocationMessages.TYPE_BOT_BOTH else LocationMessages.TYPE_USER_BOTH
} else -> -1
}
chatsShareInfo.values.forEach { shareInfo ->
if (shareInfo.pendingTdLib >= 10) {
bufferedMessagesFull = true
}
val message = BufferMessage(
shareInfo.chatId, latitude, longitude, location.altitude, location.speed.toDouble(),
location.accuracy.toDouble(), location.bearing.toDouble(), System.currentTimeMillis(), type
)
if (type == LocationMessages.TYPE_USER_MAP || type == LocationMessages.TYPE_BOT_MAP) {
prepareMapMessage(shareInfo, message, isBot, sharingMode)
} else if (type == LocationMessages.TYPE_USER_TEXT || type == LocationMessages.TYPE_BOT_TEXT) {
prepareTextMessage(shareInfo, message, isBot, sharingMode)
} else if (type == LocationMessages.TYPE_USER_BOTH || type == LocationMessages.TYPE_BOT_BOTH) {
prepareMapAndTextMessage(shareInfo, message, isBot, sharingMode)
}
}
if (bufferedMessagesFull) {
checkNetworkType()
}
}
private fun prepareTextMessage(shareInfo: TelegramSettings.ShareChatInfo,message: BufferMessage,isBot:Boolean, sharingMode: String) {
log.debug("prepareTextMessage $message")
shareInfo.collectedMessages++
if (shareInfo.currentTextMessageId == -1L) {
if (shareInfo.pendingTextMessage) {
app.locationMessages.addBufferedMessage(message)
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_TEXT)
} else {
app.telegramHelper.sendNewTextLocation(shareInfo, message)
}
}
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_TEXT)
} else {
if (shareInfo.pendingTdLib < 10) {
app.telegramHelper.editTextLocation(shareInfo, message)
} else {
app.locationMessages.addBufferedMessage(message)
}
}
}
}
private fun prepareMapMessage(shareInfo: TelegramSettings.ShareChatInfo,message: BufferMessage,isBot:Boolean, sharingMode: String) {
log.debug("prepareMapMessage $message")
shareInfo.collectedMessages++
if (shareInfo.currentMapMessageId == -1L) {
if (shareInfo.pendingMapMessage) {
app.locationMessages.addBufferedMessage(message)
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP)
} else {
app.telegramHelper.sendNewMapLocation(shareInfo, message)
}
}
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP)
} else {
if (shareInfo.pendingTdLib < 10) {
app.telegramHelper.editMapLocation(shareInfo, message)
} else {
shareInfo.collectedMessages++
app.locationMessages.addBufferedMessage(message)
}
}
}
}
private fun prepareMapAndTextMessage(shareInfo: TelegramSettings.ShareChatInfo, message: BufferMessage, isBot:Boolean, sharingMode: String) {
log.debug("prepareMapAndTextMessage $message")
shareInfo.collectedMessages++
if (shareInfo.pendingMapMessage || shareInfo.pendingTextMessage || shareInfo.pendingTdLib >= 10) {
app.locationMessages.addBufferedMessage(message)
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP_AND_TEXT)
} else {
if (shareInfo.currentMapMessageId == -1L) {
app.telegramHelper.sendNewMapLocation(shareInfo, message)
} else {
app.telegramHelper.editMapLocation(shareInfo, message)
}
if (shareInfo.currentTextMessageId == -1L) {
app.telegramHelper.sendNewTextLocation(shareInfo, message)
} else {
app.telegramHelper.editTextLocation(shareInfo, message)
}
}
}
}
private fun checkNetworkType(){
if (app.isInternetConnectionAvailable) {
val networkType = when {
app.isWifiConnected -> TdApi.NetworkTypeWiFi()
app.isMobileConnected -> TdApi.NetworkTypeMobile()
else -> TdApi.NetworkTypeOther()
}
app.telegramHelper.networkChange(networkType)
}
}
private fun sendLocationToBot(locationMessage: BufferMessage, sharingMode: String, shareInfo: TelegramSettings.ShareChatInfo, shareType: String) {
if (app.isInternetConnectionAvailable) {
log.debug("sendLocationToBot $locationMessage")
val url = getDeviceSharingUrl(locationMessage, sharingMode)
AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false,
object : AndroidNetworkUtils.OnRequestResultListener {
override fun onResult(result: String?) {
val chatsShareInfo = app.settings.getChatsShareInfo()
val success = checkResultAndUpdateShareInfoSuccessfulSendTime(result, chatsShareInfo)
val osmandBotId = app.telegramHelper.getOsmandBot()?.id ?: -1
val device = app.settings.getCurrentSharingDevice()
if (success && shareInfo.shouldSendViaBotMessage && osmandBotId != -1 && device != null) {
app.telegramHelper.sendViaBotLocationMessage(osmandBotId, shareInfo, TdApi.Location(locationMessage.lat, locationMessage.lon), device, shareType)
shareInfo.shouldSendViaBotMessage = false
}
}
})
}
}
private fun getDeviceSharingUrl(loc: BufferMessage, sharingMode: String): String {
val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.lat}&lon=${loc.lon}"
val builder = StringBuilder(url) val builder = StringBuilder(url)
if (loc.hasBearing() && loc.bearing != 0.0f) { if (loc.bearing != 0.0) {
builder.append("&azi=${loc.bearing}") builder.append("&azi=${loc.bearing}")
} }
if (loc.hasSpeed() && loc.speed != 0.0f) { if (loc.speed != 0.0) {
builder.append("&spd=${loc.speed}") builder.append("&spd=${loc.speed}")
} }
if (loc.hasAltitude() && loc.altitude != 0.0) { if (loc.altitude != 0.0) {
builder.append("&alt=${loc.altitude}") builder.append("&alt=${loc.altitude}")
} }
if (loc.hasAccuracy() && loc.accuracy != 0.0f) { if (loc.hdop != 0.0) {
builder.append("&hdop=${loc.accuracy}") builder.append("&hdop=${loc.hdop}")
} }
return builder.toString() return builder.toString()
} }
private fun updateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>) { private fun checkResultAndUpdateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>):Boolean {
if (result != null) { if (result != null) {
try { try {
val jsonResult = JSONObject(result) val jsonResult = JSONObject(result)
@ -186,22 +360,12 @@ class ShareLocationHelper(private val app: TelegramApplication) {
chatsShareInfo.forEach { (_, shareInfo) -> chatsShareInfo.forEach { (_, shareInfo) ->
shareInfo.lastSuccessfulSendTimeMs = currentTime shareInfo.lastSuccessfulSendTimeMs = currentTime
} }
return true
} }
} catch (e: JSONException) { } catch (e: JSONException) {
} }
} }
} return false
private fun checkAndSendViaBotMessages(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: TdApi.Location, osmandBot: TdApi.User) {
val device = app.settings.getCurrentSharingDevice()
if (device != null) {
chatsShareInfo.forEach { (_, shareInfo) ->
if (shareInfo.shouldSendViaBotMessage) {
app.telegramHelper.sendViaBotLocationMessage(osmandBot.id, shareInfo, location, device,app.settings.shareTypeValue)
shareInfo.shouldSendViaBotMessage = false
}
}
}
} }
private fun refreshNotification() { private fun refreshNotification() {
@ -216,4 +380,4 @@ class ShareLocationHelper(private val app: TelegramApplication) {
const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC - 1 const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC - 1
const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC + 1 const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC + 1
} }
} }

View file

@ -8,10 +8,11 @@ import net.osmand.aidl.map.ALatLon
import net.osmand.aidl.maplayer.point.AMapPoint import net.osmand.aidl.maplayer.point.AMapPoint
import net.osmand.telegram.R import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation
import net.osmand.telegram.helpers.TelegramUiHelper.ListItem import net.osmand.telegram.helpers.TelegramUiHelper.ListItem
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation
import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
import java.io.File import java.io.File
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -62,7 +63,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
execOsmandApi { execOsmandApi {
val messages = telegramHelper.getMessages() val messages = telegramHelper.getMessages()
for (message in messages) { for (message in messages) {
val date = telegramHelper.getLastUpdatedTime(message) val date = OsmandLocationUtils.getLastUpdatedTime(message)
val messageShowingTime = System.currentTimeMillis() / 1000 - date val messageShowingTime = System.currentTimeMillis() / 1000 - date
if (messageShowingTime > app.settings.locHistoryTime) { if (messageShowingTime > app.settings.locHistoryTime) {
removeMapPoint(message.chatId, message) removeMapPoint(message.chatId, message)
@ -78,9 +79,9 @@ class ShowLocationHelper(private val app: TelegramApplication) {
val chatId = message.chatId val chatId = message.chatId
val chatTitle = telegramHelper.getChat(message.chatId)?.title val chatTitle = telegramHelper.getChat(message.chatId)?.title
val content = message.content val content = message.content
val date = telegramHelper.getLastUpdatedTime(message) val date = OsmandLocationUtils.getLastUpdatedTime(message)
val stale = System.currentTimeMillis() / 1000 - date > app.settings.staleLocTime val stale = System.currentTimeMillis() / 1000 - date > app.settings.staleLocTime
if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserTextLocation && content.isValid()))) { if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserLocation && content.isValid()))) {
var userName = "" var userName = ""
var photoPath: String? = null var photoPath: String? = null
val user = telegramHelper.getUser(message.senderUserId) val user = telegramHelper.getUser(message.senderUserId)
@ -105,7 +106,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
val params = generatePointParams(photoPath, stale) val params = generatePointParams(photoPath, stale)
val aLatLon = when (content) { val aLatLon = when (content) {
is TdApi.MessageLocation -> ALatLon(content.location.latitude, content.location.longitude) is TdApi.MessageLocation -> ALatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> ALatLon(content.lat, content.lon) is MessageUserLocation -> ALatLon(content.lat, content.lon)
else -> null else -> null
} }
if (aLatLon != null) { if (aLatLon != null) {
@ -250,7 +251,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
private fun removeMapPoint(chatId: Long, message: TdApi.Message) { private fun removeMapPoint(chatId: Long, message: TdApi.Message) {
val content = message.content val content = message.content
if (content is TdApi.MessageLocation || content is MessageUserTextLocation) { if (content is TdApi.MessageLocation || content is MessageUserLocation) {
osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}") osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}")
} else if (content is MessageOsmAndBotLocation) { } else if (content is MessageOsmAndBotLocation) {
osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${content.name}") osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${content.name}")

View file

@ -1,23 +1,25 @@
package net.osmand.telegram.helpers package net.osmand.telegram.helpers
import android.text.TextUtils import android.text.TextUtils
import net.osmand.Location
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.SHARE_TYPE_MAP import net.osmand.telegram.*
import net.osmand.telegram.SHARE_TYPE_MAP_AND_TEXT
import net.osmand.telegram.SHARE_TYPE_TEXT
import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.* import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.*
import net.osmand.telegram.utils.BASE_SHARING_URL
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_DIR import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_DIR
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_EXT import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_EXT
import net.osmand.util.GeoPointParserUtil import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.utils.OsmandLocationUtils.DEVICE_PREFIX
import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation
import net.osmand.telegram.utils.OsmandLocationUtils.USER_TEXT_LOCATION_TITLE
import net.osmand.telegram.utils.OsmandLocationUtils.getLastUpdatedTime
import net.osmand.telegram.utils.OsmandLocationUtils.parseOsmAndBotLocation
import net.osmand.telegram.utils.OsmandLocationUtils.parseOsmAndBotLocationContent
import net.osmand.telegram.utils.OsmandLocationUtils.parseTextLocation
import org.drinkless.td.libcore.telegram.Client import org.drinkless.td.libcore.telegram.Client
import org.drinkless.td.libcore.telegram.Client.ResultHandler import org.drinkless.td.libcore.telegram.Client.ResultHandler
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
import org.drinkless.td.libcore.telegram.TdApi.AuthorizationState import org.drinkless.td.libcore.telegram.TdApi.AuthorizationState
import java.io.File import java.io.File
import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -36,32 +38,6 @@ class TelegramHelper private constructor() {
private const val IGNORED_ERROR_CODE = 406 private const val IGNORED_ERROR_CODE = 406
private const val MESSAGE_CANNOT_BE_EDITED_ERROR_CODE = 5 private const val MESSAGE_CANNOT_BE_EDITED_ERROR_CODE = 5
private const val DEVICE_PREFIX = "Device: "
private const val LOCATION_PREFIX = "Location: "
private const val LAST_LOCATION_PREFIX = "Last location: "
private const val UPDATED_PREFIX = "Updated: "
private const val USER_TEXT_LOCATION_TITLE = "\uD83D\uDDFA OsmAnd sharing:"
private const val SHARING_LINK = "https://play.google.com/store/apps/details?id=net.osmand.telegram"
private const val ALTITUDE_PREFIX = "Altitude: "
private const val SPEED_PREFIX = "Speed: "
private const val HDOP_PREFIX = "Horizontal precision: "
private const val NOW = "now"
private const val FEW_SECONDS_AGO = "few seconds ago"
private const val SECONDS_AGO_SUFFIX = " seconds ago"
private const val MINUTES_AGO_SUFFIX = " minutes ago"
private const val HOURS_AGO_SUFFIX = " hours ago"
private const val UTC_FORMAT_SUFFIX = " UTC"
private val UTC_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
private val UTC_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
// min and max values for the Telegram API // min and max values for the Telegram API
const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61 const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61
const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // one day const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // one day
@ -172,6 +148,8 @@ class TelegramHelper private constructor() {
fun getCurrentUser() = currentUser fun getCurrentUser() = currentUser
fun getCurrentUserId() = currentUser?.id ?: -1
fun getUserMessage(user: TdApi.User) = fun getUserMessage(user: TdApi.User) =
usersLocationMessages.values.firstOrNull { it.senderUserId == user.id } usersLocationMessages.values.firstOrNull { it.senderUserId == user.id }
@ -216,15 +194,6 @@ class TelegramHelper private constructor() {
return chat.type is TdApi.ChatTypeSupergroup || chat.type is TdApi.ChatTypeBasicGroup return chat.type is TdApi.ChatTypeSupergroup || chat.type is TdApi.ChatTypeBasicGroup
} }
fun getLastUpdatedTime(message: TdApi.Message): Int {
val content = message.content
return when (content) {
is MessageOsmAndBotLocation -> content.lastUpdated
is MessageUserTextLocation -> content.lastUpdated
else -> Math.max(message.editDate, message.date)
}
}
fun isPrivateChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypePrivate fun isPrivateChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypePrivate
fun isSecretChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypeSecret fun isSecretChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypeSecret
@ -324,7 +293,7 @@ class TelegramHelper private constructor() {
try { try {
log.debug("Loading native tdlib...") log.debug("Loading native tdlib...")
System.loadLibrary("tdjni") System.loadLibrary("tdjni")
Client.setLogVerbosityLevel(0) Client.setLogVerbosityLevel(1)
libraryLoaded = true libraryLoaded = true
} catch (e: Throwable) { } catch (e: Throwable) {
log.error("Failed to load tdlib", e) log.error("Failed to load tdlib", e)
@ -349,6 +318,12 @@ class TelegramHelper private constructor() {
} }
} }
fun networkChange(networkType: TdApi.NetworkType) {
client?.send(TdApi.SetNetworkType(networkType)) { obj ->
log.debug(obj)
}
}
fun isInit() = client != null && haveAuthorization fun isInit() = client != null && haveAuthorization
fun getUserPhotoPath(user: TdApi.User?) = when { fun getUserPhotoPath(user: TdApi.User?) = when {
@ -421,7 +396,7 @@ class TelegramHelper private constructor() {
private fun isUserLocationMessage(message: TdApi.Message): Boolean { private fun isUserLocationMessage(message: TdApi.Message): Boolean {
val cont = message.content val cont = message.content
return (cont is MessageUserTextLocation || cont is TdApi.MessageLocation) return (cont is MessageUserLocation || cont is TdApi.MessageLocation)
} }
private fun hasLocalUserPhoto(user: TdApi.User): Boolean { private fun hasLocalUserPhoto(user: TdApi.User): Boolean {
@ -713,10 +688,6 @@ class TelegramHelper private constructor() {
} }
} }
fun loadMessage(chatId: Long, messageId: Long) {
requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage)
}
private fun requestMessage(chatId: Long, messageId: Long, onComplete: (TdApi.Message) -> Unit) { private fun requestMessage(chatId: Long, messageId: Long, onComplete: (TdApi.Message) -> Unit) {
client?.send(TdApi.GetMessage(chatId, messageId)) { obj -> client?.send(TdApi.GetMessage(chatId, messageId)) { obj ->
if (obj is TdApi.Message) { if (obj is TdApi.Message) {
@ -728,7 +699,10 @@ class TelegramHelper private constructor() {
private fun addNewMessage(message: TdApi.Message) { private fun addNewMessage(message: TdApi.Message) {
lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, Math.max(message.date, message.editDate)) lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, Math.max(message.date, message.editDate))
if (message.isAppropriate()) { if (message.isAppropriate()) {
log.debug("addNewMessage: $message") if (message.isOutgoing) {
return
}
log.debug("addNewMessage: ${message.id}")
val fromBot = isOsmAndBot(message.senderUserId) val fromBot = isOsmAndBot(message.senderUserId)
val viaBot = isOsmAndBot(message.viaBotUserId) val viaBot = isOsmAndBot(message.viaBotUserId)
val oldContent = message.content val oldContent = message.content
@ -741,21 +715,15 @@ class TelegramHelper private constructor() {
} else if (oldContent is TdApi.MessageLocation && (fromBot || viaBot)) { } else if (oldContent is TdApi.MessageLocation && (fromBot || viaBot)) {
message.content = parseOsmAndBotLocation(message) message.content = parseOsmAndBotLocation(message)
} }
if (message.isOutgoing) { removeOldMessages(message, fromBot, viaBot)
outgoingMessagesListeners.forEach { val oldMessage = usersLocationMessages.values.firstOrNull { getSenderMessageId(it) == getSenderMessageId(message) && !fromBot && !viaBot }
it.onUpdateMessages(listOf(message)) val hasNewerMessage = oldMessage != null && (Math.max(message.editDate, message.date) < Math.max(oldMessage.editDate, oldMessage.date))
} if (!hasNewerMessage) {
} else { usersLocationMessages[message.id] = message
removeOldMessages(message, fromBot, viaBot) }
val oldMessage = usersLocationMessages.values.firstOrNull { getSenderMessageId(it) == getSenderMessageId(message) && !fromBot && !viaBot } incomingMessagesListeners.forEach {
val hasNewerMessage = oldMessage != null && (Math.max(message.editDate, message.date) < Math.max(oldMessage.editDate, oldMessage.date)) if (!hasNewerMessage || it is TelegramService) {
if (!hasNewerMessage) { it.onReceiveChatLocationMessages(message.chatId, message)
usersLocationMessages[message.id] = message
}
incomingMessagesListeners.forEach {
if (!hasNewerMessage || it is SavingTracksDbHelper) {
it.onReceiveChatLocationMessages(message.chatId, message)
}
} }
} }
} }
@ -779,34 +747,13 @@ class TelegramHelper private constructor() {
} }
} }
} else if (sameSender && isUserLocationMessage(message) && isUserLocationMessage(newMessage) } else if (sameSender && isUserLocationMessage(message) && isUserLocationMessage(newMessage)
&& Math.max(newMessage.editDate, newMessage.date) > Math.max(message.editDate, message.date)) { && Math.max(newMessage.editDate, newMessage.date) >= Math.max(message.editDate, message.date)) {
iterator.remove() iterator.remove()
} }
} }
} }
} }
/**
* @chatId Id of the chat
* @livePeriod Period for which the location can be updated, in seconds; should be between 60 and 86400 for a live location and 0 otherwise.
* @latitude Latitude of the location
* @longitude Longitude of the location
*/
fun sendLiveLocationMessage(chatsShareInfo:Map<Long, TelegramSettings.ShareChatInfo>, latitude: Double, longitude: Double): Boolean {
if (!requestingActiveLiveLocationMessages && haveAuthorization) {
if (needRefreshActiveLiveLocationMessages) {
getActiveLiveLocationMessages {
sendLiveLocationImpl(chatsShareInfo, latitude, longitude)
}
needRefreshActiveLiveLocationMessages = false
} else {
sendLiveLocationImpl(chatsShareInfo, latitude, longitude)
}
return true
}
return false
}
fun stopSendingLiveLocationToChat(shareInfo: TelegramSettings.ShareChatInfo) { fun stopSendingLiveLocationToChat(shareInfo: TelegramSettings.ShareChatInfo) {
if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) { if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) {
client?.send( client?.send(
@ -854,10 +801,7 @@ class TelegramHelper private constructor() {
} }
} }
private fun recreateLiveLocationMessage( private fun recreateLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) {
shareInfo: TelegramSettings.ShareChatInfo,
content: TdApi.InputMessageContent
) {
if (shareInfo.chatId != -1L) { if (shareInfo.chatId != -1L) {
val array = LongArray(1) val array = LongArray(1)
if (content is TdApi.InputMessageLocation) { if (content is TdApi.InputMessageLocation) {
@ -889,71 +833,67 @@ class TelegramHelper private constructor() {
private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) { private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) {
needRefreshActiveLiveLocationMessages = true needRefreshActiveLiveLocationMessages = true
log.debug("sendNewLiveLocationMessage") log.debug("sendNewLiveLocationMessage")
client?.send( client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj ->
TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj -> if (content is TdApi.InputMessageText) {
handleMapLocationMessageUpdate(obj, shareInfo) handleTextLocationMessageUpdate(obj, shareInfo)
} } else if (content is TdApi.InputMessageLocation) {
} handleMapLocationMessageUpdate(obj, shareInfo)
private fun sendLiveLocationImpl(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, latitude: Double, longitude: Double) {
val location = TdApi.Location(latitude, longitude)
chatsShareInfo.forEach { (chatId, shareInfo) ->
if (shareInfo.getChatLiveMessageExpireTime() <= 0) {
return@forEach
}
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)
val msgId = shareInfo.currentMapMessageId
val timeAfterLastSendMessage = ((System.currentTimeMillis() / 1000) - shareInfo.lastSendMapMessageTime)
log.debug("sendLiveLocationImpl - $msgId pendingMapMessage ${shareInfo.pendingMapMessage}")
if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousMapMessage) {
recreateLiveLocationMessage(shareInfo, content)
shareInfo.shouldDeletePreviousMapMessage = false
shareInfo.currentMapMessageId = -1
} else {
log.debug("EditMessageLiveLocation - $msgId")
client?.send(
TdApi.EditMessageLiveLocation(chatId, msgId, null, location)) { obj ->
handleMapLocationMessageUpdate(obj, shareInfo)
}
}
} else if (!shareInfo.pendingMapMessage || shareInfo.pendingMapMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) {
sendNewLiveLocationMessage(shareInfo, content)
} }
} }
} }
fun sendLiveLocationText(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: Location) { fun sendNewTextLocation(shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.BufferMessage) {
chatsShareInfo.forEach { (chatId, shareInfo) -> shareInfo.updateTextMessageId = 1
if (shareInfo.getChatLiveMessageExpireTime() <= 0) { val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, location)
return@forEach if (!shareInfo.pendingTextMessage) {
shareInfo.pendingTextMessage = true
shareInfo.pendingTdLib++
log.error("sendNewTextLocation ${shareInfo.pendingTdLib}")
client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj ->
handleTextLocationMessageUpdate(obj, shareInfo)
} }
val msgId = shareInfo.currentTextMessageId }
if (msgId == -1L) { }
shareInfo.updateTextMessageId = 1
fun editTextLocation(shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.BufferMessage) {
val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, location)
if (shareInfo.currentTextMessageId!=-1L) {
shareInfo.pendingTdLib++
log.info("editTextLocation ${shareInfo.currentTextMessageId} pendingTdLib: ${shareInfo.pendingTdLib}")
client?.send(TdApi.EditMessageText(shareInfo.chatId, shareInfo.currentTextMessageId, null, content)) { obj ->
handleTextLocationMessageUpdate(obj, shareInfo)
} }
val content = getTextMessageContent(shareInfo.updateTextMessageId, location) }
val timeAfterLastSendMessage = ((System.currentTimeMillis() / 1000) - shareInfo.lastSendTextMessageTime) }
log.debug("sendLiveLocationText - $msgId pendingMapMessage ${shareInfo.pendingTextMessage}")
if (msgId != -1L) { fun sendNewMapLocation(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.BufferMessage) {
if (shareInfo.shouldDeletePreviousTextMessage) { needRefreshActiveLiveLocationMessages = true
recreateLiveLocationMessage(shareInfo, content) val location = TdApi.Location(locationMessage.lat, locationMessage.lon)
shareInfo.shouldDeletePreviousTextMessage = false val livePeriod =
} else { if (shareInfo.currentMessageLimit > (shareInfo.start + MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC)) {
client?.send(TdApi.EditMessageText(chatId, msgId, null, content)) { obj -> MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC
handleTextLocationMessageUpdate(obj, shareInfo) } else {
} shareInfo.livePeriod.toInt()
} }
} else if (!shareInfo.pendingTextMessage || shareInfo.pendingTextMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) { val content = TdApi.InputMessageLocation(location, livePeriod)
client?.send(TdApi.SendMessage(chatId, 0, false, false, null, content)) { obj -> if (!shareInfo.pendingMapMessage) {
handleTextLocationMessageUpdate(obj, shareInfo) shareInfo.pendingMapMessage = true
} shareInfo.pendingTdLib++
log.error("sendNewMapLocation ${shareInfo.pendingTdLib}")
client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj ->
handleMapLocationMessageUpdate(obj, shareInfo)
}
}
}
fun editMapLocation(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.BufferMessage) {
needRefreshActiveLiveLocationMessages = true
val location = TdApi.Location(locationMessage.lat, locationMessage.lon)
if (shareInfo.currentMapMessageId!=-1L) {
shareInfo.pendingTdLib++
log.info("editMapLocation ${shareInfo.currentMapMessageId} pendingTdLib: ${shareInfo.pendingTdLib}")
client?.send(TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, location)) { obj ->
handleMapLocationMessageUpdate(obj, shareInfo)
} }
} }
} }
@ -961,6 +901,7 @@ class TelegramHelper private constructor() {
private fun handleMapLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) { private fun handleMapLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) {
when (obj.constructor) { when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> { TdApi.Error.CONSTRUCTOR -> {
log.debug("handleMapLocationMessageUpdate - ERROR $obj")
val error = obj as TdApi.Error val error = obj as TdApi.Error
needRefreshActiveLiveLocationMessages = true needRefreshActiveLiveLocationMessages = true
if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) { if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) {
@ -978,6 +919,8 @@ class TelegramHelper private constructor() {
obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> { obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
shareInfo.hasSharingError = true shareInfo.hasSharingError = true
needRefreshActiveLiveLocationMessages = true needRefreshActiveLiveLocationMessages = true
shareInfo.pendingMapMessage = false
log.debug("handleTextLocationMessageUpdate - MessageSendingStateFailed")
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(-1, "Map location message ${obj.id} failed to send") it.onSendLiveLocationError(-1, "Map location message ${obj.id} failed to send")
} }
@ -986,9 +929,14 @@ class TelegramHelper private constructor() {
shareInfo.pendingMapMessage = true shareInfo.pendingMapMessage = true
shareInfo.lastSendMapMessageTime = obj.date shareInfo.lastSendMapMessageTime = obj.date
log.debug("handleMapLocationMessageUpdate - MessageSendingStatePending") log.debug("handleMapLocationMessageUpdate - MessageSendingStatePending")
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj))
}
} }
else -> { else -> {
shareInfo.hasSharingError = false shareInfo.hasSharingError = false
shareInfo.pendingMapMessage = false
log.debug("handleMapLocationMessageUpdate - MessageSendingStateSuccess")
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj)) it.onUpdateMessages(listOf(obj))
} }
@ -1002,6 +950,7 @@ class TelegramHelper private constructor() {
private fun handleTextLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) { private fun handleTextLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) {
when (obj.constructor) { when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> { TdApi.Error.CONSTRUCTOR -> {
log.debug("handleTextLocationMessageUpdate - ERROR")
val error = obj as TdApi.Error val error = obj as TdApi.Error
if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) { if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) {
shareInfo.shouldDeletePreviousTextMessage = true shareInfo.shouldDeletePreviousTextMessage = true
@ -1017,7 +966,9 @@ class TelegramHelper private constructor() {
when { when {
obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> { obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
shareInfo.hasSharingError = true shareInfo.hasSharingError = true
shareInfo.pendingTextMessage = false
needRefreshActiveLiveLocationMessages = true needRefreshActiveLiveLocationMessages = true
log.debug("handleTextLocationMessageUpdate - MessageSendingStateFailed")
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(-1, "Text location message ${obj.id} failed to send") it.onSendLiveLocationError(-1, "Text location message ${obj.id} failed to send")
} }
@ -1026,9 +977,14 @@ class TelegramHelper private constructor() {
shareInfo.pendingTextMessage = true shareInfo.pendingTextMessage = true
shareInfo.lastSendTextMessageTime = obj.date shareInfo.lastSendTextMessageTime = obj.date
log.debug("handleTextLocationMessageUpdate - MessageSendingStatePending") log.debug("handleTextLocationMessageUpdate - MessageSendingStatePending")
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj))
}
} }
else -> { else -> {
shareInfo.hasSharingError = false shareInfo.hasSharingError = false
shareInfo.pendingTextMessage = false
log.debug("handleTextLocationMessageUpdate - MessageSendingStateSuccess")
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj)) it.onUpdateMessages(listOf(obj))
} }
@ -1039,54 +995,6 @@ class TelegramHelper private constructor() {
} }
} }
private fun formatLocation(sig: Location): String {
return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude)
}
private fun formatFullTime(ti: Long): String {
val dt = Date(ti)
return UTC_DATE_FORMAT.format(dt) + " " + UTC_TIME_FORMAT.format(dt) + " UTC"
}
private fun getTextMessageContent(updateId: Int, location: Location): TdApi.InputMessageText {
val entities = mutableListOf<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.latitude}&lon=${location.longitude}")))
builder.append("$locationMessage\n")
if (location.hasAltitude() && location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.hasSpeed() && location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hasAccuracy() && location.accuracy != 0.0f && location.speed == 0.0f) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.accuracy.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
} else {
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
}
val textMessage = builder.toString().trim()
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
}
fun logout(): Boolean { fun logout(): Boolean {
return if (libraryLoaded) { return if (libraryLoaded) {
haveAuthorization = false haveAuthorization = false
@ -1215,188 +1123,6 @@ class TelegramHelper private constructor() {
} }
} }
private fun parseOsmAndBotLocation(message: TdApi.Message): MessageOsmAndBotLocation {
val messageLocation = message.content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = getOsmAndBotDeviceName(message)
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = getLastUpdatedTime(message)
}
}
private fun parseOsmAndBotLocationContent(oldContent:MessageOsmAndBotLocation, content: TdApi.MessageContent): MessageOsmAndBotLocation {
val messageLocation = content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = oldContent.name
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = (System.currentTimeMillis() / 1000).toInt()
}
}
private fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean = true): MessageLocation {
val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserTextLocation()
var locationNA = false
for (s in text.text.lines()) {
when {
s.startsWith(DEVICE_PREFIX) -> {
if (res is MessageOsmAndBotLocation) {
res.name = s.removePrefix(DEVICE_PREFIX)
}
}
s.startsWith(LOCATION_PREFIX) || s.startsWith(LAST_LOCATION_PREFIX) -> {
var locStr: String
var parse = true
if (s.startsWith(LAST_LOCATION_PREFIX)) {
locStr = s.removePrefix(LAST_LOCATION_PREFIX)
if (!locationNA) {
parse = false
}
} else {
locStr = s.removePrefix(LOCATION_PREFIX)
if (locStr.trim() == "n/a") {
locationNA = true
parse = false
}
}
if (parse) {
try {
val urlTextEntity = text.entities.firstOrNull { it.type is TdApi.TextEntityTypeTextUrl }
if (urlTextEntity != null && urlTextEntity.offset == text.text.indexOf(locStr)) {
val url = (urlTextEntity.type as TdApi.TextEntityTypeTextUrl).url
val point: GeoPointParserUtil.GeoParsedPoint? = GeoPointParserUtil.parse(url)
if (point != null) {
res.lat = point.latitude
res.lon = point.longitude
}
} else {
val (latS, lonS) = locStr.split(" ")
res.lat = latS.dropLast(1).toDouble()
res.lon = lonS.toDouble()
val timeIndex = locStr.indexOf("(")
if (timeIndex != -1) {
val updatedS = locStr.substring(timeIndex, locStr.length)
res.lastUpdated = (parseTime(updatedS.removePrefix("(").removeSuffix(")")) / 1000).toInt()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
s.startsWith(ALTITUDE_PREFIX) -> {
val altStr = s.removePrefix(ALTITUDE_PREFIX)
try {
val alt = altStr.split(" ").first()
res.altitude = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(SPEED_PREFIX) -> {
val altStr = s.removePrefix(SPEED_PREFIX)
try {
val alt = altStr.split(" ").first()
res.speed = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(HDOP_PREFIX) -> {
val altStr = s.removePrefix(HDOP_PREFIX)
try {
val alt = altStr.split(" ").first()
res.hdop = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(UPDATED_PREFIX) -> {
if (res.lastUpdated == 0) {
val updatedStr = s.removePrefix(UPDATED_PREFIX)
val endIndex = updatedStr.indexOf("(")
val updatedS = updatedStr.substring(0, if (endIndex != -1) endIndex else updatedStr.length)
val parsedTime = (parseTime(updatedS.trim()) / 1000).toInt()
val currentTime = (System.currentTimeMillis() / 1000) - 1
res.lastUpdated = if (parsedTime < currentTime) parsedTime else currentTime.toInt()
}
}
}
}
return res
}
private fun parseTime(timeS: String): Long {
try {
when {
timeS.endsWith(FEW_SECONDS_AGO) -> return System.currentTimeMillis() - 5000
timeS.endsWith(SECONDS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX)
return System.currentTimeMillis() - locStr.toLong() * 1000
}
timeS.endsWith(MINUTES_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX)
val minutes = locStr.toLong()
return System.currentTimeMillis() - minutes * 60 * 1000
}
timeS.endsWith(HOURS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(HOURS_AGO_SUFFIX)
val hours = locStr.toLong()
return (System.currentTimeMillis() - hours * 60 * 60 * 1000)
}
timeS.endsWith(UTC_FORMAT_SUFFIX) -> {
val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX)
val (latS, lonS) = locStr.split(" ")
val date = UTC_DATE_FORMAT.parse(latS)
val time = UTC_TIME_FORMAT.parse(lonS)
val res = date.time + time.time
return res
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return 0
}
abstract class MessageLocation : TdApi.MessageContent() {
var lat: Double = Double.NaN
internal set
var lon: Double = Double.NaN
internal set
var lastUpdated: Int = 0
internal set
var speed: Double = 0.0
internal set
var altitude: Double = 0.0
internal set
var hdop: Double = 0.0
internal set
override fun getConstructor() = -1
abstract fun isValid(): Boolean
}
class MessageOsmAndBotLocation : MessageLocation() {
var name: String = ""
internal set
override fun isValid() = name != "" && lat != Double.NaN && lon != Double.NaN
}
class MessageUserTextLocation : MessageLocation() {
override fun isValid() = lat != Double.NaN && lon != Double.NaN
}
class OrderedChat internal constructor(internal val order: Long, internal val chatId: Long, internal val isChannel: Boolean) : Comparable<OrderedChat> { class OrderedChat internal constructor(internal val order: Long, internal val chatId: Long, internal val isChannel: Boolean) : Comparable<OrderedChat> {
override fun compareTo(other: OrderedChat): Int { override fun compareTo(other: OrderedChat): Int {
@ -1583,26 +1309,10 @@ class TelegramHelper private constructor() {
} }
} }
} }
TdApi.UpdateMessageEdited.CONSTRUCTOR -> {
val updateMessageEdited = obj as TdApi.UpdateMessageEdited
val message = usersLocationMessages[updateMessageEdited.messageId]
if (message == null) {
updateMessageEdited.apply {
requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage)
}
} else {
synchronized(message) {
message.editDate = updateMessageEdited.editDate
lastTelegramUpdateTime = Math.max(message.date, message.editDate)
}
incomingMessagesListeners.forEach {
it.updateLocationMessages()
}
}
}
TdApi.UpdateMessageContent.CONSTRUCTOR -> { TdApi.UpdateMessageContent.CONSTRUCTOR -> {
val updateMessageContent = obj as TdApi.UpdateMessageContent val updateMessageContent = obj as TdApi.UpdateMessageContent
val message = usersLocationMessages[updateMessageContent.messageId] val message = usersLocationMessages[updateMessageContent.messageId]
log.debug("UpdateMessageContent " + updateMessageContent.messageId)
if (message == null) { if (message == null) {
updateMessageContent.apply { updateMessageContent.apply {
requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage) requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage)
@ -1622,7 +1332,6 @@ class TelegramHelper private constructor() {
newContent newContent
} }
} }
log.debug("UpdateMessageContent " + message.senderUserId)
incomingMessagesListeners.forEach { incomingMessagesListeners.forEach {
it.onReceiveChatLocationMessages(message.chatId, message) it.onReceiveChatLocationMessages(message.chatId, message)
} }
@ -1630,6 +1339,7 @@ class TelegramHelper private constructor() {
} }
TdApi.UpdateNewMessage.CONSTRUCTOR -> { TdApi.UpdateNewMessage.CONSTRUCTOR -> {
addNewMessage((obj as TdApi.UpdateNewMessage).message) addNewMessage((obj as TdApi.UpdateNewMessage).message)
log.debug("UpdateNewMessage " + obj.message.id)
} }
TdApi.UpdateMessageMentionRead.CONSTRUCTOR -> { TdApi.UpdateMessageMentionRead.CONSTRUCTOR -> {
val updateChat = obj as TdApi.UpdateMessageMentionRead val updateChat = obj as TdApi.UpdateMessageMentionRead
@ -1736,9 +1446,9 @@ class TelegramHelper private constructor() {
} }
} }
TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR -> { TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR -> {
val udateMessageSendSucceeded = obj as TdApi.UpdateMessageSendSucceeded val updateSucceeded = obj as TdApi.UpdateMessageSendSucceeded
val message = udateMessageSendSucceeded.message val message = updateSucceeded.message
log.debug("UpdateMessageSendSucceeded: $message") log.debug("UpdateMessageSendSucceeded: ${message.id} oldId: ${updateSucceeded.oldMessageId}")
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(message)) it.onUpdateMessages(listOf(message))
} }

View file

@ -6,9 +6,10 @@ import android.widget.ImageView
import net.osmand.data.LatLon import net.osmand.data.LatLon
import net.osmand.telegram.R import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation import net.osmand.telegram.utils.GPXUtilities.GPXFile
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.utils.GPXUtilities import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
object TelegramUiHelper { object TelegramUiHelper {
@ -65,11 +66,11 @@ object TelegramUiHelper {
val user = helper.getUser(userId) val user = helper.getUser(userId)
val message = messages.firstOrNull { it.viaBotUserId == 0 } val message = messages.firstOrNull { it.viaBotUserId == 0 }
if (message != null) { if (message != null) {
res.lastUpdated = helper.getLastUpdatedTime(message) res.lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
val content = message.content val content = message.content
if (content is TdApi.MessageLocation) { if (content is TdApi.MessageLocation) {
res.latLon = LatLon(content.location.latitude, content.location.longitude) res.latLon = LatLon(content.location.latitude, content.location.longitude)
} else if (content is MessageUserTextLocation) { } else if (content is MessageUserLocation) {
res.latLon = LatLon(content.lat, content.lon) res.latLon = LatLon(content.lat, content.lon)
} }
} }
@ -110,7 +111,7 @@ object TelegramUiHelper {
val content = message.content val content = message.content
return when (content) { return when (content) {
is MessageOsmAndBotLocation -> botMessageToLocationItem(chat, content) is MessageOsmAndBotLocation -> botMessageToLocationItem(chat, content)
is MessageUserTextLocation -> locationMessageToLocationItem(helper, chat, message) is MessageUserLocation -> locationMessageToLocationItem(helper, chat, message)
is TdApi.MessageLocation -> locationMessageToLocationItem(helper, chat, message) is TdApi.MessageLocation -> locationMessageToLocationItem(helper, chat, message)
else -> null else -> null
} }
@ -125,12 +126,12 @@ object TelegramUiHelper {
return when (content) { return when (content) {
is MessageOsmAndBotLocation -> botMessageToChatItem(helper, chat, content) is MessageOsmAndBotLocation -> botMessageToChatItem(helper, chat, content)
is TdApi.MessageLocation -> locationMessageToChatItem(helper, chat, message) is TdApi.MessageLocation -> locationMessageToChatItem(helper, chat, message)
is MessageUserTextLocation -> locationMessageToChatItem(helper, chat, message) is MessageUserLocation -> locationMessageToChatItem(helper, chat, message)
else -> null else -> null
} }
} }
fun gpxToChatItem(helper: TelegramHelper, gpx: GPXUtilities.GPXFile, simpleUserItem: Boolean): GpxChatItem? { fun gpxToChatItem(helper: TelegramHelper, gpx: GPXFile, simpleUserItem: Boolean): GpxChatItem? {
return if (simpleUserItem) gpxToUserGpxChatItem(helper, gpx) else gpxToGpxChatItem(helper, gpx) return if (simpleUserItem) gpxToUserGpxChatItem(helper, gpx) else gpxToGpxChatItem(helper, gpx)
} }
@ -165,14 +166,14 @@ object TelegramUiHelper {
name = TelegramUiHelper.getUserName(user) name = TelegramUiHelper.getUserName(user)
latLon = when (content) { latLon = when (content) {
is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude) is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> LatLon(content.lat, content.lon) is MessageUserLocation -> LatLon(content.lat, content.lon)
else -> null else -> null
} }
photoPath = helper.getUserPhotoPath(user) photoPath = helper.getUserPhotoPath(user)
grayscalePhotoPath = helper.getUserGreyPhotoPath(user) grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
placeholderId = R.drawable.img_user_picture placeholderId = R.drawable.img_user_picture
userId = message.senderUserId userId = message.senderUserId
lastUpdated = helper.getLastUpdatedTime(message) lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
} }
} }
@ -211,7 +212,7 @@ object TelegramUiHelper {
name = TelegramUiHelper.getUserName(user) name = TelegramUiHelper.getUserName(user)
latLon = when (content) { latLon = when (content) {
is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude) is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> LatLon(content.lat, content.lon) is MessageUserLocation -> LatLon(content.lat, content.lon)
else -> null else -> null
} }
if (helper.isGroup(chat)) { if (helper.isGroup(chat)) {
@ -225,13 +226,13 @@ object TelegramUiHelper {
userId = message.senderUserId userId = message.senderUserId
privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat) privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat)
chatWithBot = helper.isBot(userId) chatWithBot = helper.isBot(userId)
lastUpdated = helper.getLastUpdatedTime(message) lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
} }
} }
private fun gpxToGpxChatItem( private fun gpxToGpxChatItem(
helper: TelegramHelper, helper: TelegramHelper,
gpx: GPXUtilities.GPXFile gpx: GPXFile
): GpxChatItem? { ): GpxChatItem? {
val user = helper.getUser(gpx.userId) ?: return null val user = helper.getUser(gpx.userId) ?: return null
val chat = helper.getChat(gpx.chatId) ?: return null val chat = helper.getChat(gpx.chatId) ?: return null
@ -255,9 +256,36 @@ object TelegramUiHelper {
} }
} }
fun locationMessagesToChatItem(
helper: TelegramHelper,
messages: List<LocationMessages.LocationMessage>
): LocationMessagesChatItem? {
val message = messages.firstOrNull()
val user = helper.getUser(message?.userId ?: -1) ?: return null
val chat = helper.getChat(message?.chatId ?: -1) ?: return null
return LocationMessagesChatItem().apply {
chatId = chat.id
chatTitle = chat.title
locationMessages = messages
name = TelegramUiHelper.getUserName(user)
if (helper.isGroup(chat)) {
photoPath = helper.getUserPhotoPath(user)
groupPhotoPath = chat.photo?.small?.local?.path
} else {
photoPath = user.profilePhoto?.small?.local?.path
}
grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
placeholderId = R.drawable.img_user_picture
userId = user.id
privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat)
chatWithBot = helper.isBot(userId)
lastUpdated = (messages.maxBy { it.time }?.time ?: -1).toInt()
}
}
private fun gpxToUserGpxChatItem( private fun gpxToUserGpxChatItem(
helper: TelegramHelper, helper: TelegramHelper,
gpx: GPXUtilities.GPXFile gpx: GPXFile
): GpxChatItem? { ): GpxChatItem? {
val user = helper.getUser(gpx.userId) ?: return null val user = helper.getUser(gpx.userId) ?: return null
return GpxChatItem().apply { return GpxChatItem().apply {
@ -322,7 +350,25 @@ object TelegramUiHelper {
class GpxChatItem : ListItem() { class GpxChatItem : ListItem() {
var gpxFile: GPXUtilities.GPXFile? = null var gpxFile: GPXFile? = null
internal set
var groupPhotoPath: String? = null
internal set
var privateChat: Boolean = false
internal set
var chatWithBot: Boolean = false
internal set
override fun canBeOpenedOnMap() = latLon != null
override fun getMapPointId() = "${chatId}_$userId"
override fun getVisibleName() = chatTitle
}
class LocationMessagesChatItem : ListItem() {
var locationMessages: List<LocationMessages.LocationMessage> = emptyList()
internal set internal set
var groupPhotoPath: String? = null var groupPhotoPath: String? = null
internal set internal set

View file

@ -19,7 +19,6 @@ import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener
import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener
import net.osmand.telegram.TelegramSettings import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.helpers.SavingTracksDbHelper
import net.osmand.telegram.helpers.TelegramHelper.* import net.osmand.telegram.helpers.TelegramHelper.*
import net.osmand.telegram.helpers.TelegramUiHelper import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.helpers.TelegramUiHelper.ChatItem import net.osmand.telegram.helpers.TelegramUiHelper.ChatItem

View file

@ -19,6 +19,8 @@ import android.widget.*
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.R import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.LocationMessages
import net.osmand.telegram.helpers.LocationMessages.LocationMessage
import net.osmand.telegram.helpers.OsmandAidlHelper import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.TelegramHelper import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.helpers.TelegramHelper.* import net.osmand.telegram.helpers.TelegramHelper.*
@ -183,7 +185,6 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
settings.save() settings.save()
app.messagesDbHelper.saveMessages()
} }
override fun onDestroy() { override fun onDestroy() {
@ -291,6 +292,11 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
if (!app.showLocationHelper.showingLocation && settings.hasAnyChatToShowOnMap()) { if (!app.showLocationHelper.showingLocation && settings.hasAnyChatToShowOnMap()) {
app.showLocationHelper.startShowingLocation() app.showLocationHelper.startShowingLocation()
} }
if (app.telegramService == null) {
messages.forEach {
app.locationMessages.addNewLocationMessage(it)
}
}
} }
override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {} override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {}
@ -341,7 +347,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
fun logoutTelegram(silent: Boolean = false) { fun logoutTelegram(silent: Boolean = false) {
if (telegramHelper.getTelegramAuthorizationState() == TelegramHelper.TelegramAuthorizationState.READY) { if (telegramHelper.getTelegramAuthorizationState() == TelegramHelper.TelegramAuthorizationState.READY) {
if (app.isInternetConnectionAvailable) { if (app.isInternetConnectionAvailable) {
app.messagesDbHelper.clearMessages() app.locationMessages.clearBufferedMessages()
settings.clear() settings.clear()
telegramHelper.logout() telegramHelper.logout()
} else { } else {

View file

@ -231,7 +231,6 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
updateCurrentUserPhoto() updateCurrentUserPhoto()
telegramHelper.getActiveLiveLocationMessages(null)
updateContent() updateContent()
updateEnable = true updateEnable = true
startHandler() startHandler()
@ -599,6 +598,9 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
} }
holder.title?.text = title holder.title?.text = title
holder.icon?.setOnClickListener {
app.forceUpdateMyLocation()
}
if (holder is ChatViewHolder) { if (holder is ChatViewHolder) {
holder.description?.visibility = View.GONE holder.description?.visibility = View.GONE
if (live) { if (live) {
@ -714,6 +716,17 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
OsmandFormatter.getFormattedDuration(context!!, expiresIn, true) OsmandFormatter.getFormattedDuration(context!!, expiresIn, true)
)})" )})"
} }
holder.gpsPointsCollected?.apply {
if (shareInfo != null) {
val bufferedMessages = shareInfo.pendingTdLib + app.locationMessages.getBufferedMessagesForChat(shareInfo.chatId).size
text = "$bufferedMessages"
}
}
holder.gpsPointsSent?.apply {
if (shareInfo != null) {
text = "${shareInfo.sentMessages}"
}
}
} }
} }
@ -751,10 +764,12 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
val stopSharingDescr: TextView? = view.findViewById(R.id.stop_in) val stopSharingDescr: TextView? = view.findViewById(R.id.stop_in)
val stopSharingFirstPart: TextView? = view.findViewById(R.id.ending_in_first_part) val stopSharingFirstPart: TextView? = view.findViewById(R.id.ending_in_first_part)
val stopSharingSecondPart: TextView? = view.findViewById(R.id.ending_in_second_part) val stopSharingSecondPart: TextView? = view.findViewById(R.id.ending_in_second_part)
val gpsPointsCollected: TextView? = view.findViewById(R.id.gps_points_collected)
val gpsPointsSent: TextView? = view.findViewById(R.id.gps_points_sent)
} }
} }
interface ActionButtonsListener { interface ActionButtonsListener {
fun switchButtonsVisibility(visible: Boolean) fun switchButtonsVisibility(visible: Boolean)
} }
} }

View file

@ -21,6 +21,7 @@ import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.ui.SetTimeDialogFragment.SetTimeListAdapter.ChatViewHolder import net.osmand.telegram.ui.SetTimeDialogFragment.SetTimeListAdapter.ChatViewHolder
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandFormatter import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.utils.UiUtils import net.osmand.telegram.utils.UiUtils
import net.osmand.util.MapUtils import net.osmand.util.MapUtils
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
@ -328,7 +329,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
val message = telegramHelper.getChatMessages(itemId).firstOrNull() val message = telegramHelper.getChatMessages(itemId).firstOrNull()
val content = message?.content val content = message?.content
if (message != null && content is TdApi.MessageLocation && (location != null && content.location != null)) { if (message != null && content is TdApi.MessageLocation && (location != null && content.location != null)) {
val lastUpdated = telegramHelper.getLastUpdatedTime(message) val lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
holder.description?.visibility = View.VISIBLE holder.description?.visibility = View.VISIBLE
holder.description?.text = OsmandFormatter.getListItemLiveTimeDescr(app, lastUpdated) holder.description?.text = OsmandFormatter.getListItemLiveTimeDescr(app, lastUpdated)

View file

@ -4,6 +4,7 @@ import android.app.DatePickerDialog
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.support.annotation.DrawableRes import android.support.annotation.DrawableRes
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
@ -17,11 +18,11 @@ import android.widget.TextView
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.R import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.LocationMessages
import net.osmand.telegram.helpers.TelegramUiHelper import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.helpers.TelegramUiHelper.ListItem import net.osmand.telegram.helpers.TelegramUiHelper.ListItem
import net.osmand.telegram.ui.TimelineTabFragment.LiveNowListAdapter.BaseViewHolder import net.osmand.telegram.ui.TimelineTabFragment.LiveNowListAdapter.BaseViewHolder
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.GPXUtilities
import net.osmand.telegram.utils.OsmandFormatter import net.osmand.telegram.utils.OsmandFormatter
import java.util.* import java.util.*
@ -38,13 +39,14 @@ class TimelineTabFragment : Fragment() {
private lateinit var adapter: LiveNowListAdapter private lateinit var adapter: LiveNowListAdapter
private lateinit var dateStartBtn: TextView private lateinit var dateBtn: TextView
private lateinit var dateEndBtn: TextView
private lateinit var mainView: View private lateinit var mainView: View
private var start = 0L private var start = 0L
private var end = 0L private var end = 0L
private var updateEnable: Boolean = false
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -74,43 +76,50 @@ class TimelineTabFragment : Fragment() {
monitoringTv.setText(if (monitoringEnabled) R.string.monitoring_is_enabled else R.string.monitoring_is_disabled) monitoringTv.setText(if (monitoringEnabled) R.string.monitoring_is_enabled else R.string.monitoring_is_disabled)
} }
dateStartBtn = mainView.findViewById<TextView>(R.id.date_start_btn).apply { dateBtn = mainView.findViewById<TextView>(R.id.date_btn).apply {
setOnClickListener { setOnClickListener {
selectStartDate() selectDate()
} }
setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_start), null, null, null) setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_start), null, null, null)
setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light))
} }
dateEndBtn = mainView.findViewById<TextView>(R.id.date_end_btn).apply {
setOnClickListener {
selectEndDate()
}
setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_add), null, null, null)
}
setupBtnTextColor(dateStartBtn)
setupBtnTextColor(dateEndBtn)
return mainView return mainView
} }
private fun setupBtnTextColor(textView: TextView) { override fun onResume() {
textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light)) super.onResume()
updateEnable = true
startHandler()
} }
private fun selectStartDate() { override fun onPause() {
super.onPause()
updateEnable = false
}
private fun selectDate() {
val dateFromDialog = val dateFromDialog =
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
val from = Calendar.getInstance() val from = Calendar.getInstance()
from.set(Calendar.YEAR, year) from.set(Calendar.YEAR, year)
from.set(Calendar.MONTH, monthOfYear) from.set(Calendar.MONTH, monthOfYear)
from.set(Calendar.DAY_OF_MONTH, dayOfMonth) from.set(Calendar.DAY_OF_MONTH, dayOfMonth)
from.set(Calendar.HOUR_OF_DAY, 0) from.set(Calendar.HOUR_OF_DAY, 0)
from.clear(Calendar.MINUTE) from.clear(Calendar.MINUTE)
from.clear(Calendar.SECOND) from.clear(Calendar.SECOND)
from.clear(Calendar.MILLISECOND) from.clear(Calendar.MILLISECOND)
start = from.timeInMillis start = from.timeInMillis
from.set(Calendar.HOUR_OF_DAY, 23)
from.set(Calendar.MINUTE, 59)
from.set(Calendar.SECOND, 59)
from.set(Calendar.MILLISECOND, 999)
end = from.timeInMillis
updateList() updateList()
updateDateButtons() updateDateButton()
} }
val startCalendar = Calendar.getInstance() val startCalendar = Calendar.getInstance()
startCalendar.timeInMillis = start startCalendar.timeInMillis = start
@ -121,34 +130,8 @@ class TimelineTabFragment : Fragment() {
).show() ).show()
} }
private fun selectEndDate() { private fun updateDateButton() {
val dateFromDialog = dateBtn.text = OsmandFormatter.getFormattedDate(start / 1000)
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
val from = Calendar.getInstance()
from.set(Calendar.YEAR, year)
from.set(Calendar.MONTH, monthOfYear)
from.set(Calendar.DAY_OF_MONTH, dayOfMonth)
from.set(Calendar.HOUR_OF_DAY, 23)
from.set(Calendar.MINUTE, 59)
from.set(Calendar.SECOND, 59)
from.set(Calendar.MILLISECOND, 999)
end = from.timeInMillis
updateList()
updateDateButtons()
}
val endCalendar = Calendar.getInstance()
endCalendar.timeInMillis = end
DatePickerDialog(context, dateFromDialog,
endCalendar.get(Calendar.YEAR),
endCalendar.get(Calendar.MONTH),
endCalendar.get(Calendar.DAY_OF_MONTH)
).show()
}
private fun updateDateButtons() {
dateStartBtn.text = OsmandFormatter.getFormattedDate(start / 1000)
dateEndBtn.text = OsmandFormatter.getFormattedDate(end / 1000)
dateEndBtn.setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_end), null, null, null)
} }
private fun getPressedStateIcon(@DrawableRes iconId: Int): Drawable? { private fun getPressedStateIcon(@DrawableRes iconId: Int): Drawable? {
@ -162,32 +145,35 @@ class TimelineTabFragment : Fragment() {
return normal return normal
} }
private fun startHandler() {
val updateAdapter = Handler()
updateAdapter.postDelayed({
if (updateEnable) {
updateList()
startHandler()
}
}, ADAPTER_UPDATE_INTERVAL_MIL)
}
private fun updateList() { private fun updateList() {
val res = mutableListOf<ListItem>() val res = mutableListOf<ListItem>()
val s = System.currentTimeMillis()
log.debug("updateList $s")
val ignoredUsersIds = ArrayList<Int>()
val currentUserId = telegramHelper.getCurrentUser()?.id val currentUserId = telegramHelper.getCurrentUser()?.id
if (currentUserId != null) { if (currentUserId != null) {
val currentUserGpx:GPXUtilities.GPXFile? = app.savingTracksDbHelper.collectRecordedDataForUser(currentUserId, 0, start, end) val outgoingMessages = app.locationMessages.getMessagesForUser(currentUserId, start, end)
if (currentUserGpx != null) { TelegramUiHelper.locationMessagesToChatItem(telegramHelper, outgoingMessages)?.also { chatItem ->
TelegramUiHelper.gpxToChatItem(telegramHelper, currentUserGpx, true)?.also { res.add(chatItem)
res.add(it)
} }
} val ingoingMessages = app.locationMessages.getIngoingMessages(currentUserId, start, end)
ignoredUsersIds.add(currentUserId) val emm = ingoingMessages.distinctBy { Pair(it.userId, it.chatId) }
} emm.forEach { message ->
val gpxFiles = app.savingTracksDbHelper.collectRecordedDataForUsers(start, end, ignoredUsersIds) TelegramUiHelper.locationMessagesToChatItem(telegramHelper,
val e = System.currentTimeMillis() ingoingMessages.filter { it.chatId == message.chatId && it.userId == message.userId })?.also { chatItem ->
res.add(chatItem)
gpxFiles.forEach { }
TelegramUiHelper.gpxToChatItem(telegramHelper, it,false)?.also { chatItem ->
res.add(chatItem)
} }
} }
adapter.items = sortAdapterItems(res) adapter.items = sortAdapterItems(res)
log.debug("updateList $s dif: ${e - s}")
} }
private fun sortAdapterItems(list: MutableList<ListItem>): MutableList<ListItem> { private fun sortAdapterItems(list: MutableList<ListItem>): MutableList<ListItem> {
@ -225,24 +211,17 @@ class TimelineTabFragment : Fragment() {
holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE
holder.lastTelegramUpdateTime?.visibility = View.GONE holder.lastTelegramUpdateTime?.visibility = View.GONE
if (item is TelegramUiHelper.GpxChatItem) { if (item is TelegramUiHelper.LocationMessagesChatItem) {
val gpx = item.gpxFile val distance = OsmandFormatter.getFormattedDistance(getDistance(item.locationMessages),app)
val groupDescrRowVisible = (!item.privateChat || item.chatWithBot) && item.userId != currentUserId val name = if ((!item.privateChat || item.chatWithBot) && item.userId != currentUserId) item.getVisibleName() else ""
if (groupDescrRowVisible) { holder.groupDescrContainer?.visibility = View.VISIBLE
holder.groupDescrContainer?.visibility = View.VISIBLE holder.groupTitle?.text = "$distance (${getString(R.string.points_size, item.locationMessages.size)}) $name"
holder.groupTitle?.text = item.getVisibleName() TelegramUiHelper.setupPhoto(app, holder.groupImage, item.groupPhotoPath, item.placeholderId, false)
TelegramUiHelper.setupPhoto(app, holder.groupImage, item.groupPhotoPath, item.placeholderId, false)
} else {
holder.groupDescrContainer?.visibility = View.GONE
}
holder.userRow?.setOnClickListener { holder.userRow?.setOnClickListener {
if (gpx != null) { childFragmentManager.also {
childFragmentManager.also { UserGpxInfoFragment.showInstance(it, item.userId, item.chatId, start, end)
UserGpxInfoFragment.showInstance(it, gpx, start, end)
}
} }
} }
holder.imageButton?.visibility = View.GONE holder.imageButton?.visibility = View.GONE
holder.showOnMapRow?.visibility = View.GONE holder.showOnMapRow?.visibility = View.GONE
holder.bottomDivider?.visibility = if (lastItem) View.GONE else View.VISIBLE holder.bottomDivider?.visibility = if (lastItem) View.GONE else View.VISIBLE
@ -250,6 +229,14 @@ class TimelineTabFragment : Fragment() {
} }
} }
private fun getDistance(messages: List<LocationMessages.LocationMessage>): Float {
var dist = 0.0
messages.forEach {
dist += it.distanceFromPrev
}
return dist.toFloat()
}
override fun getItemCount() = items.size override fun getItemCount() = items.size
inner class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) { inner class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -269,4 +256,8 @@ class TimelineTabFragment : Fragment() {
val bottomDivider: View? = view.findViewById(R.id.bottom_divider) val bottomDivider: View? = view.findViewById(R.id.bottom_divider)
} }
} }
companion object {
private const val ADAPTER_UPDATE_INTERVAL_MIL = 15 * 1000L // 15 sec
}
} }

View file

@ -21,11 +21,11 @@ import net.osmand.PlatformUtil
import net.osmand.aidl.gpx.AGpxBitmap import net.osmand.aidl.gpx.AGpxBitmap
import net.osmand.telegram.R import net.osmand.telegram.R
import net.osmand.telegram.helpers.OsmandAidlHelper import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.SavingTracksDbHelper
import net.osmand.telegram.helpers.TelegramUiHelper import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.GPXUtilities import net.osmand.telegram.utils.GPXUtilities
import net.osmand.telegram.utils.OsmandFormatter import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.util.Algorithms import net.osmand.util.Algorithms
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -37,7 +37,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private val uiUtils get() = app.uiUtils private val uiUtils get() = app.uiUtils
private lateinit var gpxFile: GPXUtilities.GPXFile private var gpxFile = GPXUtilities.GPXFile()
private lateinit var dateStartBtn: TextView private lateinit var dateStartBtn: TextView
private lateinit var timeStartBtn: TextView private lateinit var timeStartBtn: TextView
@ -52,6 +52,9 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private var startCalendar = Calendar.getInstance() private var startCalendar = Calendar.getInstance()
private var endCalendar = Calendar.getInstance() private var endCalendar = Calendar.getInstance()
private var userId = -1
private var chatId = -1L
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
parent: ViewGroup?, parent: ViewGroup?,
@ -62,9 +65,6 @@ class UserGpxInfoFragment : BaseDialogFragment() {
readFromBundle(savedInstanceState ?: arguments) readFromBundle(savedInstanceState ?: arguments)
val userId = gpxFile.userId
val chatId = gpxFile.chatId
val user = app.telegramHelper.getUser(userId) val user = app.telegramHelper.getUser(userId)
if (user != null) { if (user != null) {
mainView.findViewById<TextView>(R.id.title).text = TelegramUiHelper.getUserName(user) mainView.findViewById<TextView>(R.id.title).text = TelegramUiHelper.getUserName(user)
@ -80,8 +80,6 @@ class UserGpxInfoFragment : BaseDialogFragment() {
} }
}) })
updateGPXMap()
val backBtn = mainView.findViewById<ImageView>(R.id.back_button) val backBtn = mainView.findViewById<ImageView>(R.id.back_button)
backBtn.setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_arrow_back)) backBtn.setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_arrow_back))
backBtn.setOnClickListener { backBtn.setOnClickListener {
@ -134,12 +132,13 @@ class UserGpxInfoFragment : BaseDialogFragment() {
openGpx(gpx.path) openGpx(gpx.path)
} else { } else {
saveCurrentGpxToFile(object : saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener { OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) { override fun onSavingGpxFinish(path: String) {
openGpx(path) openGpx(path)
} }
override fun onSavingGpxError(warnings: MutableList<String>?) { override fun onSavingGpxError(warnings: List<String>?) {
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show() Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
} }
}) })
@ -156,12 +155,12 @@ class UserGpxInfoFragment : BaseDialogFragment() {
(activity as MainActivity).shareGpx(gpx.path) (activity as MainActivity).shareGpx(gpx.path)
} else { } else {
saveCurrentGpxToFile(object : saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener { OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) { override fun onSavingGpxFinish(path: String) {
(activity as MainActivity).shareGpx(path) (activity as MainActivity).shareGpx(path)
} }
override fun onSavingGpxError(warnings: MutableList<String>?) { override fun onSavingGpxError(warnings: List<String>?) {
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show() Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
} }
}) })
@ -169,6 +168,8 @@ class UserGpxInfoFragment : BaseDialogFragment() {
} }
} }
updateGpxInfo()
return mainView return mainView
} }
@ -189,12 +190,16 @@ class UserGpxInfoFragment : BaseDialogFragment() {
} }
} }
private fun saveCurrentGpxToFile(listener: SavingTracksDbHelper.SaveGpxListener) { private fun saveCurrentGpxToFile(listener: OsmandLocationUtils.SaveGpxListener) {
app.savingTracksDbHelper.saveGpx(listener, app.getExternalFilesDir(null), gpxFile) if (!gpxFile.isEmpty) {
OsmandLocationUtils.saveGpx(app, listener, app.getExternalFilesDir(null)!!, gpxFile)
}
} }
private fun readFromBundle(bundle: Bundle?) { private fun readFromBundle(bundle: Bundle?) {
bundle?.also { bundle?.also {
userId = it.getInt(USER_ID_KEY)
chatId = it.getLong(CHAT_ID_KEY)
startCalendar.timeInMillis = it.getLong(START_KEY) startCalendar.timeInMillis = it.getLong(START_KEY)
endCalendar.timeInMillis = it.getLong(END_KEY) endCalendar.timeInMillis = it.getLong(END_KEY)
} }
@ -205,7 +210,10 @@ class UserGpxInfoFragment : BaseDialogFragment() {
} }
private fun updateGpxInfo() { private fun updateGpxInfo() {
gpxFile = app.savingTracksDbHelper.collectRecordedDataForUser(gpxFile.userId, gpxFile.chatId, startCalendar.timeInMillis, endCalendar.timeInMillis) val emm = app.locationMessages.getMessagesForUserInChat(
userId, chatId, startCalendar.timeInMillis, endCalendar.timeInMillis)
gpxFile = OsmandLocationUtils.convertLocationMessagesToGpxFiles(emm).firstOrNull()?:GPXUtilities.GPXFile()
updateGPXStatisticRow() updateGPXStatisticRow()
updateDateAndTimeButtons() updateDateAndTimeButtons()
updateGPXMap() updateGPXMap()
@ -229,7 +237,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private fun updateGPXMap() { private fun updateGPXMap() {
saveCurrentGpxToFile(object : saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener { OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) { override fun onSavingGpxFinish(path: String) {
val mgr = activity?.getSystemService(Context.WINDOW_SERVICE) val mgr = activity?.getSystemService(Context.WINDOW_SERVICE)
if (mgr != null) { if (mgr != null) {
@ -242,7 +250,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
} }
} }
override fun onSavingGpxError(warnings: MutableList<String>?) { override fun onSavingGpxError(warnings: List<String>?) {
log.debug("onSavingGpxError ${warnings?.firstOrNull()}") log.debug("onSavingGpxError ${warnings?.firstOrNull()}")
} }
}) })
@ -299,18 +307,21 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private const val TAG = "UserGpxInfoFragment" private const val TAG = "UserGpxInfoFragment"
private const val START_KEY = "start_key" private const val START_KEY = "start_key"
private const val END_KEY = "end_key" private const val END_KEY = "end_key"
private const val USER_ID_KEY = "user_id_key"
private const val CHAT_ID_KEY = "chat_id_key"
private const val GPX_TRACK_COLOR = -65536 private const val GPX_TRACK_COLOR = -65536
fun showInstance(fm: FragmentManager, gpxFile: GPXUtilities.GPXFile, start: Long, end: Long): Boolean { fun showInstance(fm: FragmentManager,userId:Int,chatId:Long, start: Long, end: Long): Boolean {
return try { return try {
val fragment = UserGpxInfoFragment().apply { val fragment = UserGpxInfoFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putInt(USER_ID_KEY, userId)
putLong(CHAT_ID_KEY, chatId)
putLong(START_KEY, start) putLong(START_KEY, start)
putLong(END_KEY, end) putLong(END_KEY, end)
} }
} }
fragment.gpxFile = gpxFile
fragment.show(fm, TAG) fragment.show(fm, TAG)
true true
} catch (e: RuntimeException) { } catch (e: RuntimeException) {

View file

@ -0,0 +1,557 @@
package net.osmand.telegram.utils
import android.os.AsyncTask
import net.osmand.Location
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.LocationMessages
import net.osmand.telegram.helpers.LocationMessages.BufferMessage
import net.osmand.telegram.helpers.LocationMessages.LocationMessage
import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.util.GeoPointParserUtil
import net.osmand.util.MapUtils
import org.drinkless.td.libcore.telegram.TdApi
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
object OsmandLocationUtils {
const val DEVICE_PREFIX = "Device: "
const val LOCATION_PREFIX = "Location: "
const val LAST_LOCATION_PREFIX = "Last location: "
const val UPDATED_PREFIX = "Updated: "
const val USER_TEXT_LOCATION_TITLE = "\uD83D\uDDFA OsmAnd sharing:"
const val SHARING_LINK = "https://play.google.com/store/apps/details?id=net.osmand.telegram"
const val ALTITUDE_PREFIX = "Altitude: "
const val SPEED_PREFIX = "Speed: "
const val HDOP_PREFIX = "Horizontal precision: "
const val NOW = "now"
const val FEW_SECONDS_AGO = "few seconds ago"
const val SECONDS_AGO_SUFFIX = " seconds ago"
const val MINUTES_AGO_SUFFIX = " minutes ago"
const val HOURS_AGO_SUFFIX = " hours ago"
const val UTC_FORMAT_SUFFIX = " UTC"
val UTC_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
val UTC_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
fun getLastUpdatedTime(message: TdApi.Message): Int {
val content = message.content
return when (content) {
is MessageOsmAndBotLocation -> content.lastUpdated
is MessageUserLocation -> content.lastUpdated
else -> Math.max(message.editDate, message.date)
}
}
fun getOsmAndBotDeviceName(message: TdApi.Message): String {
var deviceName = ""
if (message.replyMarkup is TdApi.ReplyMarkupInlineKeyboard) {
val replyMarkup = message.replyMarkup as TdApi.ReplyMarkupInlineKeyboard
try {
deviceName = replyMarkup.rows[0][1].text.split("\\s".toRegex())[1]
} catch (e: Exception) {
}
}
return deviceName
}
fun parseOsmAndBotLocation(message: TdApi.Message): MessageOsmAndBotLocation {
val messageLocation = message.content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = getOsmAndBotDeviceName(message)
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = getLastUpdatedTime(message)
}
}
fun parseUserMapLocation(message: TdApi.Message): MessageUserLocation {
val messageLocation = message.content as TdApi.MessageLocation
return MessageUserLocation().apply {
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = getLastUpdatedTime(message)
}
}
fun parseMessage(message: TdApi.Message, helper: TelegramHelper, previousMessage: LocationMessage?): LocationMessage? {
var locationMessage: LocationMessage? = null
val oldContent = message.content
val messageType = getMessageType(message,helper)
var parsedMessageContent: MessageLocation? = null
if (messageType != -1) {
parsedMessageContent = when (messageType) {
LocationMessages.TYPE_BOT_TEXT -> {
parseTextLocation((oldContent as TdApi.MessageText).text)
}
LocationMessages.TYPE_USER_TEXT -> {
if (oldContent is TdApi.MessageText) {
parseTextLocation(oldContent.text, false)
} else {
oldContent as MessageUserLocation
}
}
LocationMessages.TYPE_BOT_MAP -> {
parseOsmAndBotLocation(message)
}
LocationMessages.TYPE_USER_MAP -> {
parseUserMapLocation(message)
}
else -> null
}
}
if (parsedMessageContent != null) {
val distanceFromPrev = if (previousMessage != null) {
MapUtils.getDistance(previousMessage.lat, previousMessage.lon,
parsedMessageContent.lat, parsedMessageContent.lon)
} else 0.0
locationMessage = LocationMessage(helper.getSenderMessageId(message), message.chatId, parsedMessageContent.lat,
parsedMessageContent.lon, parsedMessageContent.altitude, parsedMessageContent.speed, parsedMessageContent.hdop,
parsedMessageContent.bearing, parsedMessageContent.lastUpdated * 1000L, messageType, message.id, distanceFromPrev)
}
return locationMessage
}
fun getMessageType(message: TdApi.Message, helper: TelegramHelper): Int {
val fromBot = helper.isOsmAndBot(message.senderUserId)
val viaBot = helper.isOsmAndBot(message.viaBotUserId)
val oldContent = message.content
return if (oldContent is TdApi.MessageText) {
if (fromBot || viaBot) {
LocationMessages.TYPE_BOT_TEXT
} else {
LocationMessages.TYPE_USER_TEXT
}
} else if (oldContent is TdApi.MessageLocation) {
if (fromBot || viaBot) {
LocationMessages.TYPE_BOT_MAP
} else {
LocationMessages.TYPE_USER_MAP
}
} else if (oldContent is MessageLocation) {
oldContent.type
} else {
-1
}
}
fun formatLocation(sig: Location): String {
return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude)
}
fun formatLocation(sig: LocationMessage): String {
return String.format(Locale.US, "%.5f, %.5f", sig.lat, sig.lon)
}
fun formatLocation(sig: BufferMessage): String {
return String.format(Locale.US, "%.5f, %.5f", sig.lat, sig.lon)
}
fun formatFullTime(ti: Long): String {
val dt = Date(ti)
return UTC_DATE_FORMAT.format(dt) + " " + UTC_TIME_FORMAT.format(dt) + " UTC"
}
fun parseOsmAndBotLocationContent(oldContent: MessageOsmAndBotLocation, content: TdApi.MessageContent): MessageOsmAndBotLocation {
val messageLocation = content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = oldContent.name
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = (System.currentTimeMillis() / 1000).toInt()
type = LocationMessages.TYPE_BOT_MAP
}
}
fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean = true): MessageLocation {
val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserLocation()
res.type = if (botLocation) LocationMessages.TYPE_BOT_TEXT else LocationMessages.TYPE_USER_TEXT
var locationNA = false
for (s in text.text.lines()) {
when {
s.startsWith(DEVICE_PREFIX) -> {
if (res is MessageOsmAndBotLocation) {
res.name = s.removePrefix(DEVICE_PREFIX)
}
}
s.startsWith(LOCATION_PREFIX) || s.startsWith(LAST_LOCATION_PREFIX) -> {
var locStr: String
var parse = true
if (s.startsWith(LAST_LOCATION_PREFIX)) {
locStr = s.removePrefix(LAST_LOCATION_PREFIX)
if (!locationNA) {
parse = false
}
} else {
locStr = s.removePrefix(LOCATION_PREFIX)
if (locStr.trim() == "n/a") {
locationNA = true
parse = false
}
}
if (parse) {
try {
val urlTextEntity =
text.entities.firstOrNull { it.type is TdApi.TextEntityTypeTextUrl }
if (urlTextEntity != null && urlTextEntity.offset == text.text.indexOf(
locStr
)
) {
val url = (urlTextEntity.type as TdApi.TextEntityTypeTextUrl).url
val point: GeoPointParserUtil.GeoParsedPoint? =
GeoPointParserUtil.parse(url)
if (point != null) {
res.lat = point.latitude
res.lon = point.longitude
}
} else {
val (latS, lonS) = locStr.split(" ")
res.lat = latS.dropLast(1).toDouble()
res.lon = lonS.toDouble()
val timeIndex = locStr.indexOf("(")
if (timeIndex != -1) {
val updatedS = locStr.substring(timeIndex, locStr.length)
res.lastUpdated =
(parseTime(updatedS.removePrefix("(").removeSuffix(")")) / 1000).toInt()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
s.startsWith(ALTITUDE_PREFIX) -> {
val altStr = s.removePrefix(ALTITUDE_PREFIX)
try {
val alt = altStr.split(" ").first()
res.altitude = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(SPEED_PREFIX) -> {
val altStr = s.removePrefix(SPEED_PREFIX)
try {
val alt = altStr.split(" ").first()
res.speed = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(HDOP_PREFIX) -> {
val altStr = s.removePrefix(HDOP_PREFIX)
try {
val alt = altStr.split(" ").first()
res.hdop = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(UPDATED_PREFIX) -> {
if (res.lastUpdated == 0) {
val updatedStr = s.removePrefix(UPDATED_PREFIX)
val endIndex = updatedStr.indexOf("(")
val updatedS = updatedStr.substring(
0,
if (endIndex != -1) endIndex else updatedStr.length
)
val parsedTime = (parseTime(updatedS.trim()) / 1000).toInt()
val currentTime = (System.currentTimeMillis() / 1000) - 1
res.lastUpdated =
if (parsedTime < currentTime) parsedTime else currentTime.toInt()
}
}
}
}
return res
}
fun parseTime(timeS: String): Long {
try {
when {
timeS.endsWith(FEW_SECONDS_AGO) -> return System.currentTimeMillis() - 5000
timeS.endsWith(SECONDS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX)
return System.currentTimeMillis() - locStr.toLong() * 1000
}
timeS.endsWith(MINUTES_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX)
val minutes = locStr.toLong()
return System.currentTimeMillis() - minutes * 60 * 1000
}
timeS.endsWith(HOURS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(HOURS_AGO_SUFFIX)
val hours = locStr.toLong()
return (System.currentTimeMillis() - hours * 60 * 60 * 1000)
}
timeS.endsWith(UTC_FORMAT_SUFFIX) -> {
val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX)
val (latS, lonS) = locStr.split(" ")
val date = UTC_DATE_FORMAT.parse(latS)
val time = UTC_TIME_FORMAT.parse(lonS)
val res = date.time + time.time
return res
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return 0
}
fun getTextMessageContent(updateId: Int, location: LocationMessage): TdApi.InputMessageText {
val entities = mutableListOf<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}")))
builder.append("$locationMessage\n")
if (location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hdop != 0.0 && location.speed == 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
} else {
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
}
val textMessage = builder.toString().trim()
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
}
fun getTextMessageContent(updateId: Int, location: BufferMessage): TdApi.InputMessageText {
val entities = mutableListOf<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}")))
builder.append("$locationMessage\n")
if (location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hdop != 0.0 && location.speed == 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
} else {
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
}
val textMessage = builder.toString().trim()
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
}
fun convertLocationMessagesToGpxFiles(items: List<LocationMessage>, newGpxPerChat: Boolean = true): List<GPXUtilities.GPXFile> {
val dataTracks = ArrayList<GPXUtilities.GPXFile>()
var previousTime: Long = -1
var previousChatId: Long = -1
var previousUserId = -1
var segment: GPXUtilities.TrkSegment? = null
var track: GPXUtilities.Track? = null
var gpx: GPXUtilities.GPXFile? = null
items.forEach {
val userId = it.userId
val chatId = it.chatId
val time = it.time
if (previousUserId != userId || (newGpxPerChat && previousChatId != chatId)) {
gpx = GPXUtilities.GPXFile()
gpx!!.chatId = chatId
gpx!!.userId = userId
previousTime = 0
track = null
segment = null
dataTracks.add(gpx!!)
}
val pt = GPXUtilities.WptPt()
pt.userId = userId
pt.chatId = chatId
pt.lat = it.lat
pt.lon = it.lon
pt.ele = it.altitude
pt.speed = it.speed
pt.hdop = it.hdop
pt.time = time
val currentInterval = Math.abs(time - previousTime)
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment!!.points.add(pt)
} else {
segment = GPXUtilities.TrkSegment()
segment!!.points.add(pt)
track!!.segments.add(segment)
}
} else {
track = GPXUtilities.Track()
segment = GPXUtilities.TrkSegment()
track!!.segments.add(segment)
segment!!.points.add(pt)
gpx!!.tracks.add(track)
}
previousTime = time
previousUserId = userId
previousChatId = chatId
}
return dataTracks
}
fun saveGpx(app: TelegramApplication, listener: SaveGpxListener, dir: File, gpxFile: GPXUtilities.GPXFile) {
if (!gpxFile.isEmpty) {
val task = SaveGPXTrackToFileTask(app, listener, gpxFile, dir, 0)
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
}
abstract class MessageLocation : TdApi.MessageContent() {
var lat: Double = Double.NaN
internal set
var lon: Double = Double.NaN
internal set
var lastUpdated: Int = 0
internal set
var speed: Double = 0.0
internal set
var altitude: Double = 0.0
internal set
var hdop: Double = 0.0
internal set
var bearing: Double = 0.0
internal set
var type: Int = -1
internal set
override fun getConstructor() = -1
abstract fun isValid(): Boolean
}
class MessageOsmAndBotLocation : MessageLocation() {
var name: String = ""
internal set
override fun isValid() = name != "" && lat != Double.NaN && lon != Double.NaN
}
class MessageUserLocation : MessageLocation() {
override fun isValid() = lat != Double.NaN && lon != Double.NaN
}
private class SaveGPXTrackToFileTask internal constructor(
private val app: TelegramApplication, private val listener: SaveGpxListener?,
private val gpxFile: GPXUtilities.GPXFile, private val dir: File, private val userId: Int
) :
AsyncTask<Void, Void, List<String>>() {
override fun doInBackground(vararg params: Void): List<String> {
val warnings = ArrayList<String>()
dir.mkdirs()
if (dir.parentFile.canWrite()) {
if (dir.exists()) {
// save file
var fout = File(dir, "$userId.gpx")
if (!gpxFile.isEmpty) {
val pt = gpxFile.findPointToShow()
val user = app.telegramHelper.getUser(pt!!.userId)
val fileName: String
fileName = if (user != null) {
(TelegramUiHelper.getUserName(user) + "_" + SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(Date(pt.time)))
} else {
userId.toString() + "_" + SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(Date(pt.time))
}
fout = File(dir, "$fileName.gpx")
var ind = 1
while (fout.exists()) {
fout = File(dir, "${fileName}_${++ind}.gpx")
}
}
val warn = GPXUtilities.writeGpxFile(fout, gpxFile, app)
if (warn != null) {
warnings.add(warn)
return warnings
}
}
}
return warnings
}
override fun onPostExecute(warnings: List<String>?) {
if (listener != null) {
if (warnings != null && warnings.isEmpty()) {
listener.onSavingGpxFinish(gpxFile.path)
} else {
listener.onSavingGpxError(warnings)
}
}
}
}
interface SaveGpxListener {
fun onSavingGpxFinish(path: String)
fun onSavingGpxError(warnings: List<String>?)
}
}