From 656faca47191578c97e4e3fc7124ff81f70ccd29 Mon Sep 17 00:00:00 2001 From: crimean Date: Sat, 9 Jun 2018 12:20:21 +0300 Subject: [PATCH] Telegram - introduced live location sharing with chats --- OsmAnd-telegram/build.gradle | 2 + OsmAnd-telegram/src/main/AndroidManifest.xml | 12 + .../{telegram/utils => }/PlatformUtil.java | 2 +- .../net/osmand/telegram/LocationService.kt | 143 ++++++++++ .../osmand/telegram/LoginDialogFragment.kt | 2 +- .../java/net/osmand/telegram/MainActivity.kt | 76 ++++- .../osmand/telegram/TelegramApplication.kt | 52 +++- .../net/osmand/telegram/TelegramSettings.kt | 108 +++++++ .../telegram/helpers/ShareLocationHelper.kt | 89 ++++++ .../telegram/{ => helpers}/TelegramHelper.kt | 88 +++++- .../NotificationDismissReceiver.kt | 39 +++ .../notifications/NotificationHelper.kt | 140 +++++++++ .../ShareLocationNotification.kt | 152 ++++++++++ .../notifications/TelegramNotification.kt | 134 +++++++++ .../net/osmand/telegram/utils/AndroidUtils.kt | 7 + .../telegram/utils/OsmandFormatter.java | 267 ++++++++++++++++++ .../drawable-hdpi/ic_action_polygom_dark.png | Bin 0 -> 1437 bytes .../res/drawable-hdpi/ic_action_rec_start.png | Bin 0 -> 1282 bytes .../res/drawable-hdpi/ic_action_rec_stop.png | Bin 0 -> 1076 bytes .../src/main/res/drawable-hdpi/ic_pause.png | Bin 0 -> 188 bytes .../drawable-mdpi/ic_action_polygom_dark.png | Bin 0 -> 1275 bytes .../res/drawable-mdpi/ic_action_rec_start.png | Bin 0 -> 1168 bytes .../res/drawable-mdpi/ic_action_rec_stop.png | Bin 0 -> 1060 bytes .../src/main/res/drawable-mdpi/ic_pause.png | Bin 0 -> 174 bytes .../drawable-xhdpi/ic_action_polygom_dark.png | Bin 0 -> 1568 bytes .../drawable-xhdpi/ic_action_rec_start.png | Bin 0 -> 1362 bytes .../res/drawable-xhdpi/ic_action_rec_stop.png | Bin 0 -> 1080 bytes .../src/main/res/drawable-xhdpi/ic_pause.png | Bin 0 -> 193 bytes .../ic_action_polygom_dark.png | Bin 0 -> 1911 bytes .../drawable-xxhdpi/ic_action_rec_start.png | Bin 0 -> 1594 bytes .../drawable-xxhdpi/ic_action_rec_stop.png | Bin 0 -> 1141 bytes .../src/main/res/drawable-xxhdpi/ic_pause.png | Bin 0 -> 215 bytes .../src/main/res/values/colors.xml | 2 + .../src/main/res/values/strings.xml | 44 +++ 34 files changed, 1338 insertions(+), 21 deletions(-) rename OsmAnd-telegram/src/main/java/net/osmand/{telegram/utils => }/PlatformUtil.java (99%) create mode 100644 OsmAnd-telegram/src/main/java/net/osmand/telegram/LocationService.kt create mode 100644 OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramSettings.kt create mode 100644 OsmAnd-telegram/src/main/java/net/osmand/telegram/helpers/ShareLocationHelper.kt rename OsmAnd-telegram/src/main/java/net/osmand/telegram/{ => helpers}/TelegramHelper.kt (82%) create mode 100644 OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/NotificationDismissReceiver.kt create mode 100644 OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/NotificationHelper.kt create mode 100644 OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/ShareLocationNotification.kt create mode 100644 OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/TelegramNotification.kt create mode 100644 OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/OsmandFormatter.java create mode 100644 OsmAnd-telegram/src/main/res/drawable-hdpi/ic_action_polygom_dark.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-hdpi/ic_action_rec_start.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-hdpi/ic_action_rec_stop.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-hdpi/ic_pause.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-mdpi/ic_action_polygom_dark.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-mdpi/ic_action_rec_start.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-mdpi/ic_action_rec_stop.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-mdpi/ic_pause.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_polygom_dark.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_rec_start.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_rec_stop.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_pause.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_polygom_dark.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_rec_start.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_rec_stop.png create mode 100644 OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_pause.png diff --git a/OsmAnd-telegram/build.gradle b/OsmAnd-telegram/build.gradle index a8420b3f70..9ae14d619b 100644 --- a/OsmAnd-telegram/build.gradle +++ b/OsmAnd-telegram/build.gradle @@ -113,6 +113,7 @@ afterEvaluate { } dependencies { + implementation project(path: ':OsmAnd-java', configuration: 'android') implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -121,6 +122,7 @@ dependencies { implementation 'com.android.support:support-annotations:27.1.1' implementation 'commons-logging:commons-logging-api:1.1' implementation 'com.android.support:recyclerview-v7:27.1.1' + implementation 'com.vividsolutions:jts-core:1.14.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' diff --git a/OsmAnd-telegram/src/main/AndroidManifest.xml b/OsmAnd-telegram/src/main/AndroidManifest.xml index 586867506b..d99383f3e6 100644 --- a/OsmAnd-telegram/src/main/AndroidManifest.xml +++ b/OsmAnd-telegram/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + @@ -19,6 +20,7 @@ @@ -30,6 +32,16 @@ + + + + + + + \ No newline at end of file diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/PlatformUtil.java b/OsmAnd-telegram/src/main/java/net/osmand/PlatformUtil.java similarity index 99% rename from OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/PlatformUtil.java rename to OsmAnd-telegram/src/main/java/net/osmand/PlatformUtil.java index d783e2ab28..06759253ff 100644 --- a/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/PlatformUtil.java +++ b/OsmAnd-telegram/src/main/java/net/osmand/PlatformUtil.java @@ -1,4 +1,4 @@ -package net.osmand.telegram.utils; +package net.osmand; import org.apache.commons.logging.Log; diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/LocationService.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/LocationService.kt new file mode 100644 index 0000000000..0a6734763b --- /dev/null +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/LocationService.kt @@ -0,0 +1,143 @@ +package net.osmand.telegram + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Binder +import android.os.Bundle +import android.os.Handler +import android.os.IBinder +import android.util.Log +import android.widget.Toast +import net.osmand.telegram.notifications.TelegramNotification +import net.osmand.PlatformUtil + +class LocationService : Service(), LocationListener { + + private val binder = LocationServiceBinder() + + var handler: Handler? = null + + class LocationServiceBinder : Binder() + + override fun onBind(intent: Intent): IBinder? { + return binder + } + + fun stopIfNeeded(ctx: Context) { + val serviceIntent = Intent(ctx, LocationService::class.java) + ctx.stopService(serviceIntent) + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + handler = Handler() + val app = app() + + app.locationService = this + + // requesting + // request location updates + val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager + try { + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, this@LocationService) + } catch (e: SecurityException) { + Toast.makeText(this, R.string.no_location_permission, Toast.LENGTH_LONG).show() + Log.d(PlatformUtil.TAG, "Location service permission not granted") + } catch (e: IllegalArgumentException) { + Toast.makeText(this, R.string.gps_not_available, Toast.LENGTH_LONG).show() + Log.d(PlatformUtil.TAG, "GPS location provider not available") + } + + // registering icon at top level + // Leave icon visible even for navigation for proper display + val notification = app.notificationHelper.buildTopNotification() + if (notification != null) { + startForeground(TelegramNotification.TOP_NOTIFICATION_SERVICE_ID, notification) + app.notificationHelper.refreshNotification(TelegramNotification.NotificationType.SHARE_LOCATION) + //app.notificationHelper.refreshNotifications() + } + return Service.START_REDELIVER_INTENT + } + + private fun app() = application as TelegramApplication + + override fun onDestroy() { + super.onDestroy() + val app = app() + app.locationService = null + + // remove updates + val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager + try { + locationManager.removeUpdates(this) + } catch (e: SecurityException) { + Log.d(PlatformUtil.TAG, "Location service permission not granted") + } + + // remove notification + stopForeground(java.lang.Boolean.TRUE) + app.notificationHelper.updateTopNotification() + + app.runInUIThread({ + app.notificationHelper.refreshNotification(TelegramNotification.NotificationType.SHARE_LOCATION) + //app.notificationHelper.refreshNotifications() + }, 500) + } + + override fun onLocationChanged(l: Location?) { + if (l != null) { + val location = convertLocation(l) + app().shareLocationHelper.updateLocation(location) + } + } + + override fun onProviderDisabled(provider: String) { + Toast.makeText(this, getString(R.string.location_service_no_gps_available), Toast.LENGTH_LONG).show() + } + + + override fun onProviderEnabled(provider: String) {} + + + override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {} + + override fun onTaskRemoved(rootIntent: Intent) { + val app = app() + app.notificationHelper.removeNotifications() + if (app.locationService != null) { + this@LocationService.stopSelf() + } + } + + companion object { + + fun convertLocation(l: Location?): net.osmand.Location? { + if (l == null) { + return null + } + val r = net.osmand.Location(l.provider) + r.latitude = l.latitude + r.longitude = l.longitude + r.time = l.time + if (l.hasAccuracy()) { + r.accuracy = l.accuracy + } + if (l.hasSpeed()) { + r.speed = l.speed + } + if (l.hasAltitude()) { + r.altitude = l.altitude + } + if (l.hasBearing()) { + r.bearing = l.bearing + } + if (l.hasAltitude()) { + r.altitude = l.altitude + } + return r + } + } +} diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/LoginDialogFragment.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/LoginDialogFragment.kt index a6bdcea70b..66b840bd13 100644 --- a/OsmAnd-telegram/src/main/java/net/osmand/telegram/LoginDialogFragment.kt +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/LoginDialogFragment.kt @@ -12,7 +12,7 @@ import android.view.inputmethod.EditorInfo import android.widget.Button import android.widget.EditText import net.osmand.telegram.utils.AndroidUtils -import net.osmand.telegram.utils.PlatformUtil +import net.osmand.PlatformUtil class LoginDialogFragment : DialogFragment() { diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/MainActivity.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/MainActivity.kt index e211676575..24a9f24558 100644 --- a/OsmAnd-telegram/src/main/java/net/osmand/telegram/MainActivity.kt +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/MainActivity.kt @@ -1,23 +1,33 @@ package net.osmand.telegram +import android.Manifest +import android.content.pm.PackageManager import android.os.Bundle +import android.support.v4.app.ActivityCompat import android.support.v7.app.AppCompatActivity import android.support.v7.widget.* import android.view.* import android.widget.Toast +import net.osmand.PlatformUtil import net.osmand.telegram.LoginDialogFragment.LoginDialogType -import net.osmand.telegram.TelegramHelper.* +import net.osmand.telegram.helpers.TelegramHelper +import net.osmand.telegram.helpers.TelegramHelper.* +import net.osmand.telegram.utils.AndroidUtils import org.drinkless.td.libcore.telegram.TdApi class MainActivity : AppCompatActivity(), TelegramListener { companion object { + private const val PERMISSION_REQUEST_LOCATION = 1 + private const val LOGIN_MENU_ID = 0 private const val LOGOUT_MENU_ID = 1 private const val PROGRESS_MENU_ID = 2 } + private val log = PlatformUtil.getLog(TelegramHelper::class.java) + private var telegramAuthorizationRequestHandler: TelegramAuthorizationRequestHandler? = null private var paused: Boolean = false @@ -28,8 +38,8 @@ class MainActivity : AppCompatActivity(), TelegramListener { private val app: TelegramApplication get() = application as TelegramApplication - private val telegramHelper: TelegramHelper - get() = app.telegramHelper + private val telegramHelper get() = app.telegramHelper + private val settings get() = app.settings override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -74,6 +84,10 @@ class MainActivity : AppCompatActivity(), TelegramListener { invalidateOptionsMenu() updateTitle() + + if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) { + requestLocationPermission() + } } override fun onPause() { @@ -81,6 +95,16 @@ class MainActivity : AppCompatActivity(), TelegramListener { paused = true } + override fun onStop() { + super.onStop() + settings.save() + } + + override fun onDestroy() { + super.onDestroy() + telegramHelper.close() + } + override fun onTelegramStatusChanged(prevTelegramAuthorizationState: TelegramAuthorizationState, newTelegramAuthorizationState: TelegramAuthorizationState) { runOnUi { @@ -120,6 +144,10 @@ class MainActivity : AppCompatActivity(), TelegramListener { } } + override fun onSendLiveLicationError(code: Int, message: String) { + log.error("Send live location error: $code - $message") + } + private fun updateChatsList() { val chatList = telegramHelper.getChatList() val chats: MutableList = mutableListOf() @@ -237,9 +265,23 @@ class MainActivity : AppCompatActivity(), TelegramListener { } } - override fun onDestroy() { - super.onDestroy() - telegramHelper.close() + private fun requestLocationPermission() { + if (!AndroidUtils.isLocationPermissionAvailable(this)) { + ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_REQUEST_LOCATION) + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + when (requestCode) { + PERMISSION_REQUEST_LOCATION -> { + if (settings.hasAnyChatToShareLocation()) { + app.shareLocationHelper.startSharingLocation() + } + } + } + } } inner class ChatsAdapter : @@ -258,16 +300,28 @@ class MainActivity : AppCompatActivity(), TelegramListener { val showOnMapSwitch: SwitchCompat? = view.findViewById(R.id.show_on_map_switch) } - override fun onCreateViewHolder(parent: ViewGroup, - viewType: Int): ChatsAdapter.ViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.chat_list_item, parent, false) - + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatsAdapter.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.chat_list_item, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val chatId = chats[position].id holder.groupName?.text = chats[position].title + holder.shareLocationSwitch?.setOnCheckedChangeListener(null) + holder.shareLocationSwitch?.isChecked = settings.isSharingLocationToChat(chatId) + holder.shareLocationSwitch?.setOnCheckedChangeListener { view, isChecked -> + settings.shareLocationToChat(chatId, isChecked) + if (settings.hasAnyChatToShareLocation()) { + if (!AndroidUtils.isLocationPermissionAvailable(view.context)) { + requestLocationPermission() + } else { + app.shareLocationHelper.startSharingLocation() + } + } else { + app.shareLocationHelper.stopSharingLocation() + } + } } override fun getItemCount() = chats.size diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramApplication.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramApplication.kt index 58f4252af2..45765a2674 100644 --- a/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramApplication.kt +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramApplication.kt @@ -2,12 +2,26 @@ package net.osmand.telegram import android.app.Application import android.content.Context +import android.content.Intent import android.net.ConnectivityManager import android.net.NetworkInfo +import android.os.Build +import android.os.Handler +import net.osmand.telegram.helpers.ShareLocationHelper +import net.osmand.telegram.helpers.TelegramHelper +import net.osmand.telegram.notifications.NotificationHelper +import net.osmand.telegram.utils.AndroidUtils class TelegramApplication : Application() { - val telegramHelper: TelegramHelper = TelegramHelper.instance + val telegramHelper = TelegramHelper.instance + lateinit var settings: TelegramSettings private set + lateinit var shareLocationHelper: ShareLocationHelper private set + lateinit var notificationHelper: NotificationHelper private set + + var locationService: LocationService? = null + + private val uiHandler = Handler() private val lastTimeInternetConnectionChecked: Long = 0 private var internetConnectionAvailable = true @@ -15,6 +29,14 @@ class TelegramApplication : Application() { override fun onCreate() { super.onCreate() telegramHelper.appDir = filesDir.absolutePath + + settings = TelegramSettings(this) + shareLocationHelper = ShareLocationHelper(this) + notificationHelper = NotificationHelper(this) + + if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) { + shareLocationHelper.startSharingLocation() + } } val isWifiConnected: Boolean @@ -47,4 +69,32 @@ class TelegramApplication : Application() { } return internetConnectionAvailable } + + fun startLocationService(restart: Boolean = false) { + val serviceIntent = Intent(this, LocationService::class.java) + + val locationService = locationService + if (locationService != null && restart) { + locationService.stopSelf() + } + if (locationService == null || restart) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(serviceIntent) + } else { + startService(serviceIntent) + } + } + } + + fun stopLocationService() { + locationService?.stopIfNeeded(this) + } + + fun runInUIThread(action: (() -> Unit)) { + uiHandler.post(action) + } + + fun runInUIThread(action: (() -> Unit), delay: Long) { + uiHandler.postDelayed(action, delay) + } } diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramSettings.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramSettings.kt new file mode 100644 index 0000000000..2037e1f5a5 --- /dev/null +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramSettings.kt @@ -0,0 +1,108 @@ +package net.osmand.telegram + +import android.content.Context +import net.osmand.telegram.utils.OsmandFormatter.MetricsConstants +import net.osmand.telegram.utils.OsmandFormatter.SpeedConstants + +class TelegramSettings(private val app: TelegramApplication) { + + companion object { + + private const val SETTINGS_NAME = "osmand_telegram_settings" + + private const val SHARE_LOCATION_CHATS_KEY = "share_location_chats_key" + private const val SHOW_ON_MAP_CHATS_KEY = "show_on_map_chats_key" + + private const val METRICS_CONSTANTS_KEY = "metrics_constants_key" + private const val SPEED_CONSTANTS_KEY = "speed_constants_key" + + private const val SHOW_NOTIFICATION_ALWAYS_KEY = "show_notification_always_key" + } + + private var shareLocationChats: Set = emptySet() + private var showOnMapChats: Set = emptySet() + + var metricsConstants = MetricsConstants.KILOMETERS_AND_METERS + var speedConstants = SpeedConstants.KILOMETERS_PER_HOUR + + var showNotificationAlways = true + + init { + read() + } + + fun hasAnyChatToShareLocation(): Boolean { + return shareLocationChats.isNotEmpty() + } + + fun isSharingLocationToChat(chatId: Long): Boolean { + return shareLocationChats.contains(chatId) + } + + fun shareLocationToChat(chatId: Long, share: Boolean) { + val shareLocationChats = shareLocationChats.toMutableList() + if (share) { + shareLocationChats.add(chatId) + } else { + shareLocationChats.remove(chatId) + } + this.shareLocationChats = shareLocationChats.toHashSet() + } + + fun getShareLocationChats() = ArrayList(shareLocationChats) + + fun save() { + val prefs = app.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE) + val edit = prefs.edit() + + val shareLocationChatsSet = mutableSetOf() + val shareLocationChats = ArrayList(shareLocationChats) + for (chatId in shareLocationChats) { + shareLocationChatsSet.add(chatId.toString()) + } + edit.putStringSet(SHARE_LOCATION_CHATS_KEY, shareLocationChatsSet) + + val showOnMapChatsSet = mutableSetOf() + val showOnMapChats = ArrayList(showOnMapChats) + for (chatId in showOnMapChats) { + showOnMapChatsSet.add(chatId.toString()) + } + edit.putStringSet(SHOW_ON_MAP_CHATS_KEY, showOnMapChatsSet) + + edit.putString(METRICS_CONSTANTS_KEY, metricsConstants.name) + edit.putString(SPEED_CONSTANTS_KEY, speedConstants.name) + + edit.putBoolean(SHOW_NOTIFICATION_ALWAYS_KEY, showNotificationAlways) + + edit.apply() + } + + fun read() { + val prefs = app.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE) + + val shareLocationChats = mutableSetOf() + val shareLocationChatsSet = prefs.getStringSet(SHARE_LOCATION_CHATS_KEY, mutableSetOf()) + for (chatIdStr in shareLocationChatsSet) { + val chatId = chatIdStr.toLongOrNull() + if (chatId != null) { + shareLocationChats.add(chatId) + } + } + this.shareLocationChats = shareLocationChats + + val showOnMapChats = mutableSetOf() + val showOnMapChatsSet = prefs.getStringSet(SHOW_ON_MAP_CHATS_KEY, mutableSetOf()) + for (chatIdStr in showOnMapChatsSet) { + val chatId = chatIdStr.toLongOrNull() + if (chatId != null) { + showOnMapChats.add(chatId) + } + } + this.showOnMapChats = showOnMapChats + + metricsConstants = MetricsConstants.valueOf(prefs.getString(METRICS_CONSTANTS_KEY, MetricsConstants.KILOMETERS_AND_METERS.name)) + speedConstants = SpeedConstants.valueOf(prefs.getString(SPEED_CONSTANTS_KEY, SpeedConstants.KILOMETERS_PER_HOUR.name)) + + showNotificationAlways = prefs.getBoolean(SHOW_NOTIFICATION_ALWAYS_KEY, true) + } +} diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/helpers/ShareLocationHelper.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/helpers/ShareLocationHelper.kt new file mode 100644 index 0000000000..e9f4b4e3e7 --- /dev/null +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/helpers/ShareLocationHelper.kt @@ -0,0 +1,89 @@ +package net.osmand.telegram.helpers + +import net.osmand.Location +import net.osmand.telegram.TelegramApplication +import net.osmand.telegram.notifications.TelegramNotification.NotificationType + +class ShareLocationHelper(private val app: TelegramApplication) { + + companion object { + const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // day + } + + var sharingLocation: Boolean = false + private set + + var duration: Long = 0 + private set + + var distance: Int = 0 + private set + + var lastLocationMessageSentTime: Long = 0 + + private var lastTimeInMillis: Long = 0L + + private var lastLocation: Location? = null + set(value) { + if (lastTimeInMillis == 0L) { + lastTimeInMillis = System.currentTimeMillis() + } else { + val currentTimeInMillis = System.currentTimeMillis() + duration += currentTimeInMillis - lastTimeInMillis + lastTimeInMillis = currentTimeInMillis + } + if (lastLocation != null && value != null) { + distance += value.distanceTo(lastLocation).toInt() + } + field = value + } + + fun updateLocation(location: Location?) { + lastLocation = location + + if (location != null) { + val shareLocationChats = app.settings.getShareLocationChats() + if (shareLocationChats.isNotEmpty()) { + app.telegramHelper.sendLiveLocation(shareLocationChats, MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC, location.latitude, location.longitude) + } + lastLocationMessageSentTime = System.currentTimeMillis() + } + refreshNotification() + } + + fun startSharingLocation() { + sharingLocation = true + + app.startLocationService() + + refreshNotification() + } + + fun stopSharingLocation() { + sharingLocation = false + + app.stopLocationService() + lastLocation = null + lastTimeInMillis = 0L + distance = 0 + duration = 0 + + refreshNotification() + } + + fun pauseSharingLocation() { + sharingLocation = false + + app.stopLocationService() + lastLocation = null + lastTimeInMillis = 0L + + refreshNotification() + } + + private fun refreshNotification() { + app.runInUIThread { + app.notificationHelper.refreshNotification(NotificationType.SHARE_LOCATION) + } + } +} diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramHelper.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/helpers/TelegramHelper.kt similarity index 82% rename from OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramHelper.kt rename to OsmAnd-telegram/src/main/java/net/osmand/telegram/helpers/TelegramHelper.kt index 5a0a57c569..a40a268950 100644 --- a/OsmAnd-telegram/src/main/java/net/osmand/telegram/TelegramHelper.kt +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/helpers/TelegramHelper.kt @@ -1,9 +1,8 @@ -package net.osmand.telegram +package net.osmand.telegram.helpers import android.text.TextUtils -import net.osmand.telegram.TelegramHelper.TelegramAuthenticationParameterType.* -import net.osmand.telegram.utils.CancellableAsyncTask -import net.osmand.telegram.utils.PlatformUtil +import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.* +import net.osmand.PlatformUtil import org.drinkless.td.libcore.telegram.Client import org.drinkless.td.libcore.telegram.Client.ResultHandler import org.drinkless.td.libcore.telegram.TdApi @@ -104,6 +103,7 @@ class TelegramHelper private constructor() { fun onTelegramChatsRead() fun onTelegramError(code: Int, message: String) + fun onSendLiveLicationError(code: Int, message: String) } interface TelegramAuthorizationRequestListener { @@ -205,6 +205,77 @@ class TelegramHelper private constructor() { listener?.onTelegramChatsRead() } + /** + * @chatId Id of the chat + * @livePeriod Period for which the location can be updated, in seconds; should be between 60 and 86400 for a live location and 0 otherwise. + * @latitude Latitude of the location + * @longitude Longitude of the location + */ + fun sendLiveLocation(chatIds: List, livePeriod: Int = 61, latitude: Double, longitude: Double) { + + val lp = livePeriod.coerceAtLeast(61) + val location = TdApi.Location(latitude, longitude) + val content = TdApi.InputMessageLocation(location, lp) + + client?.send(TdApi.GetActiveLiveLocationMessages(), { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + listener?.onSendLiveLicationError(error.code, error.message) + } + TdApi.Messages.CONSTRUCTOR -> { + val messages = (obj as TdApi.Messages).messages + val processedChatIds = mutableListOf() + if (messages.isNotEmpty()) { + for (msg in messages) { + if (chatIds.contains(msg.chatId)) { + processedChatIds.add(msg.chatId) + client?.send(TdApi.EditMessageLiveLocation(msg.chatId, msg.id, null, location), { o-> + when (o.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = o as TdApi.Error + listener?.onSendLiveLicationError(error.code, error.message) + } + else -> listener?.onSendLiveLicationError(-1, "Receive wrong response from TDLib: $o") + } + }) + } + } + } + if (chatIds.size != processedChatIds.size) { + val mutableChatIds = chatIds.toMutableList() + mutableChatIds.removeAll(processedChatIds) + for (chatId in mutableChatIds) { + client?.send(TdApi.SendMessage(chatId, 0, false, true, null, content), { o-> + when (o.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = o as TdApi.Error + listener?.onSendLiveLicationError(error.code, error.message) + } + else -> listener?.onSendLiveLicationError(-1, "Receive wrong response from TDLib: $o") + } + }) + } + } + } + else -> listener?.onSendLiveLicationError(-1, "Receive wrong response from TDLib: $obj") + } + }) + } + + /** + * @chatId Id of the chat + * @message Text of the message + */ + fun sendText(chatId: Long, message: String) { + // initialize reply markup just for testing + //val row = arrayOf(TdApi.InlineKeyboardButton("https://telegram.org?1", TdApi.InlineKeyboardButtonTypeUrl()), TdApi.InlineKeyboardButton("https://telegram.org?2", TdApi.InlineKeyboardButtonTypeUrl()), TdApi.InlineKeyboardButton("https://telegram.org?3", TdApi.InlineKeyboardButtonTypeUrl())) + //val replyMarkup = TdApi.ReplyMarkupInlineKeyboard(arrayOf(row, row, row)) + + val content = TdApi.InputMessageText(TdApi.FormattedText(message, null), false, true) + client?.send(TdApi.SendMessage(chatId, 0, false, true, null, content), defaultHandler) + } + fun logout(): Boolean { return if (libraryLoaded) { isHaveAuthorization = false @@ -354,9 +425,7 @@ class TelegramHelper private constructor() { chat.order = 0 setChatOrder(chat, order) } - CancellableAsyncTask.run("onTelegramChatsRead", 100, { - listener?.onTelegramChatsRead() - }) + listener?.onTelegramChatsRead() } TdApi.UpdateChatTitle.CONSTRUCTOR -> { val updateChat = obj as TdApi.UpdateChatTitle @@ -424,6 +493,11 @@ class TelegramHelper private constructor() { chat.unreadMentionCount = updateChat.unreadMentionCount } } + + TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR -> { + val updateMessageSent = obj as TdApi.UpdateMessageSendSucceeded + } + TdApi.UpdateChatReplyMarkup.CONSTRUCTOR -> { val updateChat = obj as TdApi.UpdateChatReplyMarkup val chat = chats[updateChat.chatId] diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/NotificationDismissReceiver.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/NotificationDismissReceiver.kt new file mode 100644 index 0000000000..555cb95952 --- /dev/null +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/NotificationDismissReceiver.kt @@ -0,0 +1,39 @@ +package net.osmand.telegram.notifications + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.text.TextUtils + +import net.osmand.telegram.TelegramApplication +import net.osmand.telegram.notifications.TelegramNotification.NotificationType + +class NotificationDismissReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + val helper = (context.applicationContext as TelegramApplication).notificationHelper + val notificationTypeStr = intent.extras!!.getString(NOTIFICATION_TYPE_KEY_NAME) + if (!TextUtils.isEmpty(notificationTypeStr)) { + try { + val notificationType = NotificationType.valueOf(notificationTypeStr) + helper.onNotificationDismissed(notificationType) + } catch (e: Exception) { + //ignored + } + + } + } + + companion object { + + const val NOTIFICATION_TYPE_KEY_NAME = "net.osmand.telegram.notifications.NotificationType" + + fun createIntent(context: Context, notificationType: NotificationType): PendingIntent { + val intent = Intent(context, NotificationDismissReceiver::class.java) + intent.putExtra(NOTIFICATION_TYPE_KEY_NAME, notificationType.name) + return PendingIntent.getBroadcast(context.applicationContext, + 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + } +} diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/NotificationHelper.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/NotificationHelper.kt new file mode 100644 index 0000000000..9f9f9533d9 --- /dev/null +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/NotificationHelper.kt @@ -0,0 +1,140 @@ +package net.osmand.telegram.notifications + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.support.v4.app.NotificationManagerCompat +import net.osmand.telegram.R +import net.osmand.telegram.TelegramApplication +import net.osmand.telegram.notifications.TelegramNotification.NotificationType +import java.util.* + +class NotificationHelper(private val app: TelegramApplication) { + + private var shareLocationNotification: ShareLocationNotification? = null + private val all = ArrayList() + + init { + init() + } + + private fun init() { + val shareLocationNotification = ShareLocationNotification(app) + this.shareLocationNotification = shareLocationNotification + all.add(shareLocationNotification) + } + + fun buildTopNotification(): Notification? { + val notification = acquireTopNotification() + if (notification != null) { + removeNotification(notification.type) + setTopNotification(notification) + val notificationBuilder = notification.buildNotification(false) + return notificationBuilder?.build() + } + return null + } + + private fun acquireTopNotification(): TelegramNotification? { + var notification: TelegramNotification? = null + if (shareLocationNotification!!.isEnabled && shareLocationNotification!!.isActive) { + notification = shareLocationNotification + } + return notification + } + + fun updateTopNotification() { + val notification = acquireTopNotification() + setTopNotification(notification) + } + + private fun setTopNotification(notification: TelegramNotification?) { + for (n in all) { + n.isTop = n === notification + } + } + + fun showNotifications() { + if (!hasAnyTopNotification()) { + removeTopNotification() + } + for (notification in all) { + notification.showNotification() + } + } + + fun refreshNotification(notificationType: NotificationType) { + for (notification in all) { + if (notification.type == notificationType) { + notification.refreshNotification() + break + } + } + } + + fun onNotificationDismissed(notificationType: NotificationType) { + for (notification in all) { + if (notification.type == notificationType) { + notification.onNotificationDismissed() + break + } + } + } + + fun hasAnyTopNotification(): Boolean { + for (notification in all) { + if (notification.isTop) { + return true + } + } + return false + } + + fun refreshNotifications() { + if (!hasAnyTopNotification()) { + removeTopNotification() + } + for (notification in all) { + notification.refreshNotification() + } + } + + fun removeTopNotification() { + val notificationManager = NotificationManagerCompat.from(app) + notificationManager.cancel(TelegramNotification.TOP_NOTIFICATION_SERVICE_ID) + } + + fun removeNotification(notificationType: NotificationType) { + for (notification in all) { + if (notification.type == notificationType) { + notification.removeNotification() + break + } + } + } + + fun removeNotifications() { + for (notification in all) { + notification.removeNotification() + } + } + + @TargetApi(26) + fun createNotificationChannel() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + val channel = NotificationChannel(NOTIFICATION_CHANEL_ID, + app.getString(R.string.osmand_service), NotificationManager.IMPORTANCE_LOW) + channel.enableVibration(false) + channel.description = app.getString(R.string.osmand_service_descr) + val mNotificationManager = app + .getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + mNotificationManager.createNotificationChannel(channel) + } + } + + companion object { + const val NOTIFICATION_CHANEL_ID = "osmand_telegram_background_service" + } +} diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/ShareLocationNotification.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/ShareLocationNotification.kt new file mode 100644 index 0000000000..8ac3664204 --- /dev/null +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/ShareLocationNotification.kt @@ -0,0 +1,152 @@ +package net.osmand.telegram.notifications + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.support.v4.app.NotificationCompat +import android.support.v4.content.ContextCompat +import net.osmand.telegram.R +import net.osmand.telegram.TelegramApplication +import net.osmand.telegram.utils.OsmandFormatter +import net.osmand.util.Algorithms + +class ShareLocationNotification(app: TelegramApplication) : TelegramNotification(app, GROUP_NAME) { + + private var wasNoDataDismissed: Boolean = false + private var lastBuiltNoData: Boolean = false + + override val type: TelegramNotification.NotificationType + get() = TelegramNotification.NotificationType.SHARE_LOCATION + + override val priority: Int + get() = NotificationCompat.PRIORITY_DEFAULT + + override val isActive: Boolean + get() { + val service = app.locationService + return isEnabled && service != null + } + + override val isEnabled: Boolean + get() = app.settings.hasAnyChatToShareLocation() + + override val osmandNotificationId: Int + get() = TelegramNotification.SHARE_LOCATION_NOTIFICATION_SERVICE_ID + + override val osmandWearableNotificationId: Int + get() = TelegramNotification.WEAR_SHARE_LOCATION_NOTIFICATION_SERVICE_ID + + init { + app.registerReceiver(object : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + app.shareLocationHelper.startSharingLocation() + } + }, IntentFilter(OSMAND_START_LOCATION_SHARING_SERVICE_ACTION)) + + app.registerReceiver(object : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + app.shareLocationHelper.pauseSharingLocation() + } + }, IntentFilter(OSMAND_PAUSE_LOCATION_SHARING_SERVICE_ACTION)) + + app.registerReceiver(object : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + app.shareLocationHelper.stopSharingLocation() + } + }, IntentFilter(OSMAND_STOP_LOCATION_SHARING_SERVICE_ACTION)) + } + + override fun onNotificationDismissed() { + if (!wasNoDataDismissed) { + wasNoDataDismissed = lastBuiltNoData + } + } + + override fun buildNotification(wearable: Boolean): NotificationCompat.Builder? { + if (!isEnabled) { + return null + } + val notificationTitle: String + val notificationText: String + color = 0 + icon = R.drawable.ic_action_polygom_dark + val shareLocationHelper = app.shareLocationHelper + val isSharingLocation = shareLocationHelper.sharingLocation + val sharedDistance = shareLocationHelper.distance.toFloat() + ongoing = true + lastBuiltNoData = false + if (isSharingLocation) { + color = ContextCompat.getColor(app, R.color.osmand_orange) + notificationTitle = (app.getString(R.string.sharing_location) + " • " + + Algorithms.formatDuration((shareLocationHelper.duration / 1000).toInt(), true)) + notificationText = (app.getString(R.string.shared_string_distance) + + ": " + OsmandFormatter.getFormattedDistance(sharedDistance, app)) + } else { + if (sharedDistance > 0) { + notificationTitle = (app.getString(R.string.shared_string_paused) + " • " + + Algorithms.formatDuration((shareLocationHelper.duration / 1000).toInt(), true)) + notificationText = (app.getString(R.string.shared_string_distance) + + ": " + OsmandFormatter.getFormattedDistance(sharedDistance, app)) + } else { + ongoing = false + notificationTitle = app.getString(R.string.share_location) + notificationText = app.getString(R.string.shared_string_no_data) + lastBuiltNoData = true + } + } + + if ((wasNoDataDismissed || !app.settings.showNotificationAlways) && !ongoing) { + return null + } + + val notificationBuilder = createBuilder(wearable) + .setContentTitle(notificationTitle) + .setStyle(NotificationCompat.BigTextStyle().bigText(notificationText)) + + val stopIntent = Intent(OSMAND_STOP_LOCATION_SHARING_SERVICE_ACTION) + val stopPendingIntent = PendingIntent.getBroadcast(app, 0, stopIntent, + PendingIntent.FLAG_UPDATE_CURRENT) + if (isSharingLocation) { + if (app.shareLocationHelper.distance > 0) { + val pauseIntent = Intent(OSMAND_PAUSE_LOCATION_SHARING_SERVICE_ACTION) + val pausePendingIntent = PendingIntent.getBroadcast(app, 0, pauseIntent, + PendingIntent.FLAG_UPDATE_CURRENT) + notificationBuilder.addAction(R.drawable.ic_pause, + app.getString(R.string.shared_string_pause), pausePendingIntent) + notificationBuilder.addAction(R.drawable.ic_action_rec_stop, + app.getString(R.string.shared_string_stop), stopPendingIntent) + } else { + notificationBuilder.addAction(R.drawable.ic_action_rec_stop, + app.getString(R.string.shared_string_stop), stopPendingIntent) + } + } else { + val startIntent = Intent(OSMAND_START_LOCATION_SHARING_SERVICE_ACTION) + val startPendingIntent = PendingIntent.getBroadcast(app, 0, startIntent, + PendingIntent.FLAG_UPDATE_CURRENT) + if (sharedDistance > 0) { + notificationBuilder.addAction(R.drawable.ic_action_rec_start, + app.getString(R.string.shared_string_continue), startPendingIntent) + notificationBuilder.addAction(R.drawable.ic_action_rec_stop, + app.getString(R.string.shared_string_stop), stopPendingIntent) + } else { + notificationBuilder.addAction(R.drawable.ic_action_rec_start, + app.getString(R.string.shared_string_start), startPendingIntent) + } + } + + return notificationBuilder + } + + companion object { + + const val OSMAND_START_LOCATION_SHARING_SERVICE_ACTION = "osmand_start_location_sharing_service_action" + const val OSMAND_PAUSE_LOCATION_SHARING_SERVICE_ACTION = "osmand_pause_location_sharing_service_action" + const val OSMAND_STOP_LOCATION_SHARING_SERVICE_ACTION = "osmand_stop_location_sharing_service_action" + const val GROUP_NAME = "share_location" + } +} diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/TelegramNotification.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/TelegramNotification.kt new file mode 100644 index 0000000000..c333fb3471 --- /dev/null +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/notifications/TelegramNotification.kt @@ -0,0 +1,134 @@ +package net.osmand.telegram.notifications + +import android.annotation.SuppressLint +import android.app.Notification +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.support.v4.app.NotificationCompat +import android.support.v4.app.NotificationManagerCompat + +import net.osmand.telegram.MainActivity +import net.osmand.telegram.TelegramApplication + + +abstract class TelegramNotification(protected var app: TelegramApplication, val groupName: String) { + protected var ongoing = true + protected var color: Int = 0 + protected var icon: Int = 0 + var isTop: Boolean = false + + abstract val type: NotificationType + + abstract val osmandNotificationId: Int + + abstract val osmandWearableNotificationId: Int + + abstract val priority: Int + + abstract val isActive: Boolean + + abstract val isEnabled: Boolean + + enum class NotificationType { + SHARE_LOCATION + } + + @SuppressLint("InlinedApi") + protected fun createBuilder(wearable: Boolean): NotificationCompat.Builder { + val contentIntent = Intent(app, MainActivity::class.java) + val contentPendingIntent = PendingIntent.getActivity(app, 0, contentIntent, + PendingIntent.FLAG_UPDATE_CURRENT) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + app.notificationHelper.createNotificationChannel() + } + val builder = NotificationCompat.Builder(app, NotificationHelper.NOTIFICATION_CHANEL_ID) + .setVisibility(android.support.v4.app.NotificationCompat.VISIBILITY_PUBLIC) + .setPriority(if (isTop) NotificationCompat.PRIORITY_HIGH else priority) + .setOngoing(ongoing && !wearable) + .setContentIntent(contentPendingIntent) + .setDeleteIntent(NotificationDismissReceiver.createIntent(app, type)) + .setGroup(groupName).setGroupSummary(!wearable) + + if (color != 0) { + builder.color = color + } + if (icon != 0) { + builder.setSmallIcon(icon) + } + + return builder + } + + abstract fun buildNotification(wearable: Boolean): NotificationCompat.Builder? + + fun setupNotification(notification: Notification) {} + + open fun onNotificationDismissed() {} + + private fun notifyWearable(notificationManager: NotificationManagerCompat) { + val wearNotificationBuilder = buildNotification(true) + if (wearNotificationBuilder != null) { + val wearNotification = wearNotificationBuilder.build() + notificationManager.notify(osmandWearableNotificationId, wearNotification) + } + } + + fun showNotification(): Boolean { + val notificationManager = NotificationManagerCompat.from(app) + if (isEnabled) { + val notificationBuilder = buildNotification(false) + if (notificationBuilder != null) { + val notification = notificationBuilder.build() + setupNotification(notification) + notificationManager.notify(if (isTop) TOP_NOTIFICATION_SERVICE_ID else osmandNotificationId, notification) + notifyWearable(notificationManager) + return true + } + } + return false + } + + fun refreshNotification(): Boolean { + val notificationManager = NotificationManagerCompat.from(app) + if (isEnabled) { + val notificationBuilder = buildNotification(false) + if (notificationBuilder != null) { + val notification = notificationBuilder.build() + setupNotification(notification) + if (isTop) { + notificationManager.cancel(osmandNotificationId) + notificationManager.notify(TOP_NOTIFICATION_SERVICE_ID, notification) + } else { + notificationManager.notify(osmandNotificationId, notification) + } + notifyWearable(notificationManager) + return true + } else { + notificationManager.cancel(osmandNotificationId) + } + } else { + notificationManager.cancel(osmandNotificationId) + } + return false + } + + fun removeNotification() { + val notificationManager = NotificationManagerCompat.from(app) + notificationManager.cancel(osmandNotificationId) + notificationManager.cancel(osmandWearableNotificationId) + } + + fun closeSystemDialogs(context: Context) { + val it = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + context.sendBroadcast(it) + } + + companion object { + + const val SHARE_LOCATION_NOTIFICATION_SERVICE_ID = 6 + const val TOP_NOTIFICATION_SERVICE_ID = 100 + + const val WEAR_SHARE_LOCATION_NOTIFICATION_SERVICE_ID = 1006 + } +} diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/AndroidUtils.kt b/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/AndroidUtils.kt index 208f05226a..6f2493a548 100644 --- a/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/AndroidUtils.kt +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/AndroidUtils.kt @@ -1,8 +1,11 @@ package net.osmand.telegram.utils +import android.Manifest import android.app.Activity import android.content.Context +import android.content.pm.PackageManager import android.content.res.Configuration +import android.support.v4.app.ActivityCompat import android.view.View import android.view.inputmethod.InputMethodManager @@ -32,4 +35,8 @@ object AndroidUtils { } } } + + fun isLocationPermissionAvailable(context: Context): Boolean { + return ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED + } } diff --git a/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/OsmandFormatter.java b/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/OsmandFormatter.java new file mode 100644 index 0000000000..b2d09a3183 --- /dev/null +++ b/OsmAnd-telegram/src/main/java/net/osmand/telegram/utils/OsmandFormatter.java @@ -0,0 +1,267 @@ +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/main/res/drawable-hdpi/ic_action_polygom_dark.png b/OsmAnd-telegram/src/main/res/drawable-hdpi/ic_action_polygom_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..2b6151f95500287207035dffe2f406ec1afffd1e GIT binary patch literal 1437 zcmaJ>YfKbZ6dodCMbRo&)0XNuJlfjM&SPgEGhspY0c`6cE?dD!Oy_YIChYD^XJ%pH zhi$g4q?Hx}5l@c|!HleWw&{|W{=Urf_Ka7*i+n>0|-R2C4M>O@w;3JcKu7zYi6fx)A(a44d4F$XqnmxKGnF@d4e zDn^|Hn;})@D@R?522hL1ii;%0pf=V-S*$kJ_9{w4nIP>1P2wcWSx6QnVU^Vp*0TX@aJ4sDbN^5rdE65j{I;!3}g#lT|}j zB51-QFDOyNfkCD-A%s<*Z_aQ;pG_1@84=@Ef-;drIGl)UT3a{#;C~vA)z$-zDj@to zSE8B->k-UOf?@8S>?olK*>D>*85V^PxfL-Q1`)&Sc3|+vB*_xTSR_dhtX7=1S{a-r z85U<*iN>vf0v1tVKv0m9b{>Z(nIe0!(_ZYhIa!MGFi@cFBuQJHPM3xBut}^pq8off z1WDgA^gWC9JQ2&eG{75*7EqK>vJ1*<6+_W$6%}>4rmK#=R4GR!r9po=QKp%w-9VG; zfmEz1VRSmroctILEN{14#2|%RMAnK60wdzAO=9pMWwY`Y(#+e)AU2Da9?9QKpTZLu zhnl3r@uFzqX_3bTVB>L$0t_zDRvH&5AW@7YQXo_zfbQ&YOLH1uayR(#X$Q1<=HpLdLPyMDUX7w=r$N7++exw0ptiY!pi zr;HrY4_^K9*6Ae!)p;Lfp7`g|)f-&S*50mXl;E;0tCTy$Z0oJ{+D!x?xv^%zP zT}xxnr3E9ME6PVJ`g(u+we|Ps>kB@eQ4Zd7|(W8+Dc2sN@v$$$i}ePd^XATjlGWRrFFOU+HzOy%?M_u=o)#LNs{9`?JEe!@xlIp`uHY&aSs zqZS-OaRiOAXTL%11QD1t2a!^7kWXr8$h1S$9~T!X z(lkQUpcLa`*$7ITd&gar81Eg>$1^%Gs68EIdtSi?EaXEnZw=d?k`GZuy9(YHj#-K< zs`!}@wN5G->n9_Qi%5x)X`SOmG6)!6l7k@FLJC-Bxi(hdXbvb+tHKFn>7lSU*GMUG zwY%gCOCc)l`&or$bGaOoYh@gFh~)tQEGMvnKw}Ns8?$|wr)_U%*@B8Z-8Hkm>DXk! zBGjA_KSW`s>mgX#SZu?v?UfRRQ^w|DmgO0awX8y1MQzWIqyK4as_hMoWf2=ko-^X= zxE`sUWiZa&y&V-4F&m}NHE~hkuNTgVG^3g%lHk_Q-Tqx6e)GBh#QDk(xjzt#p zteBf{0Gt73K{99oaw%GD6@xSc2BeX$1=|1)pG)yn32)q$zx6)FCpeC5nU12#0_62J zi8eqhqZU$;@p#7zkH2z+*kRAt31SwGoARUEu>^Hqm_Yi{-*tW)+^|6VaU z`f8W;=C$Ryh7+}m++1Sc*E#=kn7;H6_er`&Id<~6dw6u}#+^B>XYE_`_iWR@hhnj_ zOH-+pmm7ZyoLt({1f&~@2<+Q2IniIYcK_@-B0$WZdTaX3IcmP}SBiG^svmVeGw~m& CRJ0xd literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-hdpi/ic_action_rec_stop.png b/OsmAnd-telegram/src/main/res/drawable-hdpi/ic_action_rec_stop.png new file mode 100644 index 0000000000000000000000000000000000000000..eb53313d7df702664a9db2c76016e2f77b764ad5 GIT binary patch literal 1076 zcmaJ=y>HV%6t~(?RTb@q_>e%B%K#D=f5dijRzpeSh6a&Rr9sVvdiGrstF_PASK?+w zEKD#UQT~MrnAku|bShl|phQB!JRnRL9RUPM-V%MG`; z;X*9U-xTK(iyQC>Lt^4@2CUL%5i%UhR!&qp^a<|*fc5_)8wPh7T zC@Y$*X@F~hcspPy0YN-FvS5?gjl7U~G!RpZ$e~-TC~>BP5d5%KJ2D)^!$k3v$q5Q& zHLJ+JpT^bKj#-`jr*Wco+}I9@TqiNzid?nNFi3fi=(yXBLB zRqdk0U$WT4mTq9|IHn0S)6@Y}bO<2C8Ze1U4A;>~%fTZ%C*gCjQZ~v9nhkYbRTuSw zTC#0JH}Vkbnhr}NY&D1(3S2Vs?Q!2jta>WeDn|q{8Z{{09Cg7;hcX&>XegG;{i=)C z*1Q1IZhSp0(;#Y_MBXEUD-rd@ex5Dw1P)Na$kYrBG^DhEp40OHL5u)#oqPfE@RgP- z4e|I`{sw)@Z+INxh>qo$8dBYY0Wi!VpsIwY6dD?6Ax*{5L?n;$lFTW}Y3m$orO_4d z&-8dS7+g3SEfVl~iumAs+56q%LszNVrAD&)`t{_5K8ZIKwg*1Z6#Q=gR))59u6N7nkj~3+s>n E0WCjIF#rGn literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-hdpi/ic_pause.png b/OsmAnd-telegram/src/main/res/drawable-hdpi/ic_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..b4bdbb558816223029dd7cb3ada0af2c70b0b0e8 GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeOgEbxddW?b7_Mk^G4pGl&??a>Aa9y+WkF!G8?TgU#si*3Q4j5- dF84_^GvvGoVElAI_%%>JgQu&X%Q~loCICj*GUfmP literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-mdpi/ic_action_polygom_dark.png b/OsmAnd-telegram/src/main/res/drawable-mdpi/ic_action_polygom_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4b53807fc69614d13bc5d3eabb533cfad5e0f319 GIT binary patch literal 1275 zcmaJ>Z*0_L81Diub7YCp3{XQ-IwYX<@3p;avvav!@7PUmo5zujgeY&@_jb*$e{8$E z+enN~qAVDlMumU|gbY4Nzyvi;COX0;3SvSuf@H?UFY^N`MlnQ5@a^qx@?oJ#`@YZn zKELPrJ^wo1*R#2$`JrYE!&*{ZN(QZ~gSV*>edkKin`n8^PYn25?3`Z$F2rKGtwKCy zfLNz>1 zBx}Bq6eaS$@5nS=DwU{Gh_c;5nw2DpW;mMTNTfk}BbE=!q~*2MEhx~_T*L7V+rk5j zK(&YcD1n$RgkU=9^rB(Qt0fAhj4lI*W+{d?%^5EbKsS_b=rS+px~hsI$%!IQN(?WNlB9E_ z2w5m-Di3q2UbnLpp5^&SC@F|axKm=;1Ro2<#blgI#8^>Ggd?#!Hf4D}uryfrZ6Mz@ ztZ-MX9Csn`Z8vM%g?bnC^JI<l&*8K+uRH9kk9JtyDik{TXZ*2LlOE7gpf1O*!v3n=*N7flPI#*3Szp?APp>_QH){3~+mD`^n+r*z_KiIY0Gt<4eMi zkaUk7n)r47&P(ro(YjMScxg3r@z@_vO|CNfkH?Y)Z}U$dPCRgZX5WcF$#XwETKKwq w|J?EM$rcFoRTzj8|cE%+U!5z>% literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-mdpi/ic_action_rec_start.png b/OsmAnd-telegram/src/main/res/drawable-mdpi/ic_action_rec_start.png new file mode 100644 index 0000000000000000000000000000000000000000..01ae53fc8b1890c7e12bc4ddb7b0f42c9e3f834f GIT binary patch literal 1168 zcmaJ>PiP!f7$4niQ>y`~2h*T1Osy#N?flvKlQ(2dvNKt`;%?*aLKj5nn|W`OA(?sO z%!@n8MJu9h)Cl&bhl*ajC|-mX4^p&8VyP>BWl&g+P3>n~gn$4$Qpwz4!gT z@Av(i3yaO?Cyq=V5d>kPe#&j}buxbsAL8HJ{mMUlImQ;ctWA5Yj}juxW9kvH9-<}E zA_%X)`XiYUgu+`vr^~vH7j2)0C6w!wvM}aoL717%V&tz7CVFHkh$_-w-+U{H0j@}A zjE2&P9kLvp+DJ%yquKE{R(y!1+2_QW%;p9{!jPDSt5Ira6=`7C=KK6umc)SyTd7Dx zQr*U)=+K0SM#%)eqUxdrOR8a7&^jq009Rdi0Kuz&v;N zca$q~HumWx;6*{JF7?+!60y2lk@!ss2iVpPj6KgZfo7UIfQk+QgjfS6QHkMuI_Y_M zWamJSD*oN{@(rdUSJ8e!{E%24E!k?$b)J;YY;i?yAEAV!l8 zrK_VZSX`!zrpq)I9cNH=@tHG0glRv0HZRjKYL_IzYXsL48j6EF+ra@Gpn{R985n3t z=>fg0TL3|f0P#I*8uIX!o+|C(@jdw)_9;K%al|7!wr6Tc^`{MhVHp8cB|N3j&_EAq zDuyN^7P2InQn6>Md!N*P4Q;o&L49-$2LE3uKjlS?x7RyneNp2pLTzkxas_QTQ6MteRb@S zcg{ULccZygys~>?{L1AUS05jL>8bBeG{1f?+uXe5E=W}{4lZufHV%6n6Yx+S%nHi(oejYP%7*>`D7YoD>N#O;KD z1pyNSGfYefu`(b=cGQ6h!5_fNfK<*&Tsl;>WZ%8#_j~X6zMieEE>BHfo)iROs& zMP5VejCII=8pm2^-Q9%99g@+VG~oT{&6HqXyORSIhMbLkI}LeL(6&c|oruJ&9hN9LzsOze_q&E!Jg|1tx z)ticCG_4ZbiZh1dfRwSrBK8PtortyFlpsdaE~VS$6s+|rqgkIOqU#R3F3#QwV@wCx zwW3XpMKm{XHGceGQ(gS);w*Z0|0TTFD9rE;*o+^#;_(=Um zbILV5kEEny`=*A}pl$#RYY3<+;U$HJ26{+SF*FgekR{2SqFjv5kx?pDd_Ifo;bL&% zaJ5Lx_bKIz^ZLWbI$t`oR)+rSGmts m`t0-RO=~G=WkLM z=IP=XVsSe8&v^%_gd~YyQV{_a8w3)L9F$j^CwJAr#nhnhhz=KnWFU+F^~=BbfI1jF MUHx3vIVCg!056X$)c^nh literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_polygom_dark.png b/OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_polygom_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..50f39eb08d5dc979702262c64001c9e38a1fdda4 GIT binary patch literal 1568 zcmaJ>eM}Q)7%x;r>Ttx(O`Xbd2m#0Sde_f8S{?mRs#Z&>qtS?5@7~f2y=lFniK5|fSRPi&`CuiA5d?uMa1_T8Q3DCp`UNV2_(RzV3uX|agKR)x zc|R1lNV)l?f&mtp4u!x4Y_=DM{h{GRi77)PQ~<@~3Y6pGaSdvR1PAz^#w)c$&e{M# z9U#On4boygJlP4bn7h|IiYtn2$oWB5EDGf_^Yl^<_yw!k0E;(rhGj^Vnqk~-jRwIr z8Wln)R0Kj0432032Gq1$1w3vhVdqu&nHnvQsR)Z%Yt&(wMWs`kG)AS;Y$Vi5Os6v^ zuvULap!_sQ_+~}l!&vicv7{*oD1i?;dES@k0=t(N_>h+mKqk{*)uFsH*3az+Uq~}4?FSDgs#;NO5WQ_}>7h|hOOZ(|7m94(;f0Ue#BmHcc&Qm;Tt= z`{|z#W7jgfH^K$AV`p{dZ>naOrQGSTsB;Nwfp zyfiIq!tG|xt*#f^=QuJ^%eYD3^yC7W&$fkIkdnH&! z>q$qSg5&byQ;T*N)_+H?3FJiU8eO-ZgcP#wd#&u*eO^gZ?3{o6KusZ{Vq?|u1;olbc>@;r^0(UFuh1O;b*@iFx98sL&9L2g zxHu0juCCp7>9^KcUHAIfxj@6Nwerl59CuI1cI|JO^tHaO`(WDuK3eGMO3%oyOV)3g ze*XTWitTf@+$f#6_UAEsjU^ZE_8HDx{=)Qq?UAdNx+_2Bp5J|J)%v2Mi%X#QK-qTN zI@vmH^MmHTeuw0TktGeMZ?%m0_USldd0W~s--?~F#T%YQ4!`j`IFFQ$xu8%#JD8ew z`DV%vOYeYw<@lUA<+F>XBWGs!&2K1rSLK5z*pqvc3$KJF?=Zb3vGSzyOBD^PH(58| zlX9*P*DhFb=-mBf-UHH}q=(CE`j!>;XP=O4sP8#8H@~4J{?E2r=9%{zD^~mmwctLP literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_rec_start.png b/OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_rec_start.png new file mode 100644 index 0000000000000000000000000000000000000000..9ce3fc5d3dabf8c2c36d0f92d146f97bc147b24f GIT binary patch literal 1362 zcmaJ>eN5bB81HUCjuRAcGjo&Cb#cy*UO%qw(T*F8D+d=jcHAxj#KraFJ*d$3))wwS z=Es~8vWXfYfq%?QmdGDq78m^k4a3DO`iGm@OpLN9E}6z<8B-TBmWaLwcWi%H;6H~l9|U{0N*XHr9rqn?$ot92h4<%k}QDiQ%xD5 zVO{C~F(9dZhrb0?2(s~*7H@T0BaMP$>Ru_M<4x;HXpJCM)#;?9bO8sIL5F4pu!S$q zVyLDDuogB#Mv_6$sWtRl;6Q&#>*a8Tq!`rCdnuOl^Zw^h8lDNVlAuYPpm?4qNSdH&9BSZppW#Sp+^~1%Jcz(nEG_A1 zrh#TWO0wDG1TctnH3dBxiL5y`>?~1`G9oP{3Cc?nx}M2vMcZ~_;2w;1we5Ic5)d(9 zn?05S>yg-*GltwfI8a6rq7n968Z3&`Et*P?4h$zO1~B-=t7@viu&OG{9EZ~!$KX84 z@Ho$_G|mAEu!_upgskTLtY`0|_c1=ME-3O0Ls4~1rH|)pX_^sfUJSBzOwKlJ*p6f< zAQxMMv9q?d540767LXj%ikoJ4t_z}_reoTjW)cksSE`QgZqW?YOxaIo%Cwrb2rR7^ zs3FVL(G{KrZ5<3esY)EpsyHo?37qjUeq7>J2?vVoujFC+WP-x7_Ue87t@bIDz&w&U zID*X45~WnKxXSw^oT31vlxJx?A<>k|a}w}NevE(+iA?L<*Gf4N?9a@0Z7|?rZM1*^ z=gESD6a9Cw9YHpzVR2tP{qv3SJ;u43lDG9^;s@Nw!`Q)dPY!H&Vf&Hm?rSB@-m<(w zH=j3BHR_3#dPefh<{M-}omLJ@9`1W`250p-{rG=x$zQ$0F7Ajvx@lr*Y-4-0tn5zX++y2@p?J>e=#;uKsvvSHSi8n=`YQ7e0>s(v#P8mF(CveI~!WzPUliuiU&abF%LnJN(n~ zBkW%@6AQ~0)9c{RH&@WF$-=;t{1g~}^`%RvD!zZ&_uF;y2UESrK=Qn++Es*1R*fQm TpVCS*f4Oj7llV#Pp`-r;6hzJZ literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_rec_stop.png b/OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_action_rec_stop.png new file mode 100644 index 0000000000000000000000000000000000000000..6590363955f84374f89dc305d1dac6bce03c6517 GIT binary patch literal 1080 zcmaJ=%WKp?7>}*^C=}{Z1&!&Yf_cs6u@f3gyV=?mx0dZfdvubS-3{GjVls8pUKA-v zFXG9Ax1PM|tq30MQBU6e3j_tPdULY7sYOaZNapqZzVG+F_SaVLO;25$5(HtovEsG( zsuka*3;e$^@#h6!uCn?jTc;g1Kq(OxG4+Yqh)|cb2*Nv$zmR!BnA{87n{2cBzzJxi zph8EMkWCF{GZ?O=B+fLL0% zE6(Q*HxLnq#60T9nUlNH(5}Px#kDGlLlw5=N+VL6%{8$`QzDv*B?p>rh&EJo)3Tv` zM+E$=YE>0zvIZTq;%Gn|i^RR9xZ||E<*_e*a-|+)iKD86!9W>Q6q;0;HHwP3 zhT0ixk$*H!)y~>G2~k@lquXh~>(QAl!8~_oJ1P`88|Psf@}i)=M}zH%#H``D5`R%} zh#kYk*!L|<29{;WP&1$mAqKKVbYcd+K{`Gz?VN_MfJLKX)vF#f3|+4q)e3}502m&C zUd^lN}&nl4gxxc79uvXC6!ZDi`F^OO63*r&tiK#82oTN zS|sN4l=8uObK~YdAG-SuZ?T literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_pause.png b/OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..14b6d17d4a46396d02905e9886265f2beaee6b65 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz`!jG!i)^F=12eq zEj?WvLn02py=KV8pdi5P`1!wkcKzH3g&dR1x-%#5tr7=X&v|9SX+EG@FxbO%r<~z| Z`~!xlCmD?We#%84QBPMtmvv4FO#nNzF>C+; literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_polygom_dark.png b/OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_polygom_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8ed0d20f44328a707d3931f748fdb1c8557e2aee GIT binary patch literal 1911 zcmaJ?4^$I%9G`@!@W3fzpdsfFg!Fd1z3Q1#nv=3~V;16!A=lLNHqs5}p~h*oPCvrwnouRtOeLAhX#W*PynY)6xIXc&@fR zWu=vdbhMq#wvl{2jG-f7K6n4`$gRk;Q7p1Cd{Kx@oF%i(w1v~+N)dk}rWi_rN-4@< zkjX$qCPP6?f?^d4j2&~EhUXGC?zo&XfU88h{-7wG{SNjA(ccC za*0tijHmvUzo9-?*gMnrsny0^~;gFieQE!x7*lZ_vo3t??_{5%$FadnFGhu>eX%b28|)UkDraAW?` zYY{UGM!{-OXDUfMB5c6#EuH}4<9(ib_x$HZ1>YNQ1KT<6Eu4ox#@H zE4AJy3oqpAuk4f=i1q0KYa-8gJ`%uDV#y*6b$+O%y>-iG^S?-iBp z@70g{AoYD!^F6pCu-E+ao0m7&(cbO99V?*rF1YsD`l#sxt>c$6y7DK2qTFO=lBU2O z`mT9f=7DQ7l0r@js>VIU4B=)k)d5;{``C=cl2whzn`%;uz1y}D7RRT;?Ypq^vAyKh z4>OIYf9xqxG_A>P_igE}XSZzJpGw(tlpVW!R+mS+ z*9rL>%gU{#CgH-4pdE?V5$LnKEmJN&cBFOHm4`LqO8c?G`UjIQObE_W1VprTDX$9$ zItS|ah09){qAI&<-ps|%FI{eX>RX$nKRNm@dG*bko1*V_0aI&>W+kv+d$iuk`8Yr$ zZ~DDIwn}|8-PJsRw_gwHK9kuOTR0BxIaAh?^G2QpGS>EI`JTegz1F*R$4gXgzqWBr zadXGap6TK8@&h$LJncMj^@hi<2iJC8+I(@n;*_#4QW$hA<=moO_g#&ShynF}z;DH) z(g(u%^t91`9`Rp&J4&2bl3DO}FnTC3r1{<+SI&eLey-es4fp+ZOZGI%Tz#h(PW1e! zJoO4RroC_Z`pOicM@RB6B^z3PS~-8QXIi;3TvhPnpMf==ez_F|ap+(8=DL^%)q%|~ z8>b{r^U6NBB%tn`zFvfC;+0)N(UIM0@^{)Rg2NAS5to-d2w?V@{7YBwQ-5xZPlj&Y zSyTM!*)hyy&ys`ySFj{<)5R!FVn^Sh)N!L8nI8Lx^#!F>E~x4=92BtCuK)JG#}glk YK-cJ+t^x7M?q8ucJ`t~)`_|gO0nV50P5=M^ literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_rec_start.png b/OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_rec_start.png new file mode 100644 index 0000000000000000000000000000000000000000..178daad052e687212bac89181d720bb3392b23fc GIT binary patch literal 1594 zcmaJ>drT8|96u{cC-`7dB8`vZL^mSW>s_x`uA@rV^5|xfKsV6nbKvay2jyZ9WOs64Z$`TDxH?ebQd@!d|F>{%_0!9BYUVeAK*Z1-H ze!k!D)fX2Pj2)vJ13}POXQ87+oeN{HcC`9^ukT2KI;F{Z<#H+SmBWk(AREVf0PGAh zOF;=>xa#E{AP0g*uJOCe<#N|N3(E)fOiV_v1O-(af^z060>cJ?412&*f5?j5YdeX+ ze$I-NnOp{!UbVT}k%Yl_@#O@O62WbPa|N3o~|fCaf`ZF6Y&awh5v5XwDU5*OdIJ^=#^M@~mgExf9L7tfC3m#| zuo58gRU)hA!#g9ctaA5iM=?Pajb*;*SChh2I(W7!2tu;cVMWv%J?G~vq>1A^9*RN< ziXu_kK+-5pa|B8O9GF-S3A`RIZs%qCgw0Om6S&zylO&Gkkv1yd?y#vWQZ~|HCe3kO zXGoHn5DVhI{i^RFUFwy(7P|--nHSwWUm350VjnN_l8+Z)yL}+*@SA1+5XXn5%vhQR zqjmt%{~_S=MLq}*@NDtF1Osj07>Y1)D8U%KC}||kC_{4$3RsUhn^wa&cyVM%pL>D7 z!8%n>)HsAV9E*n{7@W;Ep&V^wP#gy;rL>7ay$peKG{pcjV@5C)A{Hy17fLC9q}FF_ zIoufPX1KLLNbM(4ZJfi&?U@jyndfxa+{)#TJ9MFES~Tz^`Rc0f=z+BwE6=+6E7^-f9AT+Q0b7pLVU@=ERXqZ`Ktk7V^3 zn{^+oy`uT%6Ycm@{r-dup*4D@9itQb*Q}nntiIvY?AO|tUp~A3@pfHqsqoS2Hn#2Y z_VqKrxVYJu{OIY`-##80Nvl4Xcye-NM(%<0QsmxDN|$o(N=n7)2WDSq?h`UpRqw2< zy1(JC^sRra(p`HuVDxoCQ<~VxhmV~;o(wXNXK9w<3C)*~MxDL(c9v#8nb3^5Zf$b^ z^hRTW{rn>?rK=@9*PJ?Hr%oe#Qo1&!=eDJeXt{&WZGB)#m1;8{Uot|zp0X*ap09SB zAm5o~Bo_IhF{6{%a&6oEZ5{2ikaN47H@~~B`#l4tqd#he)J3(6l;-2xwpO$*5*s#- zJT`u1Q4*Yzw63Rc^sh+6HeUtuVd1Q s3-y_0&vS~v>!0UTTlY_6*@)3l;=?tM$_rY1V}EmJUXkO_+ZC(-2hq<#ga7~l literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_rec_stop.png b/OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_action_rec_stop.png new file mode 100644 index 0000000000000000000000000000000000000000..c536100a95644f6185648c51334304b8d97821fa GIT binary patch literal 1141 zcmaJ>%WKp?9FDa}DHOzm_&7MEmkMT+%;wPvUE6ij$3k~4>#99=l9_Eon@pNa-EF}K zDv01g^x#4N06`GJn;txf2ajGnhk~A^diLt**Iq>Lm5S8SguiwZZASL;-S<`B9g{}m1TM50eRd4!R zt3D#~1M=nM~TwWT!KqeKM7(Uun1%lvBx%eNfws&TnOV@ZAWmF_5&qCrgm_w z>IF>=!_2QPI^_-e&x}3MX>&8CYJ;Y1Bk@H(TBA8x#BP5@8Bkc`oKFIg6ujm#e?SYWki*TOP^f?8S%W3Y>v@(M5)#>(fTr znyI*D5k;$PXtrVJ+-j6^9QicY9SGfhF5H*vR1%6gOPY+W@gY(bMK5Nj>PC>pkc5y1-eJ$oDp_qCQT z_xWU3{CagNPJ|zEZjR$w5bOT9sSs4eimp=;Qe;A=81cs(Q-1~8zo_zfIw)|!C#@ouH zF_=D$WNC2dFqpa_9XQe*2L^}k*PpF_Ts{8~-+FTFzZ>a*xX<7@X<%?pP+wizKK1TJ lxjrw}JwP8OUHx)&NxJrR@PYN~&*|(Ts7}}2=jWDg{srJBT{!>% literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_pause.png b/OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..72dfa9fa6ca284c97ddf67b8f79d895c7415017e GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6!iCWaSX}0_x9XIUIqn*!yCT+U;L0sO~C2IOyeEO`JoCBus~(?%Jw=a2aQ@Fd*NsU c1k|znM*W_{DEU_~3TQNgr>mdKI;Vst0CxpC-~a#s literal 0 HcmV?d00001 diff --git a/OsmAnd-telegram/src/main/res/values/colors.xml b/OsmAnd-telegram/src/main/res/values/colors.xml index e41fe93e86..47081acdc2 100644 --- a/OsmAnd-telegram/src/main/res/values/colors.xml +++ b/OsmAnd-telegram/src/main/res/values/colors.xml @@ -9,4 +9,6 @@ #ccc #ff4f4f4f + #ff8f00 + diff --git a/OsmAnd-telegram/src/main/res/values/strings.xml b/OsmAnd-telegram/src/main/res/values/strings.xml index 5d2887e1fb..7ba6672170 100644 --- a/OsmAnd-telegram/src/main/res/values/strings.xml +++ b/OsmAnd-telegram/src/main/res/values/strings.xml @@ -12,4 +12,48 @@ Closing Continue Cancel + Location service not enabled. Turn it on? + Settings + App has no permission to access location data. + Please enable GPS in the settings + The share location service requires a location provider to be turned on. + Background mode + OsmAnd Telegram runs in the background with the screen off. + Distance + Share location + Sharing location + Paused + No data + Pause + Start + Stop + OsmAnd Telegram location service + + yd + ft + mi + km + m + nmi + min/m + min/km + nmi/h + m/s + km/h + mph + Kilometers per hour + Miles per hour + Meters per second + Minutes per kilometer + Minutes per mile + Nautical miles per hour (knot) + Miles/feet + Miles/yards + Kilometers/meters + Nautical miles + Miles/meters + h + min + +