package net.osmand.telegram.utils import android.content.Context import android.graphics.* import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.hardware.Sensor import android.hardware.SensorManager 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 android.view.Surface import android.view.WindowManager import android.widget.ImageView import android.widget.TextView import net.osmand.Location import net.osmand.PlatformUtil import net.osmand.data.LatLon import net.osmand.telegram.R import net.osmand.telegram.TelegramApplication import net.osmand.telegram.ui.views.DirectionDrawable import org.drinkless.td.libcore.telegram.TdApi import java.io.File import java.io.FileOutputStream import java.io.IOException import java.util.* const val PROFILE_GREY_PHOTOS_DIR = "profile_grey_photos/" class UiUtils(private val app: TelegramApplication) { private val log = PlatformUtil.getLog(UiUtils::class.java) 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_light else 0) } fun getActiveIcon(@DrawableRes id: Int): Drawable? { return getDrawable(id, if (isLightContent) R.color.ctrl_active_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_light else 0) } fun checkUserGreyscaleImage(user: TdApi.User) { val path = user.profilePhoto?.small?.local?.path if (path != null && app.telegramHelper.getUserGreyPhotoPath(user) == null) { app.uiUtils.convertToGrayscaleAndSave(path, "${app.filesDir.absolutePath}/$PROFILE_GREY_PHOTOS_DIR${user.id}.png") } } fun convertToGrayscaleAndSave(coloredImagePath: String, newFilePath: String) { val currentImage = BitmapFactory.decodeFile(coloredImagePath) val greyedImage = toGrayscale(currentImage) saveBitmap(greyedImage, newFilePath) } private fun toGrayscale(bmpOriginal: Bitmap): Bitmap { val bmpGrayscale = Bitmap.createBitmap(bmpOriginal.width, bmpOriginal.height, Bitmap.Config.ARGB_8888) val c = Canvas(bmpGrayscale) val paint = Paint() val cm = ColorMatrix() cm.setSaturation(0f) val f = ColorMatrixColorFilter(cm) paint.colorFilter = f c.drawBitmap(bmpOriginal, 0f, 0f, paint) return bmpGrayscale } private fun saveBitmap(bitmap: Bitmap, newFilePath: String) { var fout: FileOutputStream? = null try { val file = File(newFilePath) if (file.parentFile != null) { file.parentFile.mkdirs() } fout = FileOutputStream(file) bitmap.compress(Bitmap.CompressFormat.PNG, 100, fout) } catch (e: Exception) { log.error(e) } finally { try { fout?.close() } catch (e: IOException) { log.error(e) } } } private 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 } fun updateLocationView( arrow: ImageView?, text: TextView?, toLoc: LatLon?, cache: UpdateLocationViewCache ) { val fromLoc = app.locationProvider.lastKnownLocationLatLon val heading = app.locationProvider.heading val mes = FloatArray(2) val locPassive = fromLoc == null || toLoc == null || cache.outdatedLocation val colorId = if (locPassive) R.color.icon_light else R.color.ctrl_active_light if (fromLoc != null && toLoc != null) { Location.distanceBetween( toLoc.latitude, toLoc.longitude, fromLoc.latitude, fromLoc.longitude, mes ) } if (arrow != null) { var newImage = false val drawable = arrow.drawable val dd = if (drawable is DirectionDrawable) { drawable } else { newImage = true DirectionDrawable(app) } dd.setImage(R.drawable.ic_direction_arrow, colorId) if (fromLoc == null || toLoc == null || heading == null) { dd.setAngle(0f) } else { dd.setAngle(mes[1] - heading + 180 + cache.screenOrientation) } if (newImage) { arrow.setImageDrawable(dd) } arrow.invalidate() } if (text != null) { text.setTextColor(ContextCompat.getColor(app, colorId)) val meters = if (fromLoc == null || toLoc == null) 0f else mes[0] text.text = OsmandFormatter.getFormattedDistance(meters, app) } } fun getUpdateLocationViewCache() = UpdateLocationViewCache().apply { screenOrientation = getScreenOrientation() } private fun getScreenOrientation(): Int { // screenOrientation correction must not be applied for devices without compass val sensorManager = app.getSystemService(Context.SENSOR_SERVICE) as SensorManager? if (sensorManager?.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) == null) { return 0 } val windowManager = app.getSystemService(Context.WINDOW_SERVICE) as WindowManager? val rotation = windowManager?.defaultDisplay?.rotation ?: return 0 return when (rotation) { Surface.ROTATION_90 -> 90 Surface.ROTATION_180 -> 180 Surface.ROTATION_270 -> 270 else -> 0 } } class UpdateLocationViewCache { var screenOrientation: Int = 0 var outdatedLocation: Boolean = false } }