diff --git a/OsmAnd-telegram/res/values/strings.xml b/OsmAnd-telegram/res/values/strings.xml index 738f0e9714..11ffae080c 100644 --- a/OsmAnd-telegram/res/values/strings.xml +++ b/OsmAnd-telegram/res/values/strings.xml @@ -1,4 +1,7 @@ + ago + Last response + Group To properly log out from your Telegram account, the Internet is needed. Close 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. diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt index 7fc019a9d6..74f4ad5c77 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt @@ -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) } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt index 519bc862d0..8f07cf800a 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt @@ -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) diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt index 6e11d46490..2146140780 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt @@ -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 diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt index af2037eb4b..a2adff4bb7 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt @@ -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 + messages: List, + addOnlyViaBotMessages: Boolean = false ): List { val res = mutableListOf() 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 diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt index ddc2238eff..3d9af9f7aa 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt @@ -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()) { diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt index 17d5297909..e3692b053f 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt @@ -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() + 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(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(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() @@ -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) diff --git a/OsmAnd/res/drawable-hdpi/img_user_picture.png b/OsmAnd/res/drawable-hdpi/img_user_picture.png index 155805c7c5..2c15e0e884 100644 Binary files a/OsmAnd/res/drawable-hdpi/img_user_picture.png and b/OsmAnd/res/drawable-hdpi/img_user_picture.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_pin_user_stale_location_day.png b/OsmAnd/res/drawable-hdpi/map_pin_user_stale_location_day.png new file mode 100644 index 0000000000..8f75c4498d Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_pin_user_stale_location_day.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_pin_user_stale_location_night.png b/OsmAnd/res/drawable-hdpi/map_pin_user_stale_location_night.png new file mode 100644 index 0000000000..20c38a1592 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_pin_user_stale_location_night.png differ diff --git a/OsmAnd/res/drawable-mdpi/img_user_picture.png b/OsmAnd/res/drawable-mdpi/img_user_picture.png index 5a5f81bd69..973d09a6b0 100644 Binary files a/OsmAnd/res/drawable-mdpi/img_user_picture.png and b/OsmAnd/res/drawable-mdpi/img_user_picture.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_pin_user_stale_location_day.png b/OsmAnd/res/drawable-mdpi/map_pin_user_stale_location_day.png new file mode 100644 index 0000000000..44f94adaa3 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_pin_user_stale_location_day.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_pin_user_stale_location_night.png b/OsmAnd/res/drawable-mdpi/map_pin_user_stale_location_night.png new file mode 100644 index 0000000000..ee3b397ca8 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_pin_user_stale_location_night.png differ diff --git a/OsmAnd/res/drawable-xhdpi/img_user_picture.png b/OsmAnd/res/drawable-xhdpi/img_user_picture.png index 0ef799e12a..cc5f380d8b 100644 Binary files a/OsmAnd/res/drawable-xhdpi/img_user_picture.png and b/OsmAnd/res/drawable-xhdpi/img_user_picture.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_pin_user_stale_location_day.png b/OsmAnd/res/drawable-xhdpi/map_pin_user_stale_location_day.png new file mode 100644 index 0000000000..c11db9f38c Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_pin_user_stale_location_day.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_pin_user_stale_location_night.png b/OsmAnd/res/drawable-xhdpi/map_pin_user_stale_location_night.png new file mode 100644 index 0000000000..56f248faae Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_pin_user_stale_location_night.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/img_user_picture.png b/OsmAnd/res/drawable-xxhdpi/img_user_picture.png index 6bbbe923eb..8d599162fb 100644 Binary files a/OsmAnd/res/drawable-xxhdpi/img_user_picture.png and b/OsmAnd/res/drawable-xxhdpi/img_user_picture.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_pin_user_stale_location_day.png b/OsmAnd/res/drawable-xxhdpi/map_pin_user_stale_location_day.png new file mode 100644 index 0000000000..a5971c9b8d Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_pin_user_stale_location_day.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_pin_user_stale_location_night.png b/OsmAnd/res/drawable-xxhdpi/map_pin_user_stale_location_night.png new file mode 100644 index 0000000000..4f3bc9fd25 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_pin_user_stale_location_night.png differ