Merge branch 'master' into app_profiles_2
# Conflicts: # OsmAnd/res/values/strings.xml
|
@ -133,6 +133,35 @@
|
|||
|
||||
<include layout="@layout/list_item_divider" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/card_bg_color"
|
||||
android:orientation="vertical">
|
||||
|
||||
<net.osmand.telegram.ui.views.TextViewEx
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/list_header_height"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:paddingLeft="@dimen/content_padding_standard"
|
||||
android:paddingRight="@dimen/content_padding_standard"
|
||||
android:text="@string/gpx_settings"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="@dimen/list_item_title_text_size"
|
||||
app:typeface="@string/font_roboto_medium" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gpx_settings_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/list_item_divider"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingLeft="@dimen/content_padding_standard"
|
||||
android:paddingRight="@dimen/content_padding_standard"
|
||||
tools:background="@color/card_bg_light">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/content_padding_standard"
|
||||
android:layout_marginLeft="@dimen/content_padding_standard"
|
||||
android:layout_marginTop="@dimen/image_button_padding"
|
||||
android:layout_marginEnd="@dimen/content_padding_big"
|
||||
android:layout_marginRight="@dimen/content_padding_big"
|
||||
|
@ -53,10 +53,8 @@
|
|||
<ImageView
|
||||
android:id="@+id/icon_right"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingLeft="@dimen/image_button_padding"
|
||||
android:paddingRight="@dimen/image_button_padding"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
tools:src="@drawable/ic_action_additional_option"
|
||||
tools:tint="@color/icon_light"
|
||||
|
@ -65,10 +63,14 @@
|
|||
<Switch
|
||||
android:id="@+id/switcher"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="@null"
|
||||
android:clickable="false"
|
||||
android:focusable="false" />
|
||||
android:focusable="false"
|
||||
android:paddingStart="@dimen/image_button_padding"
|
||||
android:paddingLeft="@dimen/image_button_padding"
|
||||
android:paddingEnd="@dimen/content_padding_standard"
|
||||
android:paddingRight="@dimen/content_padding_standard" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="shared_string_select">Select</string>
|
||||
<string name="min_logging_distance">Minimum logging distance</string>
|
||||
<string name="min_logging_distance_descr">Filter: minimum distance to log a new point</string>
|
||||
<string name="min_logging_accuracy">Minimum logging accuracy</string>
|
||||
<string name="min_logging_accuracy_descr">Filter: no logging unless the accuracy is reached</string>
|
||||
<string name="min_logging_speed">Minimum logging speed</string>
|
||||
<string name="min_logging_speed_descr">Filter: no logging below selected speed</string>
|
||||
<string name="gpx_settings">GPX settings</string>
|
||||
<string name="proxy_key">Key</string>
|
||||
<string name="proxy_password">Password</string>
|
||||
<string name="proxy_username">Username</string>
|
||||
|
|
|
@ -15,6 +15,7 @@ public class AMapPoint implements Parcelable {
|
|||
public static final String POINT_SPEED_PARAM = "point_speed_param";
|
||||
public static final String POINT_TYPE_ICON_NAME_PARAM = "point_type_icon_name_param";
|
||||
public static final String POINT_STALE_LOC_PARAM = "point_stale_loc_param";
|
||||
public static final String POINT_BEARING_PARAM = "point_bearing_param";
|
||||
|
||||
private String id;
|
||||
private String shortName;
|
||||
|
|
|
@ -53,12 +53,10 @@ class TelegramApplication : Application(), OsmandHelperListener {
|
|||
listOf(-1)
|
||||
)
|
||||
showLocationHelper.addDirectionContextMenuButton()
|
||||
if (settings.hasAnyChatToShowOnMap()) {
|
||||
showLocationHelper.startShowingLocation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
osmandAidlHelper.setUpdatesListener(object : UpdatesListener {
|
||||
override fun update() {
|
||||
showLocationHelper.startUpdateMessagesTask()
|
||||
|
|
|
@ -43,6 +43,9 @@ private val LOC_HISTORY_VALUES_SEC = listOf(
|
|||
12 * 60 * 60L,
|
||||
24 * 60 * 60L
|
||||
)
|
||||
private val MIN_LOCATION_DISTANCE = listOf(0f, 2.0f, 5.0f, 10.0f, 20.0f, 30.0f, 50.0f)
|
||||
private val MIN_LOCATION_ACCURACY = listOf(0f, 1.0f, 2.0f, 5.0f, 10.0f, 15.0f, 20.0f, 50.0f, 100.0f)
|
||||
private val MIN_LOCATION_SPEED = listOf(0f, 0.000001f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f)
|
||||
|
||||
const val SHARE_TYPE_MAP = "Map"
|
||||
const val SHARE_TYPE_TEXT = "Text"
|
||||
|
@ -53,6 +56,9 @@ private const val SEND_MY_LOC_DEFAULT_INDEX = 6
|
|||
private const val STALE_LOC_DEFAULT_INDEX = 0
|
||||
private const val LOC_HISTORY_DEFAULT_INDEX = 7
|
||||
private const val SHARE_TYPE_DEFAULT_INDEX = 2
|
||||
private const val MIN_LOCATION_DISTANCE_INDEX = 0
|
||||
private const val MIN_LOCATION_ACCURACY_INDEX = 0
|
||||
private const val MIN_LOCATION_SPEED_INDEX = 0
|
||||
|
||||
private const val SETTINGS_NAME = "osmand_telegram_settings"
|
||||
|
||||
|
@ -69,6 +75,10 @@ private const val STALE_LOC_TIME_KEY = "stale_loc_time"
|
|||
private const val LOC_HISTORY_TIME_KEY = "loc_history_time"
|
||||
private const val SHARE_TYPE_KEY = "share_type"
|
||||
|
||||
private const val MIN_LOCATION_DISTANCE_KEY = "min_location_distance"
|
||||
private const val MIN_LOCATION_ACCURACY_KEY = "min_location_accuracy"
|
||||
private const val MIN_LOCATION_SPEED_KEY = "min_location_speed"
|
||||
|
||||
private const val APP_TO_CONNECT_PACKAGE_KEY = "app_to_connect_package"
|
||||
|
||||
private const val DEFAULT_VISIBLE_TIME_SECONDS = 60 * 60L // 1 hour
|
||||
|
@ -117,12 +127,17 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
var locHistoryTime = LOC_HISTORY_VALUES_SEC[LOC_HISTORY_DEFAULT_INDEX]
|
||||
var shareTypeValue = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX]
|
||||
|
||||
var minLocationDistance = MIN_LOCATION_DISTANCE[MIN_LOCATION_DISTANCE_INDEX]
|
||||
var minLocationAccuracy = MIN_LOCATION_ACCURACY[MIN_LOCATION_ACCURACY_INDEX]
|
||||
var minLocationSpeed = MIN_LOCATION_SPEED[MIN_LOCATION_SPEED_INDEX]
|
||||
|
||||
var appToConnectPackage = ""
|
||||
private set
|
||||
|
||||
var liveNowSortType = LiveNowSortType.SORT_BY_DISTANCE
|
||||
|
||||
val gpsAndLocPrefs = listOf(SendMyLocPref(), StaleLocPref(), LocHistoryPref(), ShareTypePref())
|
||||
val gpxLoggingPrefs = listOf(MinLocationDistance(), MinLocationAccuracy(), MinLocationSpeed())
|
||||
|
||||
var batteryOptimisationAsked = false
|
||||
|
||||
|
@ -460,15 +475,20 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
if (shareInfo.lastTextSuccessfulSendTime == -1L && shareInfo.lastMapSuccessfulSendTime == -1L
|
||||
&& ((statusChangeTime / 1000 - shareInfo.start) < SHARING_INITIALIZATION_TIME)) {
|
||||
initializing = true
|
||||
} else if (shareInfo.hasSharingError
|
||||
|| (shareInfo.lastSendTextMessageTime - shareInfo.lastTextSuccessfulSendTime > WAITING_TDLIB_TIME)
|
||||
|| (shareInfo.lastSendMapMessageTime - shareInfo.lastMapSuccessfulSendTime > WAITING_TDLIB_TIME)
|
||||
} else {
|
||||
val textSharingError = shareInfo.lastSendTextMessageTime - shareInfo.lastTextSuccessfulSendTime > WAITING_TDLIB_TIME
|
||||
val mapSharingError = shareInfo.lastSendMapMessageTime - shareInfo.lastMapSuccessfulSendTime > WAITING_TDLIB_TIME
|
||||
if (shareInfo.hasSharingError
|
||||
|| (shareTypeValue == SHARE_TYPE_MAP_AND_TEXT && (textSharingError || mapSharingError))
|
||||
|| textSharingError && (shareTypeValue == SHARE_TYPE_TEXT)
|
||||
|| mapSharingError && (shareTypeValue == SHARE_TYPE_MAP)
|
||||
) {
|
||||
sendChatsErrors = true
|
||||
locationTime = Math.max(shareInfo.lastTextSuccessfulSendTime, shareInfo.lastMapSuccessfulSendTime)
|
||||
chatsIds.add(shareInfo.chatId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sendChatsErrors) {
|
||||
title = app.getString(R.string.not_possible_to_send_to_telegram_chats)
|
||||
|
@ -477,7 +497,7 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
} else if (!initializing) {
|
||||
when {
|
||||
!gpsEnabled -> {
|
||||
locationTime = app.shareLocationHelper.lastLocationUpdateTime
|
||||
locationTime = app.shareLocationHelper.lastLocationUpdateTime / 1000
|
||||
if (locationTime <= 0) {
|
||||
locationTime = getLastSuccessfulSendTime()
|
||||
}
|
||||
|
@ -557,6 +577,10 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
edit.putLong(STALE_LOC_TIME_KEY, staleLocTime)
|
||||
edit.putLong(LOC_HISTORY_TIME_KEY, locHistoryTime)
|
||||
|
||||
edit.putFloat(MIN_LOCATION_DISTANCE_KEY, minLocationDistance)
|
||||
edit.putFloat(MIN_LOCATION_ACCURACY_KEY, minLocationAccuracy)
|
||||
edit.putFloat(MIN_LOCATION_SPEED_KEY, minLocationSpeed)
|
||||
|
||||
edit.putString(SHARE_TYPE_KEY, shareTypeValue)
|
||||
|
||||
edit.putString(APP_TO_CONNECT_PACKAGE_KEY, appToConnectPackage)
|
||||
|
@ -623,6 +647,13 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
val shareTypeDef = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX]
|
||||
shareTypeValue = prefs.getString(SHARE_TYPE_KEY, shareTypeDef)
|
||||
|
||||
val minLocationDistanceDef = MIN_LOCATION_DISTANCE[MIN_LOCATION_DISTANCE_INDEX]
|
||||
minLocationDistance = prefs.getFloat(MIN_LOCATION_DISTANCE_KEY, minLocationDistanceDef)
|
||||
val minLocationPrecisionDef = MIN_LOCATION_ACCURACY[MIN_LOCATION_ACCURACY_INDEX]
|
||||
minLocationAccuracy = prefs.getFloat(MIN_LOCATION_ACCURACY_KEY, minLocationPrecisionDef)
|
||||
val minLocationSpeedDef = MIN_LOCATION_SPEED[MIN_LOCATION_SPEED_INDEX]
|
||||
minLocationSpeed = prefs.getFloat(MIN_LOCATION_SPEED_KEY, minLocationSpeedDef)
|
||||
|
||||
val currentUserId = app.telegramHelper.getCurrentUserId()
|
||||
currentSharingMode = prefs.getString(SHARING_MODE_KEY, if (currentUserId != -1) currentUserId.toString() else "")
|
||||
|
||||
|
@ -787,7 +818,7 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
}
|
||||
}
|
||||
|
||||
inner class SendMyLocPref : DurationPref(
|
||||
inner class SendMyLocPref : NumericPref(
|
||||
R.drawable.ic_action_share_location,
|
||||
R.string.send_my_location,
|
||||
R.string.send_my_location_desc,
|
||||
|
@ -798,12 +829,15 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
OsmandFormatter.getFormattedDuration(app, sendMyLocInterval)
|
||||
|
||||
override fun setCurrentValue(index: Int) {
|
||||
sendMyLocInterval = values[index]
|
||||
sendMyLocInterval = values[index].toLong()
|
||||
app.updateSendLocationInterval()
|
||||
}
|
||||
|
||||
override fun getMenuItems() =
|
||||
values.map { OsmandFormatter.getFormattedDuration(app, it.toLong()) }
|
||||
}
|
||||
|
||||
inner class StaleLocPref : DurationPref(
|
||||
inner class StaleLocPref : NumericPref(
|
||||
R.drawable.ic_action_time_span,
|
||||
R.string.stale_location,
|
||||
R.string.stale_location_desc,
|
||||
|
@ -814,11 +848,14 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
OsmandFormatter.getFormattedDuration(app, staleLocTime)
|
||||
|
||||
override fun setCurrentValue(index: Int) {
|
||||
staleLocTime = values[index]
|
||||
}
|
||||
staleLocTime = values[index].toLong()
|
||||
}
|
||||
|
||||
inner class LocHistoryPref : DurationPref(
|
||||
override fun getMenuItems() =
|
||||
values.map { OsmandFormatter.getFormattedDuration(app, it.toLong()) }
|
||||
}
|
||||
|
||||
inner class LocHistoryPref : NumericPref(
|
||||
R.drawable.ic_action_location_history,
|
||||
R.string.location_history,
|
||||
R.string.location_history_desc,
|
||||
|
@ -830,12 +867,15 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
|
||||
override fun setCurrentValue(index: Int) {
|
||||
val value = values[index]
|
||||
locHistoryTime = value
|
||||
app.telegramHelper.messageActiveTimeSec = value
|
||||
}
|
||||
locHistoryTime = value.toLong()
|
||||
app.telegramHelper.messageActiveTimeSec = value.toLong()
|
||||
}
|
||||
|
||||
inner class ShareTypePref : DurationPref(
|
||||
override fun getMenuItems() =
|
||||
values.map { OsmandFormatter.getFormattedDuration(app, it.toLong()) }
|
||||
}
|
||||
|
||||
inner class ShareTypePref : NumericPref(
|
||||
R.drawable.ic_action_location_history,
|
||||
R.string.send_location_as,
|
||||
R.string.send_location_as_descr,
|
||||
|
@ -871,18 +911,84 @@ class TelegramSettings(private val app: TelegramApplication) {
|
|||
}
|
||||
}
|
||||
|
||||
abstract inner class DurationPref(
|
||||
inner class MinLocationDistance : NumericPref(
|
||||
0, R.string.min_logging_distance,
|
||||
R.string.min_logging_distance_descr,
|
||||
MIN_LOCATION_DISTANCE
|
||||
) {
|
||||
|
||||
override fun getCurrentValue() = getFormattedValue(minLocationDistance)
|
||||
|
||||
override fun setCurrentValue(index: Int) {
|
||||
val value = values[index]
|
||||
minLocationDistance = value.toFloat()
|
||||
}
|
||||
|
||||
override fun getMenuItems() = values.map { getFormattedValue(it.toFloat()) }
|
||||
|
||||
private fun getFormattedValue(value: Float): String {
|
||||
return if (value == 0f) app.getString(R.string.shared_string_select)
|
||||
else OsmandFormatter.getFormattedDistance(value, app)
|
||||
}
|
||||
}
|
||||
|
||||
inner class MinLocationAccuracy : NumericPref(
|
||||
0, R.string.min_logging_accuracy,
|
||||
R.string.min_logging_accuracy_descr,
|
||||
MIN_LOCATION_ACCURACY
|
||||
) {
|
||||
|
||||
override fun getCurrentValue() = getFormattedValue(minLocationAccuracy)
|
||||
|
||||
override fun setCurrentValue(index: Int) {
|
||||
val value = values[index]
|
||||
minLocationAccuracy = value.toFloat()
|
||||
}
|
||||
|
||||
override fun getMenuItems() = values.map { getFormattedValue(it.toFloat()) }
|
||||
|
||||
private fun getFormattedValue(value: Float): String {
|
||||
return if (value == 0f) app.getString(R.string.shared_string_select)
|
||||
else OsmandFormatter.getFormattedDistance(value, app)
|
||||
}
|
||||
}
|
||||
|
||||
inner class MinLocationSpeed : NumericPref(
|
||||
0, R.string.min_logging_speed,
|
||||
R.string.min_logging_speed_descr,
|
||||
MIN_LOCATION_SPEED
|
||||
) {
|
||||
|
||||
override fun getCurrentValue() = getFormattedValue(minLocationSpeed)
|
||||
|
||||
override fun setCurrentValue(index: Int) {
|
||||
val value = values[index]
|
||||
minLocationSpeed = value.toFloat()
|
||||
}
|
||||
|
||||
override fun getMenuItems() = values.map { getFormattedValue(it.toFloat()) }
|
||||
|
||||
private fun getFormattedValue(value: Float): String {
|
||||
return when (value) {
|
||||
MIN_LOCATION_SPEED[0] -> app.getString(R.string.shared_string_select)
|
||||
MIN_LOCATION_SPEED[1] -> "> 0"
|
||||
else -> OsmandFormatter.getFormattedSpeed(value, app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract inner class NumericPref(
|
||||
@DrawableRes val iconId: Int,
|
||||
@StringRes val titleId: Int,
|
||||
@StringRes val descriptionId: Int,
|
||||
val values: List<Long>
|
||||
val values: List<Number>
|
||||
) {
|
||||
|
||||
abstract fun getCurrentValue(): String
|
||||
|
||||
abstract fun setCurrentValue(index: Int)
|
||||
|
||||
open fun getMenuItems() = values.map { OsmandFormatter.getFormattedDuration(app, it) }
|
||||
abstract fun getMenuItems(): List<String>
|
||||
}
|
||||
|
||||
enum class AppConnect(
|
||||
|
|
|
@ -7,6 +7,7 @@ import net.osmand.telegram.helpers.LocationMessages.BufferMessage
|
|||
import net.osmand.telegram.notifications.TelegramNotification.NotificationType
|
||||
import net.osmand.telegram.utils.AndroidNetworkUtils
|
||||
import net.osmand.telegram.utils.BASE_URL
|
||||
import net.osmand.util.MapUtils
|
||||
import org.drinkless.td.libcore.telegram.TdApi
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
@ -15,6 +16,8 @@ private const val USER_SET_LIVE_PERIOD_DELAY_MS = 5000 // 5 sec
|
|||
|
||||
private const val UPDATE_LOCATION_ACCURACY = 150 // 150 meters
|
||||
|
||||
private const val SENT_LOCATIONS_INTERVAL_TIME_MS = 4000 // 4 sec
|
||||
|
||||
class ShareLocationHelper(private val app: TelegramApplication) {
|
||||
|
||||
private val log = PlatformUtil.getLog(ShareLocationHelper::class.java)
|
||||
|
@ -30,6 +33,8 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
|
||||
var lastLocationUpdateTime: Long = 0
|
||||
|
||||
var lastLocationSentTime: Long = 0
|
||||
|
||||
var lastLocation: Location? = null
|
||||
set(value) {
|
||||
if (lastTimeInMillis == 0L) {
|
||||
|
@ -48,14 +53,33 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
private var lastTimeInMillis: Long = 0L
|
||||
|
||||
fun updateLocation(location: Location?) {
|
||||
lastLocation = location
|
||||
val lastPoint = lastLocation
|
||||
var record = true
|
||||
if (location != null) {
|
||||
val minDistance = app.settings.minLocationDistance
|
||||
if (minDistance > 0 && lastPoint != null) {
|
||||
val calculatedDistance = MapUtils.getDistance(lastPoint.latitude, lastPoint.longitude, location.latitude, location.longitude)
|
||||
if (calculatedDistance < minDistance) {
|
||||
record = false
|
||||
}
|
||||
}
|
||||
val accuracy = app.settings.minLocationAccuracy
|
||||
if (accuracy > 0 && (!location.hasAccuracy() || location.accuracy > accuracy)) {
|
||||
record = false
|
||||
}
|
||||
val minSpeed = app.settings.minLocationSpeed
|
||||
if (minSpeed > 0 && (!location.hasSpeed() || location.speed < minSpeed)) {
|
||||
record = false
|
||||
}
|
||||
|
||||
if (location != null && location.accuracy < UPDATE_LOCATION_ACCURACY) {
|
||||
if (record) {
|
||||
lastLocationUpdateTime = System.currentTimeMillis()
|
||||
lastLocation = location
|
||||
if (app.settings.getChatsShareInfo().isNotEmpty()) {
|
||||
shareLocationMessages(location, app.telegramHelper.getCurrentUserId())
|
||||
}
|
||||
}
|
||||
}
|
||||
app.settings.updateSharingStatusHistory()
|
||||
refreshNotification()
|
||||
}
|
||||
|
@ -108,12 +132,16 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
}
|
||||
|
||||
fun checkAndSendBufferMessagesToChat(chatId: Long) {
|
||||
val shareInfo = app.settings.getChatsShareInfo()[chatId]
|
||||
if (shareInfo != null) {
|
||||
val shareChatsInfo = app.settings.getChatsShareInfo()
|
||||
val shareInfo = shareChatsInfo[chatId]
|
||||
val chatsCounts = shareChatsInfo.size
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (shareInfo != null && currentTime - lastLocationSentTime > chatsCounts * SENT_LOCATIONS_INTERVAL_TIME_MS) {
|
||||
app.locationMessages.getBufferedTextMessagesForChat(chatId).take(MAX_MESSAGES_IN_TDLIB_PER_CHAT).forEach {
|
||||
if (shareInfo.pendingTdLibText < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||
if (it.deviceName.isEmpty()) {
|
||||
if (!shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
|
||||
lastLocationSentTime = System.currentTimeMillis()
|
||||
app.telegramHelper.editTextLocation(shareInfo, it)
|
||||
app.locationMessages.removeBufferedMessage(it)
|
||||
}
|
||||
|
@ -126,6 +154,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
if (shareInfo.pendingTdLibMap < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||
if (it.deviceName.isEmpty()) {
|
||||
if (!shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
|
||||
lastLocationSentTime = System.currentTimeMillis()
|
||||
app.telegramHelper.editMapLocation(shareInfo, it)
|
||||
app.locationMessages.removeBufferedMessage(it)
|
||||
}
|
||||
|
@ -188,6 +217,14 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
val isBot = app.settings.currentSharingMode != userId.toString()
|
||||
val deviceName = if (isBot) app.settings.currentSharingMode else ""
|
||||
var bufferedMessagesFull = false
|
||||
val chatsCounts = chatsShareInfo.size
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
app.locationMessages.addMyLocationMessage(location)
|
||||
|
||||
if (currentTime - lastLocationSentTime <= chatsCounts * SENT_LOCATIONS_INTERVAL_TIME_MS) {
|
||||
return
|
||||
}
|
||||
|
||||
chatsShareInfo.values.forEach { shareInfo ->
|
||||
if (shareInfo.pendingTdLibText >= MAX_MESSAGES_IN_TDLIB_PER_CHAT || shareInfo.pendingTdLibMap >= MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||
|
@ -209,8 +246,8 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
prepareTextMessage(shareInfo, messageText, isBot)
|
||||
}
|
||||
}
|
||||
checkAndSendBufferMessagesToChat(shareInfo.chatId)
|
||||
}
|
||||
app.locationMessages.addMyLocationMessage(location)
|
||||
if (bufferedMessagesFull) {
|
||||
checkNetworkType()
|
||||
}
|
||||
|
@ -225,6 +262,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
if (isBot) {
|
||||
sendLocationToBot(message, shareInfo, SHARE_TYPE_TEXT)
|
||||
} else {
|
||||
lastLocationSentTime = System.currentTimeMillis()
|
||||
app.telegramHelper.sendNewTextLocation(shareInfo, message)
|
||||
}
|
||||
}
|
||||
|
@ -237,6 +275,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
}
|
||||
} else {
|
||||
if (shareInfo.pendingTdLibText < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||
lastLocationSentTime = System.currentTimeMillis()
|
||||
app.telegramHelper.editTextLocation(shareInfo, message)
|
||||
} else {
|
||||
app.locationMessages.addBufferedMessage(message)
|
||||
|
@ -258,6 +297,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
app.locationMessages.addBufferedMessage(message)
|
||||
}
|
||||
} else {
|
||||
lastLocationSentTime = System.currentTimeMillis()
|
||||
app.telegramHelper.sendNewMapLocation(shareInfo, message)
|
||||
}
|
||||
}
|
||||
|
@ -270,6 +310,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
}
|
||||
} else {
|
||||
if (shareInfo.pendingTdLibMap < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||
lastLocationSentTime = System.currentTimeMillis()
|
||||
app.telegramHelper.editMapLocation(shareInfo, message)
|
||||
} else {
|
||||
app.locationMessages.addBufferedMessage(message)
|
||||
|
@ -298,6 +339,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
|||
} else if (shareType == SHARE_TYPE_MAP) {
|
||||
shareInfo.lastSendMapMessageTime = (System.currentTimeMillis() / 1000).toInt()
|
||||
}
|
||||
lastLocationSentTime = System.currentTimeMillis()
|
||||
AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false,
|
||||
object : AndroidNetworkUtils.OnRequestResultListener {
|
||||
override fun onResult(result: String?) {
|
||||
|
|
|
@ -90,6 +90,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
if (item.latLon == null) {
|
||||
return
|
||||
}
|
||||
setupMapLayer()
|
||||
osmandAidlHelper.execOsmandApi {
|
||||
osmandAidlHelper.showMapPoint(
|
||||
MAP_LAYER_ID,
|
||||
|
@ -100,7 +101,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
Color.WHITE,
|
||||
ALatLon(item.latLon!!.latitude, item.latLon!!.longitude),
|
||||
generatePointDetails(item.bearing?.toFloat(), item.altitude?.toFloat(), item.precision?.toFloat()),
|
||||
generatePointParams(if (stale) item.grayscalePhotoPath else item.photoPath, stale, item.speed?.toFloat())
|
||||
generatePointParams(if (stale) item.grayscalePhotoPath else item.photoPath, stale, item.speed?.toFloat(), item.bearing?.toFloat())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -147,8 +148,13 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
}
|
||||
}
|
||||
setupMapLayer()
|
||||
val params = generatePointParams(photoPath, stale, if (content is MessageUserLocation) content.speed.toFloat() else null)
|
||||
|
||||
var speed = 0f
|
||||
var bearing = 0f
|
||||
if (content is MessageUserLocation) {
|
||||
speed = content.speed.toFloat()
|
||||
bearing = content.bearing.toFloat()
|
||||
}
|
||||
val params = generatePointParams(photoPath, stale, speed, bearing)
|
||||
val typeName = if (isGroup) chatTitle else OsmandFormatter.getListItemLiveTimeDescr(app, date, app.getString(R.string.last_response) + ": ")
|
||||
if (update) {
|
||||
osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, pointId, name, name, typeName, Color.WHITE, aLatLon, details, params)
|
||||
|
@ -158,7 +164,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
points[pointId] = message
|
||||
} else if (content is MessageOsmAndBotLocation && content.isValid()) {
|
||||
setupMapLayer()
|
||||
val params = generatePointParams(null, stale, content.speed.toFloat())
|
||||
val params = generatePointParams(null, stale, content.speed.toFloat(), content.bearing.toFloat())
|
||||
if (update) {
|
||||
osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, pointId, name, name, chatTitle, Color.WHITE, aLatLon, details, params)
|
||||
} else {
|
||||
|
@ -340,7 +346,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
private fun generatePointDetails(bearing: Float?, altitude: Float?, precision: Float?): List<String> {
|
||||
val details = mutableListOf<String>()
|
||||
if (bearing != null && bearing != 0.0f) {
|
||||
details.add(String.format(Locale.US, "${OsmandLocationUtils.BEARING_PREFIX}%.1f \n", bearing))
|
||||
details.add(String.format(Locale.US, "${OsmandLocationUtils.BEARING_PREFIX}%.1f${OsmandLocationUtils.BEARING_SUFFIX} \n", bearing))
|
||||
}
|
||||
if (altitude != null && altitude != 0.0f) {
|
||||
details.add(String.format(Locale.US, "${OsmandLocationUtils.ALTITUDE_PREFIX}%.1f m\n", altitude))
|
||||
|
@ -352,7 +358,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
return details
|
||||
}
|
||||
|
||||
private fun generatePointParams(photoPath: String?, stale: Boolean, speed: Float?): Map<String, String> {
|
||||
private fun generatePointParams(photoPath: String?, stale: Boolean, speed: Float?, bearing: Float?): Map<String, String> {
|
||||
val photoUri = generatePhotoUri(photoPath, stale)
|
||||
app.grantUriPermission(
|
||||
app.settings.appToConnectPackage,
|
||||
|
@ -366,6 +372,9 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
|||
if (speed != 0.0f) {
|
||||
params[AMapPoint.POINT_SPEED_PARAM] = speed.toString()
|
||||
}
|
||||
if (bearing != 0.0f) {
|
||||
params[AMapPoint.POINT_BEARING_PARAM] = bearing.toString()
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import android.view.ViewGroup
|
|||
import android.widget.*
|
||||
import net.osmand.telegram.R
|
||||
import net.osmand.telegram.TelegramSettings
|
||||
import net.osmand.telegram.TelegramSettings.DurationPref
|
||||
import net.osmand.telegram.TelegramSettings.NumericPref
|
||||
import net.osmand.telegram.helpers.TelegramHelper.Companion.OSMAND_BOT_USERNAME
|
||||
import net.osmand.telegram.helpers.TelegramUiHelper
|
||||
import net.osmand.telegram.utils.AndroidUtils
|
||||
|
@ -49,18 +49,8 @@ class SettingsDialogFragment : BaseDialogFragment() {
|
|||
window.statusBarColor = ContextCompat.getColor(app, R.color.card_bg_light)
|
||||
}
|
||||
var container = mainView.findViewById<ViewGroup>(R.id.gps_and_loc_container)
|
||||
for (pref in settings.gpsAndLocPrefs) {
|
||||
inflater.inflate(R.layout.item_with_desc_and_right_value, container, false).apply {
|
||||
findViewById<ImageView>(R.id.icon).setImageDrawable(uiUtils.getThemedIcon(pref.iconId))
|
||||
findViewById<TextView>(R.id.title).setText(pref.titleId)
|
||||
findViewById<TextView>(R.id.description).setText(pref.descriptionId)
|
||||
val valueView = findViewById<TextView>(R.id.value)
|
||||
valueView.text = pref.getCurrentValue()
|
||||
setOnClickListener {
|
||||
showPopupMenu(pref, valueView)
|
||||
}
|
||||
container.addView(this)
|
||||
}
|
||||
settings.gpsAndLocPrefs.forEach {
|
||||
createNumericPref(inflater, container, it)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
|
@ -98,22 +88,21 @@ class SettingsDialogFragment : BaseDialogFragment() {
|
|||
findViewById<ImageView>(R.id.icon_right).apply {
|
||||
visibility = View.VISIBLE
|
||||
setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_additional_option))
|
||||
setOnClickListener {
|
||||
activity?.supportFragmentManager?.also { ProxySettingsDialogFragment.showInstance(it, this@SettingsDialogFragment) }
|
||||
}
|
||||
}
|
||||
findViewById<TextView>(R.id.title).text = getText(R.string.proxy)
|
||||
val description = findViewById<TextView>(R.id.description).apply {
|
||||
text = if (settings.proxyEnabled) getText(R.string.proxy_connected) else getText(R.string.proxy_disconnected)
|
||||
}
|
||||
val switcher = findViewById<Switch>(R.id.switcher).apply {
|
||||
findViewById<Switch>(R.id.switcher).apply {
|
||||
isClickable = true
|
||||
isChecked = app.settings.proxyEnabled
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
settings.updateProxySetting(isChecked)
|
||||
description.text = if (isChecked) getText(R.string.proxy_connected) else getText(R.string.proxy_disconnected)
|
||||
}
|
||||
}
|
||||
setOnClickListener {
|
||||
val checked = !app.settings.proxyEnabled
|
||||
switcher.isChecked = checked
|
||||
settings.updateProxySetting(checked)
|
||||
description.text = if (checked) getText(R.string.proxy_connected) else getText(R.string.proxy_disconnected)
|
||||
activity?.supportFragmentManager?.also { ProxySettingsDialogFragment.showInstance(it, this@SettingsDialogFragment) }
|
||||
}
|
||||
container.addView(this)
|
||||
}
|
||||
|
@ -148,6 +137,11 @@ class SettingsDialogFragment : BaseDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
container = mainView.findViewById<ViewGroup>(R.id.gpx_settings_container)
|
||||
settings.gpxLoggingPrefs.forEach {
|
||||
createNumericPref(inflater, container, it)
|
||||
}
|
||||
|
||||
container = mainView.findViewById(R.id.osmand_connect_container)
|
||||
for (appConn in TelegramSettings.AppConnect.values()) {
|
||||
val pack = appConn.appPackage
|
||||
|
@ -249,6 +243,26 @@ class SettingsDialogFragment : BaseDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun createNumericPref(inflater: LayoutInflater, container: ViewGroup, pref: NumericPref) {
|
||||
inflater.inflate(R.layout.item_with_desc_and_right_value, container, false).apply {
|
||||
findViewById<ImageView>(R.id.icon).apply {
|
||||
if (pref.iconId != 0) {
|
||||
setImageDrawable(uiUtils.getThemedIcon(pref.iconId))
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
findViewById<TextView>(R.id.title).setText(pref.titleId)
|
||||
findViewById<TextView>(R.id.description).setText(pref.descriptionId)
|
||||
val valueView = findViewById<TextView>(R.id.value)
|
||||
valueView.text = pref.getCurrentValue()
|
||||
setOnClickListener {
|
||||
showPopupMenu(pref, valueView)
|
||||
}
|
||||
container.addView(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addItemToContainer(inflater: LayoutInflater, container: ViewGroup, tag: String, title: String) {
|
||||
inflater.inflate(R.layout.item_with_rb_and_btn, container, false).apply {
|
||||
val checked = tag == settings.currentSharingMode
|
||||
|
@ -270,7 +284,7 @@ class SettingsDialogFragment : BaseDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun showPopupMenu(pref: DurationPref, valueView: TextView) {
|
||||
private fun showPopupMenu(pref: NumericPref, valueView: TextView) {
|
||||
val menuList = pref.getMenuItems()
|
||||
val ctx = valueView.context
|
||||
ListPopupWindow(ctx).apply {
|
||||
|
|
|
@ -32,6 +32,7 @@ object OsmandLocationUtils {
|
|||
const val BEARING_PREFIX = "Bearing: "
|
||||
const val SPEED_PREFIX = "Speed: "
|
||||
const val HDOP_PREFIX = "Horizontal precision: "
|
||||
const val BEARING_SUFFIX = "°"
|
||||
|
||||
const val NOW = "now"
|
||||
const val FEW_SECONDS_AGO = "few seconds ago"
|
||||
|
@ -241,19 +242,28 @@ object OsmandLocationUtils {
|
|||
}
|
||||
}
|
||||
s.startsWith(SPEED_PREFIX) -> {
|
||||
val altStr = s.removePrefix(SPEED_PREFIX)
|
||||
val speedStr = s.removePrefix(SPEED_PREFIX)
|
||||
try {
|
||||
val alt = altStr.split(" ").first()
|
||||
res.speed = alt.toDouble()
|
||||
val speed = speedStr.split(" ").first()
|
||||
res.speed = speed.toDouble()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
s.startsWith(BEARING_PREFIX) -> {
|
||||
val bearingStr = s.removePrefix(BEARING_PREFIX)
|
||||
try {
|
||||
val bearing = bearingStr.removeSuffix(BEARING_SUFFIX)
|
||||
res.bearing = bearing.toDouble()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
s.startsWith(HDOP_PREFIX) -> {
|
||||
val altStr = s.removePrefix(HDOP_PREFIX)
|
||||
val hdopStr = s.removePrefix(HDOP_PREFIX)
|
||||
try {
|
||||
val alt = altStr.split(" ").first()
|
||||
res.hdop = alt.toDouble()
|
||||
val hdop = hdopStr.split(" ").first()
|
||||
res.hdop = hdop.toDouble()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
@ -336,6 +346,10 @@ object OsmandLocationUtils {
|
|||
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.bearing > 0) {
|
||||
entities.add(TdApi.TextEntity(builder.lastIndex, BEARING_PREFIX.length, TdApi.TextEntityTypeBold()))
|
||||
builder.append(String.format(Locale.US, "$BEARING_PREFIX%.1f$BEARING_SUFFIX\n", location.bearing))
|
||||
}
|
||||
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()))
|
||||
|
@ -375,6 +389,10 @@ object OsmandLocationUtils {
|
|||
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.bearing > 0) {
|
||||
entities.add(TdApi.TextEntity(builder.lastIndex, BEARING_PREFIX.length, TdApi.TextEntityTypeBold()))
|
||||
builder.append(String.format(Locale.US, "$BEARING_PREFIX%.1f$BEARING_SUFFIX\n", location.bearing))
|
||||
}
|
||||
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()))
|
||||
|
|
BIN
OsmAnd/res/drawable-hdpi/ic_action_bearing_16.png
Normal file
After Width: | Height: | Size: 515 B |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 7.5 KiB |
BIN
OsmAnd/res/drawable-hdpi/map_pin_user_location_selected_day.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 7.3 KiB |
BIN
OsmAnd/res/drawable-mdpi/ic_action_bearing_16.png
Normal file
After Width: | Height: | Size: 349 B |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.1 KiB |
BIN
OsmAnd/res/drawable-mdpi/map_pin_user_location_selected_day.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
BIN
OsmAnd/res/drawable-xhdpi/ic_action_bearing_16.png
Normal file
After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 12 KiB |
BIN
OsmAnd/res/drawable-xhdpi/map_pin_user_location_selected_day.png
Normal file
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 11 KiB |
BIN
OsmAnd/res/drawable-xxhdpi/ic_action_bearing_16.png
Normal file
After Width: | Height: | Size: 965 B |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 21 KiB |
BIN
OsmAnd/res/drawable-xxxhdpi/ic_action_bearing_16.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
OsmAnd/res/drawable-xxxhdpi/map_pin_user_location_day.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
OsmAnd/res/drawable-xxxhdpi/map_pin_user_location_night.png
Normal file
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 33 KiB |
|
@ -258,6 +258,8 @@
|
|||
|
||||
<color name="shadow_color">#33888888</color>
|
||||
|
||||
<color name="compass_control_active">#EE5622</color>
|
||||
|
||||
<color name="sherpafy_selection">#ff33b5e5</color>
|
||||
<color name="sherpafy_add_text">#b9b9b9</color>
|
||||
|
||||
|
|
|
@ -8,5 +8,8 @@
|
|||
<!-- Time control widget ids-->
|
||||
<item name="time_control_widget_state_arrival_time" type="id"/>
|
||||
<item name="time_control_widget_state_time_to_go" type="id"/>
|
||||
<!-- Compass control widget ids-->
|
||||
<item name="compass_ruler_control_widget_state_show" type="id"/>
|
||||
<item name="compass_ruler_control_widget_state_hide" type="id"/>
|
||||
|
||||
</resources>
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
-->
|
||||
<string name="base_profile_descr_ski">Skiing</string>
|
||||
<string name="show_compass_ruler">Show compass ruler</string>
|
||||
<string name="hide_compass_ruler">Hide compass ruler</string>
|
||||
<string name="select_icon_profile_dialog_title">Select icon</string>
|
||||
<string name="settings_routing_mode_string">Mode: %s</string>
|
||||
<string name="settings_derived_routing_mode_string">User Mode, derived from: %s</string>
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.graphics.Bitmap;
|
|||
import android.graphics.BitmapFactory;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import net.osmand.osm.io.Base64;
|
||||
import net.osmand.osm.io.NetworkUtils;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
|
@ -15,18 +16,23 @@ import org.apache.commons.logging.Log;
|
|||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class AndroidNetworkUtils {
|
||||
|
||||
|
@ -215,6 +221,88 @@ public class AndroidNetworkUtils {
|
|||
return res;
|
||||
}
|
||||
|
||||
private static final String BOUNDARY = "CowMooCowMooCowCowCow";
|
||||
|
||||
public static String uploadFile(String urlText, File file, boolean gzip, Map<String, String> additionalParams) throws IOException {
|
||||
return uploadFile(urlText, new FileInputStream(file), file.getName(), gzip, additionalParams);
|
||||
}
|
||||
|
||||
public static String uploadFile(String urlText, InputStream inputStream, String fileName, boolean gzip, Map<String, String> additionalParams) {
|
||||
URL url;
|
||||
try {
|
||||
boolean firstPrm = !urlText.contains("?");
|
||||
StringBuilder sb = new StringBuilder(urlText);
|
||||
for (String key : additionalParams.keySet()) {
|
||||
sb.append(firstPrm ? "?" : "&").append(key).append("=").append(URLEncoder.encode(additionalParams.get(key), "UTF-8"));
|
||||
firstPrm = false;
|
||||
}
|
||||
urlText = sb.toString();
|
||||
|
||||
LOG.info("Start uploading file to " + urlText + " " + fileName);
|
||||
url = new URL(urlText);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
|
||||
conn.setRequestProperty("User-Agent", "OsmAnd");
|
||||
|
||||
OutputStream ous = conn.getOutputStream();
|
||||
ous.write(("--" + BOUNDARY + "\r\n").getBytes());
|
||||
if (gzip) {
|
||||
fileName += ".gz";
|
||||
}
|
||||
ous.write(("content-disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n").getBytes());
|
||||
ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes());
|
||||
|
||||
BufferedInputStream bis = new BufferedInputStream(inputStream, 20 * 1024);
|
||||
ous.flush();
|
||||
if (gzip) {
|
||||
GZIPOutputStream gous = new GZIPOutputStream(ous, 1024);
|
||||
Algorithms.streamCopy(bis, gous);
|
||||
gous.flush();
|
||||
gous.finish();
|
||||
} else {
|
||||
Algorithms.streamCopy(bis, ous);
|
||||
}
|
||||
|
||||
ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes());
|
||||
ous.flush();
|
||||
Algorithms.closeStream(bis);
|
||||
Algorithms.closeStream(ous);
|
||||
|
||||
LOG.info("Finish uploading file " + fileName);
|
||||
LOG.info("Response code and message : " + conn.getResponseCode() + " " + conn.getResponseMessage());
|
||||
if (conn.getResponseCode() != 200) {
|
||||
return conn.getResponseMessage();
|
||||
}
|
||||
InputStream is = conn.getInputStream();
|
||||
StringBuilder responseBody = new StringBuilder();
|
||||
if (is != null) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8"));
|
||||
String s;
|
||||
boolean first = true;
|
||||
while ((s = in.readLine()) != null) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
responseBody.append("\n");
|
||||
}
|
||||
responseBody.append(s);
|
||||
|
||||
}
|
||||
is.close();
|
||||
}
|
||||
String response = responseBody.toString();
|
||||
LOG.info("Response : " + response);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private static void showToast(OsmandApplication ctx, String message) {
|
||||
ctx.showToastMessage(message);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ public class AMapPoint implements Parcelable {
|
|||
public static final String POINT_SPEED_PARAM = "point_speed_param";
|
||||
public static final String POINT_TYPE_ICON_NAME_PARAM = "point_type_icon_name_param";
|
||||
public static final String POINT_STALE_LOC_PARAM = "point_stale_loc_param";
|
||||
public static final String POINT_BEARING_PARAM = "point_bearing_param";
|
||||
|
||||
private String id;
|
||||
private String shortName;
|
||||
|
|
268
OsmAnd/src/net/osmand/plus/AnalyticsHelper.java
Normal file
|
@ -0,0 +1,268 @@
|
|||
package net.osmand.plus;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.provider.Settings;
|
||||
|
||||
import net.osmand.AndroidNetworkUtils;
|
||||
import net.osmand.PlatformUtil;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class AnalyticsHelper extends SQLiteOpenHelper {
|
||||
|
||||
private final static Log LOG = PlatformUtil.getLog(AnalyticsHelper.class);
|
||||
|
||||
private final static String ANALYTICS_UPLOAD_URL = "https://test.osmand.net/api/submit_analytics";
|
||||
private final static String ANALYTICS_FILE_NAME = "analytics.json";
|
||||
|
||||
private final static int DATA_PARCEL_SIZE = 500; // 500 events
|
||||
private final static int SUBMIT_DATA_INTERVAL = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
private final static String PARAM_START_DATE = "startDate";
|
||||
private final static String PARAM_FINISH_DATE = "finishDate";
|
||||
private final static String PARAM_FIRST_INSTALL_DAYS = "nd";
|
||||
private final static String PARAM_NUMBER_OF_STARTS = "ns";
|
||||
private final static String PARAM_USER_ID = "aid";
|
||||
private final static String PARAM_VERSION = "version";
|
||||
private final static String PARAM_LANG = "lang";
|
||||
private final static String PARAM_EVENTS = "events";
|
||||
|
||||
private final static String JSON_DATE = "date";
|
||||
private final static String JSON_EVENT = "event";
|
||||
|
||||
private final static String DATABASE_NAME = "analytics";
|
||||
private final static int DATABASE_VERSION = 1;
|
||||
|
||||
private final static String TABLE_NAME = "events";
|
||||
private final static String COL_DATE = "date";
|
||||
private final static String COL_EVENT = "event";
|
||||
|
||||
private OsmandApplication ctx;
|
||||
private String insertEventScript;
|
||||
private long lastSubmittedTime;
|
||||
|
||||
private ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private Future<?> submittingTask;
|
||||
|
||||
private static class AnalyticsItem {
|
||||
long date;
|
||||
String event;
|
||||
}
|
||||
|
||||
private static class AnalyticsData {
|
||||
long startDate;
|
||||
long finishDate;
|
||||
List<AnalyticsItem> items;
|
||||
}
|
||||
|
||||
AnalyticsHelper(OsmandApplication ctx) {
|
||||
super(ctx, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
this.ctx = ctx;
|
||||
insertEventScript = "INSERT INTO " + TABLE_NAME + " VALUES (?, ?)";
|
||||
submitCollectedDataAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
createTable(db);
|
||||
}
|
||||
|
||||
private void createTable(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + COL_DATE + " long, " + COL_EVENT + " text )");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
|
||||
private long getCollectedRowsCount() {
|
||||
long res = -1;
|
||||
try {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
if (db != null && db.isOpen()) {
|
||||
try {
|
||||
res = DatabaseUtils.queryNumEntries(db, TABLE_NAME);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// ignore
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private void clearDB( long finishDate) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
if (db != null && db.isOpen()) {
|
||||
try {
|
||||
db.execSQL("DELETE FROM " + TABLE_NAME + " WHERE " + COL_DATE + " <= ?", new Object[]{finishDate});
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean submitCollectedDataAsync() {
|
||||
if (ctx.getSettings().isInternetConnectionAvailable()) {
|
||||
long collectedRowsCount = getCollectedRowsCount();
|
||||
if (collectedRowsCount > DATA_PARCEL_SIZE) {
|
||||
if (submittingTask == null || submittingTask.isDone()) {
|
||||
submittingTask = executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
submitCollectedData();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
private boolean submitCollectedData() {
|
||||
List<AnalyticsData> data = collectRecordedData();
|
||||
for (AnalyticsData d : data) {
|
||||
if (d.items != null && d.items.size() > 0) {
|
||||
try {
|
||||
JSONArray jsonItemsArray = new JSONArray();
|
||||
for (AnalyticsItem item : d.items) {
|
||||
JSONObject jsonItem = new JSONObject();
|
||||
jsonItem.put(JSON_DATE, item.date);
|
||||
jsonItem.put(JSON_EVENT, item.event);
|
||||
jsonItemsArray.put(jsonItem);
|
||||
}
|
||||
|
||||
Map<String, String> additionalData = new LinkedHashMap<String, String>();
|
||||
additionalData.put(PARAM_START_DATE, String.valueOf(d.startDate));
|
||||
additionalData.put(PARAM_FINISH_DATE, String.valueOf(d.finishDate));
|
||||
additionalData.put(PARAM_VERSION, Version.getFullVersion(ctx));
|
||||
additionalData.put(PARAM_LANG, ctx.getLanguage() + "");
|
||||
additionalData.put(PARAM_FIRST_INSTALL_DAYS, String.valueOf(ctx.getAppInitializer().getFirstInstalledDays()));
|
||||
additionalData.put(PARAM_NUMBER_OF_STARTS, String.valueOf(ctx.getAppInitializer().getNumberOfStarts()));
|
||||
try {
|
||||
additionalData.put(PARAM_USER_ID, Settings.Secure.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID));
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
for (Map.Entry<String, String> entry : additionalData.entrySet()) {
|
||||
json.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
json.put(PARAM_EVENTS, jsonItemsArray);
|
||||
|
||||
String jsonStr = json.toString();
|
||||
InputStream inputStream = new ByteArrayInputStream(jsonStr.getBytes());
|
||||
String res = AndroidNetworkUtils.uploadFile(ANALYTICS_UPLOAD_URL, inputStream, ANALYTICS_FILE_NAME, true, additionalData);
|
||||
if (res != null) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error(e);
|
||||
return false;
|
||||
}
|
||||
clearDB(d.finishDate);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<AnalyticsData> collectRecordedData() {
|
||||
List<AnalyticsData> data = new ArrayList<>();
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
if (db != null && db.isOpen()) {
|
||||
try {
|
||||
collectDBData(db, data);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void collectDBData(SQLiteDatabase db, List<AnalyticsData> data) {
|
||||
Cursor query = db.rawQuery("SELECT " + COL_DATE + "," + COL_EVENT + " FROM " + TABLE_NAME + " ORDER BY " + COL_DATE + " ASC", null);
|
||||
List<AnalyticsItem> items = new ArrayList<>();
|
||||
int itemsCounter = 0;
|
||||
long startDate = Long.MAX_VALUE;
|
||||
long finishDate = 0;
|
||||
if (query.moveToFirst()) {
|
||||
do {
|
||||
AnalyticsItem item = new AnalyticsItem();
|
||||
long date = query.getLong(0);
|
||||
item.date = date;
|
||||
item.event = query.getString(1);
|
||||
items.add(item);
|
||||
itemsCounter++;
|
||||
|
||||
if (startDate > date) {
|
||||
startDate = date;
|
||||
}
|
||||
if (finishDate < date) {
|
||||
finishDate = date;
|
||||
}
|
||||
|
||||
if (itemsCounter >= DATA_PARCEL_SIZE) {
|
||||
AnalyticsData d = new AnalyticsData();
|
||||
d.startDate = startDate;
|
||||
d.finishDate = finishDate;
|
||||
d.items = items;
|
||||
data.add(d);
|
||||
items = new ArrayList<>();
|
||||
itemsCounter = 0;
|
||||
startDate = Long.MAX_VALUE;
|
||||
finishDate = 0;
|
||||
}
|
||||
|
||||
} while (query.moveToNext());
|
||||
|
||||
if (itemsCounter > 0) {
|
||||
AnalyticsData d = new AnalyticsData();
|
||||
d.startDate = startDate;
|
||||
d.finishDate = finishDate;
|
||||
d.items = items;
|
||||
data.add(d);
|
||||
}
|
||||
}
|
||||
query.close();
|
||||
}
|
||||
|
||||
public void addEvent(String event) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
if (db != null && db.isOpen()) {
|
||||
try {
|
||||
db.execSQL(insertEventScript, new Object[]{System.currentTimeMillis(), event});
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
if (lastSubmittedTime + SUBMIT_DATA_INTERVAL < currentTimeMillis) {
|
||||
if (!submitCollectedDataAsync()) {
|
||||
lastSubmittedTime = currentTimeMillis - SUBMIT_DATA_INTERVAL / 4;
|
||||
} else {
|
||||
lastSubmittedTime = currentTimeMillis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -472,6 +472,7 @@ public class AppInitializer implements IProgress {
|
|||
app.locationProvider = startupInit(new OsmAndLocationProvider(app), OsmAndLocationProvider.class);
|
||||
app.avoidSpecificRoads = startupInit(new AvoidSpecificRoads(app), AvoidSpecificRoads.class);
|
||||
app.savingTrackHelper = startupInit(new SavingTrackHelper(app), SavingTrackHelper.class);
|
||||
app.analyticsHelper = startupInit(new AnalyticsHelper(app), AnalyticsHelper.class);
|
||||
app.notificationHelper = startupInit(new NotificationHelper(app), NotificationHelper.class);
|
||||
app.liveMonitoringHelper = startupInit(new LiveMonitoringHelper(app), LiveMonitoringHelper.class);
|
||||
app.selectedGpxHelper = startupInit(new GpxSelectionHelper(app, app.savingTrackHelper), GpxSelectionHelper.class);
|
||||
|
|
|
@ -110,6 +110,7 @@ public class OsmandApplication extends MultiDexApplication {
|
|||
GpxSelectionHelper selectedGpxHelper;
|
||||
GPXDatabase gpxDatabase;
|
||||
SavingTrackHelper savingTrackHelper;
|
||||
AnalyticsHelper analyticsHelper;
|
||||
NotificationHelper notificationHelper;
|
||||
LiveMonitoringHelper liveMonitoringHelper;
|
||||
TargetPointsHelper targetPointsHelper;
|
||||
|
@ -921,10 +922,10 @@ public class OsmandApplication extends MultiDexApplication {
|
|||
try {
|
||||
if (Version.isGooglePlayEnabled(this) && !Version.isPaidVersion(this)
|
||||
&& !osmandSettings.DO_NOT_SEND_ANONYMOUS_APP_USAGE.get()) {
|
||||
// not implemented yet
|
||||
analyticsHelper.addEvent(event);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -803,6 +803,8 @@ public class OsmandSettings {
|
|||
|
||||
public final CommonPreference<RulerMode> RULER_MODE = new EnumIntPreference<>("ruler_mode", RulerMode.FIRST, RulerMode.values()).makeGlobal();
|
||||
|
||||
public final OsmandPreference<Boolean> SHOW_COMPASS_CONTROL_RULER = new BooleanPreference("show_compass_ruler", true).makeGlobal();
|
||||
|
||||
public final CommonPreference<Boolean> SHOW_LINES_TO_FIRST_MARKERS = new BooleanPreference("show_lines_to_first_markers", false).makeProfile();
|
||||
public final CommonPreference<Boolean> SHOW_ARROWS_TO_FIRST_MARKERS = new BooleanPreference("show_arrows_to_first_markers", false).makeProfile();
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import java.util.Map;
|
|||
|
||||
public class AMapPointMenuController extends MenuController {
|
||||
|
||||
private static final float NO_SPEED = -1;
|
||||
private static final float NO_VALUE = -1;
|
||||
private static final int NO_ICON = 0;
|
||||
|
||||
private AMapPoint point;
|
||||
|
@ -137,17 +137,38 @@ public class AMapPointMenuController extends MenuController {
|
|||
MapActivity activity = getMapActivity();
|
||||
if (activity != null) {
|
||||
float speed = getPointSpeed();
|
||||
if (speed != NO_SPEED) {
|
||||
String formatted = OsmAndFormatter.getFormattedSpeed(speed, activity.getMyApplication());
|
||||
return activity.getString(R.string.map_widget_speed) + ": " + formatted;
|
||||
if (speed != NO_VALUE) {
|
||||
return OsmAndFormatter.getFormattedSpeed(speed, activity.getMyApplication());
|
||||
}
|
||||
}
|
||||
return super.getAdditionalInfoStr();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getSubtypeStr() {
|
||||
MapActivity activity = getMapActivity();
|
||||
if (activity != null) {
|
||||
float bearing = getPointBearing();
|
||||
if (bearing != NO_VALUE) {
|
||||
return OsmAndFormatter.getFormattedAzimuth(bearing, activity.getMyApplication());
|
||||
}
|
||||
}
|
||||
return super.getSubtypeStr();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable getSubtypeIcon() {
|
||||
if (getPointBearing() != NO_VALUE) {
|
||||
return getIcon(R.drawable.ic_action_bearing_16);
|
||||
}
|
||||
return super.getSubtypeIcon();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAdditionalInfoIconRes() {
|
||||
if (getPointSpeed() != NO_SPEED) {
|
||||
if (getPointSpeed() != NO_VALUE) {
|
||||
return R.drawable.ic_action_speed_16;
|
||||
}
|
||||
return super.getAdditionalInfoIconRes();
|
||||
|
@ -214,10 +235,22 @@ public class AMapPointMenuController extends MenuController {
|
|||
try {
|
||||
return Float.parseFloat(speed);
|
||||
} catch (NumberFormatException e) {
|
||||
return NO_SPEED;
|
||||
return NO_VALUE;
|
||||
}
|
||||
}
|
||||
return NO_SPEED;
|
||||
return NO_VALUE;
|
||||
}
|
||||
|
||||
private float getPointBearing() {
|
||||
String bearing = point.getParams().get(AMapPoint.POINT_BEARING_PARAM);
|
||||
if (!TextUtils.isEmpty(bearing)) {
|
||||
try {
|
||||
return Float.parseFloat(bearing);
|
||||
} catch (NumberFormatException e) {
|
||||
return NO_VALUE;
|
||||
}
|
||||
}
|
||||
return NO_VALUE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package net.osmand.plus.routepreparationmenu;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
@ -27,6 +30,7 @@ import net.osmand.data.PointDescription;
|
|||
import net.osmand.plus.FavouritesDbHelper;
|
||||
import net.osmand.plus.MapMarkersHelper;
|
||||
import net.osmand.plus.MapMarkersHelper.MapMarker;
|
||||
import net.osmand.plus.OsmAndLocationProvider;
|
||||
import net.osmand.plus.OsmandApplication;
|
||||
import net.osmand.plus.R;
|
||||
import net.osmand.plus.TargetPointsHelper;
|
||||
|
@ -213,14 +217,17 @@ public class AddPointBottomSheetDialog extends MenuBottomSheetDialogFragment {
|
|||
|
||||
private void createMyLocItem() {
|
||||
BaseBottomSheetItem myLocationItem = new SimpleBottomSheetItem.Builder()
|
||||
.setIcon(getIcon(R.drawable.ic_action_location_color, 0))
|
||||
.setIcon(getIcon(OsmAndLocationProvider.isLocationPermissionAvailable(getActivity())
|
||||
? R.drawable.ic_action_location_color : R.drawable.ic_action_location_color_lost, 0))
|
||||
.setTitle(getString(R.string.shared_string_my_location))
|
||||
.setLayoutId(R.layout.bottom_sheet_item_simple_56dp)
|
||||
.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
OsmandApplication app = getMyApplication();
|
||||
Activity activity = getActivity();
|
||||
if (app != null) {
|
||||
if (OsmAndLocationProvider.isLocationPermissionAvailable(app)) {
|
||||
TargetPointsHelper targetPointsHelper = app.getTargetPointsHelper();
|
||||
Location myLocation = app.getLocationProvider().getLastKnownLocation();
|
||||
if (myLocation != null) {
|
||||
|
@ -250,6 +257,11 @@ public class AddPointBottomSheetDialog extends MenuBottomSheetDialogFragment {
|
|||
break;
|
||||
}
|
||||
}
|
||||
} else if (activity != null) {
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
|
||||
OsmAndLocationProvider.REQUEST_LOCATION_PERMISSION);
|
||||
}
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
|
|
@ -1659,9 +1659,14 @@ public class MapRouteInfoMenu implements IRouteInformationListener, CardListener
|
|||
|
||||
public void updateFromIcon(View parentView) {
|
||||
MapActivity mapActivity = getMapActivity();
|
||||
|
||||
int locationIconResByStatus = OsmAndLocationProvider.isLocationPermissionAvailable(mapActivity)
|
||||
? R.drawable.ic_action_location_color : R.drawable.ic_action_location_color_lost;
|
||||
|
||||
if (mapActivity != null) {
|
||||
((ImageView) parentView.findViewById(R.id.fromIcon)).setImageDrawable(ContextCompat.getDrawable(mapActivity,
|
||||
mapActivity.getMyApplication().getTargetPointsHelper().getPointToStart() == null ? R.drawable.ic_action_location_color : R.drawable.list_startpoint));
|
||||
mapActivity.getMyApplication().getTargetPointsHelper().getPointToStart() == null
|
||||
? locationIconResByStatus : R.drawable.list_startpoint));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import net.osmand.plus.activities.MapActivity;
|
|||
import net.osmand.plus.helpers.AndroidUiHelper;
|
||||
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu.TrackChartPoints;
|
||||
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory;
|
||||
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.CompassRulerControlWidgetState;
|
||||
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopCoordinatesView;
|
||||
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopTextView;
|
||||
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
|
||||
|
@ -200,7 +201,7 @@ public class MapInfoLayer extends OsmandMapLayer {
|
|||
TextInfoWidget battery = ric.createBatteryControl(map);
|
||||
registerSideWidget(battery, R.drawable.ic_action_battery, R.string.map_widget_battery, "battery", false, 42);
|
||||
TextInfoWidget ruler = mic.createRulerControl(map);
|
||||
registerSideWidget(ruler, R.drawable.ic_action_ruler_circle, R.string.map_widget_ruler_control, "ruler", false, 43);
|
||||
registerSideWidget(ruler, new CompassRulerControlWidgetState(app), "ruler", false, 43);
|
||||
}
|
||||
|
||||
public void recreateControls() {
|
||||
|
|
|
@ -3,17 +3,23 @@ package net.osmand.plus.views;
|
|||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathMeasure;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import net.osmand.AndroidUtils;
|
||||
import net.osmand.Location;
|
||||
import net.osmand.data.LatLon;
|
||||
import net.osmand.data.QuadPoint;
|
||||
|
@ -36,6 +42,7 @@ public class RulerControlLayer extends OsmandMapLayer {
|
|||
private static final long DELAY_BEFORE_DRAW = 500;
|
||||
private static final int TEXT_SIZE = 14;
|
||||
private static final int DISTANCE_TEXT_SIZE = 16;
|
||||
private static final int COMPASS_CIRCLE_ID = 2;
|
||||
|
||||
private final MapActivity mapActivity;
|
||||
private OsmandApplication app;
|
||||
|
@ -74,11 +81,26 @@ public class RulerControlLayer extends OsmandMapLayer {
|
|||
private Bitmap centerIconDay;
|
||||
private Bitmap centerIconNight;
|
||||
private Paint bitmapPaint;
|
||||
private Paint triangleHeadingPaint;
|
||||
private Paint triangleNorthPaint;
|
||||
private Paint redLinesPaint;
|
||||
private Paint blueLinesPaint;
|
||||
|
||||
private RenderingLineAttributes lineAttrs;
|
||||
private RenderingLineAttributes lineFontAttrs;
|
||||
private RenderingLineAttributes circleAttrs;
|
||||
private RenderingLineAttributes circleAttrsAlt;
|
||||
|
||||
private Path compass = new Path();
|
||||
private Path arrow = new Path();
|
||||
private Path arrowArc = new Path();
|
||||
private Path redCompassLines = new Path();
|
||||
|
||||
private double[] degrees = new double[72];
|
||||
private int[] arcColors = {Color.parseColor("#00237BFF"), Color.parseColor("#237BFF"), Color.parseColor("#00237BFF")};
|
||||
|
||||
private float cachedHeading = 0;
|
||||
|
||||
private Handler handler;
|
||||
|
||||
public RulerControlLayer(MapActivity mapActivity) {
|
||||
|
@ -118,6 +140,14 @@ public class RulerControlLayer extends OsmandMapLayer {
|
|||
bitmapPaint.setDither(true);
|
||||
bitmapPaint.setFilterBitmap(true);
|
||||
|
||||
int colorNorthArrow = ContextCompat.getColor(app, R.color.compass_control_active);
|
||||
int colorHeadingArrow = ContextCompat.getColor(app, R.color.active_buttons_and_links_light);
|
||||
|
||||
triangleNorthPaint = initPaintWithStyle(Style.FILL, colorNorthArrow);
|
||||
triangleHeadingPaint = initPaintWithStyle(Style.FILL, colorHeadingArrow);
|
||||
redLinesPaint = initPaintWithStyle(Style.STROKE, colorNorthArrow);
|
||||
blueLinesPaint = initPaintWithStyle(Style.STROKE, colorHeadingArrow);
|
||||
|
||||
lineAttrs = new RenderingLineAttributes("rulerLine");
|
||||
|
||||
float circleTextSize = TEXT_SIZE * mapActivity.getResources().getDisplayMetrics().density;
|
||||
|
@ -141,6 +171,18 @@ public class RulerControlLayer extends OsmandMapLayer {
|
|||
view.refreshMap();
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < 72; i++) {
|
||||
degrees[i] = Math.toRadians(i * 5);
|
||||
}
|
||||
}
|
||||
|
||||
private Paint initPaintWithStyle(Paint.Style style, int color) {
|
||||
Paint paint = new Paint();
|
||||
paint.setStyle(style);
|
||||
paint.setColor(color);
|
||||
paint.setAntiAlias(true);
|
||||
return paint;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -190,6 +232,7 @@ public class RulerControlLayer extends OsmandMapLayer {
|
|||
circleAttrsAlt.paint2.setStyle(Style.FILL);
|
||||
final QuadPoint center = tb.getCenterPixelPoint();
|
||||
final RulerMode mode = app.getSettings().RULER_MODE.get();
|
||||
boolean showCompass = app.getSettings().SHOW_COMPASS_CONTROL_RULER.get();
|
||||
final long currentTime = System.currentTimeMillis();
|
||||
|
||||
if (cacheMultiTouchEndTime != view.getMultiTouchEndTime()) {
|
||||
|
@ -226,19 +269,41 @@ public class RulerControlLayer extends OsmandMapLayer {
|
|||
}
|
||||
if (mode == RulerMode.FIRST || mode == RulerMode.SECOND) {
|
||||
updateData(tb, center);
|
||||
if (showCompass) {
|
||||
updateHeading();
|
||||
resetDrawingPaths();
|
||||
}
|
||||
RenderingLineAttributes attrs = mode == RulerMode.FIRST ? circleAttrs : circleAttrsAlt;
|
||||
for (int i = 1; i <= cacheDistances.size(); i++) {
|
||||
if (showCompass && i == COMPASS_CIRCLE_ID) {
|
||||
drawCompassCircle(canvas, tb, center, attrs);
|
||||
} else {
|
||||
drawCircle(canvas, tb, i, center, attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean rulerModeOn() {
|
||||
return mapActivity.getMapLayers().getMapWidgetRegistry().isVisible("ruler") &&
|
||||
rightWidgetsPanel.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
private void updateHeading() {
|
||||
Float heading = mapActivity.getMapViewTrackingUtilities().getHeading();
|
||||
if (heading != null && heading != cachedHeading) {
|
||||
cachedHeading = heading;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetDrawingPaths() {
|
||||
redCompassLines.reset();
|
||||
arrowArc.reset();
|
||||
compass.reset();
|
||||
arrow.reset();
|
||||
}
|
||||
|
||||
private void refreshMapDelayed() {
|
||||
handler.sendEmptyMessageDelayed(0, DRAW_TIME + 50);
|
||||
}
|
||||
|
@ -393,8 +458,27 @@ public class RulerControlLayer extends OsmandMapLayer {
|
|||
private void drawCircle(Canvas canvas, RotatedTileBox tb, int circleNumber, QuadPoint center,
|
||||
RenderingLineAttributes attrs) {
|
||||
if (!tb.isZoomAnimated()) {
|
||||
Rect bounds = new Rect();
|
||||
float circleRadius = radius * circleNumber;
|
||||
String text = cacheDistances.get(circleNumber - 1);
|
||||
float[] textCoords = calculateDistanceTextCoords(text, circleRadius, center, attrs);
|
||||
|
||||
canvas.rotate(-tb.getRotate(), center.x, center.y);
|
||||
canvas.drawCircle(center.x, center.y, radius * circleNumber, attrs.shadowPaint);
|
||||
canvas.drawCircle(center.x, center.y, radius * circleNumber, attrs.paint);
|
||||
drawTextCoords(canvas, text, textCoords, attrs);
|
||||
canvas.rotate(tb.getRotate(), center.x, center.y);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawTextCoords(Canvas canvas, String text, float[] textCoords, RenderingLineAttributes attrs) {
|
||||
canvas.drawText(text, textCoords[0], textCoords[1], attrs.paint3);
|
||||
canvas.drawText(text, textCoords[0], textCoords[1], attrs.paint2);
|
||||
canvas.drawText(text, textCoords[2], textCoords[3], attrs.paint3);
|
||||
canvas.drawText(text, textCoords[2], textCoords[3], attrs.paint2);
|
||||
}
|
||||
|
||||
private float[] calculateDistanceTextCoords(String text, float drawingTextRadius, QuadPoint center, RenderingLineAttributes attrs) {
|
||||
Rect bounds = new Rect();
|
||||
attrs.paint2.getTextBounds(text, 0, text.length(), bounds);
|
||||
|
||||
// coords of left or top text
|
||||
|
@ -406,25 +490,164 @@ public class RulerControlLayer extends OsmandMapLayer {
|
|||
|
||||
if (textSide == TextSide.VERTICAL) {
|
||||
x1 = center.x - bounds.width() / 2;
|
||||
y1 = center.y - radius * circleNumber + bounds.height() / 2;
|
||||
y1 = center.y - drawingTextRadius + bounds.height() / 2;
|
||||
x2 = center.x - bounds.width() / 2;
|
||||
y2 = center.y + radius * circleNumber + bounds.height() / 2;
|
||||
y2 = center.y + drawingTextRadius + bounds.height() / 2;
|
||||
} else if (textSide == TextSide.HORIZONTAL) {
|
||||
x1 = center.x - radius * circleNumber - bounds.width() / 2;
|
||||
x1 = center.x - drawingTextRadius - bounds.width() / 2;
|
||||
y1 = center.y + bounds.height() / 2;
|
||||
x2 = center.x + radius * circleNumber - bounds.width() / 2;
|
||||
x2 = center.x + drawingTextRadius - bounds.width() / 2;
|
||||
y2 = center.y + bounds.height() / 2;
|
||||
}
|
||||
|
||||
canvas.rotate(-tb.getRotate(), center.x, center.y);
|
||||
canvas.drawCircle(center.x, center.y, radius * circleNumber, attrs.shadowPaint);
|
||||
canvas.drawCircle(center.x, center.y, radius * circleNumber, attrs.paint);
|
||||
canvas.drawText(text, x1, y1, attrs.paint3);
|
||||
canvas.drawText(text, x1, y1, attrs.paint2);
|
||||
canvas.drawText(text, x2, y2, attrs.paint3);
|
||||
canvas.drawText(text, x2, y2, attrs.paint2);
|
||||
canvas.rotate(tb.getRotate(), center.x, center.y);
|
||||
return new float[]{x1, y1, x2, y2};
|
||||
}
|
||||
|
||||
private void drawCompassCircle(Canvas canvas, RotatedTileBox tileBox, QuadPoint center,
|
||||
RenderingLineAttributes attrs) {
|
||||
if (!tileBox.isZoomAnimated()) {
|
||||
float radiusLength = radius * COMPASS_CIRCLE_ID;
|
||||
float innerRadiusLength = radiusLength - attrs.paint.getStrokeWidth() / 2;
|
||||
|
||||
updateArcShader(radiusLength, center);
|
||||
updateCompassPaths(center, innerRadiusLength, radiusLength);
|
||||
drawCardinalDirections(canvas, center, radiusLength, tileBox, attrs);
|
||||
|
||||
redLinesPaint.setStrokeWidth(attrs.paint.getStrokeWidth());
|
||||
blueLinesPaint.setStrokeWidth(attrs.paint.getStrokeWidth());
|
||||
|
||||
canvas.drawPath(compass, attrs.shadowPaint);
|
||||
canvas.drawPath(compass, attrs.paint);
|
||||
canvas.drawPath(redCompassLines, redLinesPaint);
|
||||
|
||||
canvas.rotate(cachedHeading, center.x, center.y);
|
||||
canvas.drawPath(arrowArc, blueLinesPaint);
|
||||
canvas.rotate(-cachedHeading, center.x, center.y);
|
||||
|
||||
canvas.drawPath(arrow, attrs.shadowPaint);
|
||||
canvas.drawPath(arrow, triangleNorthPaint);
|
||||
|
||||
canvas.rotate(cachedHeading, center.x, center.y);
|
||||
canvas.drawPath(arrow, attrs.shadowPaint);
|
||||
canvas.drawPath(arrow, triangleHeadingPaint);
|
||||
canvas.rotate(-cachedHeading, center.x, center.y);
|
||||
|
||||
String text = cacheDistances.get(COMPASS_CIRCLE_ID - 1);
|
||||
float[] textCoords = calculateDistanceTextCoords(text, radiusLength + AndroidUtils.dpToPx(app, 16), center, attrs);
|
||||
canvas.rotate(-tileBox.getRotate(), center.x, center.y);
|
||||
drawTextCoords(canvas, text, textCoords, attrs);
|
||||
canvas.rotate(tileBox.getRotate(), center.x, center.y);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCompassPaths(QuadPoint center, float innerRadiusLength, float radiusLength) {
|
||||
compass.addCircle(center.x, center.y, radiusLength, Path.Direction.CCW);
|
||||
|
||||
arrowArc.addArc(new RectF(center.x - radiusLength, center.y - radiusLength, center.x + radiusLength, center.y + radiusLength), -45, -90);
|
||||
|
||||
for (int i = 0; i < degrees.length; i++) {
|
||||
double degree = degrees[i];
|
||||
float x = (float) Math.cos(degree);
|
||||
float y = -(float) Math.sin(degree);
|
||||
|
||||
float lineStartX = center.x + x * innerRadiusLength;
|
||||
float lineStartY = center.y + y * innerRadiusLength;
|
||||
|
||||
float lineLength = getCompassLineHeight(i);
|
||||
|
||||
float lineStopX = center.x + x * (innerRadiusLength - lineLength);
|
||||
float lineStopY = center.y + y * (innerRadiusLength - lineLength);
|
||||
|
||||
if (i == 18) {
|
||||
float shortLineMargin = AndroidUtils.dpToPx(app, 5.66f);
|
||||
float shortLineHeight = AndroidUtils.dpToPx(app, 2.94f);
|
||||
float startY = center.y + y * (radiusLength - shortLineMargin);
|
||||
float stopY = center.y + y * (radiusLength - shortLineMargin - shortLineHeight);
|
||||
|
||||
compass.moveTo(center.x, startY);
|
||||
compass.lineTo(center.x, stopY);
|
||||
|
||||
float firstPointY = center.y + y * (radiusLength + AndroidUtils.dpToPx(app, 5));
|
||||
|
||||
float secondPointX = center.x - AndroidUtils.dpToPx(app, 4);
|
||||
float secondPointY = center.y + y * (radiusLength - AndroidUtils.dpToPx(app, 2));
|
||||
|
||||
float thirdPointX = center.x + AndroidUtils.dpToPx(app, 4);
|
||||
float thirdPointY = center.y + y * (radiusLength - AndroidUtils.dpToPx(app, 2));
|
||||
|
||||
arrow.moveTo(center.x, firstPointY);
|
||||
arrow.lineTo(secondPointX, secondPointY);
|
||||
arrow.lineTo(thirdPointX, thirdPointY);
|
||||
arrow.lineTo(center.x, firstPointY);
|
||||
arrow.close();
|
||||
} else {
|
||||
compass.moveTo(lineStartX, lineStartY);
|
||||
compass.lineTo(lineStopX, lineStopY);
|
||||
}
|
||||
if (i % 9 == 0 && i % 6 != 0) {
|
||||
redCompassLines.moveTo(lineStartX, lineStartY);
|
||||
redCompassLines.lineTo(lineStopX, lineStopY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float getCompassLineHeight(int index) {
|
||||
if (index % 6 == 0) {
|
||||
return AndroidUtils.dpToPx(app, 8);
|
||||
} else if (index % 9 == 0 || index % 2 != 0) {
|
||||
return AndroidUtils.dpToPx(app, 3);
|
||||
} else {
|
||||
return AndroidUtils.dpToPx(app, 6);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCardinalDirections(Canvas canvas, QuadPoint center, float radiusLength, RotatedTileBox tileBox, RenderingLineAttributes attrs) {
|
||||
float textMargin = AndroidUtils.dpToPx(app, 14);
|
||||
attrs.paint2.setTextAlign(Paint.Align.CENTER);
|
||||
attrs.paint3.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
for (int i = 0; i < degrees.length; i += 9) {
|
||||
String cardinalDirection = getCardinalDirection(i);
|
||||
if (cardinalDirection != null) {
|
||||
float textWidth = AndroidUtils.getTextWidth(attrs.paint2.getTextSize(), cardinalDirection);
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(center.x, center.y);
|
||||
canvas.rotate(i * 5 + 90);
|
||||
canvas.translate(0, radiusLength - textMargin - textWidth / 2);
|
||||
canvas.rotate(-i * 5 - tileBox.getRotate() - 90);
|
||||
|
||||
canvas.drawText(cardinalDirection, 0, 0, attrs.paint3);
|
||||
canvas.drawText(cardinalDirection, 0, 0, attrs.paint2);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateArcShader(float radiusLength, QuadPoint center) {
|
||||
float arcLength = (float) (2 * Math.PI * radiusLength * (90f / 360));
|
||||
LinearGradient shader = new LinearGradient((float) (center.x - arcLength * 0.25), center.y, (float) (center.x + arcLength * 0.25), center.y, arcColors, null, Shader.TileMode.CLAMP);
|
||||
blueLinesPaint.setShader(shader);
|
||||
}
|
||||
|
||||
private String getCardinalDirection(int i) {
|
||||
if (i == 0) {
|
||||
return "E";
|
||||
} else if (i == 9) {
|
||||
return "NE";
|
||||
} else if (i == 18) {
|
||||
return "N";
|
||||
} else if (i == 27) {
|
||||
return "NW";
|
||||
} else if (i == 36) {
|
||||
return "W";
|
||||
} else if (i == 45) {
|
||||
return "SW";
|
||||
} else if (i == 54) {
|
||||
return "S";
|
||||
} else if (i == 63) {
|
||||
return "SE";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private enum TextSide {
|
||||
|
|
|
@ -56,6 +56,7 @@ import net.osmand.plus.routing.RoutingHelper;
|
|||
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
|
||||
import net.osmand.plus.views.OsmandMapTileView;
|
||||
import net.osmand.plus.views.RulerControlLayer;
|
||||
import net.osmand.plus.views.mapwidgets.MapWidgetRegistry.WidgetState;
|
||||
import net.osmand.plus.views.mapwidgets.NextTurnInfoWidget.TurnDrawable;
|
||||
import net.osmand.router.TurnType;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
@ -138,6 +139,54 @@ public class MapInfoWidgetsFactory {
|
|||
return gpsInfoControl;
|
||||
}
|
||||
|
||||
public static class CompassRulerControlWidgetState extends WidgetState {
|
||||
|
||||
public static final int COMPASS_CONTROL_WIDGET_STATE_SHOW = R.id.compass_ruler_control_widget_state_show;
|
||||
public static final int COMPASS_CONTROL_WIDGET_STATE_HIDE = R.id.compass_ruler_control_widget_state_hide;
|
||||
|
||||
private final OsmandSettings.OsmandPreference<Boolean> showCompass;
|
||||
|
||||
public CompassRulerControlWidgetState(OsmandApplication ctx) {
|
||||
super(ctx);
|
||||
showCompass = ctx.getSettings().SHOW_COMPASS_CONTROL_RULER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuTitleId() {
|
||||
return R.string.map_widget_ruler_control;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuIconId() {
|
||||
return R.drawable.ic_action_ruler_circle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuItemId() {
|
||||
return showCompass.get() ? COMPASS_CONTROL_WIDGET_STATE_SHOW : COMPASS_CONTROL_WIDGET_STATE_HIDE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getMenuTitleIds() {
|
||||
return new int[]{R.string.show_compass_ruler, R.string.hide_compass_ruler};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getMenuIconIds() {
|
||||
return new int[]{R.drawable.ic_action_compass_widget, R.drawable.ic_action_compass_widget_hide};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getMenuItemIds() {
|
||||
return new int[]{COMPASS_CONTROL_WIDGET_STATE_SHOW, COMPASS_CONTROL_WIDGET_STATE_HIDE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeState(int stateId) {
|
||||
showCompass.set(stateId == COMPASS_CONTROL_WIDGET_STATE_SHOW);
|
||||
}
|
||||
}
|
||||
|
||||
public TextInfoWidget createRulerControl(final MapActivity map) {
|
||||
final String title = "—";
|
||||
final TextInfoWidget rulerControl = new TextInfoWidget(map) {
|
||||
|
|