Merge branch 'master' into app_profiles_2
# Conflicts: # OsmAnd/res/values/strings.xml
|
@ -133,6 +133,35 @@
|
||||||
|
|
||||||
<include layout="@layout/list_item_divider" />
|
<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
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:paddingLeft="@dimen/content_padding_standard"
|
|
||||||
android:paddingRight="@dimen/content_padding_standard"
|
|
||||||
tools:background="@color/card_bg_light">
|
tools:background="@color/card_bg_light">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/icon"
|
android:id="@+id/icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="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_marginTop="@dimen/image_button_padding"
|
||||||
android:layout_marginEnd="@dimen/content_padding_big"
|
android:layout_marginEnd="@dimen/content_padding_big"
|
||||||
android:layout_marginRight="@dimen/content_padding_big"
|
android:layout_marginRight="@dimen/content_padding_big"
|
||||||
|
@ -53,10 +53,8 @@
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/icon_right"
|
android:id="@+id/icon_right"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:layout_gravity="center_vertical"
|
||||||
android:paddingLeft="@dimen/image_button_padding"
|
|
||||||
android:paddingRight="@dimen/image_button_padding"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:src="@drawable/ic_action_additional_option"
|
tools:src="@drawable/ic_action_additional_option"
|
||||||
tools:tint="@color/icon_light"
|
tools:tint="@color/icon_light"
|
||||||
|
@ -65,10 +63,14 @@
|
||||||
<Switch
|
<Switch
|
||||||
android:id="@+id/switcher"
|
android:id="@+id/switcher"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:background="@null"
|
android:background="@null"
|
||||||
android:clickable="false"
|
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>
|
</LinearLayout>
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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_key">Key</string>
|
||||||
<string name="proxy_password">Password</string>
|
<string name="proxy_password">Password</string>
|
||||||
<string name="proxy_username">Username</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_SPEED_PARAM = "point_speed_param";
|
||||||
public static final String POINT_TYPE_ICON_NAME_PARAM = "point_type_icon_name_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_STALE_LOC_PARAM = "point_stale_loc_param";
|
||||||
|
public static final String POINT_BEARING_PARAM = "point_bearing_param";
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String shortName;
|
private String shortName;
|
||||||
|
|
|
@ -53,12 +53,10 @@ class TelegramApplication : Application(), OsmandHelperListener {
|
||||||
listOf(-1)
|
listOf(-1)
|
||||||
)
|
)
|
||||||
showLocationHelper.addDirectionContextMenuButton()
|
showLocationHelper.addDirectionContextMenuButton()
|
||||||
if (settings.hasAnyChatToShowOnMap()) {
|
|
||||||
showLocationHelper.startShowingLocation()
|
showLocationHelper.startShowingLocation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
osmandAidlHelper.setUpdatesListener(object : UpdatesListener {
|
osmandAidlHelper.setUpdatesListener(object : UpdatesListener {
|
||||||
override fun update() {
|
override fun update() {
|
||||||
showLocationHelper.startUpdateMessagesTask()
|
showLocationHelper.startUpdateMessagesTask()
|
||||||
|
|
|
@ -43,6 +43,9 @@ private val LOC_HISTORY_VALUES_SEC = listOf(
|
||||||
12 * 60 * 60L,
|
12 * 60 * 60L,
|
||||||
24 * 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_MAP = "Map"
|
||||||
const val SHARE_TYPE_TEXT = "Text"
|
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 STALE_LOC_DEFAULT_INDEX = 0
|
||||||
private const val LOC_HISTORY_DEFAULT_INDEX = 7
|
private const val LOC_HISTORY_DEFAULT_INDEX = 7
|
||||||
private const val SHARE_TYPE_DEFAULT_INDEX = 2
|
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"
|
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 LOC_HISTORY_TIME_KEY = "loc_history_time"
|
||||||
private const val SHARE_TYPE_KEY = "share_type"
|
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 APP_TO_CONNECT_PACKAGE_KEY = "app_to_connect_package"
|
||||||
|
|
||||||
private const val DEFAULT_VISIBLE_TIME_SECONDS = 60 * 60L // 1 hour
|
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 locHistoryTime = LOC_HISTORY_VALUES_SEC[LOC_HISTORY_DEFAULT_INDEX]
|
||||||
var shareTypeValue = SHARE_TYPE_VALUES[SHARE_TYPE_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 = ""
|
var appToConnectPackage = ""
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var liveNowSortType = LiveNowSortType.SORT_BY_DISTANCE
|
var liveNowSortType = LiveNowSortType.SORT_BY_DISTANCE
|
||||||
|
|
||||||
val gpsAndLocPrefs = listOf(SendMyLocPref(), StaleLocPref(), LocHistoryPref(), ShareTypePref())
|
val gpsAndLocPrefs = listOf(SendMyLocPref(), StaleLocPref(), LocHistoryPref(), ShareTypePref())
|
||||||
|
val gpxLoggingPrefs = listOf(MinLocationDistance(), MinLocationAccuracy(), MinLocationSpeed())
|
||||||
|
|
||||||
var batteryOptimisationAsked = false
|
var batteryOptimisationAsked = false
|
||||||
|
|
||||||
|
@ -460,15 +475,20 @@ class TelegramSettings(private val app: TelegramApplication) {
|
||||||
if (shareInfo.lastTextSuccessfulSendTime == -1L && shareInfo.lastMapSuccessfulSendTime == -1L
|
if (shareInfo.lastTextSuccessfulSendTime == -1L && shareInfo.lastMapSuccessfulSendTime == -1L
|
||||||
&& ((statusChangeTime / 1000 - shareInfo.start) < SHARING_INITIALIZATION_TIME)) {
|
&& ((statusChangeTime / 1000 - shareInfo.start) < SHARING_INITIALIZATION_TIME)) {
|
||||||
initializing = true
|
initializing = true
|
||||||
} else if (shareInfo.hasSharingError
|
} else {
|
||||||
|| (shareInfo.lastSendTextMessageTime - shareInfo.lastTextSuccessfulSendTime > WAITING_TDLIB_TIME)
|
val textSharingError = shareInfo.lastSendTextMessageTime - shareInfo.lastTextSuccessfulSendTime > WAITING_TDLIB_TIME
|
||||||
|| (shareInfo.lastSendMapMessageTime - shareInfo.lastMapSuccessfulSendTime > 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
|
sendChatsErrors = true
|
||||||
locationTime = Math.max(shareInfo.lastTextSuccessfulSendTime, shareInfo.lastMapSuccessfulSendTime)
|
locationTime = Math.max(shareInfo.lastTextSuccessfulSendTime, shareInfo.lastMapSuccessfulSendTime)
|
||||||
chatsIds.add(shareInfo.chatId)
|
chatsIds.add(shareInfo.chatId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (sendChatsErrors) {
|
if (sendChatsErrors) {
|
||||||
title = app.getString(R.string.not_possible_to_send_to_telegram_chats)
|
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) {
|
} else if (!initializing) {
|
||||||
when {
|
when {
|
||||||
!gpsEnabled -> {
|
!gpsEnabled -> {
|
||||||
locationTime = app.shareLocationHelper.lastLocationUpdateTime
|
locationTime = app.shareLocationHelper.lastLocationUpdateTime / 1000
|
||||||
if (locationTime <= 0) {
|
if (locationTime <= 0) {
|
||||||
locationTime = getLastSuccessfulSendTime()
|
locationTime = getLastSuccessfulSendTime()
|
||||||
}
|
}
|
||||||
|
@ -557,6 +577,10 @@ class TelegramSettings(private val app: TelegramApplication) {
|
||||||
edit.putLong(STALE_LOC_TIME_KEY, staleLocTime)
|
edit.putLong(STALE_LOC_TIME_KEY, staleLocTime)
|
||||||
edit.putLong(LOC_HISTORY_TIME_KEY, locHistoryTime)
|
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(SHARE_TYPE_KEY, shareTypeValue)
|
||||||
|
|
||||||
edit.putString(APP_TO_CONNECT_PACKAGE_KEY, appToConnectPackage)
|
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]
|
val shareTypeDef = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX]
|
||||||
shareTypeValue = prefs.getString(SHARE_TYPE_KEY, shareTypeDef)
|
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()
|
val currentUserId = app.telegramHelper.getCurrentUserId()
|
||||||
currentSharingMode = prefs.getString(SHARING_MODE_KEY, if (currentUserId != -1) currentUserId.toString() else "")
|
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.drawable.ic_action_share_location,
|
||||||
R.string.send_my_location,
|
R.string.send_my_location,
|
||||||
R.string.send_my_location_desc,
|
R.string.send_my_location_desc,
|
||||||
|
@ -798,12 +829,15 @@ class TelegramSettings(private val app: TelegramApplication) {
|
||||||
OsmandFormatter.getFormattedDuration(app, sendMyLocInterval)
|
OsmandFormatter.getFormattedDuration(app, sendMyLocInterval)
|
||||||
|
|
||||||
override fun setCurrentValue(index: Int) {
|
override fun setCurrentValue(index: Int) {
|
||||||
sendMyLocInterval = values[index]
|
sendMyLocInterval = values[index].toLong()
|
||||||
app.updateSendLocationInterval()
|
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.drawable.ic_action_time_span,
|
||||||
R.string.stale_location,
|
R.string.stale_location,
|
||||||
R.string.stale_location_desc,
|
R.string.stale_location_desc,
|
||||||
|
@ -814,11 +848,14 @@ class TelegramSettings(private val app: TelegramApplication) {
|
||||||
OsmandFormatter.getFormattedDuration(app, staleLocTime)
|
OsmandFormatter.getFormattedDuration(app, staleLocTime)
|
||||||
|
|
||||||
override fun setCurrentValue(index: Int) {
|
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.drawable.ic_action_location_history,
|
||||||
R.string.location_history,
|
R.string.location_history,
|
||||||
R.string.location_history_desc,
|
R.string.location_history_desc,
|
||||||
|
@ -830,12 +867,15 @@ class TelegramSettings(private val app: TelegramApplication) {
|
||||||
|
|
||||||
override fun setCurrentValue(index: Int) {
|
override fun setCurrentValue(index: Int) {
|
||||||
val value = values[index]
|
val value = values[index]
|
||||||
locHistoryTime = value
|
locHistoryTime = value.toLong()
|
||||||
app.telegramHelper.messageActiveTimeSec = value
|
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.drawable.ic_action_location_history,
|
||||||
R.string.send_location_as,
|
R.string.send_location_as,
|
||||||
R.string.send_location_as_descr,
|
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,
|
@DrawableRes val iconId: Int,
|
||||||
@StringRes val titleId: Int,
|
@StringRes val titleId: Int,
|
||||||
@StringRes val descriptionId: Int,
|
@StringRes val descriptionId: Int,
|
||||||
val values: List<Long>
|
val values: List<Number>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
abstract fun getCurrentValue(): String
|
abstract fun getCurrentValue(): String
|
||||||
|
|
||||||
abstract fun setCurrentValue(index: Int)
|
abstract fun setCurrentValue(index: Int)
|
||||||
|
|
||||||
open fun getMenuItems() = values.map { OsmandFormatter.getFormattedDuration(app, it) }
|
abstract fun getMenuItems(): List<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class AppConnect(
|
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.notifications.TelegramNotification.NotificationType
|
||||||
import net.osmand.telegram.utils.AndroidNetworkUtils
|
import net.osmand.telegram.utils.AndroidNetworkUtils
|
||||||
import net.osmand.telegram.utils.BASE_URL
|
import net.osmand.telegram.utils.BASE_URL
|
||||||
|
import net.osmand.util.MapUtils
|
||||||
import org.drinkless.td.libcore.telegram.TdApi
|
import org.drinkless.td.libcore.telegram.TdApi
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
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 UPDATE_LOCATION_ACCURACY = 150 // 150 meters
|
||||||
|
|
||||||
|
private const val SENT_LOCATIONS_INTERVAL_TIME_MS = 4000 // 4 sec
|
||||||
|
|
||||||
class ShareLocationHelper(private val app: TelegramApplication) {
|
class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
|
|
||||||
private val log = PlatformUtil.getLog(ShareLocationHelper::class.java)
|
private val log = PlatformUtil.getLog(ShareLocationHelper::class.java)
|
||||||
|
@ -30,6 +33,8 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
|
|
||||||
var lastLocationUpdateTime: Long = 0
|
var lastLocationUpdateTime: Long = 0
|
||||||
|
|
||||||
|
var lastLocationSentTime: Long = 0
|
||||||
|
|
||||||
var lastLocation: Location? = null
|
var lastLocation: Location? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
if (lastTimeInMillis == 0L) {
|
if (lastTimeInMillis == 0L) {
|
||||||
|
@ -48,14 +53,33 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
private var lastTimeInMillis: Long = 0L
|
private var lastTimeInMillis: Long = 0L
|
||||||
|
|
||||||
fun updateLocation(location: Location?) {
|
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()
|
lastLocationUpdateTime = System.currentTimeMillis()
|
||||||
|
lastLocation = location
|
||||||
if (app.settings.getChatsShareInfo().isNotEmpty()) {
|
if (app.settings.getChatsShareInfo().isNotEmpty()) {
|
||||||
shareLocationMessages(location, app.telegramHelper.getCurrentUserId())
|
shareLocationMessages(location, app.telegramHelper.getCurrentUserId())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
app.settings.updateSharingStatusHistory()
|
app.settings.updateSharingStatusHistory()
|
||||||
refreshNotification()
|
refreshNotification()
|
||||||
}
|
}
|
||||||
|
@ -108,12 +132,16 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkAndSendBufferMessagesToChat(chatId: Long) {
|
fun checkAndSendBufferMessagesToChat(chatId: Long) {
|
||||||
val shareInfo = app.settings.getChatsShareInfo()[chatId]
|
val shareChatsInfo = app.settings.getChatsShareInfo()
|
||||||
if (shareInfo != null) {
|
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 {
|
app.locationMessages.getBufferedTextMessagesForChat(chatId).take(MAX_MESSAGES_IN_TDLIB_PER_CHAT).forEach {
|
||||||
if (shareInfo.pendingTdLibText < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
if (shareInfo.pendingTdLibText < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||||
if (it.deviceName.isEmpty()) {
|
if (it.deviceName.isEmpty()) {
|
||||||
if (!shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
|
if (!shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
|
||||||
|
lastLocationSentTime = System.currentTimeMillis()
|
||||||
app.telegramHelper.editTextLocation(shareInfo, it)
|
app.telegramHelper.editTextLocation(shareInfo, it)
|
||||||
app.locationMessages.removeBufferedMessage(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 (shareInfo.pendingTdLibMap < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||||
if (it.deviceName.isEmpty()) {
|
if (it.deviceName.isEmpty()) {
|
||||||
if (!shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
|
if (!shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
|
||||||
|
lastLocationSentTime = System.currentTimeMillis()
|
||||||
app.telegramHelper.editMapLocation(shareInfo, it)
|
app.telegramHelper.editMapLocation(shareInfo, it)
|
||||||
app.locationMessages.removeBufferedMessage(it)
|
app.locationMessages.removeBufferedMessage(it)
|
||||||
}
|
}
|
||||||
|
@ -188,6 +217,14 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
val isBot = app.settings.currentSharingMode != userId.toString()
|
val isBot = app.settings.currentSharingMode != userId.toString()
|
||||||
val deviceName = if (isBot) app.settings.currentSharingMode else ""
|
val deviceName = if (isBot) app.settings.currentSharingMode else ""
|
||||||
var bufferedMessagesFull = false
|
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 ->
|
chatsShareInfo.values.forEach { shareInfo ->
|
||||||
if (shareInfo.pendingTdLibText >= MAX_MESSAGES_IN_TDLIB_PER_CHAT || shareInfo.pendingTdLibMap >= MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
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)
|
prepareTextMessage(shareInfo, messageText, isBot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
checkAndSendBufferMessagesToChat(shareInfo.chatId)
|
||||||
}
|
}
|
||||||
app.locationMessages.addMyLocationMessage(location)
|
|
||||||
if (bufferedMessagesFull) {
|
if (bufferedMessagesFull) {
|
||||||
checkNetworkType()
|
checkNetworkType()
|
||||||
}
|
}
|
||||||
|
@ -225,6 +262,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
if (isBot) {
|
if (isBot) {
|
||||||
sendLocationToBot(message, shareInfo, SHARE_TYPE_TEXT)
|
sendLocationToBot(message, shareInfo, SHARE_TYPE_TEXT)
|
||||||
} else {
|
} else {
|
||||||
|
lastLocationSentTime = System.currentTimeMillis()
|
||||||
app.telegramHelper.sendNewTextLocation(shareInfo, message)
|
app.telegramHelper.sendNewTextLocation(shareInfo, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,6 +275,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (shareInfo.pendingTdLibText < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
if (shareInfo.pendingTdLibText < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||||
|
lastLocationSentTime = System.currentTimeMillis()
|
||||||
app.telegramHelper.editTextLocation(shareInfo, message)
|
app.telegramHelper.editTextLocation(shareInfo, message)
|
||||||
} else {
|
} else {
|
||||||
app.locationMessages.addBufferedMessage(message)
|
app.locationMessages.addBufferedMessage(message)
|
||||||
|
@ -258,6 +297,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
app.locationMessages.addBufferedMessage(message)
|
app.locationMessages.addBufferedMessage(message)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
lastLocationSentTime = System.currentTimeMillis()
|
||||||
app.telegramHelper.sendNewMapLocation(shareInfo, message)
|
app.telegramHelper.sendNewMapLocation(shareInfo, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,6 +310,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (shareInfo.pendingTdLibMap < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
if (shareInfo.pendingTdLibMap < MAX_MESSAGES_IN_TDLIB_PER_CHAT) {
|
||||||
|
lastLocationSentTime = System.currentTimeMillis()
|
||||||
app.telegramHelper.editMapLocation(shareInfo, message)
|
app.telegramHelper.editMapLocation(shareInfo, message)
|
||||||
} else {
|
} else {
|
||||||
app.locationMessages.addBufferedMessage(message)
|
app.locationMessages.addBufferedMessage(message)
|
||||||
|
@ -298,6 +339,7 @@ class ShareLocationHelper(private val app: TelegramApplication) {
|
||||||
} else if (shareType == SHARE_TYPE_MAP) {
|
} else if (shareType == SHARE_TYPE_MAP) {
|
||||||
shareInfo.lastSendMapMessageTime = (System.currentTimeMillis() / 1000).toInt()
|
shareInfo.lastSendMapMessageTime = (System.currentTimeMillis() / 1000).toInt()
|
||||||
}
|
}
|
||||||
|
lastLocationSentTime = System.currentTimeMillis()
|
||||||
AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false,
|
AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false,
|
||||||
object : AndroidNetworkUtils.OnRequestResultListener {
|
object : AndroidNetworkUtils.OnRequestResultListener {
|
||||||
override fun onResult(result: String?) {
|
override fun onResult(result: String?) {
|
||||||
|
|
|
@ -90,6 +90,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
||||||
if (item.latLon == null) {
|
if (item.latLon == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
setupMapLayer()
|
||||||
osmandAidlHelper.execOsmandApi {
|
osmandAidlHelper.execOsmandApi {
|
||||||
osmandAidlHelper.showMapPoint(
|
osmandAidlHelper.showMapPoint(
|
||||||
MAP_LAYER_ID,
|
MAP_LAYER_ID,
|
||||||
|
@ -100,7 +101,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
||||||
Color.WHITE,
|
Color.WHITE,
|
||||||
ALatLon(item.latLon!!.latitude, item.latLon!!.longitude),
|
ALatLon(item.latLon!!.latitude, item.latLon!!.longitude),
|
||||||
generatePointDetails(item.bearing?.toFloat(), item.altitude?.toFloat(), item.precision?.toFloat()),
|
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()
|
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) + ": ")
|
val typeName = if (isGroup) chatTitle else OsmandFormatter.getListItemLiveTimeDescr(app, date, app.getString(R.string.last_response) + ": ")
|
||||||
if (update) {
|
if (update) {
|
||||||
osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, pointId, name, name, typeName, Color.WHITE, aLatLon, details, params)
|
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
|
points[pointId] = message
|
||||||
} else if (content is MessageOsmAndBotLocation && content.isValid()) {
|
} else if (content is MessageOsmAndBotLocation && content.isValid()) {
|
||||||
setupMapLayer()
|
setupMapLayer()
|
||||||
val params = generatePointParams(null, stale, content.speed.toFloat())
|
val params = generatePointParams(null, stale, content.speed.toFloat(), content.bearing.toFloat())
|
||||||
if (update) {
|
if (update) {
|
||||||
osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, pointId, name, name, chatTitle, Color.WHITE, aLatLon, details, params)
|
osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, pointId, name, name, chatTitle, Color.WHITE, aLatLon, details, params)
|
||||||
} else {
|
} else {
|
||||||
|
@ -340,7 +346,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
||||||
private fun generatePointDetails(bearing: Float?, altitude: Float?, precision: Float?): List<String> {
|
private fun generatePointDetails(bearing: Float?, altitude: Float?, precision: Float?): List<String> {
|
||||||
val details = mutableListOf<String>()
|
val details = mutableListOf<String>()
|
||||||
if (bearing != null && bearing != 0.0f) {
|
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) {
|
if (altitude != null && altitude != 0.0f) {
|
||||||
details.add(String.format(Locale.US, "${OsmandLocationUtils.ALTITUDE_PREFIX}%.1f m\n", altitude))
|
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
|
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)
|
val photoUri = generatePhotoUri(photoPath, stale)
|
||||||
app.grantUriPermission(
|
app.grantUriPermission(
|
||||||
app.settings.appToConnectPackage,
|
app.settings.appToConnectPackage,
|
||||||
|
@ -366,6 +372,9 @@ class ShowLocationHelper(private val app: TelegramApplication) {
|
||||||
if (speed != 0.0f) {
|
if (speed != 0.0f) {
|
||||||
params[AMapPoint.POINT_SPEED_PARAM] = speed.toString()
|
params[AMapPoint.POINT_SPEED_PARAM] = speed.toString()
|
||||||
}
|
}
|
||||||
|
if (bearing != 0.0f) {
|
||||||
|
params[AMapPoint.POINT_BEARING_PARAM] = bearing.toString()
|
||||||
|
}
|
||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import net.osmand.telegram.R
|
import net.osmand.telegram.R
|
||||||
import net.osmand.telegram.TelegramSettings
|
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.TelegramHelper.Companion.OSMAND_BOT_USERNAME
|
||||||
import net.osmand.telegram.helpers.TelegramUiHelper
|
import net.osmand.telegram.helpers.TelegramUiHelper
|
||||||
import net.osmand.telegram.utils.AndroidUtils
|
import net.osmand.telegram.utils.AndroidUtils
|
||||||
|
@ -49,18 +49,8 @@ class SettingsDialogFragment : BaseDialogFragment() {
|
||||||
window.statusBarColor = ContextCompat.getColor(app, R.color.card_bg_light)
|
window.statusBarColor = ContextCompat.getColor(app, R.color.card_bg_light)
|
||||||
}
|
}
|
||||||
var container = mainView.findViewById<ViewGroup>(R.id.gps_and_loc_container)
|
var container = mainView.findViewById<ViewGroup>(R.id.gps_and_loc_container)
|
||||||
for (pref in settings.gpsAndLocPrefs) {
|
settings.gpsAndLocPrefs.forEach {
|
||||||
inflater.inflate(R.layout.item_with_desc_and_right_value, container, false).apply {
|
createNumericPref(inflater, container, it)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 26) {
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
@ -98,22 +88,21 @@ class SettingsDialogFragment : BaseDialogFragment() {
|
||||||
findViewById<ImageView>(R.id.icon_right).apply {
|
findViewById<ImageView>(R.id.icon_right).apply {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_additional_option))
|
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)
|
findViewById<TextView>(R.id.title).text = getText(R.string.proxy)
|
||||||
val description = findViewById<TextView>(R.id.description).apply {
|
val description = findViewById<TextView>(R.id.description).apply {
|
||||||
text = if (settings.proxyEnabled) getText(R.string.proxy_connected) else getText(R.string.proxy_disconnected)
|
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
|
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 {
|
setOnClickListener {
|
||||||
val checked = !app.settings.proxyEnabled
|
activity?.supportFragmentManager?.also { ProxySettingsDialogFragment.showInstance(it, this@SettingsDialogFragment) }
|
||||||
switcher.isChecked = checked
|
|
||||||
settings.updateProxySetting(checked)
|
|
||||||
description.text = if (checked) getText(R.string.proxy_connected) else getText(R.string.proxy_disconnected)
|
|
||||||
}
|
}
|
||||||
container.addView(this)
|
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)
|
container = mainView.findViewById(R.id.osmand_connect_container)
|
||||||
for (appConn in TelegramSettings.AppConnect.values()) {
|
for (appConn in TelegramSettings.AppConnect.values()) {
|
||||||
val pack = appConn.appPackage
|
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) {
|
private fun addItemToContainer(inflater: LayoutInflater, container: ViewGroup, tag: String, title: String) {
|
||||||
inflater.inflate(R.layout.item_with_rb_and_btn, container, false).apply {
|
inflater.inflate(R.layout.item_with_rb_and_btn, container, false).apply {
|
||||||
val checked = tag == settings.currentSharingMode
|
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 menuList = pref.getMenuItems()
|
||||||
val ctx = valueView.context
|
val ctx = valueView.context
|
||||||
ListPopupWindow(ctx).apply {
|
ListPopupWindow(ctx).apply {
|
||||||
|
|
|
@ -32,6 +32,7 @@ object OsmandLocationUtils {
|
||||||
const val BEARING_PREFIX = "Bearing: "
|
const val BEARING_PREFIX = "Bearing: "
|
||||||
const val SPEED_PREFIX = "Speed: "
|
const val SPEED_PREFIX = "Speed: "
|
||||||
const val HDOP_PREFIX = "Horizontal precision: "
|
const val HDOP_PREFIX = "Horizontal precision: "
|
||||||
|
const val BEARING_SUFFIX = "°"
|
||||||
|
|
||||||
const val NOW = "now"
|
const val NOW = "now"
|
||||||
const val FEW_SECONDS_AGO = "few seconds ago"
|
const val FEW_SECONDS_AGO = "few seconds ago"
|
||||||
|
@ -241,19 +242,28 @@ object OsmandLocationUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.startsWith(SPEED_PREFIX) -> {
|
s.startsWith(SPEED_PREFIX) -> {
|
||||||
val altStr = s.removePrefix(SPEED_PREFIX)
|
val speedStr = s.removePrefix(SPEED_PREFIX)
|
||||||
try {
|
try {
|
||||||
val alt = altStr.split(" ").first()
|
val speed = speedStr.split(" ").first()
|
||||||
res.speed = alt.toDouble()
|
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) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.startsWith(HDOP_PREFIX) -> {
|
s.startsWith(HDOP_PREFIX) -> {
|
||||||
val altStr = s.removePrefix(HDOP_PREFIX)
|
val hdopStr = s.removePrefix(HDOP_PREFIX)
|
||||||
try {
|
try {
|
||||||
val alt = altStr.split(" ").first()
|
val hdop = hdopStr.split(" ").first()
|
||||||
res.hdop = alt.toDouble()
|
res.hdop = hdop.toDouble()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
@ -336,6 +346,10 @@ object OsmandLocationUtils {
|
||||||
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
|
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))
|
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) {
|
if (location.hdop != 0.0 && location.speed == 0.0) {
|
||||||
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
|
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()))
|
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()))
|
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))
|
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) {
|
if (location.hdop != 0.0 && location.speed == 0.0) {
|
||||||
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
|
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()))
|
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="shadow_color">#33888888</color>
|
||||||
|
|
||||||
|
<color name="compass_control_active">#EE5622</color>
|
||||||
|
|
||||||
<color name="sherpafy_selection">#ff33b5e5</color>
|
<color name="sherpafy_selection">#ff33b5e5</color>
|
||||||
<color name="sherpafy_add_text">#b9b9b9</color>
|
<color name="sherpafy_add_text">#b9b9b9</color>
|
||||||
|
|
||||||
|
|
|
@ -8,5 +8,8 @@
|
||||||
<!-- Time control widget ids-->
|
<!-- Time control widget ids-->
|
||||||
<item name="time_control_widget_state_arrival_time" type="id"/>
|
<item name="time_control_widget_state_arrival_time" type="id"/>
|
||||||
<item name="time_control_widget_state_time_to_go" 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>
|
</resources>
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<string name="base_profile_descr_ski">Skiing</string>
|
<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="select_icon_profile_dialog_title">Select icon</string>
|
||||||
<string name="settings_routing_mode_string">Mode: %s</string>
|
<string name="settings_routing_mode_string">Mode: %s</string>
|
||||||
<string name="settings_derived_routing_mode_string">User Mode, derived from: %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.graphics.BitmapFactory;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import net.osmand.osm.io.Base64;
|
||||||
import net.osmand.osm.io.NetworkUtils;
|
import net.osmand.osm.io.NetworkUtils;
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
import net.osmand.plus.R;
|
import net.osmand.plus.R;
|
||||||
|
@ -15,18 +16,23 @@ import org.apache.commons.logging.Log;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
public class AndroidNetworkUtils {
|
public class AndroidNetworkUtils {
|
||||||
|
|
||||||
|
@ -215,6 +221,88 @@ public class AndroidNetworkUtils {
|
||||||
return res;
|
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) {
|
private static void showToast(OsmandApplication ctx, String message) {
|
||||||
ctx.showToastMessage(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_SPEED_PARAM = "point_speed_param";
|
||||||
public static final String POINT_TYPE_ICON_NAME_PARAM = "point_type_icon_name_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_STALE_LOC_PARAM = "point_stale_loc_param";
|
||||||
|
public static final String POINT_BEARING_PARAM = "point_bearing_param";
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String shortName;
|
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.locationProvider = startupInit(new OsmAndLocationProvider(app), OsmAndLocationProvider.class);
|
||||||
app.avoidSpecificRoads = startupInit(new AvoidSpecificRoads(app), AvoidSpecificRoads.class);
|
app.avoidSpecificRoads = startupInit(new AvoidSpecificRoads(app), AvoidSpecificRoads.class);
|
||||||
app.savingTrackHelper = startupInit(new SavingTrackHelper(app), SavingTrackHelper.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.notificationHelper = startupInit(new NotificationHelper(app), NotificationHelper.class);
|
||||||
app.liveMonitoringHelper = startupInit(new LiveMonitoringHelper(app), LiveMonitoringHelper.class);
|
app.liveMonitoringHelper = startupInit(new LiveMonitoringHelper(app), LiveMonitoringHelper.class);
|
||||||
app.selectedGpxHelper = startupInit(new GpxSelectionHelper(app, app.savingTrackHelper), GpxSelectionHelper.class);
|
app.selectedGpxHelper = startupInit(new GpxSelectionHelper(app, app.savingTrackHelper), GpxSelectionHelper.class);
|
||||||
|
|
|
@ -110,6 +110,7 @@ public class OsmandApplication extends MultiDexApplication {
|
||||||
GpxSelectionHelper selectedGpxHelper;
|
GpxSelectionHelper selectedGpxHelper;
|
||||||
GPXDatabase gpxDatabase;
|
GPXDatabase gpxDatabase;
|
||||||
SavingTrackHelper savingTrackHelper;
|
SavingTrackHelper savingTrackHelper;
|
||||||
|
AnalyticsHelper analyticsHelper;
|
||||||
NotificationHelper notificationHelper;
|
NotificationHelper notificationHelper;
|
||||||
LiveMonitoringHelper liveMonitoringHelper;
|
LiveMonitoringHelper liveMonitoringHelper;
|
||||||
TargetPointsHelper targetPointsHelper;
|
TargetPointsHelper targetPointsHelper;
|
||||||
|
@ -921,10 +922,10 @@ public class OsmandApplication extends MultiDexApplication {
|
||||||
try {
|
try {
|
||||||
if (Version.isGooglePlayEnabled(this) && !Version.isPaidVersion(this)
|
if (Version.isGooglePlayEnabled(this) && !Version.isPaidVersion(this)
|
||||||
&& !osmandSettings.DO_NOT_SEND_ANONYMOUS_APP_USAGE.get()) {
|
&& !osmandSettings.DO_NOT_SEND_ANONYMOUS_APP_USAGE.get()) {
|
||||||
// not implemented yet
|
analyticsHelper.addEvent(event);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} 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 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_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();
|
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 {
|
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 static final int NO_ICON = 0;
|
||||||
|
|
||||||
private AMapPoint point;
|
private AMapPoint point;
|
||||||
|
@ -137,17 +137,38 @@ public class AMapPointMenuController extends MenuController {
|
||||||
MapActivity activity = getMapActivity();
|
MapActivity activity = getMapActivity();
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
float speed = getPointSpeed();
|
float speed = getPointSpeed();
|
||||||
if (speed != NO_SPEED) {
|
if (speed != NO_VALUE) {
|
||||||
String formatted = OsmAndFormatter.getFormattedSpeed(speed, activity.getMyApplication());
|
return OsmAndFormatter.getFormattedSpeed(speed, activity.getMyApplication());
|
||||||
return activity.getString(R.string.map_widget_speed) + ": " + formatted;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.getAdditionalInfoStr();
|
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
|
@Override
|
||||||
public int getAdditionalInfoIconRes() {
|
public int getAdditionalInfoIconRes() {
|
||||||
if (getPointSpeed() != NO_SPEED) {
|
if (getPointSpeed() != NO_VALUE) {
|
||||||
return R.drawable.ic_action_speed_16;
|
return R.drawable.ic_action_speed_16;
|
||||||
}
|
}
|
||||||
return super.getAdditionalInfoIconRes();
|
return super.getAdditionalInfoIconRes();
|
||||||
|
@ -214,10 +235,22 @@ public class AMapPointMenuController extends MenuController {
|
||||||
try {
|
try {
|
||||||
return Float.parseFloat(speed);
|
return Float.parseFloat(speed);
|
||||||
} catch (NumberFormatException e) {
|
} 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
|
@Nullable
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package net.osmand.plus.routepreparationmenu;
|
package net.osmand.plus.routepreparationmenu;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
@ -27,6 +30,7 @@ import net.osmand.data.PointDescription;
|
||||||
import net.osmand.plus.FavouritesDbHelper;
|
import net.osmand.plus.FavouritesDbHelper;
|
||||||
import net.osmand.plus.MapMarkersHelper;
|
import net.osmand.plus.MapMarkersHelper;
|
||||||
import net.osmand.plus.MapMarkersHelper.MapMarker;
|
import net.osmand.plus.MapMarkersHelper.MapMarker;
|
||||||
|
import net.osmand.plus.OsmAndLocationProvider;
|
||||||
import net.osmand.plus.OsmandApplication;
|
import net.osmand.plus.OsmandApplication;
|
||||||
import net.osmand.plus.R;
|
import net.osmand.plus.R;
|
||||||
import net.osmand.plus.TargetPointsHelper;
|
import net.osmand.plus.TargetPointsHelper;
|
||||||
|
@ -213,14 +217,17 @@ public class AddPointBottomSheetDialog extends MenuBottomSheetDialogFragment {
|
||||||
|
|
||||||
private void createMyLocItem() {
|
private void createMyLocItem() {
|
||||||
BaseBottomSheetItem myLocationItem = new SimpleBottomSheetItem.Builder()
|
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))
|
.setTitle(getString(R.string.shared_string_my_location))
|
||||||
.setLayoutId(R.layout.bottom_sheet_item_simple_56dp)
|
.setLayoutId(R.layout.bottom_sheet_item_simple_56dp)
|
||||||
.setOnClickListener(new OnClickListener() {
|
.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
OsmandApplication app = getMyApplication();
|
OsmandApplication app = getMyApplication();
|
||||||
|
Activity activity = getActivity();
|
||||||
if (app != null) {
|
if (app != null) {
|
||||||
|
if (OsmAndLocationProvider.isLocationPermissionAvailable(app)) {
|
||||||
TargetPointsHelper targetPointsHelper = app.getTargetPointsHelper();
|
TargetPointsHelper targetPointsHelper = app.getTargetPointsHelper();
|
||||||
Location myLocation = app.getLocationProvider().getLastKnownLocation();
|
Location myLocation = app.getLocationProvider().getLastKnownLocation();
|
||||||
if (myLocation != null) {
|
if (myLocation != null) {
|
||||||
|
@ -250,6 +257,11 @@ public class AddPointBottomSheetDialog extends MenuBottomSheetDialogFragment {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (activity != null) {
|
||||||
|
ActivityCompat.requestPermissions(activity,
|
||||||
|
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
|
||||||
|
OsmAndLocationProvider.REQUEST_LOCATION_PERMISSION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1659,9 +1659,14 @@ public class MapRouteInfoMenu implements IRouteInformationListener, CardListener
|
||||||
|
|
||||||
public void updateFromIcon(View parentView) {
|
public void updateFromIcon(View parentView) {
|
||||||
MapActivity mapActivity = getMapActivity();
|
MapActivity mapActivity = getMapActivity();
|
||||||
|
|
||||||
|
int locationIconResByStatus = OsmAndLocationProvider.isLocationPermissionAvailable(mapActivity)
|
||||||
|
? R.drawable.ic_action_location_color : R.drawable.ic_action_location_color_lost;
|
||||||
|
|
||||||
if (mapActivity != null) {
|
if (mapActivity != null) {
|
||||||
((ImageView) parentView.findViewById(R.id.fromIcon)).setImageDrawable(ContextCompat.getDrawable(mapActivity,
|
((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.helpers.AndroidUiHelper;
|
||||||
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu.TrackChartPoints;
|
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu.TrackChartPoints;
|
||||||
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory;
|
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.TopCoordinatesView;
|
||||||
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopTextView;
|
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopTextView;
|
||||||
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
|
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
|
||||||
|
@ -200,7 +201,7 @@ public class MapInfoLayer extends OsmandMapLayer {
|
||||||
TextInfoWidget battery = ric.createBatteryControl(map);
|
TextInfoWidget battery = ric.createBatteryControl(map);
|
||||||
registerSideWidget(battery, R.drawable.ic_action_battery, R.string.map_widget_battery, "battery", false, 42);
|
registerSideWidget(battery, R.drawable.ic_action_battery, R.string.map_widget_battery, "battery", false, 42);
|
||||||
TextInfoWidget ruler = mic.createRulerControl(map);
|
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() {
|
public void recreateControls() {
|
||||||
|
|
|
@ -3,17 +3,23 @@ package net.osmand.plus.views;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.LinearGradient;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Paint.Style;
|
import android.graphics.Paint.Style;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.PathMeasure;
|
import android.graphics.PathMeasure;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Shader;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import net.osmand.AndroidUtils;
|
||||||
import net.osmand.Location;
|
import net.osmand.Location;
|
||||||
import net.osmand.data.LatLon;
|
import net.osmand.data.LatLon;
|
||||||
import net.osmand.data.QuadPoint;
|
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 long DELAY_BEFORE_DRAW = 500;
|
||||||
private static final int TEXT_SIZE = 14;
|
private static final int TEXT_SIZE = 14;
|
||||||
private static final int DISTANCE_TEXT_SIZE = 16;
|
private static final int DISTANCE_TEXT_SIZE = 16;
|
||||||
|
private static final int COMPASS_CIRCLE_ID = 2;
|
||||||
|
|
||||||
private final MapActivity mapActivity;
|
private final MapActivity mapActivity;
|
||||||
private OsmandApplication app;
|
private OsmandApplication app;
|
||||||
|
@ -74,11 +81,26 @@ public class RulerControlLayer extends OsmandMapLayer {
|
||||||
private Bitmap centerIconDay;
|
private Bitmap centerIconDay;
|
||||||
private Bitmap centerIconNight;
|
private Bitmap centerIconNight;
|
||||||
private Paint bitmapPaint;
|
private Paint bitmapPaint;
|
||||||
|
private Paint triangleHeadingPaint;
|
||||||
|
private Paint triangleNorthPaint;
|
||||||
|
private Paint redLinesPaint;
|
||||||
|
private Paint blueLinesPaint;
|
||||||
|
|
||||||
private RenderingLineAttributes lineAttrs;
|
private RenderingLineAttributes lineAttrs;
|
||||||
private RenderingLineAttributes lineFontAttrs;
|
private RenderingLineAttributes lineFontAttrs;
|
||||||
private RenderingLineAttributes circleAttrs;
|
private RenderingLineAttributes circleAttrs;
|
||||||
private RenderingLineAttributes circleAttrsAlt;
|
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;
|
private Handler handler;
|
||||||
|
|
||||||
public RulerControlLayer(MapActivity mapActivity) {
|
public RulerControlLayer(MapActivity mapActivity) {
|
||||||
|
@ -118,6 +140,14 @@ public class RulerControlLayer extends OsmandMapLayer {
|
||||||
bitmapPaint.setDither(true);
|
bitmapPaint.setDither(true);
|
||||||
bitmapPaint.setFilterBitmap(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");
|
lineAttrs = new RenderingLineAttributes("rulerLine");
|
||||||
|
|
||||||
float circleTextSize = TEXT_SIZE * mapActivity.getResources().getDisplayMetrics().density;
|
float circleTextSize = TEXT_SIZE * mapActivity.getResources().getDisplayMetrics().density;
|
||||||
|
@ -141,6 +171,18 @@ public class RulerControlLayer extends OsmandMapLayer {
|
||||||
view.refreshMap();
|
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
|
@Override
|
||||||
|
@ -190,6 +232,7 @@ public class RulerControlLayer extends OsmandMapLayer {
|
||||||
circleAttrsAlt.paint2.setStyle(Style.FILL);
|
circleAttrsAlt.paint2.setStyle(Style.FILL);
|
||||||
final QuadPoint center = tb.getCenterPixelPoint();
|
final QuadPoint center = tb.getCenterPixelPoint();
|
||||||
final RulerMode mode = app.getSettings().RULER_MODE.get();
|
final RulerMode mode = app.getSettings().RULER_MODE.get();
|
||||||
|
boolean showCompass = app.getSettings().SHOW_COMPASS_CONTROL_RULER.get();
|
||||||
final long currentTime = System.currentTimeMillis();
|
final long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
if (cacheMultiTouchEndTime != view.getMultiTouchEndTime()) {
|
if (cacheMultiTouchEndTime != view.getMultiTouchEndTime()) {
|
||||||
|
@ -226,19 +269,41 @@ public class RulerControlLayer extends OsmandMapLayer {
|
||||||
}
|
}
|
||||||
if (mode == RulerMode.FIRST || mode == RulerMode.SECOND) {
|
if (mode == RulerMode.FIRST || mode == RulerMode.SECOND) {
|
||||||
updateData(tb, center);
|
updateData(tb, center);
|
||||||
|
if (showCompass) {
|
||||||
|
updateHeading();
|
||||||
|
resetDrawingPaths();
|
||||||
|
}
|
||||||
RenderingLineAttributes attrs = mode == RulerMode.FIRST ? circleAttrs : circleAttrsAlt;
|
RenderingLineAttributes attrs = mode == RulerMode.FIRST ? circleAttrs : circleAttrsAlt;
|
||||||
for (int i = 1; i <= cacheDistances.size(); i++) {
|
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);
|
drawCircle(canvas, tb, i, center, attrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean rulerModeOn() {
|
public boolean rulerModeOn() {
|
||||||
return mapActivity.getMapLayers().getMapWidgetRegistry().isVisible("ruler") &&
|
return mapActivity.getMapLayers().getMapWidgetRegistry().isVisible("ruler") &&
|
||||||
rightWidgetsPanel.getVisibility() == View.VISIBLE;
|
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() {
|
private void refreshMapDelayed() {
|
||||||
handler.sendEmptyMessageDelayed(0, DRAW_TIME + 50);
|
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,
|
private void drawCircle(Canvas canvas, RotatedTileBox tb, int circleNumber, QuadPoint center,
|
||||||
RenderingLineAttributes attrs) {
|
RenderingLineAttributes attrs) {
|
||||||
if (!tb.isZoomAnimated()) {
|
if (!tb.isZoomAnimated()) {
|
||||||
Rect bounds = new Rect();
|
float circleRadius = radius * circleNumber;
|
||||||
String text = cacheDistances.get(circleNumber - 1);
|
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);
|
attrs.paint2.getTextBounds(text, 0, text.length(), bounds);
|
||||||
|
|
||||||
// coords of left or top text
|
// coords of left or top text
|
||||||
|
@ -406,25 +490,164 @@ public class RulerControlLayer extends OsmandMapLayer {
|
||||||
|
|
||||||
if (textSide == TextSide.VERTICAL) {
|
if (textSide == TextSide.VERTICAL) {
|
||||||
x1 = center.x - bounds.width() / 2;
|
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;
|
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) {
|
} 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;
|
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;
|
y2 = center.y + bounds.height() / 2;
|
||||||
}
|
}
|
||||||
|
return new float[]{x1, y1, x2, y2};
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
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.OsmandMapLayer.DrawSettings;
|
||||||
import net.osmand.plus.views.OsmandMapTileView;
|
import net.osmand.plus.views.OsmandMapTileView;
|
||||||
import net.osmand.plus.views.RulerControlLayer;
|
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.plus.views.mapwidgets.NextTurnInfoWidget.TurnDrawable;
|
||||||
import net.osmand.router.TurnType;
|
import net.osmand.router.TurnType;
|
||||||
import net.osmand.util.Algorithms;
|
import net.osmand.util.Algorithms;
|
||||||
|
@ -138,6 +139,54 @@ public class MapInfoWidgetsFactory {
|
||||||
return gpsInfoControl;
|
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) {
|
public TextInfoWidget createRulerControl(final MapActivity map) {
|
||||||
final String title = "—";
|
final String title = "—";
|
||||||
final TextInfoWidget rulerControl = new TextInfoWidget(map) {
|
final TextInfoWidget rulerControl = new TextInfoWidget(map) {
|
||||||
|
|