diff --git a/OsmAnd-telegram/res/layout/chat_list_item.xml b/OsmAnd-telegram/res/layout/chat_list_item.xml
index 18882a304b..a47ad13068 100644
--- a/OsmAnd-telegram/res/layout/chat_list_item.xml
+++ b/OsmAnd-telegram/res/layout/chat_list_item.xml
@@ -19,11 +19,10 @@
diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt
index 811524a9ab..d758f0cfda 100644
--- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt
+++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt
@@ -14,11 +14,13 @@ import net.osmand.telegram.helpers.ShowLocationHelper
import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.notifications.NotificationHelper
import net.osmand.telegram.utils.AndroidUtils
+import net.osmand.telegram.utils.UiUtils
class TelegramApplication : Application(), OsmandHelperListener {
val telegramHelper = TelegramHelper.instance
lateinit var settings: TelegramSettings private set
+ lateinit var uiUtils: UiUtils private set
lateinit var shareLocationHelper: ShareLocationHelper private set
lateinit var showLocationHelper: ShowLocationHelper private set
lateinit var notificationHelper: NotificationHelper private set
@@ -36,6 +38,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
telegramHelper.appDir = filesDir.absolutePath
settings = TelegramSettings(this)
+ uiUtils = UiUtils(this)
osmandHelper = OsmandAidlHelper(this)
shareLocationHelper = ShareLocationHelper(this)
showLocationHelper = ShowLocationHelper(this)
diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt
index 5769867c1d..92e79c9ddd 100644
--- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt
+++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt
@@ -41,6 +41,8 @@ class TelegramHelper private constructor() {
private val chatList = TreeSet()
private val chatLiveMessages = ConcurrentHashMap()
+ private val downloadChatFilesMap = ConcurrentHashMap()
+
private val usersLiveMessages = ConcurrentHashMap()
private val usersFullInfo = ConcurrentHashMap()
@@ -72,6 +74,17 @@ class TelegramHelper private constructor() {
}
}
+ fun getChatIndex(chatId: Long): Int {
+ synchronized(chatList) {
+ for ((i, chat) in chatList.withIndex()) {
+ if (chat.chatId == chatId) {
+ return i
+ }
+ }
+ }
+ return -1
+ }
+
fun getChatTitles(): List {
return chatTitles.keys().toList()
}
@@ -126,6 +139,7 @@ class TelegramHelper private constructor() {
fun onTelegramChatsRead()
fun onTelegramChatsChanged()
+ fun onTelegramChatChanged(chat: TdApi.Chat)
fun onTelegramError(code: Int, message: String)
fun onSendLiveLicationError(code: Int, message: String)
}
@@ -434,8 +448,8 @@ class TelegramHelper private constructor() {
parameters.databaseDirectory = File(appDir, "tdlib").absolutePath
parameters.useMessageDatabase = true
parameters.useSecretChats = true
- parameters.apiId = 94575
- parameters.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2"
+ parameters.apiId = 293148
+ parameters.apiHash = "d1942abd0f1364efe5020e2bfed2ed15"
parameters.systemLanguageCode = "en"
parameters.deviceModel = "Android"
parameters.systemVersion = "OsmAnd Telegram"
@@ -551,7 +565,34 @@ class TelegramHelper private constructor() {
synchronized(chat!!) {
if (chat.type !is TdApi.ChatTypeSupergroup || !(chat.type as TdApi.ChatTypeSupergroup).isChannel) {
chats[chat.id] = chat
-
+ val localPhoto = chat.photo?.small?.local
+ val hasLocalPhoto = if (localPhoto != null) {
+ localPhoto.canBeDownloaded && localPhoto.isDownloadingCompleted && localPhoto.path.isNotEmpty()
+ } else {
+ false
+ }
+ if (!hasLocalPhoto) {
+ val remotePhoto = chat.photo?.small?.remote
+ if (remotePhoto != null && remotePhoto.id.isNotEmpty()) {
+ downloadChatFilesMap[remotePhoto.id] = chat
+ client!!.send(TdApi.GetRemoteFile(remotePhoto.id, null), { obj ->
+ when (obj.constructor) {
+ TdApi.Error.CONSTRUCTOR -> {
+ val error = obj as TdApi.Error
+ val code = error.code
+ if (code != IGNORED_ERROR_CODE) {
+ listener?.onTelegramError(code, error.message)
+ }
+ }
+ TdApi.File.CONSTRUCTOR -> {
+ val file = obj as TdApi.File
+ client!!.send(TdApi.DownloadFile(file.id, 10), defaultHandler)
+ }
+ else -> listener?.onTelegramError(-1, "Receive wrong response from TDLib: $obj")
+ }
+ })
+ }
+ }
val order = chat.order
chat.order = 0
setChatOrder(chat, order)
@@ -567,7 +608,7 @@ class TelegramHelper private constructor() {
chat.title = updateChat.title
}
updateChatTitles()
- listener?.onTelegramChatsChanged()
+ listener?.onTelegramChatChanged(chat)
}
TdApi.UpdateChatPhoto.CONSTRUCTOR -> {
val updateChat = obj as TdApi.UpdateChatPhoto
@@ -575,7 +616,7 @@ class TelegramHelper private constructor() {
synchronized(chat!!) {
chat.photo = updateChat.photo
}
- listener?.onTelegramChatsChanged()
+ listener?.onTelegramChatChanged(chat)
}
TdApi.UpdateChatLastMessage.CONSTRUCTOR -> {
val updateChat = obj as TdApi.UpdateChatLastMessage
@@ -697,6 +738,18 @@ class TelegramHelper private constructor() {
}
}
+ TdApi.UpdateFile.CONSTRUCTOR -> {
+ val updateFile = obj as TdApi.UpdateFile
+ if (updateFile.file.local.isDownloadingCompleted) {
+ val remoteId = updateFile.file.remote.id
+ val chat = downloadChatFilesMap.remove(remoteId)
+ if (chat != null) {
+ chat.photo?.small = updateFile.file
+ listener?.onTelegramChatChanged(chat)
+ }
+ }
+ }
+
TdApi.UpdateUserFullInfo.CONSTRUCTOR -> {
val updateUserFullInfo = obj as TdApi.UpdateUserFullInfo
usersFullInfo[updateUserFullInfo.userId] = updateUserFullInfo.userFullInfo
diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt
index 6f2493a548..c7f1a6cfdd 100644
--- a/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt
+++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt
@@ -5,38 +5,60 @@ import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
+import android.support.annotation.AttrRes
+import android.support.annotation.ColorInt
import android.support.v4.app.ActivityCompat
+import android.support.v4.content.ContextCompat
+import android.util.TypedValue
+import android.util.TypedValue.COMPLEX_UNIT_DIP
import android.view.View
import android.view.inputmethod.InputMethodManager
object AndroidUtils {
- private fun isHardwareKeyboardAvailable(context: Context): Boolean {
- return context.resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS
- }
+ private fun isHardwareKeyboardAvailable(context: Context): Boolean {
+ return context.resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS
+ }
- fun softKeyboardDelayed(view: View) {
- view.post {
- if (!isHardwareKeyboardAvailable(view.context)) {
- val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
- imm?.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
- }
- }
- }
+ fun softKeyboardDelayed(view: View) {
+ view.post {
+ if (!isHardwareKeyboardAvailable(view.context)) {
+ val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
+ imm?.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
+ }
+ }
+ }
- fun hideSoftKeyboard(activity: Activity, input: View?) {
- val inputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
- if (inputMethodManager != null) {
- if (input != null) {
- val windowToken = input.windowToken
- if (windowToken != null) {
- inputMethodManager.hideSoftInputFromWindow(windowToken, 0)
- }
- }
- }
- }
+ fun hideSoftKeyboard(activity: Activity, input: View?) {
+ val inputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
+ if (inputMethodManager != null) {
+ if (input != null) {
+ val windowToken = input.windowToken
+ if (windowToken != null) {
+ inputMethodManager.hideSoftInputFromWindow(windowToken, 0)
+ }
+ }
+ }
+ }
- fun isLocationPermissionAvailable(context: Context): Boolean {
- return ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
- }
+ fun isLocationPermissionAvailable(context: Context): Boolean {
+ return ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
+ }
+
+ fun dpToPx(ctx: Context, dp: Float): Int {
+ val r = ctx.resources
+ return TypedValue.applyDimension(
+ COMPLEX_UNIT_DIP,
+ dp,
+ r.displayMetrics
+ ).toInt()
+ }
+
+ @ColorInt
+ fun getAttrColor(ctx: Context, @AttrRes attrId: Int, @ColorInt defaultColor: Int = 0): Int {
+ val ta = ctx.theme.obtainStyledAttributes(intArrayOf(attrId))
+ val color = ta.getColor(0, defaultColor)
+ ta.recycle()
+ return color
+ }
}
diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/CancellableAsyncTask.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/CancellableAsyncTask.kt
index 92fdaf5430..6f4ff4c822 100644
--- a/OsmAnd-telegram/src/net/osmand/telegram/utils/CancellableAsyncTask.kt
+++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/CancellableAsyncTask.kt
@@ -7,70 +7,70 @@ import java.util.concurrent.atomic.AtomicInteger
class CancellableAsyncTask(val taskId: String, val executeTimeout: Long = 0) {
- companion object {
- private const val SLEEP_TIME = 50L
- private val requestNumbersMap = ConcurrentHashMap()
- private val singleThreadExecutorsMap = ConcurrentHashMap()
+ companion object {
+ private const val SLEEP_TIME = 50L
+ private val requestNumbersMap = ConcurrentHashMap()
+ private val singleThreadExecutorsMap = ConcurrentHashMap()
- fun run(taskId: String, executeTimeout: Long = 0, action: (() -> Unit)) {
- CancellableAsyncTask(taskId, executeTimeout).run(action)
- }
+ fun run(taskId: String, executeTimeout: Long = 0, action: (() -> Unit)) {
+ CancellableAsyncTask(taskId, executeTimeout).run(action)
+ }
- fun clearResources(taskId: String) {
- requestNumbersMap.remove(taskId)
- singleThreadExecutorsMap.remove(taskId)
- }
- }
+ fun clearResources(taskId: String) {
+ requestNumbersMap.remove(taskId)
+ singleThreadExecutorsMap.remove(taskId)
+ }
+ }
- private val singleThreadExecutor: ExecutorService
- private var requestNumber: AtomicInteger
+ private val singleThreadExecutor: ExecutorService
+ private var requestNumber: AtomicInteger
- var isCancelled: Boolean = false
+ var isCancelled: Boolean = false
- init {
- val requestNumber = requestNumbersMap[taskId]
- if (requestNumber == null) {
- this.requestNumber = AtomicInteger()
- requestNumbersMap[taskId] = this.requestNumber
- } else {
- this.requestNumber = requestNumber
- }
+ init {
+ val requestNumber = requestNumbersMap[taskId]
+ if (requestNumber == null) {
+ this.requestNumber = AtomicInteger()
+ requestNumbersMap[taskId] = this.requestNumber
+ } else {
+ this.requestNumber = requestNumber
+ }
- val singleThreadExecutor = singleThreadExecutorsMap[taskId]
- if (singleThreadExecutor == null) {
- this.singleThreadExecutor = Executors.newSingleThreadExecutor()
- singleThreadExecutorsMap[taskId] = this.singleThreadExecutor
- } else {
- this.singleThreadExecutor = singleThreadExecutor
- }
- }
+ val singleThreadExecutor = singleThreadExecutorsMap[taskId]
+ if (singleThreadExecutor == null) {
+ this.singleThreadExecutor = Executors.newSingleThreadExecutor()
+ singleThreadExecutorsMap[taskId] = this.singleThreadExecutor
+ } else {
+ this.singleThreadExecutor = singleThreadExecutor
+ }
+ }
- fun run(action: (() -> Unit)) {
- val req = requestNumber.incrementAndGet()
+ fun run(action: (() -> Unit)) {
+ val req = requestNumber.incrementAndGet()
- singleThreadExecutor.submit(object : Runnable {
+ singleThreadExecutor.submit(object : Runnable {
- private val isCancelled: Boolean
- get() = requestNumber.get() != req || this@CancellableAsyncTask.isCancelled
+ private val isCancelled: Boolean
+ get() = requestNumber.get() != req || this@CancellableAsyncTask.isCancelled
- override fun run() {
- try {
- if (executeTimeout > 0) {
- val startTime = System.currentTimeMillis()
- while (System.currentTimeMillis() - startTime <= executeTimeout) {
- if (isCancelled) {
- return
- }
- Thread.sleep(SLEEP_TIME)
- }
- }
- if (!isCancelled) {
- action.invoke()
- }
- } catch (e: InterruptedException) {
- // ignore
- }
- }
- })
- }
+ override fun run() {
+ try {
+ if (executeTimeout > 0) {
+ val startTime = System.currentTimeMillis()
+ while (System.currentTimeMillis() - startTime <= executeTimeout) {
+ if (isCancelled) {
+ return
+ }
+ Thread.sleep(SLEEP_TIME)
+ }
+ }
+ if (!isCancelled) {
+ action.invoke()
+ }
+ } catch (e: InterruptedException) {
+ // ignore
+ }
+ }
+ })
+ }
}
diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.java b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.java
deleted file mode 100644
index b2d09a3183..0000000000
--- a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.java
+++ /dev/null
@@ -1,267 +0,0 @@
-package net.osmand.telegram.utils;
-
-import android.content.Context;
-
-import net.osmand.telegram.R;
-import net.osmand.telegram.TelegramApplication;
-
-import java.text.DecimalFormat;
-import java.text.MessageFormat;
-
-public class OsmandFormatter {
-
- public final static float METERS_IN_KILOMETER = 1000f;
- public final static float METERS_IN_ONE_MILE = 1609.344f; // 1609.344
- public final static float METERS_IN_ONE_NAUTICALMILE = 1852f; // 1852
-
- public final static float YARDS_IN_ONE_METER = 1.0936f;
- public final static float FEET_IN_ONE_METER = YARDS_IN_ONE_METER * 3f;
- private static final DecimalFormat fixed2 = new DecimalFormat("0.00");
- private static final DecimalFormat fixed1 = new DecimalFormat("0.0");
- {
- fixed2.setMinimumFractionDigits(2);
- fixed1.setMinimumFractionDigits(1);
- fixed1.setMinimumIntegerDigits(1);
- fixed2.setMinimumIntegerDigits(1);
- }
-
- public static String getFormattedDuration(int seconds, TelegramApplication ctx) {
- int hours = seconds / (60 * 60);
- int minutes = (seconds / 60) % 60;
- if (hours > 0) {
- return hours + " "
- + ctx.getString(R.string.shared_string_hour_short)
- + (minutes > 0 ? " " + minutes + " "
- + ctx.getString(R.string.shared_string_minute_short) : "");
- } else {
- return minutes + " " + ctx.getString(R.string.shared_string_minute_short);
- }
- }
-
- public static double calculateRoundedDist(double distInMeters, TelegramApplication ctx) {
- MetricsConstants mc = ctx.getSettings().getMetricsConstants();
- double mainUnitInMeter = 1;
- double metersInSecondUnit = METERS_IN_KILOMETER;
- if (mc == MetricsConstants.MILES_AND_FEET) {
- mainUnitInMeter = FEET_IN_ONE_METER;
- metersInSecondUnit = METERS_IN_ONE_MILE;
- } else if (mc == MetricsConstants.MILES_AND_METERS) {
- mainUnitInMeter = 1;
- metersInSecondUnit = METERS_IN_ONE_MILE;
- } else if (mc == MetricsConstants.NAUTICAL_MILES) {
- mainUnitInMeter = 1;
- metersInSecondUnit = METERS_IN_ONE_NAUTICALMILE;
- } else if (mc == MetricsConstants.MILES_AND_YARDS) {
- mainUnitInMeter = YARDS_IN_ONE_METER;
- metersInSecondUnit = METERS_IN_ONE_MILE;
- }
-
- // 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000 ...
- int generator = 1;
- byte pointer = 1;
- double point = mainUnitInMeter;
- double roundDist = 1;
- while (distInMeters * point > generator) {
- roundDist = (generator / point);
- if (pointer++ % 3 == 2) {
- generator = generator * 5 / 2;
- } else {
- generator *= 2;
- }
-
- if (point == mainUnitInMeter && metersInSecondUnit * mainUnitInMeter * 0.9f <= generator) {
- point = 1 / metersInSecondUnit;
- generator = 1;
- pointer = 1;
- }
- }
- //Miles exceptions: 2000ft->0.5mi, 1000ft->0.25mi, 1000yd->0.5mi, 500yd->0.25mi, 1000m ->0.5mi, 500m -> 0.25mi
- if (mc == MetricsConstants.MILES_AND_METERS && roundDist == 1000) {
- roundDist = 0.5f * METERS_IN_ONE_MILE;
- } else if (mc == MetricsConstants.MILES_AND_METERS && roundDist == 500) {
- roundDist = 0.25f * METERS_IN_ONE_MILE;
- } else if (mc == MetricsConstants.MILES_AND_FEET && roundDist == 2000 / (double) FEET_IN_ONE_METER) {
- roundDist = 0.5f * METERS_IN_ONE_MILE;
- } else if (mc == MetricsConstants.MILES_AND_FEET && roundDist == 1000 / (double) FEET_IN_ONE_METER) {
- roundDist = 0.25f * METERS_IN_ONE_MILE;
- } else if (mc == MetricsConstants.MILES_AND_YARDS && roundDist == 1000 / (double) YARDS_IN_ONE_METER) {
- roundDist = 0.5f * METERS_IN_ONE_MILE;
- } else if (mc == MetricsConstants.MILES_AND_YARDS && roundDist == 500 / (double) YARDS_IN_ONE_METER) {
- roundDist = 0.25f * METERS_IN_ONE_MILE;
- }
- return roundDist;
- }
-
- public static String getFormattedRoundDistanceKm(float meters, int digits, TelegramApplication ctx) {
- int mainUnitStr = R.string.km;
- float mainUnitInMeters = METERS_IN_KILOMETER;
- if (digits == 0) {
- return (int) (meters / mainUnitInMeters + 0.5) + " " + ctx.getString(mainUnitStr); //$NON-NLS-1$
- } else if (digits == 1) {
- return fixed1.format(((float) meters) / mainUnitInMeters) + " " + ctx.getString(mainUnitStr);
- } else {
- return fixed2.format(((float) meters) / mainUnitInMeters) + " " + ctx.getString(mainUnitStr);
- }
- }
-
- public static String getFormattedDistance(float meters, TelegramApplication ctx) {
- return getFormattedDistance(meters, ctx, true);
- }
-
- public static String getFormattedDistance(float meters, TelegramApplication ctx, boolean forceTrailingZeros) {
- String format1 = forceTrailingZeros ? "{0,number,0.0} " : "{0,number,0.#} ";
- String format2 = forceTrailingZeros ? "{0,number,0.00} " : "{0,number,0.##} ";
-
- MetricsConstants mc = ctx.getSettings().getMetricsConstants();
- int mainUnitStr;
- float mainUnitInMeters;
- if (mc == MetricsConstants.KILOMETERS_AND_METERS) {
- mainUnitStr = R.string.km;
- mainUnitInMeters = METERS_IN_KILOMETER;
- } else if (mc == MetricsConstants.NAUTICAL_MILES) {
- mainUnitStr = R.string.nm;
- mainUnitInMeters = METERS_IN_ONE_NAUTICALMILE;
- } else {
- mainUnitStr = R.string.mile;
- mainUnitInMeters = METERS_IN_ONE_MILE;
- }
-
- if (meters >= 100 * mainUnitInMeters) {
- return (int) (meters / mainUnitInMeters + 0.5) + " " + ctx.getString(mainUnitStr); //$NON-NLS-1$
- } else if (meters > 9.99f * mainUnitInMeters) {
- return MessageFormat.format(format1 + ctx.getString(mainUnitStr), ((float) meters) / mainUnitInMeters).replace('\n', ' '); //$NON-NLS-1$
- } else if (meters > 0.999f * mainUnitInMeters) {
- return MessageFormat.format(format2 + ctx.getString(mainUnitStr), ((float) meters) / mainUnitInMeters).replace('\n', ' '); //$NON-NLS-1$
- } else if (mc == MetricsConstants.MILES_AND_FEET && meters > 0.249f * mainUnitInMeters) {
- return MessageFormat.format(format2 + ctx.getString(mainUnitStr), ((float) meters) / mainUnitInMeters).replace('\n', ' '); //$NON-NLS-1$
- } else if (mc == MetricsConstants.MILES_AND_METERS && meters > 0.249f * mainUnitInMeters) {
- return MessageFormat.format(format2 + ctx.getString(mainUnitStr), ((float) meters) / mainUnitInMeters).replace('\n', ' '); //$NON-NLS-1$
- } else if (mc == MetricsConstants.MILES_AND_YARDS && meters > 0.249f * mainUnitInMeters) {
- return MessageFormat.format(format2 + ctx.getString(mainUnitStr), ((float) meters) / mainUnitInMeters).replace('\n', ' '); //$NON-NLS-1$
- } else if (mc == MetricsConstants.NAUTICAL_MILES && meters > 0.99f * mainUnitInMeters) {
- return MessageFormat.format(format2 + ctx.getString(mainUnitStr), ((float) meters) / mainUnitInMeters).replace('\n', ' '); //$NON-NLS-1$
- } else {
- if (mc == MetricsConstants.KILOMETERS_AND_METERS || mc == MetricsConstants.MILES_AND_METERS) {
- return ((int) (meters + 0.5)) + " " + ctx.getString(R.string.m); //$NON-NLS-1$
- } else if (mc == MetricsConstants.MILES_AND_FEET) {
- int feet = (int) (meters * FEET_IN_ONE_METER + 0.5);
- return feet + " " + ctx.getString(R.string.foot); //$NON-NLS-1$
- } else if (mc == MetricsConstants.MILES_AND_YARDS) {
- int yards = (int) (meters * YARDS_IN_ONE_METER + 0.5);
- return yards + " " + ctx.getString(R.string.yard); //$NON-NLS-1$
- }
- return ((int) (meters + 0.5)) + " " + ctx.getString(R.string.m); //$NON-NLS-1$
- }
- }
-
- public static String getFormattedAlt(double alt, TelegramApplication ctx) {
- MetricsConstants mc = ctx.getSettings().getMetricsConstants();
- if (mc == MetricsConstants.KILOMETERS_AND_METERS) {
- return ((int) (alt + 0.5)) + " " + ctx.getString(R.string.m);
- } else {
- return ((int) (alt * FEET_IN_ONE_METER + 0.5)) + " " + ctx.getString(R.string.foot);
- }
- }
-
- public static String getFormattedSpeed(float metersperseconds, TelegramApplication ctx) {
- SpeedConstants mc = ctx.getSettings().getSpeedConstants();
- float kmh = metersperseconds * 3.6f;
- if (mc == SpeedConstants.KILOMETERS_PER_HOUR) {
- // e.g. car case and for high-speeds: Display rounded to 1 km/h (5% precision at 20 km/h)
- if (kmh >= 20) {
- return ((int) Math.round(kmh)) + " " + mc.toShortString(ctx);
- }
- // for smaller values display 1 decimal digit x.y km/h, (0.5% precision at 20 km/h)
- int kmh10 = (int) Math.round(kmh * 10f);
- return (kmh10 / 10f) + " " + mc.toShortString(ctx);
- } else if (mc == SpeedConstants.MILES_PER_HOUR) {
- float mph = kmh * METERS_IN_KILOMETER / METERS_IN_ONE_MILE;
- if (mph >= 20) {
- return ((int) Math.round(mph)) + " " + mc.toShortString(ctx);
- } else {
- int mph10 = (int) Math.round(mph * 10f);
- return (mph10 / 10f) + " " + mc.toShortString(ctx);
- }
- } else if (mc == SpeedConstants.NAUTICALMILES_PER_HOUR) {
- float mph = kmh * METERS_IN_KILOMETER / METERS_IN_ONE_NAUTICALMILE;
- if (mph >= 20) {
- return ((int) Math.round(mph)) + " " + mc.toShortString(ctx);
- } else {
- int mph10 = (int) Math.round(mph * 10f);
- return (mph10 / 10f) + " " + mc.toShortString(ctx);
- }
- } else if (mc == SpeedConstants.MINUTES_PER_KILOMETER) {
- if (metersperseconds < 0.111111111) {
- return "-" + mc.toShortString(ctx);
- }
- float minperkm = METERS_IN_KILOMETER / (metersperseconds * 60);
- if (minperkm >= 10) {
- return ((int) Math.round(minperkm)) + " " + mc.toShortString(ctx);
- } else {
- int mph10 = (int) Math.round(minperkm * 10f);
- return (mph10 / 10f) + " " + mc.toShortString(ctx);
- }
- } else if (mc == SpeedConstants.MINUTES_PER_MILE) {
- if (metersperseconds < 0.111111111) {
- return "-" + mc.toShortString(ctx);
- }
- float minperm = (METERS_IN_ONE_MILE) / (metersperseconds * 60);
- if (minperm >= 10) {
- return ((int) Math.round(minperm)) + " " + mc.toShortString(ctx);
- } else {
- int mph10 = (int) Math.round(minperm * 10f);
- return (mph10 / 10f) + " " + mc.toShortString(ctx);
- }
- } else /*if (mc == SpeedConstants.METERS_PER_SECOND) */ {
- if (metersperseconds >= 10) {
- return ((int) Math.round(metersperseconds)) + " " + SpeedConstants.METERS_PER_SECOND.toShortString(ctx);
- }
- // for smaller values display 1 decimal digit x.y km/h, (0.5% precision at 20 km/h)
- int kmh10 = (int) Math.round(metersperseconds * 10f);
- return (kmh10 / 10f) + " " + SpeedConstants.METERS_PER_SECOND.toShortString(ctx);
- }
- }
-
- public enum MetricsConstants {
- KILOMETERS_AND_METERS(R.string.si_km_m),
- MILES_AND_FEET(R.string.si_mi_feet),
- MILES_AND_METERS(R.string.si_mi_meters),
- MILES_AND_YARDS(R.string.si_mi_yard),
- NAUTICAL_MILES(R.string.si_nm);
-
- private final int key;
-
- MetricsConstants(int key) {
- this.key = key;
- }
-
- public String toHumanString(Context ctx) {
- return ctx.getString(key);
- }
- }
-
- public enum SpeedConstants {
- KILOMETERS_PER_HOUR(R.string.km_h, R.string.si_kmh),
- MILES_PER_HOUR(R.string.mile_per_hour, R.string.si_mph),
- METERS_PER_SECOND(R.string.m_s, R.string.si_m_s),
- MINUTES_PER_MILE(R.string.min_mile, R.string.si_min_m),
- MINUTES_PER_KILOMETER(R.string.min_km, R.string.si_min_km),
- NAUTICALMILES_PER_HOUR(R.string.nm_h, R.string.si_nm_h);
-
- private final int key;
- private int descr;
-
- SpeedConstants(int key, int descr) {
- this.key = key;
- this.descr = descr;
- }
-
- public String toHumanString(Context ctx) {
- return ctx.getString(descr);
- }
-
- public String toShortString(Context ctx) {
- return ctx.getString(key);
- }
- }
-}
diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt
new file mode 100644
index 0000000000..744b7862d7
--- /dev/null
+++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt
@@ -0,0 +1,255 @@
+package net.osmand.telegram.utils
+
+import android.content.Context
+
+import net.osmand.telegram.R
+import net.osmand.telegram.TelegramApplication
+
+import java.text.DecimalFormat
+import java.text.MessageFormat
+
+object OsmandFormatter {
+
+ val METERS_IN_KILOMETER = 1000f
+ val METERS_IN_ONE_MILE = 1609.344f // 1609.344
+ val METERS_IN_ONE_NAUTICALMILE = 1852f // 1852
+
+ val YARDS_IN_ONE_METER = 1.0936f
+ val FEET_IN_ONE_METER = YARDS_IN_ONE_METER * 3f
+ private val fixed2 = DecimalFormat("0.00")
+ private val fixed1 = DecimalFormat("0.0")
+
+ init {
+ fixed2.minimumFractionDigits = 2
+ fixed1.minimumFractionDigits = 1
+ fixed1.minimumIntegerDigits = 1
+ fixed2.minimumIntegerDigits = 1
+ }
+
+ fun getFormattedDuration(seconds: Int, ctx: TelegramApplication): String {
+ val hours = seconds / (60 * 60)
+ val minutes = seconds / 60 % 60
+ return if (hours > 0) {
+ (hours.toString() + " "
+ + ctx.getString(R.string.shared_string_hour_short)
+ + if (minutes > 0)
+ " " + minutes + " "
+ + ctx.getString(R.string.shared_string_minute_short)
+ else
+ "")
+ } else {
+ minutes.toString() + " " + ctx.getString(R.string.shared_string_minute_short)
+ }
+ }
+
+ fun calculateRoundedDist(distInMeters: Double, ctx: TelegramApplication): Double {
+ val mc = ctx.settings.metricsConstants
+ var mainUnitInMeter = 1.0
+ var metersInSecondUnit = METERS_IN_KILOMETER.toDouble()
+ if (mc == MetricsConstants.MILES_AND_FEET) {
+ mainUnitInMeter = FEET_IN_ONE_METER.toDouble()
+ metersInSecondUnit = METERS_IN_ONE_MILE.toDouble()
+ } else if (mc == MetricsConstants.MILES_AND_METERS) {
+ mainUnitInMeter = 1.0
+ metersInSecondUnit = METERS_IN_ONE_MILE.toDouble()
+ } else if (mc == MetricsConstants.NAUTICAL_MILES) {
+ mainUnitInMeter = 1.0
+ metersInSecondUnit = METERS_IN_ONE_NAUTICALMILE.toDouble()
+ } else if (mc == MetricsConstants.MILES_AND_YARDS) {
+ mainUnitInMeter = YARDS_IN_ONE_METER.toDouble()
+ metersInSecondUnit = METERS_IN_ONE_MILE.toDouble()
+ }
+
+ // 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000 ...
+ var generator = 1
+ var pointer: Byte = 1
+ var point = mainUnitInMeter
+ var roundDist = 1.0
+ while (distInMeters * point > generator) {
+ roundDist = generator / point
+ if (pointer++ % 3 == 2) {
+ generator = generator * 5 / 2
+ } else {
+ generator *= 2
+ }
+
+ if (point == mainUnitInMeter && metersInSecondUnit * mainUnitInMeter * 0.9 <= generator) {
+ point = 1 / metersInSecondUnit
+ generator = 1
+ pointer = 1
+ }
+ }
+ //Miles exceptions: 2000ft->0.5mi, 1000ft->0.25mi, 1000yd->0.5mi, 500yd->0.25mi, 1000m ->0.5mi, 500m -> 0.25mi
+ if (mc == MetricsConstants.MILES_AND_METERS && roundDist == 1000.0) {
+ roundDist = (0.5f * METERS_IN_ONE_MILE).toDouble()
+ } else if (mc == MetricsConstants.MILES_AND_METERS && roundDist == 500.0) {
+ roundDist = (0.25f * METERS_IN_ONE_MILE).toDouble()
+ } else if (mc == MetricsConstants.MILES_AND_FEET && roundDist == 2000 / FEET_IN_ONE_METER.toDouble()) {
+ roundDist = (0.5f * METERS_IN_ONE_MILE).toDouble()
+ } else if (mc == MetricsConstants.MILES_AND_FEET && roundDist == 1000 / FEET_IN_ONE_METER.toDouble()) {
+ roundDist = (0.25f * METERS_IN_ONE_MILE).toDouble()
+ } else if (mc == MetricsConstants.MILES_AND_YARDS && roundDist == 1000 / YARDS_IN_ONE_METER.toDouble()) {
+ roundDist = (0.5f * METERS_IN_ONE_MILE).toDouble()
+ } else if (mc == MetricsConstants.MILES_AND_YARDS && roundDist == 500 / YARDS_IN_ONE_METER.toDouble()) {
+ roundDist = (0.25f * METERS_IN_ONE_MILE).toDouble()
+ }
+ return roundDist
+ }
+
+ fun getFormattedRoundDistanceKm(meters: Float, digits: Int, ctx: TelegramApplication): String {
+ val mainUnitStr = R.string.km
+ val mainUnitInMeters = METERS_IN_KILOMETER
+ return if (digits == 0) {
+ (meters / mainUnitInMeters + 0.5).toInt().toString() + " " + ctx.getString(mainUnitStr) //$NON-NLS-1$
+ } else if (digits == 1) {
+ fixed1.format((meters / mainUnitInMeters).toDouble()) + " " + ctx.getString(mainUnitStr)
+ } else {
+ fixed2.format((meters / mainUnitInMeters).toDouble()) + " " + ctx.getString(mainUnitStr)
+ }
+ }
+
+ @JvmOverloads
+ fun getFormattedDistance(meters: Float, ctx: TelegramApplication, forceTrailingZeros: Boolean = true): String {
+ val format1 = if (forceTrailingZeros) "{0,number,0.0} " else "{0,number,0.#} "
+ val format2 = if (forceTrailingZeros) "{0,number,0.00} " else "{0,number,0.##} "
+
+ val mc = ctx.settings.metricsConstants
+ val mainUnitStr: Int
+ val mainUnitInMeters: Float
+ if (mc == MetricsConstants.KILOMETERS_AND_METERS) {
+ mainUnitStr = R.string.km
+ mainUnitInMeters = METERS_IN_KILOMETER
+ } else if (mc == MetricsConstants.NAUTICAL_MILES) {
+ mainUnitStr = R.string.nm
+ mainUnitInMeters = METERS_IN_ONE_NAUTICALMILE
+ } else {
+ mainUnitStr = R.string.mile
+ mainUnitInMeters = METERS_IN_ONE_MILE
+ }
+
+ if (meters >= 100 * mainUnitInMeters) {
+ return (meters / mainUnitInMeters + 0.5).toInt().toString() + " " + ctx.getString(mainUnitStr) //$NON-NLS-1$
+ } else if (meters > 9.99f * mainUnitInMeters) {
+ return MessageFormat.format(format1 + ctx.getString(mainUnitStr), meters / mainUnitInMeters).replace('\n', ' ') //$NON-NLS-1$
+ } else if (meters > 0.999f * mainUnitInMeters) {
+ return MessageFormat.format(format2 + ctx.getString(mainUnitStr), meters / mainUnitInMeters).replace('\n', ' ') //$NON-NLS-1$
+ } else if (mc == MetricsConstants.MILES_AND_FEET && meters > 0.249f * mainUnitInMeters) {
+ return MessageFormat.format(format2 + ctx.getString(mainUnitStr), meters / mainUnitInMeters).replace('\n', ' ') //$NON-NLS-1$
+ } else if (mc == MetricsConstants.MILES_AND_METERS && meters > 0.249f * mainUnitInMeters) {
+ return MessageFormat.format(format2 + ctx.getString(mainUnitStr), meters / mainUnitInMeters).replace('\n', ' ') //$NON-NLS-1$
+ } else if (mc == MetricsConstants.MILES_AND_YARDS && meters > 0.249f * mainUnitInMeters) {
+ return MessageFormat.format(format2 + ctx.getString(mainUnitStr), meters / mainUnitInMeters).replace('\n', ' ') //$NON-NLS-1$
+ } else if (mc == MetricsConstants.NAUTICAL_MILES && meters > 0.99f * mainUnitInMeters) {
+ return MessageFormat.format(format2 + ctx.getString(mainUnitStr), meters / mainUnitInMeters).replace('\n', ' ') //$NON-NLS-1$
+ } else {
+ if (mc == MetricsConstants.KILOMETERS_AND_METERS || mc == MetricsConstants.MILES_AND_METERS) {
+ return (meters + 0.5).toInt().toString() + " " + ctx.getString(R.string.m) //$NON-NLS-1$
+ } else if (mc == MetricsConstants.MILES_AND_FEET) {
+ val feet = (meters * FEET_IN_ONE_METER + 0.5).toInt()
+ return feet.toString() + " " + ctx.getString(R.string.foot) //$NON-NLS-1$
+ } else if (mc == MetricsConstants.MILES_AND_YARDS) {
+ val yards = (meters * YARDS_IN_ONE_METER + 0.5).toInt()
+ return yards.toString() + " " + ctx.getString(R.string.yard) //$NON-NLS-1$
+ }
+ return (meters + 0.5).toInt().toString() + " " + ctx.getString(R.string.m) //$NON-NLS-1$
+ }
+ }
+
+ fun getFormattedAlt(alt: Double, ctx: TelegramApplication): String {
+ val mc = ctx.settings.metricsConstants
+ return if (mc == MetricsConstants.KILOMETERS_AND_METERS) {
+ (alt + 0.5).toInt().toString() + " " + ctx.getString(R.string.m)
+ } else {
+ (alt * FEET_IN_ONE_METER + 0.5).toInt().toString() + " " + ctx.getString(R.string.foot)
+ }
+ }
+
+ fun getFormattedSpeed(metersperseconds: Float, ctx: TelegramApplication): String {
+ val mc = ctx.settings.speedConstants
+ val kmh = metersperseconds * 3.6f
+ if (mc == SpeedConstants.KILOMETERS_PER_HOUR) {
+ // e.g. car case and for high-speeds: Display rounded to 1 km/h (5% precision at 20 km/h)
+ if (kmh >= 20) {
+ return Math.round(kmh).toString() + " " + mc.toShortString(ctx)
+ }
+ // for smaller values display 1 decimal digit x.y km/h, (0.5% precision at 20 km/h)
+ val kmh10 = Math.round(kmh * 10f)
+ return (kmh10 / 10f).toString() + " " + mc.toShortString(ctx)
+ } else if (mc == SpeedConstants.MILES_PER_HOUR) {
+ val mph = kmh * METERS_IN_KILOMETER / METERS_IN_ONE_MILE
+ if (mph >= 20) {
+ return Math.round(mph).toString() + " " + mc.toShortString(ctx)
+ } else {
+ val mph10 = Math.round(mph * 10f)
+ return (mph10 / 10f).toString() + " " + mc.toShortString(ctx)
+ }
+ } else if (mc == SpeedConstants.NAUTICALMILES_PER_HOUR) {
+ val mph = kmh * METERS_IN_KILOMETER / METERS_IN_ONE_NAUTICALMILE
+ if (mph >= 20) {
+ return Math.round(mph).toString() + " " + mc.toShortString(ctx)
+ } else {
+ val mph10 = Math.round(mph * 10f)
+ return (mph10 / 10f).toString() + " " + mc.toShortString(ctx)
+ }
+ } else if (mc == SpeedConstants.MINUTES_PER_KILOMETER) {
+ if (metersperseconds < 0.111111111) {
+ return "-" + mc.toShortString(ctx)
+ }
+ val minperkm = METERS_IN_KILOMETER / (metersperseconds * 60)
+ if (minperkm >= 10) {
+ return Math.round(minperkm).toString() + " " + mc.toShortString(ctx)
+ } else {
+ val mph10 = Math.round(minperkm * 10f)
+ return (mph10 / 10f).toString() + " " + mc.toShortString(ctx)
+ }
+ } else if (mc == SpeedConstants.MINUTES_PER_MILE) {
+ if (metersperseconds < 0.111111111) {
+ return "-" + mc.toShortString(ctx)
+ }
+ val minperm = METERS_IN_ONE_MILE / (metersperseconds * 60)
+ if (minperm >= 10) {
+ return Math.round(minperm).toString() + " " + mc.toShortString(ctx)
+ } else {
+ val mph10 = Math.round(minperm * 10f)
+ return (mph10 / 10f).toString() + " " + mc.toShortString(ctx)
+ }
+ } else
+ /*if (mc == SpeedConstants.METERS_PER_SECOND) */ {
+ if (metersperseconds >= 10) {
+ return Math.round(metersperseconds).toString() + " " + SpeedConstants.METERS_PER_SECOND.toShortString(ctx)
+ }
+ // for smaller values display 1 decimal digit x.y km/h, (0.5% precision at 20 km/h)
+ val kmh10 = Math.round(metersperseconds * 10f)
+ return (kmh10 / 10f).toString() + " " + SpeedConstants.METERS_PER_SECOND.toShortString(ctx)
+ }
+ }
+
+ enum class MetricsConstants private constructor(private val key: Int) {
+ KILOMETERS_AND_METERS(R.string.si_km_m),
+ MILES_AND_FEET(R.string.si_mi_feet),
+ MILES_AND_METERS(R.string.si_mi_meters),
+ MILES_AND_YARDS(R.string.si_mi_yard),
+ NAUTICAL_MILES(R.string.si_nm);
+
+ fun toHumanString(ctx: Context): String {
+ return ctx.getString(key)
+ }
+ }
+
+ enum class SpeedConstants private constructor(private val key: Int, private val descr: Int) {
+ KILOMETERS_PER_HOUR(R.string.km_h, R.string.si_kmh),
+ MILES_PER_HOUR(R.string.mile_per_hour, R.string.si_mph),
+ METERS_PER_SECOND(R.string.m_s, R.string.si_m_s),
+ MINUTES_PER_MILE(R.string.min_mile, R.string.si_min_m),
+ MINUTES_PER_KILOMETER(R.string.min_km, R.string.si_min_km),
+ NAUTICALMILES_PER_HOUR(R.string.nm_h, R.string.si_nm_h);
+
+ fun toHumanString(ctx: Context): String {
+ return ctx.getString(descr)
+ }
+
+ fun toShortString(ctx: Context): String {
+ return ctx.getString(key)
+ }
+ }
+}
diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/UiUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/UiUtils.kt
new file mode 100644
index 0000000000..f131c0ff21
--- /dev/null
+++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/UiUtils.kt
@@ -0,0 +1,125 @@
+package net.osmand.telegram.utils
+
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.support.annotation.ColorInt
+import android.support.annotation.ColorRes
+import android.support.annotation.DrawableRes
+import android.support.v4.content.ContextCompat
+import android.support.v4.graphics.drawable.DrawableCompat
+import net.osmand.telegram.R
+import net.osmand.telegram.TelegramApplication
+import java.util.*
+
+class UiUtils(private val app: TelegramApplication) {
+
+ private val drawableCache = LinkedHashMap()
+ private val circleBitmapCache = LinkedHashMap()
+
+ private val isLightContent: Boolean
+ get() = true
+
+ fun getCircleBitmap(path: String): Bitmap? {
+ var bmp: Bitmap? = circleBitmapCache[path]
+ if (bmp == null) {
+ bmp = BitmapFactory.decodeFile(path)
+ if (bmp != null) {
+ bmp = app.uiUtils.createCircleBitmap(bmp, true)
+ circleBitmapCache[path] = bmp
+ }
+ }
+ return bmp
+ }
+
+ private fun getDrawable(@DrawableRes resId: Int, @ColorRes clrId: Int): Drawable? {
+ val hash = (resId.toLong() shl 31) + clrId
+ var d: Drawable? = drawableCache[hash]
+ if (d == null) {
+ d = ContextCompat.getDrawable(app, resId)
+ if (d != null) {
+ d = DrawableCompat.wrap(d)
+ d!!.mutate()
+ if (clrId != 0) {
+ DrawableCompat.setTint(d, ContextCompat.getColor(app, clrId))
+ }
+ drawableCache[hash] = d
+ }
+ }
+ return d
+ }
+
+ private fun getPaintedDrawable(@DrawableRes resId: Int, @ColorInt color: Int): Drawable? {
+ val hash = (resId.toLong() shl 31) + color
+ var d: Drawable? = drawableCache[hash]
+ if (d == null) {
+ d = ContextCompat.getDrawable(app, resId)
+ if (d != null) {
+ d = DrawableCompat.wrap(d)
+ d!!.mutate()
+ DrawableCompat.setTint(d, color)
+ drawableCache[hash] = d
+ }
+ }
+ return d
+ }
+
+ fun getPaintedIcon(@DrawableRes id: Int, @ColorInt color: Int): Drawable? {
+ return getPaintedDrawable(id, color)
+ }
+
+ fun getIcon(@DrawableRes id: Int, @ColorRes colorId: Int): Drawable? {
+ return getDrawable(id, colorId)
+ }
+
+ fun getIcon(@DrawableRes backgroundId: Int, @DrawableRes id: Int, @ColorRes colorId: Int): Drawable {
+ val b = getDrawable(backgroundId, 0)
+ val f = getDrawable(id, colorId)
+ val layers = arrayOfNulls(2)
+ layers[0] = b
+ layers[1] = f
+ return LayerDrawable(layers)
+ }
+
+ fun getThemedIcon(@DrawableRes id: Int): Drawable? {
+ return getDrawable(id, if (isLightContent) R.color.icon_color_light else 0)
+ }
+
+ fun getIcon(@DrawableRes id: Int): Drawable? {
+ return getDrawable(id, 0)
+ }
+
+ fun getIcon(@DrawableRes id: Int, light: Boolean): Drawable? {
+ return getDrawable(id, if (light) R.color.icon_color_light else 0)
+ }
+
+ fun createCircleBitmap(source: Bitmap, recycleSource: Boolean = false): Bitmap {
+ val size = Math.min(source.width, source.height)
+
+ val width = (source.width - size) / 2
+ val height = (source.height - size) / 2
+
+ val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+
+ val canvas = Canvas(bitmap)
+ val paint = Paint()
+ val shader = BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+ if (width != 0 || height != 0) {
+ // source isn't square, move viewport to center
+ val matrix = Matrix()
+ matrix.setTranslate((-width).toFloat(), (-height).toFloat())
+ shader.setLocalMatrix(matrix)
+ }
+ paint.shader = shader
+ paint.isAntiAlias = true
+
+ val r = size / 2f
+ canvas.drawCircle(r, r, r, paint)
+
+ if (recycleSource) {
+ source.recycle()
+ }
+
+ return bitmap
+ }
+}