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

This commit is contained in:
PaulStets 2018-08-16 17:03:43 +03:00
commit 6edf2f9dc8
19 changed files with 221 additions and 84 deletions

View file

@ -1,4 +1,7 @@
<resources>
<string name="time_ago">ago</string>
<string name="last_response">Last response</string>
<string name="shared_string_group">Group</string>
<string name="logout_no_internet_msg">To properly log out from your Telegram account, the Internet is needed.</string>
<string name="shared_string_close">Close</string>
<string name="disconnect_from_telegram_desc">To invoke access to your Telegram account from OsmAnd, open Telegram, go to Settings - Privacy and Security - Sessions and terminate OsmAnd Telegram session. After that, OsmAnd Location Sharing will no longer have access to your account, and you will not be able to use this app until you log in again.</string>

View file

@ -54,7 +54,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
val messages = telegramHelper.getMessages()
for (message in messages) {
val date = Math.max(message.date, message.editDate) * 1000L
val expired = System.currentTimeMillis() - date > app.settings.locHistoryTime
val expired = System.currentTimeMillis() - date > app.settings.locHistoryTime * 1000L
if (expired) {
removeMapPoint(message.chatId, message)
}

View file

@ -28,6 +28,7 @@ class TelegramHelper private constructor() {
private const val DEVICE_PREFIX = "Device: "
private const val LOCATION_PREFIX = "Location: "
private const val LAST_LOCATION_PREFIX = "Last location: "
private const val FEW_SECONDS_AGO = "few seconds ago"
private const val SECONDS_AGO_SUFFIX = " seconds ago"
@ -177,6 +178,8 @@ class TelegramHelper private constructor() {
return chat.type is TdApi.ChatTypeSupergroup || chat.type is TdApi.ChatTypeBasicGroup
}
fun getLastUpdatedTime(message: TdApi.Message) = Math.max(message.editDate, message.date)
fun isPrivateChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypePrivate
private fun isChannel(chat: TdApi.Chat): Boolean {
@ -481,16 +484,15 @@ class TelegramHelper private constructor() {
private fun addNewMessage(message: TdApi.Message) {
if (message.isAppropriate()) {
val fromBot = isOsmAndBot(message.senderUserId)
val viaBot = isOsmAndBot(message.viaBotUserId)
val oldContent = message.content
if (oldContent is TdApi.MessageText) {
val messageOsmAndBotLocation = parseOsmAndBotLocation(oldContent.text.text)
messageOsmAndBotLocation.created = message.date
message.content = messageOsmAndBotLocation
} else if (oldContent is TdApi.MessageLocation &&
(isOsmAndBot(message.senderUserId) || isOsmAndBot(message.viaBotUserId))) {
message.content = parseOsmAndBotLocation(oldContent.text.text)
} else if (oldContent is TdApi.MessageLocation && (fromBot || viaBot)) {
message.content = parseOsmAndBotLocation(message)
}
removeOldMessages(message.senderUserId, message.chatId)
removeOldMessages(message, fromBot, viaBot)
usersLocationMessages[message.id] = message
incomingMessagesListeners.forEach {
it.onReceiveChatLocationMessages(message.chatId, message)
@ -498,13 +500,25 @@ class TelegramHelper private constructor() {
}
}
private fun removeOldMessages(userId: Int, chatId: Long) {
val user = users[userId]
if (user != null && user.username != OSMAND_BOT_USERNAME) {
usersLocationMessages.values.filter { it.senderUserId == userId && it.chatId == chatId }
.forEach {
usersLocationMessages.remove(it.id)
private fun removeOldMessages(newMessage: TdApi.Message, fromBot: Boolean, viaBot: Boolean) {
val iterator = usersLocationMessages.entries.iterator()
while (iterator.hasNext()) {
val message = iterator.next().value
if (newMessage.chatId == message.chatId) {
val sameSender = newMessage.senderUserId == message.senderUserId
val viaSameBot = newMessage.viaBotUserId == message.viaBotUserId
if ((fromBot && sameSender) || (viaBot && viaSameBot)) {
val newCont = newMessage.content
val cont = message.content
if (newCont is MessageOsmAndBotLocation && cont is MessageOsmAndBotLocation) {
if (newCont.name == cont.name) {
iterator.remove()
}
}
} else if (sameSender) {
iterator.remove()
}
}
}
}
@ -749,7 +763,8 @@ class TelegramHelper private constructor() {
val content = content
return when (content) {
is TdApi.MessageLocation -> true
is TdApi.MessageText -> isOsmAndBot(senderUserId) || isOsmAndBot(viaBotUserId)
is TdApi.MessageText -> (isOsmAndBot(senderUserId) || isOsmAndBot(viaBotUserId))
&& content.text.text.startsWith(DEVICE_PREFIX)
else -> false
}
}
@ -760,13 +775,7 @@ class TelegramHelper private constructor() {
name = getOsmAndBotDeviceName(message)
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
created = message.date
val date = message.editDate
lastUpdated = if (date != 0) {
date
} else {
message.date
}
lastUpdated = getLastUpdatedTime(message)
}
}
@ -776,33 +785,46 @@ class TelegramHelper private constructor() {
name = oldContent.name
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
created = oldContent.created
lastUpdated = (System.currentTimeMillis() / 1000).toInt()
}
}
private fun parseOsmAndBotLocation(text: String): MessageOsmAndBotLocation {
val res = MessageOsmAndBotLocation()
var locationNA = false
for (s in text.lines()) {
when {
s.startsWith(DEVICE_PREFIX) -> {
res.name = s.removePrefix(DEVICE_PREFIX)
}
s.startsWith(LOCATION_PREFIX) -> {
val locStr = s.removePrefix(LOCATION_PREFIX)
try {
val (latS, lonS) = locStr.split(" ")
val updatedS = locStr.substring(locStr.indexOf("("), locStr.length)
val timeSecs = parseTime(updatedS.removePrefix("(").removeSuffix(")"))
res.lat = latS.dropLast(1).toDouble()
res.lon = lonS.toDouble()
if (timeSecs < messageActiveTimeSec) {
res.lastUpdated = (System.currentTimeMillis() / 1000 - timeSecs).toInt()
} else {
res.lastUpdated = timeSecs
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 (latS, lonS) = locStr.split(" ")
val updatedS = locStr.substring(locStr.indexOf("("), locStr.length)
res.lastUpdated =
(parseTime(updatedS.removePrefix("(").removeSuffix(")")) / 1000).toInt()
res.lat = latS.dropLast(1).toDouble()
res.lon = lonS.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@ -810,24 +832,24 @@ class TelegramHelper private constructor() {
return res
}
private fun parseTime(timeS: String): Int {
private fun parseTime(timeS: String): Long {
try {
when {
timeS.endsWith(FEW_SECONDS_AGO) -> return 5
timeS.endsWith(FEW_SECONDS_AGO) -> return System.currentTimeMillis() - 5000
timeS.endsWith(SECONDS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX)
return locStr.toInt()
return System.currentTimeMillis() - locStr.toLong() * 1000
}
timeS.endsWith(MINUTES_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX)
val minutes = locStr.toInt()
return minutes * 60
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.toInt()
return hours * 60 * 60
val hours = locStr.toLong()
return (System.currentTimeMillis() - hours * 60 * 60 * 1000)
}
timeS.endsWith(UTC_FORMAT_SUFFIX) -> {
val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX)
@ -835,7 +857,7 @@ class TelegramHelper private constructor() {
val date = UTC_DATE_FORMAT.parse(latS)
val time = UTC_TIME_FORMAT.parse(lonS)
val res = date.time + time.time
return res.toInt()
return res
}
}
} catch (e: Exception) {
@ -1068,9 +1090,7 @@ class TelegramHelper private constructor() {
synchronized(message) {
val newContent = updateMessageContent.newContent
message.content = if (newContent is TdApi.MessageText) {
val messageOsmAndBotLocation = parseOsmAndBotLocation(newContent.text.text)
messageOsmAndBotLocation.created = message.date
messageOsmAndBotLocation
parseOsmAndBotLocation(newContent.text.text)
} else if (newContent is TdApi.MessageLocation &&
(isOsmAndBot(message.senderUserId) || isOsmAndBot(message.viaBotUserId))) {
parseOsmAndBotLocationContent(message.content as MessageOsmAndBotLocation, newContent)

View file

@ -52,11 +52,6 @@ object TelegramUiHelper {
placeholderId = R.drawable.img_user_picture
}
val type = chat.type
val message = messages.firstOrNull()
if (message != null) {
res.lastUpdated = message.editDate
res.created = message.date
}
if (type is TdApi.ChatTypePrivate || type is TdApi.ChatTypeSecret) {
val userId = getUserIdFromChatType(type)
val chatWithBot = helper.isBot(userId)
@ -64,9 +59,13 @@ object TelegramUiHelper {
res.chatWithBot = chatWithBot
if (!chatWithBot) {
res.userId = userId
val content = message?.content
if (content is TdApi.MessageLocation) {
res.latLon = LatLon(content.location.latitude, content.location.longitude)
val message = messages.firstOrNull { it.viaBotUserId == 0 }
if (message != null) {
res.lastUpdated = helper.getLastUpdatedTime(message)
val content = message.content
if (content is TdApi.MessageLocation) {
res.latLon = LatLon(content.location.latitude, content.location.longitude)
}
}
}
} else if (type is TdApi.ChatTypeBasicGroup) {
@ -125,7 +124,6 @@ object TelegramUiHelper {
latLon = LatLon(content.lat, content.lon)
placeholderId = R.drawable.img_user_picture
lastUpdated = content.lastUpdated
created = content.created
}
} else {
null
@ -147,8 +145,7 @@ object TelegramUiHelper {
photoPath = helper.getUserPhotoPath(user)
placeholderId = R.drawable.img_user_picture
userId = message.senderUserId
lastUpdated = message.editDate
created = message.date
lastUpdated = helper.getLastUpdatedTime(message)
}
}
@ -168,8 +165,6 @@ object TelegramUiHelper {
internal set
var lastUpdated: Int = 0
internal set
var created: Int = 0
internal set
abstract fun canBeOpenedOnMap(): Boolean

View file

@ -29,6 +29,8 @@ import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.UiUtils.UpdateLocationViewCache
import net.osmand.util.MapUtils
import org.drinkless.td.libcore.telegram.TdApi
import java.text.SimpleDateFormat
import java.util.*
private const val CHAT_VIEW_TYPE = 0
private const val LOCATION_ITEM_VIEW_TYPE = 1
@ -221,31 +223,32 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
for ((id, messages) in telegramHelper.getMessagesByChatIds()) {
telegramHelper.getChat(id)?.also { chat ->
res.add(TelegramUiHelper.chatToChatItem(telegramHelper, chat, messages))
if (needLocationItems(chat.type)) {
val type = chat.type
if (type is TdApi.ChatTypeBasicGroup || type is TdApi.ChatTypeSupergroup) {
res.addAll(convertToLocationItems(chat, messages))
} else if (type is TdApi.ChatTypePrivate) {
if (telegramHelper.isOsmAndBot(type.userId)) {
res.addAll(convertToLocationItems(chat, messages))
} else if (messages.firstOrNull { it.viaBotUserId != 0 } != null) {
res.addAll(convertToLocationItems(chat, messages, true))
}
}
}
}
adapter.items = res
}
private fun needLocationItems(type: TdApi.ChatType): Boolean {
return when (type) {
is TdApi.ChatTypeBasicGroup -> true
is TdApi.ChatTypeSupergroup -> true
is TdApi.ChatTypePrivate -> telegramHelper.isOsmAndBot(type.userId)
else -> false
}
}
private fun convertToLocationItems(
chat: TdApi.Chat,
messages: List<TdApi.Message>
messages: List<TdApi.Message>,
addOnlyViaBotMessages: Boolean = false
): List<LocationItem> {
val res = mutableListOf<LocationItem>()
messages.forEach { message ->
TelegramUiHelper.messageToLocationItem(telegramHelper, chat, message)?.also {
res.add(it)
if (!addOnlyViaBotMessages || message.viaBotUserId != 0) {
TelegramUiHelper.messageToLocationItem(telegramHelper, chat, message)?.also {
res.add(it)
}
}
}
return res
@ -319,7 +322,7 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
}
if (location != null && item.latLon != null) {
holder.locationViewContainer?.visibility = View.VISIBLE
locationViewCache.outdatedLocation = System.currentTimeMillis() / 1000 - item.lastUpdated > settings.staleLocTime
locationViewCache.outdatedLocation = System.currentTimeMillis() / 1000 - item.lastUpdated > settings.staleLocTime
app.uiUtils.updateLocationView(
holder.directionIcon,
holder.distanceText,
@ -361,13 +364,19 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage
}
}
private fun getListItemLiveTimeDescr(item: ListItem):String {
return getString(R.string.shared_string_live) +
": ${OsmandFormatter.getFormattedDuration(app, getListItemLiveTime(item))}"
private fun getListItemLiveTimeDescr(item: ListItem): String {
val duration = System.currentTimeMillis() / 1000 - item.lastUpdated
var formattedTime = OsmandFormatter.getFormattedDuration(app, duration)
return if (duration > 48 * 60 * 60) {
// TODO make constant
val day = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
formattedTime = day.format(Date(item.lastUpdated * 1000.toLong()))
getString(R.string.last_response) + ": $formattedTime"
} else {
getString(R.string.last_response) + ": $formattedTime " + getString(R.string.time_ago)
}
}
private fun getListItemLiveTime(item: ListItem): Long = (System.currentTimeMillis() / 1000) - item.created
private fun showPopupMenu(holder: ChatViewHolder, chatId: Long) {
val ctx = holder.itemView.context

View file

@ -577,23 +577,25 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
}
holder.stopSharingDescr?.apply {
visibility = View.VISIBLE
visibility = getStopSharingVisibility(expiresIn)
text = "${getText(R.string.stop_at)}:"
}
holder.stopSharingFirstPart?.apply {
visibility = View.VISIBLE
visibility = getStopSharingVisibility(expiresIn)
text = OsmandFormatter.getFormattedTime(expiresIn)
}
holder.stopSharingSecondPart?.apply {
visibility = View.VISIBLE
visibility = getStopSharingVisibility(expiresIn)
text = "(${getString(R.string.in_time,
OsmandFormatter.getFormattedDuration(context!!, expiresIn, true))})"
}
}
}
private fun getStopSharingVisibility(expiresIn: Long) = if (expiresIn > 0) View.VISIBLE else View.INVISIBLE
private fun removeItem(chat: TdApi.Chat) {
chats.remove(chat)
if (chats.isEmpty()) {

View file

@ -12,16 +12,24 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import net.osmand.Location
import net.osmand.data.LatLon
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener
import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener
import net.osmand.telegram.helpers.ShareLocationHelper
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.UiUtils
import net.osmand.util.MapUtils
import org.drinkless.td.libcore.telegram.TdApi
import java.util.*
import java.util.concurrent.TimeUnit
class SetTimeDialogFragment : DialogFragment() {
class SetTimeDialogFragment : DialogFragment(), TelegramLocationListener, TelegramCompassListener {
private val app: TelegramApplication
get() = activity?.application as TelegramApplication
@ -29,6 +37,7 @@ class SetTimeDialogFragment : DialogFragment() {
private val telegramHelper get() = app.telegramHelper
private val settings get() = app.settings
private lateinit var locationViewCache: UiUtils.UpdateLocationViewCache
private val adapter = SetTimeListAdapter()
private lateinit var timeForAllTitle: TextView
@ -36,6 +45,10 @@ class SetTimeDialogFragment : DialogFragment() {
private val chatLivePeriods = HashMap<Long, Long>()
private var location: Location? = null
private var heading: Float? = null
private var locationUiUpdateAllowed: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NO_FRAME, R.style.AppTheme_NoActionbar)
@ -66,6 +79,12 @@ class SetTimeDialogFragment : DialogFragment() {
view.findViewById<RecyclerView>(R.id.recycler_view).apply {
layoutManager = LinearLayoutManager(context)
adapter = this@SetTimeDialogFragment.adapter
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
locationUiUpdateAllowed = newState == RecyclerView.SCROLL_STATE_IDLE
}
})
}
view.findViewById<TextView>(R.id.secondary_btn).apply {
@ -98,9 +117,16 @@ class SetTimeDialogFragment : DialogFragment() {
override fun onResume() {
super.onResume()
locationViewCache = app.uiUtils.getUpdateLocationViewCache()
startLocationUpdate()
updateList()
}
override fun onPause() {
super.onPause()
stopLocationUpdate()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val chats = mutableListOf<Long>()
@ -111,6 +137,47 @@ class SetTimeDialogFragment : DialogFragment() {
outState.putLongArray(CHATS_KEY, chats.toLongArray())
}
override fun updateLocation(location: Location?) {
val loc = this.location
val newLocation = loc == null && location != null
val locationChanged = loc != null && location != null
&& loc.latitude != location.latitude
&& loc.longitude != location.longitude
if (newLocation || locationChanged) {
this.location = location
updateLocationUi()
}
}
override fun updateCompassValue(value: Float) {
// 99 in next line used to one-time initialize arrows (with reference vs. fixed-north direction)
// on non-compass devices
val lastHeading = heading ?: 99f
heading = value
if (Math.abs(MapUtils.degreesDiff(lastHeading.toDouble(), value.toDouble())) > 5) {
updateLocationUi()
} else {
heading = lastHeading
}
}
private fun startLocationUpdate() {
app.locationProvider.addLocationListener(this)
app.locationProvider.addCompassListener(this)
updateLocationUi()
}
private fun stopLocationUpdate() {
app.locationProvider.removeLocationListener(this)
app.locationProvider.removeCompassListener(this)
}
private fun updateLocationUi() {
if (locationUiUpdateAllowed) {
app.runInUIThread { adapter.notifyDataSetChanged() }
}
}
private fun readFromBundle(bundle: Bundle?) {
chatLivePeriods.clear()
bundle?.getLongArray(CHATS_KEY)?.also {
@ -216,7 +283,35 @@ class SetTimeDialogFragment : DialogFragment() {
TelegramUiHelper.setupPhoto(app, holder.icon, chat.photo?.small?.local?.path, placeholderId, false)
holder.title?.text = chat.title
holder.description?.visibility = View.INVISIBLE
if (telegramHelper.isGroup(chat)) {
holder.locationViewContainer?.visibility = View.GONE
holder.description?.visibility = View.VISIBLE
holder.description?.text = getString(R.string.shared_string_group)
} else {
val message = telegramHelper.getChatMessages(chat.id).firstOrNull()
val content = message?.content
if (message != null && content is TdApi.MessageLocation && (location != null && content.location != null)) {
val lastUpdated = telegramHelper.getLastUpdatedTime(message)
holder.description?.visibility = View.VISIBLE
holder.description?.text = getListItemLiveTimeDescr(lastUpdated)
holder.locationViewContainer?.visibility = View.VISIBLE
locationViewCache.outdatedLocation = System.currentTimeMillis() / 1000 -
lastUpdated > settings.staleLocTime
app.uiUtils.updateLocationView(
holder.directionIcon,
holder.distanceText,
LatLon(content.location.latitude, content.location.longitude),
locationViewCache
)
} else {
holder.locationViewContainer?.visibility = View.GONE
holder.description?.visibility = View.INVISIBLE
}
}
holder.textInArea?.apply {
visibility = View.VISIBLE
chatLivePeriods[chat.id]?.also { text = formatLivePeriod(it) }
@ -229,9 +324,22 @@ class SetTimeDialogFragment : DialogFragment() {
override fun getItemCount() = chats.size
private fun getListItemLiveTimeDescr(lastUpdated: Int): String {
val duration = System.currentTimeMillis() / 1000 - lastUpdated
var formattedTime = OsmandFormatter.getFormattedDuration(app, duration)
if (duration > 48 * 60 * 60) {
// TODO make constant
formattedTime = Date(lastUpdated * 1000.toLong()).toString();
}
return "$formattedTime " + getString(R.string.time_ago)
}
inner class ChatViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val icon: ImageView? = view.findViewById(R.id.icon)
val title: TextView? = view.findViewById(R.id.title)
val directionIcon: ImageView? = view.findViewById(R.id.direction_icon)
val distanceText: TextView? = view.findViewById(R.id.distance_text)
val locationViewContainer: View? = view.findViewById(R.id.location_view_container)
val description: TextView? = view.findViewById(R.id.description)
val textInArea: TextView? = view.findViewById(R.id.text_in_area)
val bottomShadow: View? = view.findViewById(R.id.bottom_shadow)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB