Telegram - added chat photos
This commit is contained in:
parent
668d3e9835
commit
ae2aa88578
9 changed files with 579 additions and 357 deletions
|
@ -19,11 +19,10 @@
|
|||
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_group"
|
||||
android:tint="?attr/icon_color"
|
||||
android:visibility="visible" />
|
||||
|
||||
<android.support.v7.widget.AppCompatTextView
|
||||
|
|
|
@ -4,6 +4,8 @@ import android.Manifest
|
|||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.ActivityCompat
|
||||
|
@ -164,6 +166,12 @@ class MainActivity : AppCompatActivity(), TelegramListener {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onTelegramChatChanged(chat: TdApi.Chat) {
|
||||
runOnUi {
|
||||
updateChat(chat)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTelegramError(code: Int, message: String) {
|
||||
runOnUi {
|
||||
Toast.makeText(this@MainActivity, "$code - $message", Toast.LENGTH_LONG).show()
|
||||
|
@ -192,6 +200,15 @@ class MainActivity : AppCompatActivity(), TelegramListener {
|
|||
chatViewAdapter.chats = chats
|
||||
}
|
||||
|
||||
private fun updateChat(chat: TdApi.Chat) {
|
||||
val chatIndex = telegramHelper.getChatIndex(chat.id)
|
||||
if (chatIndex != -1) {
|
||||
chatViewAdapter.notifyItemChanged(chatIndex)
|
||||
} else {
|
||||
updateChatsList()
|
||||
}
|
||||
}
|
||||
|
||||
fun logoutTelegram(silent: Boolean = false) {
|
||||
if (telegramHelper.getTelegramAuthorizationState() == TelegramAuthorizationState.READY) {
|
||||
telegramHelper.logout()
|
||||
|
@ -358,9 +375,24 @@ class MainActivity : AppCompatActivity(), TelegramListener {
|
|||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val chatTitle = chats[position].title
|
||||
val chat = chats[position]
|
||||
val chatTitle = chat.title
|
||||
holder.groupName?.text = chatTitle
|
||||
|
||||
var drawable: Drawable? = null
|
||||
var bitmap: Bitmap? = null
|
||||
val chatPhoto = chat.photo?.small
|
||||
if (chatPhoto != null && chatPhoto.local.path.isNotEmpty()) {
|
||||
bitmap = app.uiUtils.getCircleBitmap(chatPhoto.local.path)
|
||||
}
|
||||
if (bitmap == null) {
|
||||
drawable = app.uiUtils.getThemedIcon(R.drawable.ic_group)
|
||||
}
|
||||
if (bitmap != null) {
|
||||
holder.icon?.setImageBitmap(bitmap)
|
||||
} else {
|
||||
holder.icon?.setImageDrawable(drawable)
|
||||
}
|
||||
holder.shareLocationSwitch?.setOnCheckedChangeListener(null)
|
||||
holder.shareLocationSwitch?.isChecked = settings.isSharingLocationToChat(chatTitle)
|
||||
holder.shareLocationSwitch?.setOnCheckedChangeListener { view, isChecked ->
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -41,6 +41,8 @@ class TelegramHelper private constructor() {
|
|||
private val chatList = TreeSet<OrderedChat>()
|
||||
private val chatLiveMessages = ConcurrentHashMap<Long, Long>()
|
||||
|
||||
private val downloadChatFilesMap = ConcurrentHashMap<String, TdApi.Chat>()
|
||||
|
||||
private val usersLiveMessages = ConcurrentHashMap<Long, TdApi.Message>()
|
||||
|
||||
private val usersFullInfo = ConcurrentHashMap<Int, TdApi.UserFullInfo>()
|
||||
|
@ -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<String> {
|
||||
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, AtomicInteger>()
|
||||
private val singleThreadExecutorsMap = ConcurrentHashMap<String, ExecutorService>()
|
||||
companion object {
|
||||
private const val SLEEP_TIME = 50L
|
||||
private val requestNumbersMap = ConcurrentHashMap<String, AtomicInteger>()
|
||||
private val singleThreadExecutorsMap = ConcurrentHashMap<String, ExecutorService>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
255
OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt
Normal file
255
OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
125
OsmAnd-telegram/src/net/osmand/telegram/utils/UiUtils.kt
Normal file
125
OsmAnd-telegram/src/net/osmand/telegram/utils/UiUtils.kt
Normal file
|
@ -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<Long, Drawable>()
|
||||
private val circleBitmapCache = LinkedHashMap<String, Bitmap>()
|
||||
|
||||
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<Drawable>(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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue