Telegram database refactoring
This commit is contained in:
parent
bdeb1b6de9
commit
7b41a03c47
19 changed files with 1068 additions and 996 deletions
|
@ -171,6 +171,63 @@
|
|||
|
||||
</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>
|
||||
|
||||
<Switch
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<resources>
|
||||
<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_disabled">Monitoring is disabled</string>
|
||||
<string name="time_on_the_move">time on the move</string>
|
||||
|
|
|
@ -24,8 +24,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
|
|||
lateinit var notificationHelper: NotificationHelper private set
|
||||
lateinit var osmandAidlHelper: OsmandAidlHelper private set
|
||||
lateinit var locationProvider: TelegramLocationProvider private set
|
||||
lateinit var messagesDbHelper: MessagesDbHelper private set
|
||||
lateinit var savingTracksDbHelper: SavingTracksDbHelper private set
|
||||
lateinit var locationMessages: LocationMessages private set
|
||||
|
||||
var telegramService: TelegramService? = null
|
||||
|
||||
|
@ -68,8 +67,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
|
|||
showLocationHelper = ShowLocationHelper(this)
|
||||
notificationHelper = NotificationHelper(this)
|
||||
locationProvider = TelegramLocationProvider(this)
|
||||
messagesDbHelper = MessagesDbHelper(this)
|
||||
savingTracksDbHelper = SavingTracksDbHelper(this)
|
||||
locationMessages = LocationMessages(this)
|
||||
|
||||
if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) {
|
||||
shareLocationHelper.startSharingLocation()
|
||||
|
@ -96,6 +94,13 @@ class TelegramApplication : Application(), OsmandHelperListener {
|
|||
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
|
||||
get() {
|
||||
val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
|
|
@ -13,10 +13,12 @@ import android.os.*
|
|||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import net.osmand.PlatformUtil
|
||||
import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener
|
||||
import net.osmand.telegram.helpers.LocationMessages
|
||||
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.utils.AndroidUtils
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils
|
||||
import org.drinkless.td.libcore.telegram.TdApi
|
||||
import java.util.*
|
||||
|
||||
|
@ -120,6 +122,7 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
|
|||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
val app = app()
|
||||
app.locationMessages.saveMessages()
|
||||
app.telegramHelper.stopLiveMessagesUpdates()
|
||||
app.telegramHelper.removeIncomingMessagesListener(this)
|
||||
app.telegramHelper.removeOutgoingMessagesListener(this)
|
||||
|
@ -274,6 +277,12 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
|
|||
|
||||
override fun onReceiveChatLocationMessages(chatId: Long, vararg messages: TdApi.Message) {
|
||||
app().showLocationHelper.startShowMessagesTask(chatId, *messages)
|
||||
messages.forEach {
|
||||
val locationMessage = OsmandLocationUtils.parseMessage(it, app().telegramHelper, LocationMessages.LocationMessage.STATUS_SENT)
|
||||
if (locationMessage != null) {
|
||||
app().locationMessages.addLocationMessage(locationMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {
|
||||
|
|
|
@ -851,6 +851,7 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
var lastSuccessfulSendTimeMs = -1L
|
||||
var lastSendTextMessageTime = -1
|
||||
var lastSendMapMessageTime = -1
|
||||
var bufferedMessages = 0
|
||||
var pendingTextMessage = false
|
||||
var pendingMapMessage = false
|
||||
var shouldSendViaBotMessage = false
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
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 org.apache.commons.logging.Log
|
||||
import java.util.*
|
||||
|
||||
class LocationMessages(val app: TelegramApplication) {
|
||||
|
||||
private val log: Log = PlatformUtil.getLog(LocationMessages::class.java)
|
||||
|
||||
private val locationMessages = ArrayList<LocationMessage>()
|
||||
|
||||
private val sqliteHelper: SQLiteHelper
|
||||
|
||||
init {
|
||||
sqliteHelper = SQLiteHelper(app)
|
||||
readMessages()
|
||||
}
|
||||
|
||||
fun getLocationMessages(): List<LocationMessage> {
|
||||
return this.locationMessages
|
||||
}
|
||||
|
||||
fun getPreparedToShareMessages(): List<LocationMessage> {
|
||||
val currentUserId = app.telegramHelper.getCurrentUserId()
|
||||
return locationMessages.filter { it.userId == currentUserId && it.status == LocationMessage.STATUS_PREPARING }.sortedBy { it.date }
|
||||
}
|
||||
|
||||
fun getOutgoingMessagesToChat(chatId: Long): List<LocationMessage> {
|
||||
val currentUserId = app.telegramHelper.getCurrentUserId()
|
||||
return locationMessages.filter { it.userId == currentUserId && it.chatId == chatId }.sortedBy { it.date }
|
||||
}
|
||||
|
||||
fun getOutgoingMessagesToChatFromDate(chatId: Long, date:Long): List<LocationMessage> {
|
||||
val currentUserId = app.telegramHelper.getCurrentUserId()
|
||||
return locationMessages.filter { it.userId == currentUserId && it.chatId == chatId && it.date > date }.sortedBy { it.date }
|
||||
}
|
||||
|
||||
fun getSentOutgoingMessagesToChat(chatId: Long): List<LocationMessage> {
|
||||
val currentUserId = app.telegramHelper.getCurrentUserId()
|
||||
return locationMessages.filter { it.userId == currentUserId && it.chatId == chatId && it.status == LocationMessages.LocationMessage.STATUS_SENT }.sortedBy { it.date }
|
||||
}
|
||||
|
||||
fun getIncomingMessages(): List<LocationMessage> {
|
||||
val currentUserId = app.telegramHelper.getCurrentUserId()
|
||||
return locationMessages.filter { it.userId != currentUserId }.sortedBy { it.date }
|
||||
}
|
||||
|
||||
fun addLocationMessage(locationMessage: LocationMessage) {
|
||||
log.debug("addLocationMessage $locationMessage")
|
||||
synchronized(locationMessages) {
|
||||
locationMessages.add(locationMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeMessage(locationMessage: LocationMessage) {
|
||||
synchronized(locationMessages) {
|
||||
locationMessages.remove(locationMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveMessages() {
|
||||
clearMessages()
|
||||
synchronized(locationMessages) {
|
||||
sqliteHelper.addLocationMessages(locationMessages)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearMessages() {
|
||||
sqliteHelper.clearLocationMessages()
|
||||
}
|
||||
|
||||
fun collectRecordedDataForUser(userId: Int, chatId: Long, start: Long, end: Long): List<LocationMessages.LocationMessage> {
|
||||
return if (chatId == 0L) {
|
||||
locationMessages.sortedWith(compareBy({ it.userId }, { it.chatId })).filter { it.userId == userId&&it.date in (start + 1)..(end - 1) }
|
||||
} else {
|
||||
locationMessages.sortedWith(compareBy({ it.userId }, { it.chatId })).filter {
|
||||
it.chatId == chatId && it.userId == userId && it.status == LocationMessage.STATUS_SENT && it.date in (start + 1)..(end - 1) }
|
||||
}
|
||||
}
|
||||
|
||||
fun collectRecordedDataForUsers(start: Long, end: Long, ignoredUsersIds: ArrayList<Int>): List<LocationMessages.LocationMessage> {
|
||||
return locationMessages.sortedWith(compareBy({ it.userId }, { it.chatId })).filter {
|
||||
it.date in (start + 1)..(end - 1) && !ignoredUsersIds.contains(it.userId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readMessages() {
|
||||
val messages = sqliteHelper.getLocationMessages()
|
||||
synchronized(locationMessages) {
|
||||
locationMessages.addAll(messages)
|
||||
}
|
||||
}
|
||||
|
||||
private class SQLiteHelper(context: Context) :
|
||||
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(TRACKS_TABLE_CREATE)
|
||||
db.execSQL("CREATE INDEX $TRACK_DATE_INDEX ON $TRACK_TABLE_NAME (\"$TRACK_COL_DATE\" DESC);")
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL(TRACKS_TABLE_DELETE)
|
||||
onCreate(db)
|
||||
}
|
||||
|
||||
internal fun addLocationMessages(locationMessages: List<LocationMessage>) {
|
||||
locationMessages.forEach {
|
||||
writableDatabase?.execSQL(TRACKS_TABLE_INSERT,
|
||||
arrayOf(it.userId, it.chatId, it.lat, it.lon, it.altitude, it.speed, it.hdop, it.bearing, it.date, it.type, it.status, it.messageId))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getLocationMessages(): Set<LocationMessage> {
|
||||
val res = HashSet<LocationMessage>()
|
||||
readableDatabase?.rawQuery(TRACKS_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 date = cursor.getLong(7)
|
||||
val bearing = cursor.getDouble(8)
|
||||
val textInfo = cursor.getInt(9)
|
||||
val status = cursor.getInt(10)
|
||||
val messageId = cursor.getLong(11)
|
||||
|
||||
return LocationMessage(userId, chatId, lat, lon, altitude, speed, hdop, bearing, date, textInfo, status, messageId)
|
||||
}
|
||||
|
||||
internal fun clearLocationMessages() {
|
||||
writableDatabase?.execSQL(TRACKS_TABLE_CLEAR)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val DATABASE_NAME = "location_messages"
|
||||
private const val DATABASE_VERSION = 2
|
||||
|
||||
private const val TRACK_TABLE_NAME = "track"
|
||||
private const val TRACK_COL_USER_ID = "user_id"
|
||||
private const val TRACK_COL_CHAT_ID = "chat_id"
|
||||
private const val TRACK_COL_DATE = "date"
|
||||
private const val TRACK_COL_LAT = "lat"
|
||||
private const val TRACK_COL_LON = "lon"
|
||||
private const val TRACK_COL_ALTITUDE = "altitude"
|
||||
private const val TRACK_COL_SPEED = "speed"
|
||||
private const val TRACK_COL_HDOP = "hdop"
|
||||
private const val TRACK_COL_BEARING = "bearing"
|
||||
private const val TRACK_COL_TYPE =
|
||||
"type" // 0 = user map message, 1 = user text message, 2 = bot map message, 3 = bot text message
|
||||
private const val TRACK_COL_MESSAGE_STATUS =
|
||||
"status" // 0 = preparing , 1 = pending, 2 = sent, 3 = error
|
||||
private const val TRACK_COL_MESSAGE_ID = "message_id"
|
||||
|
||||
private const val TRACK_DATE_INDEX = "date_index"
|
||||
|
||||
private const val TRACKS_TABLE_INSERT =
|
||||
("INSERT INTO $TRACK_TABLE_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_BEARING, $TRACK_COL_DATE, $TRACK_COL_TYPE, $TRACK_COL_MESSAGE_STATUS, $TRACK_COL_MESSAGE_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
|
||||
private const val TRACKS_TABLE_CREATE =
|
||||
("CREATE TABLE IF NOT EXISTS $TRACK_TABLE_NAME ($TRACK_COL_USER_ID long, $TRACK_COL_CHAT_ID long,$TRACK_COL_LAT double, $TRACK_COL_LON double, $TRACK_COL_ALTITUDE double, $TRACK_COL_SPEED float, $TRACK_COL_HDOP double, $TRACK_COL_BEARING double, $TRACK_COL_DATE long, $TRACK_COL_TYPE int, $TRACK_COL_MESSAGE_STATUS int, $TRACK_COL_MESSAGE_ID long )")
|
||||
|
||||
private const val TRACKS_TABLE_SELECT =
|
||||
"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_BEARING, $TRACK_COL_DATE, $TRACK_COL_TYPE, $TRACK_COL_MESSAGE_STATUS, $TRACK_COL_MESSAGE_ID FROM $TRACK_TABLE_NAME"
|
||||
|
||||
private const val TRACKS_TABLE_CLEAR = "DELETE FROM $TRACK_TABLE_NAME"
|
||||
|
||||
private const val TRACKS_TABLE_DELETE = "DROP TABLE IF EXISTS $TRACK_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 date: Long,
|
||||
val type: Int,
|
||||
var status: Int,
|
||||
var messageId: Long
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
const val STATUS_PREPARING = 0
|
||||
const val STATUS_PENDING = 1
|
||||
const val STATUS_SENT = 2
|
||||
const val STATUS_ERROR = 3
|
||||
|
||||
const val TYPE_USER_MAP = 0
|
||||
const val TYPE_USER_TEXT = 1
|
||||
const val TYPE_BOT_MAP = 2
|
||||
const val TYPE_BOT_TEXT = 3
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package net.osmand.telegram.helpers
|
|||
import net.osmand.Location
|
||||
import net.osmand.PlatformUtil
|
||||
import net.osmand.telegram.*
|
||||
import net.osmand.telegram.helpers.LocationMessages.LocationMessage
|
||||
import net.osmand.telegram.notifications.TelegramNotification.NotificationType
|
||||
import net.osmand.telegram.utils.AndroidNetworkUtils
|
||||
import net.osmand.telegram.utils.BASE_URL
|
||||
|
@ -48,36 +49,9 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
lastLocation = location
|
||||
|
||||
if (location != null) {
|
||||
val chatsShareInfo = app.settings.getChatsShareInfo()
|
||||
if (chatsShareInfo.isNotEmpty()) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (app.settings.getChatsShareInfo().isNotEmpty()) {
|
||||
addNewLocationMessages(location, app.telegramHelper.getCurrentUserId())
|
||||
shareMessages()
|
||||
}
|
||||
lastLocationMessageSentTime = System.currentTimeMillis()
|
||||
}
|
||||
|
@ -158,25 +132,119 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
refreshNotification()
|
||||
}
|
||||
|
||||
private fun getDeviceSharingUrl(loc: Location, sharingMode: String): String {
|
||||
val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.latitude}&lon=${loc.longitude}"
|
||||
private fun addNewLocationMessages(location: Location, userId: Int) {
|
||||
val chatsShareInfo = app.settings.getChatsShareInfo()
|
||||
val latitude = location.latitude
|
||||
val longitude = location.longitude
|
||||
val isBot = app.settings.currentSharingMode != userId.toString()
|
||||
val types = mutableListOf<Int>()
|
||||
|
||||
when (app.settings.shareTypeValue) {
|
||||
SHARE_TYPE_MAP -> {
|
||||
types.add(if (isBot) LocationMessage.TYPE_BOT_MAP else LocationMessage.TYPE_USER_MAP)
|
||||
}
|
||||
SHARE_TYPE_TEXT -> {
|
||||
types.add(if (isBot) LocationMessage.TYPE_BOT_TEXT else LocationMessage.TYPE_USER_TEXT)
|
||||
}
|
||||
SHARE_TYPE_MAP_AND_TEXT -> {
|
||||
types.add(if (isBot) LocationMessage.TYPE_BOT_MAP else LocationMessage.TYPE_USER_MAP)
|
||||
types.add(if (isBot) LocationMessage.TYPE_BOT_TEXT else LocationMessage.TYPE_USER_TEXT)
|
||||
}
|
||||
}
|
||||
chatsShareInfo.values.forEach { shareInfo ->
|
||||
types.forEach {
|
||||
val message = LocationMessage(userId, shareInfo.chatId, latitude, longitude, location.altitude, location.speed.toDouble(),
|
||||
location.accuracy.toDouble(), location.bearing.toDouble(), location.time, it, LocationMessage.STATUS_PREPARING, shareInfo.currentMapMessageId)
|
||||
app.locationMessages.addLocationMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareMessages() {
|
||||
var bufferedMessagesFull = false
|
||||
app.locationMessages.getPreparedToShareMessages().forEach {
|
||||
val shareChatInfo = app.settings.getChatsShareInfo()[it.chatId]
|
||||
if (shareChatInfo != null) {
|
||||
bufferedMessagesFull = shareChatInfo.bufferedMessages < 10
|
||||
if (bufferedMessagesFull) {
|
||||
when {
|
||||
it.type == LocationMessage.TYPE_USER_TEXT -> {
|
||||
shareChatInfo.bufferedMessages++
|
||||
it.status = LocationMessage.STATUS_PENDING
|
||||
app.telegramHelper.sendLiveLocationText(shareChatInfo, it)
|
||||
}
|
||||
it.type == LocationMessage.TYPE_USER_MAP -> {
|
||||
shareChatInfo.bufferedMessages++
|
||||
it.status = LocationMessage.STATUS_PENDING
|
||||
app.telegramHelper.sendLiveLocationMap(shareChatInfo, it)
|
||||
}
|
||||
it.type == LocationMessage.TYPE_BOT_TEXT -> {
|
||||
sendLocationToBot(it, app.settings.currentSharingMode, shareChatInfo, SHARE_TYPE_TEXT)
|
||||
}
|
||||
it.type == LocationMessage.TYPE_BOT_MAP -> {
|
||||
sendLocationToBot(it, app.settings.currentSharingMode, shareChatInfo, SHARE_TYPE_MAP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bufferedMessagesFull) {
|
||||
checkNetworkType()
|
||||
}
|
||||
}
|
||||
|
||||
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: LocationMessage, sharingMode: String, shareInfo: TelegramSettings.ShareChatInfo, shareType: String) {
|
||||
if (app.isInternetConnectionAvailable) {
|
||||
locationMessage.status = LocationMessage.STATUS_PENDING
|
||||
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()
|
||||
|
||||
locationMessage.status = if (success) LocationMessage.STATUS_SENT else LocationMessage.STATUS_ERROR
|
||||
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: LocationMessage, sharingMode: String): String {
|
||||
val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.lat}&lon=${loc.lon}"
|
||||
val builder = StringBuilder(url)
|
||||
if (loc.hasBearing() && loc.bearing != 0.0f) {
|
||||
if (loc.bearing != 0.0) {
|
||||
builder.append("&azi=${loc.bearing}")
|
||||
}
|
||||
if (loc.hasSpeed() && loc.speed != 0.0f) {
|
||||
if (loc.speed != 0.0) {
|
||||
builder.append("&spd=${loc.speed}")
|
||||
}
|
||||
if (loc.hasAltitude() && loc.altitude != 0.0) {
|
||||
if (loc.altitude != 0.0) {
|
||||
builder.append("&alt=${loc.altitude}")
|
||||
}
|
||||
if (loc.hasAccuracy() && loc.accuracy != 0.0f) {
|
||||
builder.append("&hdop=${loc.accuracy}")
|
||||
if (loc.hdop != 0.0) {
|
||||
builder.append("&hdop=${loc.hdop}")
|
||||
}
|
||||
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) {
|
||||
try {
|
||||
val jsonResult = JSONObject(result)
|
||||
|
@ -186,22 +254,12 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
chatsShareInfo.forEach { (_, shareInfo) ->
|
||||
shareInfo.lastSuccessfulSendTimeMs = currentTime
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun refreshNotification() {
|
||||
|
|
|
@ -8,10 +8,11 @@ import net.osmand.aidl.map.ALatLon
|
|||
import net.osmand.aidl.maplayer.point.AMapPoint
|
||||
import net.osmand.telegram.R
|
||||
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.utils.AndroidUtils
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserTextLocation
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
|
||||
import org.drinkless.td.libcore.telegram.TdApi
|
||||
import java.io.File
|
||||
import java.util.concurrent.Executors
|
||||
|
@ -62,7 +63,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
execOsmandApi {
|
||||
val messages = telegramHelper.getMessages()
|
||||
for (message in messages) {
|
||||
val date = telegramHelper.getLastUpdatedTime(message)
|
||||
val date = OsmandLocationUtils.getLastUpdatedTime(message)
|
||||
val messageShowingTime = System.currentTimeMillis() / 1000 - date
|
||||
if (messageShowingTime > app.settings.locHistoryTime) {
|
||||
removeMapPoint(message.chatId, message)
|
||||
|
@ -78,7 +79,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
val chatId = message.chatId
|
||||
val chatTitle = telegramHelper.getChat(message.chatId)?.title
|
||||
val content = message.content
|
||||
val date = telegramHelper.getLastUpdatedTime(message)
|
||||
val date = OsmandLocationUtils.getLastUpdatedTime(message)
|
||||
val stale = System.currentTimeMillis() / 1000 - date > app.settings.staleLocTime
|
||||
if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserTextLocation && content.isValid()))) {
|
||||
var userName = ""
|
||||
|
|
|
@ -3,15 +3,20 @@ package net.osmand.telegram.helpers
|
|||
import android.text.TextUtils
|
||||
import net.osmand.Location
|
||||
import net.osmand.PlatformUtil
|
||||
import net.osmand.telegram.SHARE_TYPE_MAP
|
||||
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.*
|
||||
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_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.MessageUserTextLocation
|
||||
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.ResultHandler
|
||||
import org.drinkless.td.libcore.telegram.TdApi
|
||||
|
@ -36,32 +41,6 @@ class TelegramHelper private constructor() {
|
|||
private const val IGNORED_ERROR_CODE = 406
|
||||
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
|
||||
const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61
|
||||
const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // one day
|
||||
|
@ -172,6 +151,8 @@ class TelegramHelper private constructor() {
|
|||
|
||||
fun getCurrentUser() = currentUser
|
||||
|
||||
fun getCurrentUserId() = currentUser?.id ?: -1
|
||||
|
||||
fun getUserMessage(user: TdApi.User) =
|
||||
usersLocationMessages.values.firstOrNull { it.senderUserId == user.id }
|
||||
|
||||
|
@ -216,15 +197,6 @@ class TelegramHelper private constructor() {
|
|||
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 isSecretChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypeSecret
|
||||
|
@ -349,6 +321,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 getUserPhotoPath(user: TdApi.User?) = when {
|
||||
|
@ -564,7 +542,7 @@ class TelegramHelper private constructor() {
|
|||
resultArticles.forEach {
|
||||
client?.send(TdApi.SendInlineQueryResultMessage(shareInfo.chatId, 0, true,
|
||||
true, inlineQueryResults.inlineQueryId, it.id)) { obj ->
|
||||
handleTextLocationMessageUpdate(obj, shareInfo)
|
||||
handleTextLocationMessageUpdate(obj, shareInfo, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -728,7 +706,7 @@ class TelegramHelper private constructor() {
|
|||
private fun addNewMessage(message: TdApi.Message) {
|
||||
lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, Math.max(message.date, message.editDate))
|
||||
if (message.isAppropriate()) {
|
||||
log.debug("addNewMessage: $message")
|
||||
log.debug("addNewMessage: ${message.id}")
|
||||
val fromBot = isOsmAndBot(message.senderUserId)
|
||||
val viaBot = isOsmAndBot(message.viaBotUserId)
|
||||
val oldContent = message.content
|
||||
|
@ -753,7 +731,7 @@ class TelegramHelper private constructor() {
|
|||
usersLocationMessages[message.id] = message
|
||||
}
|
||||
incomingMessagesListeners.forEach {
|
||||
if (!hasNewerMessage || it is SavingTracksDbHelper) {
|
||||
if (!hasNewerMessage || it is TelegramService) {
|
||||
it.onReceiveChatLocationMessages(message.chatId, message)
|
||||
}
|
||||
}
|
||||
|
@ -779,7 +757,7 @@ class TelegramHelper private constructor() {
|
|||
}
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
@ -811,7 +789,7 @@ class TelegramHelper private constructor() {
|
|||
if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) {
|
||||
client?.send(
|
||||
TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, null)) { obj ->
|
||||
handleMapLocationMessageUpdate(obj, shareInfo)
|
||||
handleMapLocationMessageUpdate(obj, shareInfo, null)
|
||||
}
|
||||
}
|
||||
needRefreshActiveLiveLocationMessages = true
|
||||
|
@ -856,7 +834,7 @@ class TelegramHelper private constructor() {
|
|||
|
||||
private fun recreateLiveLocationMessage(
|
||||
shareInfo: TelegramSettings.ShareChatInfo,
|
||||
content: TdApi.InputMessageContent
|
||||
content: TdApi.InputMessageContent,locationMessage: LocationMessages.LocationMessage?
|
||||
) {
|
||||
if (shareInfo.chatId != -1L) {
|
||||
val array = LongArray(1)
|
||||
|
@ -869,7 +847,7 @@ class TelegramHelper private constructor() {
|
|||
log.debug("recreateLiveLocationMessage - ${array[0]}")
|
||||
client?.send(TdApi.DeleteMessages(shareInfo.chatId, array, true)) { obj ->
|
||||
when (obj.constructor) {
|
||||
TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content)
|
||||
TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content,locationMessage)
|
||||
TdApi.Error.CONSTRUCTOR -> {
|
||||
val error = obj as TdApi.Error
|
||||
if (error.code != IGNORED_ERROR_CODE) {
|
||||
|
@ -886,12 +864,12 @@ class TelegramHelper private constructor() {
|
|||
needRefreshActiveLiveLocationMessages = true
|
||||
}
|
||||
|
||||
private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) {
|
||||
private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent, locationMessage: LocationMessages.LocationMessage?) {
|
||||
needRefreshActiveLiveLocationMessages = true
|
||||
log.debug("sendNewLiveLocationMessage")
|
||||
client?.send(
|
||||
TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj ->
|
||||
handleMapLocationMessageUpdate(obj, shareInfo)
|
||||
handleMapLocationMessageUpdate(obj, shareInfo, locationMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -913,52 +891,79 @@ class TelegramHelper private constructor() {
|
|||
log.debug("sendLiveLocationImpl - $msgId pendingMapMessage ${shareInfo.pendingMapMessage}")
|
||||
if (msgId != -1L) {
|
||||
if (shareInfo.shouldDeletePreviousMapMessage) {
|
||||
recreateLiveLocationMessage(shareInfo, content)
|
||||
recreateLiveLocationMessage(shareInfo, content, null)
|
||||
shareInfo.shouldDeletePreviousMapMessage = false
|
||||
shareInfo.currentMapMessageId = -1
|
||||
} else {
|
||||
log.debug("EditMessageLiveLocation - $msgId")
|
||||
client?.send(
|
||||
TdApi.EditMessageLiveLocation(chatId, msgId, null, location)) { obj ->
|
||||
handleMapLocationMessageUpdate(obj, shareInfo)
|
||||
handleMapLocationMessageUpdate(obj, shareInfo, null)
|
||||
}
|
||||
}
|
||||
} else if (!shareInfo.pendingMapMessage || shareInfo.pendingMapMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) {
|
||||
sendNewLiveLocationMessage(shareInfo, content)
|
||||
sendNewLiveLocationMessage(shareInfo, content, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendLiveLocationText(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: Location) {
|
||||
chatsShareInfo.forEach { (chatId, shareInfo) ->
|
||||
if (shareInfo.getChatLiveMessageExpireTime() <= 0) {
|
||||
return@forEach
|
||||
fun sendLiveLocationMap(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.LocationMessage) {
|
||||
val location = TdApi.Location(locationMessage.lat, locationMessage.lon)
|
||||
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, locationMessage)
|
||||
shareInfo.shouldDeletePreviousMapMessage = false
|
||||
shareInfo.currentMapMessageId = -1
|
||||
} else {
|
||||
log.debug("EditMessageLiveLocation - $msgId")
|
||||
client?.send(
|
||||
TdApi.EditMessageLiveLocation(shareInfo.chatId, msgId, null, location)) { obj ->
|
||||
handleMapLocationMessageUpdate(obj, shareInfo, locationMessage)
|
||||
}
|
||||
}
|
||||
} else if (!shareInfo.pendingMapMessage || shareInfo.pendingMapMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) {
|
||||
sendNewLiveLocationMessage(shareInfo, content, locationMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendLiveLocationText(shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.LocationMessage) {
|
||||
val msgId = shareInfo.currentTextMessageId
|
||||
if (msgId == -1L) {
|
||||
shareInfo.updateTextMessageId = 1
|
||||
}
|
||||
val content = getTextMessageContent(shareInfo.updateTextMessageId, location)
|
||||
val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, location)
|
||||
val timeAfterLastSendMessage = ((System.currentTimeMillis() / 1000) - shareInfo.lastSendTextMessageTime)
|
||||
log.debug("sendLiveLocationText - $msgId pendingMapMessage ${shareInfo.pendingTextMessage}")
|
||||
if (msgId != -1L) {
|
||||
if (shareInfo.shouldDeletePreviousTextMessage) {
|
||||
recreateLiveLocationMessage(shareInfo, content)
|
||||
recreateLiveLocationMessage(shareInfo, content, location)
|
||||
shareInfo.shouldDeletePreviousTextMessage = false
|
||||
} else {
|
||||
client?.send(TdApi.EditMessageText(chatId, msgId, null, content)) { obj ->
|
||||
handleTextLocationMessageUpdate(obj, shareInfo)
|
||||
client?.send(TdApi.EditMessageText(shareInfo.chatId, msgId, null, content)) { obj ->
|
||||
handleTextLocationMessageUpdate(obj, shareInfo, location)
|
||||
}
|
||||
}
|
||||
} else if (!shareInfo.pendingTextMessage || shareInfo.pendingTextMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) {
|
||||
client?.send(TdApi.SendMessage(chatId, 0, false, false, null, content)) { obj ->
|
||||
handleTextLocationMessageUpdate(obj, shareInfo)
|
||||
} else {
|
||||
if (!shareInfo.pendingTextMessage) {
|
||||
client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, false, null, content)) { obj ->
|
||||
handleTextLocationMessageUpdate(obj, shareInfo, location)
|
||||
}
|
||||
} else if(timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMapLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) {
|
||||
private fun handleMapLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.LocationMessage?) {
|
||||
when (obj.constructor) {
|
||||
TdApi.Error.CONSTRUCTOR -> {
|
||||
val error = obj as TdApi.Error
|
||||
|
@ -978,6 +983,7 @@ class TelegramHelper private constructor() {
|
|||
obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
|
||||
shareInfo.hasSharingError = true
|
||||
needRefreshActiveLiveLocationMessages = true
|
||||
location?.status = LocationMessages.LocationMessage.STATUS_ERROR
|
||||
outgoingMessagesListeners.forEach {
|
||||
it.onSendLiveLocationError(-1, "Map location message ${obj.id} failed to send")
|
||||
}
|
||||
|
@ -985,10 +991,14 @@ class TelegramHelper private constructor() {
|
|||
obj.sendingState?.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR -> {
|
||||
shareInfo.pendingMapMessage = true
|
||||
shareInfo.lastSendMapMessageTime = obj.date
|
||||
location?.status = LocationMessages.LocationMessage.STATUS_PENDING
|
||||
log.debug("handleMapLocationMessageUpdate - MessageSendingStatePending")
|
||||
}
|
||||
else -> {
|
||||
shareInfo.hasSharingError = false
|
||||
shareInfo.bufferedMessages--
|
||||
location?.messageId = obj.id
|
||||
location?.status = LocationMessages.LocationMessage.STATUS_SENT
|
||||
outgoingMessagesListeners.forEach {
|
||||
it.onUpdateMessages(listOf(obj))
|
||||
}
|
||||
|
@ -999,7 +1009,7 @@ class TelegramHelper private constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleTextLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) {
|
||||
private fun handleTextLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.LocationMessage?) {
|
||||
when (obj.constructor) {
|
||||
TdApi.Error.CONSTRUCTOR -> {
|
||||
val error = obj as TdApi.Error
|
||||
|
@ -1017,7 +1027,9 @@ class TelegramHelper private constructor() {
|
|||
when {
|
||||
obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
|
||||
shareInfo.hasSharingError = true
|
||||
shareInfo.bufferedMessages--
|
||||
needRefreshActiveLiveLocationMessages = true
|
||||
location?.status = LocationMessages.LocationMessage.STATUS_ERROR
|
||||
outgoingMessagesListeners.forEach {
|
||||
it.onSendLiveLocationError(-1, "Text location message ${obj.id} failed to send")
|
||||
}
|
||||
|
@ -1025,10 +1037,14 @@ class TelegramHelper private constructor() {
|
|||
obj.sendingState?.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR -> {
|
||||
shareInfo.pendingTextMessage = true
|
||||
shareInfo.lastSendTextMessageTime = obj.date
|
||||
location?.status = LocationMessages.LocationMessage.STATUS_PENDING
|
||||
log.debug("handleTextLocationMessageUpdate - MessageSendingStatePending")
|
||||
}
|
||||
else -> {
|
||||
shareInfo.hasSharingError = false
|
||||
shareInfo.bufferedMessages--
|
||||
location?.messageId = obj.id
|
||||
location?.status = LocationMessages.LocationMessage.STATUS_SENT
|
||||
outgoingMessagesListeners.forEach {
|
||||
it.onUpdateMessages(listOf(obj))
|
||||
}
|
||||
|
@ -1039,54 +1055,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 {
|
||||
return if (libraryLoaded) {
|
||||
haveAuthorization = false
|
||||
|
@ -1215,188 +1183,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> {
|
||||
|
||||
override fun compareTo(other: OrderedChat): Int {
|
||||
|
|
|
@ -6,8 +6,9 @@ import android.widget.ImageView
|
|||
import net.osmand.data.LatLon
|
||||
import net.osmand.telegram.R
|
||||
import net.osmand.telegram.TelegramApplication
|
||||
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation
|
||||
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserTextLocation
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
|
||||
import net.osmand.telegram.utils.GPXUtilities
|
||||
import org.drinkless.td.libcore.telegram.TdApi
|
||||
|
||||
|
@ -65,7 +66,7 @@ object TelegramUiHelper {
|
|||
val user = helper.getUser(userId)
|
||||
val message = messages.firstOrNull { it.viaBotUserId == 0 }
|
||||
if (message != null) {
|
||||
res.lastUpdated = helper.getLastUpdatedTime(message)
|
||||
res.lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
|
||||
val content = message.content
|
||||
if (content is TdApi.MessageLocation) {
|
||||
res.latLon = LatLon(content.location.latitude, content.location.longitude)
|
||||
|
@ -172,7 +173,7 @@ object TelegramUiHelper {
|
|||
grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
|
||||
placeholderId = R.drawable.img_user_picture
|
||||
userId = message.senderUserId
|
||||
lastUpdated = helper.getLastUpdatedTime(message)
|
||||
lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +226,7 @@ object TelegramUiHelper {
|
|||
userId = message.senderUserId
|
||||
privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat)
|
||||
chatWithBot = helper.isBot(userId)
|
||||
lastUpdated = helper.getLastUpdatedTime(message)
|
||||
lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import net.osmand.telegram.TelegramApplication
|
|||
import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener
|
||||
import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener
|
||||
import net.osmand.telegram.TelegramSettings
|
||||
import net.osmand.telegram.helpers.SavingTracksDbHelper
|
||||
import net.osmand.telegram.helpers.TelegramHelper.*
|
||||
import net.osmand.telegram.helpers.TelegramUiHelper
|
||||
import net.osmand.telegram.helpers.TelegramUiHelper.ChatItem
|
||||
|
|
|
@ -19,6 +19,7 @@ import android.widget.*
|
|||
import net.osmand.PlatformUtil
|
||||
import net.osmand.telegram.R
|
||||
import net.osmand.telegram.TelegramApplication
|
||||
import net.osmand.telegram.helpers.LocationMessages
|
||||
import net.osmand.telegram.helpers.OsmandAidlHelper
|
||||
import net.osmand.telegram.helpers.TelegramHelper
|
||||
import net.osmand.telegram.helpers.TelegramHelper.*
|
||||
|
@ -183,7 +184,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
|
|||
override fun onStop() {
|
||||
super.onStop()
|
||||
settings.save()
|
||||
app.messagesDbHelper.saveMessages()
|
||||
app.locationMessages.saveMessages()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -291,6 +292,14 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
|
|||
if (!app.showLocationHelper.showingLocation && settings.hasAnyChatToShowOnMap()) {
|
||||
app.showLocationHelper.startShowingLocation()
|
||||
}
|
||||
if (app.telegramService == null) {
|
||||
messages.forEach {
|
||||
val locationMessage = OsmandLocationUtils.parseMessage(it, telegramHelper, LocationMessages.LocationMessage.STATUS_SENT)
|
||||
if (locationMessage != null) {
|
||||
app.locationMessages.addLocationMessage(locationMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {}
|
||||
|
@ -341,7 +350,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
|
|||
fun logoutTelegram(silent: Boolean = false) {
|
||||
if (telegramHelper.getTelegramAuthorizationState() == TelegramHelper.TelegramAuthorizationState.READY) {
|
||||
if (app.isInternetConnectionAvailable) {
|
||||
app.messagesDbHelper.clearMessages()
|
||||
app.locationMessages.clearMessages()
|
||||
settings.clear()
|
||||
telegramHelper.logout()
|
||||
} else {
|
||||
|
|
|
@ -714,6 +714,18 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
|
|||
OsmandFormatter.getFormattedDuration(context!!, expiresIn, true)
|
||||
)})"
|
||||
}
|
||||
holder.gpsPointsCollected?.apply {
|
||||
if (shareInfo != null) {
|
||||
val all = app.locationMessages.getOutgoingMessagesToChat(shareInfo.chatId)
|
||||
text = "${all.size}"
|
||||
}
|
||||
}
|
||||
holder.gpsPointsSent?.apply {
|
||||
if (shareInfo != null) {
|
||||
val sent = app.locationMessages.getOutgoingMessagesToChatFromDate(shareInfo.chatId, shareInfo.start * 1000)
|
||||
text = "${sent.size}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -751,6 +763,8 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
|
|||
val stopSharingDescr: TextView? = view.findViewById(R.id.stop_in)
|
||||
val stopSharingFirstPart: TextView? = view.findViewById(R.id.ending_in_first_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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import net.osmand.telegram.helpers.TelegramUiHelper
|
|||
import net.osmand.telegram.ui.SetTimeDialogFragment.SetTimeListAdapter.ChatViewHolder
|
||||
import net.osmand.telegram.utils.AndroidUtils
|
||||
import net.osmand.telegram.utils.OsmandFormatter
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils
|
||||
import net.osmand.telegram.utils.UiUtils
|
||||
import net.osmand.util.MapUtils
|
||||
import org.drinkless.td.libcore.telegram.TdApi
|
||||
|
@ -328,7 +329,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
|
|||
val message = telegramHelper.getChatMessages(itemId).firstOrNull()
|
||||
val content = message?.content
|
||||
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?.text = OsmandFormatter.getListItemLiveTimeDescr(app, lastUpdated)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.DatePickerDialog
|
|||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.annotation.DrawableRes
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
|
@ -21,8 +22,8 @@ import net.osmand.telegram.helpers.TelegramUiHelper
|
|||
import net.osmand.telegram.helpers.TelegramUiHelper.ListItem
|
||||
import net.osmand.telegram.ui.TimelineTabFragment.LiveNowListAdapter.BaseViewHolder
|
||||
import net.osmand.telegram.utils.AndroidUtils
|
||||
import net.osmand.telegram.utils.GPXUtilities
|
||||
import net.osmand.telegram.utils.OsmandFormatter
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
@ -45,6 +46,8 @@ class TimelineTabFragment : Fragment() {
|
|||
private var start = 0L
|
||||
private var end = 0L
|
||||
|
||||
private var updateEnable: Boolean = false
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -93,6 +96,17 @@ class TimelineTabFragment : Fragment() {
|
|||
return mainView
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
updateEnable = true
|
||||
startHandler()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
updateEnable = false
|
||||
}
|
||||
|
||||
private fun setupBtnTextColor(textView: TextView) {
|
||||
textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light))
|
||||
}
|
||||
|
@ -162,32 +176,37 @@ class TimelineTabFragment : Fragment() {
|
|||
return normal
|
||||
}
|
||||
|
||||
private fun startHandler() {
|
||||
val updateAdapter = Handler()
|
||||
updateAdapter.postDelayed({
|
||||
if (updateEnable) {
|
||||
updateList()
|
||||
startHandler()
|
||||
}
|
||||
}, ADAPTER_UPDATE_INTERVAL_MIL)
|
||||
}
|
||||
|
||||
private fun updateList() {
|
||||
val res = mutableListOf<ListItem>()
|
||||
val s = System.currentTimeMillis()
|
||||
log.debug("updateList $s")
|
||||
val ignoredUsersIds = ArrayList<Int>()
|
||||
val currentUserId = telegramHelper.getCurrentUser()?.id
|
||||
if (currentUserId != null) {
|
||||
val currentUserGpx:GPXUtilities.GPXFile? = app.savingTracksDbHelper.collectRecordedDataForUser(currentUserId, 0, start, end)
|
||||
if (currentUserGpx != null) {
|
||||
TelegramUiHelper.gpxToChatItem(telegramHelper, currentUserGpx, true)?.also {
|
||||
res.add(it)
|
||||
val locationMessages = app.locationMessages.collectRecordedDataForUser(currentUserId, 0, start, end)
|
||||
OsmandLocationUtils.convertLocationMessagesToGpxFiles(locationMessages, false).forEach {
|
||||
TelegramUiHelper.gpxToChatItem(telegramHelper, it, true)?.also { chatItem ->
|
||||
res.add(chatItem)
|
||||
}
|
||||
}
|
||||
ignoredUsersIds.add(currentUserId)
|
||||
}
|
||||
val gpxFiles = app.savingTracksDbHelper.collectRecordedDataForUsers(start, end, ignoredUsersIds)
|
||||
val e = System.currentTimeMillis()
|
||||
|
||||
gpxFiles.forEach {
|
||||
val locationMessages = app.locationMessages.collectRecordedDataForUsers(start, end, ignoredUsersIds)
|
||||
OsmandLocationUtils.convertLocationMessagesToGpxFiles(locationMessages).forEach {
|
||||
TelegramUiHelper.gpxToChatItem(telegramHelper, it,false)?.also { chatItem ->
|
||||
res.add(chatItem)
|
||||
}
|
||||
}
|
||||
|
||||
adapter.items = sortAdapterItems(res)
|
||||
log.debug("updateList $s dif: ${e - s}")
|
||||
}
|
||||
|
||||
private fun sortAdapterItems(list: MutableList<ListItem>): MutableList<ListItem> {
|
||||
|
@ -269,4 +288,8 @@ class TimelineTabFragment : Fragment() {
|
|||
val bottomDivider: View? = view.findViewById(R.id.bottom_divider)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ADAPTER_UPDATE_INTERVAL_MIL = 15 * 1000L // 15 sec
|
||||
}
|
||||
}
|
|
@ -21,11 +21,11 @@ import net.osmand.PlatformUtil
|
|||
import net.osmand.aidl.gpx.AGpxBitmap
|
||||
import net.osmand.telegram.R
|
||||
import net.osmand.telegram.helpers.OsmandAidlHelper
|
||||
import net.osmand.telegram.helpers.SavingTracksDbHelper
|
||||
import net.osmand.telegram.helpers.TelegramUiHelper
|
||||
import net.osmand.telegram.utils.AndroidUtils
|
||||
import net.osmand.telegram.utils.GPXUtilities
|
||||
import net.osmand.telegram.utils.OsmandFormatter
|
||||
import net.osmand.telegram.utils.OsmandLocationUtils
|
||||
import net.osmand.util.Algorithms
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
|
@ -63,7 +63,6 @@ class UserGpxInfoFragment : BaseDialogFragment() {
|
|||
readFromBundle(savedInstanceState ?: arguments)
|
||||
|
||||
val userId = gpxFile.userId
|
||||
val chatId = gpxFile.chatId
|
||||
|
||||
val user = app.telegramHelper.getUser(userId)
|
||||
if (user != null) {
|
||||
|
@ -134,12 +133,13 @@ class UserGpxInfoFragment : BaseDialogFragment() {
|
|||
openGpx(gpx.path)
|
||||
} else {
|
||||
saveCurrentGpxToFile(object :
|
||||
SavingTracksDbHelper.SaveGpxListener {
|
||||
OsmandLocationUtils.SaveGpxListener {
|
||||
|
||||
override fun onSavingGpxFinish(path: String) {
|
||||
openGpx(path)
|
||||
}
|
||||
|
||||
override fun onSavingGpxError(warnings: MutableList<String>?) {
|
||||
override fun onSavingGpxError(warnings: List<String>?) {
|
||||
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
})
|
||||
|
@ -156,12 +156,12 @@ class UserGpxInfoFragment : BaseDialogFragment() {
|
|||
(activity as MainActivity).shareGpx(gpx.path)
|
||||
} else {
|
||||
saveCurrentGpxToFile(object :
|
||||
SavingTracksDbHelper.SaveGpxListener {
|
||||
OsmandLocationUtils.SaveGpxListener {
|
||||
override fun onSavingGpxFinish(path: String) {
|
||||
(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()
|
||||
}
|
||||
})
|
||||
|
@ -189,8 +189,10 @@ class UserGpxInfoFragment : BaseDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun saveCurrentGpxToFile(listener: SavingTracksDbHelper.SaveGpxListener) {
|
||||
app.savingTracksDbHelper.saveGpx(listener, app.getExternalFilesDir(null), gpxFile)
|
||||
private fun saveCurrentGpxToFile(listener: OsmandLocationUtils.SaveGpxListener) {
|
||||
if (!gpxFile.isEmpty) {
|
||||
OsmandLocationUtils.saveGpx(app, listener, app.getExternalFilesDir(null)!!, gpxFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readFromBundle(bundle: Bundle?) {
|
||||
|
@ -205,7 +207,14 @@ class UserGpxInfoFragment : BaseDialogFragment() {
|
|||
}
|
||||
|
||||
private fun updateGpxInfo() {
|
||||
gpxFile = app.savingTracksDbHelper.collectRecordedDataForUser(gpxFile.userId, gpxFile.chatId, startCalendar.timeInMillis, endCalendar.timeInMillis)
|
||||
gpxFile = OsmandLocationUtils.convertLocationMessagesToGpxFiles(
|
||||
app.locationMessages.collectRecordedDataForUser(
|
||||
gpxFile.userId,
|
||||
gpxFile.chatId,
|
||||
startCalendar.timeInMillis,
|
||||
endCalendar.timeInMillis
|
||||
)
|
||||
).first()
|
||||
updateGPXStatisticRow()
|
||||
updateDateAndTimeButtons()
|
||||
updateGPXMap()
|
||||
|
@ -229,7 +238,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
|
|||
|
||||
private fun updateGPXMap() {
|
||||
saveCurrentGpxToFile(object :
|
||||
SavingTracksDbHelper.SaveGpxListener {
|
||||
OsmandLocationUtils.SaveGpxListener {
|
||||
override fun onSavingGpxFinish(path: String) {
|
||||
val mgr = activity?.getSystemService(Context.WINDOW_SERVICE)
|
||||
if (mgr != null) {
|
||||
|
@ -242,7 +251,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onSavingGpxError(warnings: MutableList<String>?) {
|
||||
override fun onSavingGpxError(warnings: List<String>?) {
|
||||
log.debug("onSavingGpxError ${warnings?.firstOrNull()}")
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,474 @@
|
|||
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.TelegramHelper
|
||||
import net.osmand.telegram.helpers.TelegramUiHelper
|
||||
import net.osmand.util.GeoPointParserUtil
|
||||
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 MessageUserTextLocation -> 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 parseMessage(message: TdApi.Message, helper: TelegramHelper, status: Int): LocationMessages.LocationMessage? {
|
||||
var locationMessage: LocationMessages.LocationMessage? = null
|
||||
val oldContent = message.content
|
||||
|
||||
val fromBot = helper.isOsmAndBot(message.senderUserId)
|
||||
val viaBot = helper.isOsmAndBot(message.viaBotUserId)
|
||||
|
||||
var messageType: Int = -1
|
||||
val parsedMessageContent =
|
||||
if (oldContent is TdApi.MessageText) {
|
||||
when {
|
||||
oldContent.text.text.startsWith(DEVICE_PREFIX) -> {
|
||||
messageType = 3
|
||||
parseTextLocation(oldContent.text)
|
||||
}
|
||||
oldContent.text.text.startsWith(USER_TEXT_LOCATION_TITLE) -> {
|
||||
messageType = 1
|
||||
parseTextLocation(oldContent.text, false)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
} else if (oldContent is TdApi.MessageLocation && (fromBot || viaBot)) {
|
||||
messageType = 2
|
||||
parseOsmAndBotLocation(message)
|
||||
} else if (oldContent is MessageLocation) {
|
||||
messageType = 0
|
||||
oldContent
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (parsedMessageContent != null) {
|
||||
locationMessage = LocationMessages.LocationMessage(message.senderUserId, message.chatId, parsedMessageContent.lat,
|
||||
parsedMessageContent.lon, parsedMessageContent.altitude, parsedMessageContent.speed, parsedMessageContent.hdop,
|
||||
parsedMessageContent.bearing, parsedMessageContent.lastUpdated * 1000L, messageType, status, message.id)
|
||||
}
|
||||
return locationMessage
|
||||
}
|
||||
|
||||
fun formatLocation(sig: Location): String {
|
||||
return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude)
|
||||
}
|
||||
|
||||
fun formatLocation(sig: LocationMessages.LocationMessage): 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()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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: LocationMessages.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.date)))
|
||||
} else {
|
||||
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.date), updateId))
|
||||
}
|
||||
val textMessage = builder.toString().trim()
|
||||
|
||||
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
|
||||
}
|
||||
|
||||
fun convertLocationMessagesToGpxFiles(items: List<LocationMessages.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.date
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
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>?)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue