Merge branch 'master' into app_profiles_2

# Conflicts:
#	OsmAnd/res/values/strings.xml
This commit is contained in:
madwasp79 2019-05-28 13:30:04 +03:00
commit 2a832dd99a
50 changed files with 1058 additions and 140 deletions

View file

@ -131,6 +131,35 @@
</LinearLayout>
<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

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -53,9 +53,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
listOf(-1)
)
showLocationHelper.addDirectionContextMenuButton()
if (settings.hasAnyChatToShowOnMap()) {
showLocationHelper.startShowingLocation()
}
showLocationHelper.startShowingLocation()
}
}
}

View file

@ -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,13 +475,18 @@ 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)
) {
sendChatsErrors = true
locationTime = Math.max(shareInfo.lastTextSuccessfulSendTime, shareInfo.lastMapSuccessfulSendTime)
chatsIds.add(shareInfo.chatId)
} 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)
}
}
}
@ -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()
}
override fun getMenuItems() =
values.map { OsmandFormatter.getFormattedDuration(app, it.toLong()) }
}
inner class LocHistoryPref : DurationPref(
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()
}
override fun getMenuItems() =
values.map { OsmandFormatter.getFormattedDuration(app, it.toLong()) }
}
inner class ShareTypePref : DurationPref(
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(

View file

@ -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,12 +53,31 @@ 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) {
lastLocationUpdateTime = System.currentTimeMillis()
if (app.settings.getChatsShareInfo().isNotEmpty()) {
shareLocationMessages(location, app.telegramHelper.getCurrentUserId())
if (record) {
lastLocationUpdateTime = System.currentTimeMillis()
lastLocation = location
if (app.settings.getChatsShareInfo().isNotEmpty()) {
shareLocationMessages(location, app.telegramHelper.getCurrentUserId())
}
}
}
app.settings.updateSharingStatusHistory()
@ -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?) {

View file

@ -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
}

View file

@ -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 {

View file

@ -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()))

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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);
}

View file

@ -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;

View 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;
}
}
}
}

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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

View file

@ -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,42 +217,50 @@ 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) {
TargetPointsHelper targetPointsHelper = app.getTargetPointsHelper();
Location myLocation = app.getLocationProvider().getLastKnownLocation();
if (myLocation != null) {
LatLon ll = new LatLon(myLocation.getLatitude(), myLocation.getLongitude());
switch (pointType) {
case START:
if (targetPointsHelper.getPointToStart() != null) {
targetPointsHelper.clearStartPoint(true);
app.getSettings().backupPointToStart();
}
break;
case TARGET:
app.showShortToastMessage(R.string.add_destination_point);
targetPointsHelper.navigateToPoint(ll, true, -1);
break;
case INTERMEDIATE:
app.showShortToastMessage(R.string.add_intermediate_point);
targetPointsHelper.navigateToPoint(ll, true, targetPointsHelper.getIntermediatePoints().size());
break;
case HOME:
app.showShortToastMessage(R.string.add_intermediate_point);
targetPointsHelper.setHomePoint(ll, null);
break;
case WORK:
app.showShortToastMessage(R.string.add_intermediate_point);
targetPointsHelper.setWorkPoint(ll, null);
break;
if (OsmAndLocationProvider.isLocationPermissionAvailable(app)) {
TargetPointsHelper targetPointsHelper = app.getTargetPointsHelper();
Location myLocation = app.getLocationProvider().getLastKnownLocation();
if (myLocation != null) {
LatLon ll = new LatLon(myLocation.getLatitude(), myLocation.getLongitude());
switch (pointType) {
case START:
if (targetPointsHelper.getPointToStart() != null) {
targetPointsHelper.clearStartPoint(true);
app.getSettings().backupPointToStart();
}
break;
case TARGET:
app.showShortToastMessage(R.string.add_destination_point);
targetPointsHelper.navigateToPoint(ll, true, -1);
break;
case INTERMEDIATE:
app.showShortToastMessage(R.string.add_intermediate_point);
targetPointsHelper.navigateToPoint(ll, true, targetPointsHelper.getIntermediatePoints().size());
break;
case HOME:
app.showShortToastMessage(R.string.add_intermediate_point);
targetPointsHelper.setHomePoint(ll, null);
break;
case WORK:
app.showShortToastMessage(R.string.add_intermediate_point);
targetPointsHelper.setWorkPoint(ll, null);
break;
}
}
} else if (activity != null) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
OsmAndLocationProvider.REQUEST_LOCATION_PERMISSION);
}
}
dismiss();

View file

@ -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));
}
}

View file

@ -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() {

View file

@ -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,9 +269,17 @@ 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++) {
drawCircle(canvas, tb, i, center, attrs);
if (showCompass && i == COMPASS_CIRCLE_ID) {
drawCompassCircle(canvas, tb, center, attrs);
} else {
drawCircle(canvas, tb, i, center, attrs);
}
}
}
}
@ -239,6 +290,20 @@ public class RulerControlLayer extends OsmandMapLayer {
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);
}
@ -293,7 +358,7 @@ public class RulerControlLayer extends OsmandMapLayer {
}
private void drawCenterIcon(Canvas canvas, RotatedTileBox tb, QuadPoint center, boolean nightMode,
RulerMode mode) {
RulerMode mode) {
canvas.rotate(-tb.getRotate(), center.x, center.y);
if (nightMode || mode == RulerMode.SECOND) {
canvas.drawBitmap(centerIconNight, center.x - centerIconNight.getWidth() / 2,
@ -391,42 +456,200 @@ public class RulerControlLayer extends OsmandMapLayer {
}
private void drawCircle(Canvas canvas, RotatedTileBox tb, int circleNumber, QuadPoint center,
RenderingLineAttributes attrs) {
RenderingLineAttributes attrs) {
if (!tb.isZoomAnimated()) {
Rect bounds = new Rect();
float circleRadius = radius * circleNumber;
String text = cacheDistances.get(circleNumber - 1);
attrs.paint2.getTextBounds(text, 0, text.length(), bounds);
// coords of left or top text
float x1 = 0;
float y1 = 0;
// coords of right or bottom text
float x2 = 0;
float y2 = 0;
if (textSide == TextSide.VERTICAL) {
x1 = center.x - bounds.width() / 2;
y1 = center.y - radius * circleNumber + bounds.height() / 2;
x2 = center.x - bounds.width() / 2;
y2 = center.y + radius * circleNumber + bounds.height() / 2;
} else if (textSide == TextSide.HORIZONTAL) {
x1 = center.x - radius * circleNumber - bounds.width() / 2;
y1 = center.y + bounds.height() / 2;
x2 = center.x + radius * circleNumber - bounds.width() / 2;
y2 = center.y + bounds.height() / 2;
}
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);
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);
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
float x1 = 0;
float y1 = 0;
// coords of right or bottom text
float x2 = 0;
float y2 = 0;
if (textSide == TextSide.VERTICAL) {
x1 = center.x - bounds.width() / 2;
y1 = center.y - drawingTextRadius + bounds.height() / 2;
x2 = center.x - bounds.width() / 2;
y2 = center.y + drawingTextRadius + bounds.height() / 2;
} else if (textSide == TextSide.HORIZONTAL) {
x1 = center.x - drawingTextRadius - bounds.width() / 2;
y1 = center.y + bounds.height() / 2;
x2 = center.x + drawingTextRadius - bounds.width() / 2;
y2 = center.y + bounds.height() / 2;
}
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 {
VERTICAL,
HORIZONTAL
@ -441,4 +664,4 @@ public class RulerControlLayer extends OsmandMapLayer {
public boolean drawInScreenPixels() {
return false;
}
}
}

View file

@ -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) {