Telegram - introduced live location sharing with chats
|
@ -113,6 +113,7 @@ afterEvaluate {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(path: ':OsmAnd-java', configuration: 'android')
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
@ -121,6 +122,7 @@ dependencies {
|
||||||
implementation 'com.android.support:support-annotations:27.1.1'
|
implementation 'com.android.support:support-annotations:27.1.1'
|
||||||
implementation 'commons-logging:commons-logging-api:1.1'
|
implementation 'commons-logging:commons-logging-api:1.1'
|
||||||
implementation 'com.android.support:recyclerview-v7:27.1.1'
|
implementation 'com.android.support:recyclerview-v7:27.1.1'
|
||||||
|
implementation 'com.vividsolutions:jts-core:1.14.0'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="net.osmand.telegram">
|
package="net.osmand.telegram">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
|
||||||
|
@ -30,6 +32,16 @@
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:label="@string/process_location_service"
|
||||||
|
android:name="net.osmand.telegram.LocationService"
|
||||||
|
android:stopWithTask="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="net.osmand.telegram.LocationService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver android:name=".notifications.NotificationDismissReceiver" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -1,4 +1,4 @@
|
||||||
package net.osmand.telegram.utils;
|
package net.osmand;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import net.osmand.telegram.utils.AndroidUtils
|
import net.osmand.telegram.utils.AndroidUtils
|
||||||
import net.osmand.telegram.utils.PlatformUtil
|
import net.osmand.PlatformUtil
|
||||||
|
|
||||||
|
|
||||||
class LoginDialogFragment : DialogFragment() {
|
class LoginDialogFragment : DialogFragment() {
|
||||||
|
|
|
@ -1,23 +1,33 @@
|
||||||
package net.osmand.telegram
|
package net.osmand.telegram
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.ActivityCompat
|
||||||
import android.support.v7.app.AppCompatActivity
|
import android.support.v7.app.AppCompatActivity
|
||||||
import android.support.v7.widget.*
|
import android.support.v7.widget.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import net.osmand.PlatformUtil
|
||||||
import net.osmand.telegram.LoginDialogFragment.LoginDialogType
|
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
|
import org.drinkless.td.libcore.telegram.TdApi
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), TelegramListener {
|
class MainActivity : AppCompatActivity(), TelegramListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val PERMISSION_REQUEST_LOCATION = 1
|
||||||
|
|
||||||
private const val LOGIN_MENU_ID = 0
|
private const val LOGIN_MENU_ID = 0
|
||||||
private const val LOGOUT_MENU_ID = 1
|
private const val LOGOUT_MENU_ID = 1
|
||||||
private const val PROGRESS_MENU_ID = 2
|
private const val PROGRESS_MENU_ID = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val log = PlatformUtil.getLog(TelegramHelper::class.java)
|
||||||
|
|
||||||
private var telegramAuthorizationRequestHandler: TelegramAuthorizationRequestHandler? = null
|
private var telegramAuthorizationRequestHandler: TelegramAuthorizationRequestHandler? = null
|
||||||
private var paused: Boolean = false
|
private var paused: Boolean = false
|
||||||
|
|
||||||
|
@ -28,8 +38,8 @@ class MainActivity : AppCompatActivity(), TelegramListener {
|
||||||
private val app: TelegramApplication
|
private val app: TelegramApplication
|
||||||
get() = application as TelegramApplication
|
get() = application as TelegramApplication
|
||||||
|
|
||||||
private val telegramHelper: TelegramHelper
|
private val telegramHelper get() = app.telegramHelper
|
||||||
get() = app.telegramHelper
|
private val settings get() = app.settings
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -74,6 +84,10 @@ class MainActivity : AppCompatActivity(), TelegramListener {
|
||||||
|
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
updateTitle()
|
updateTitle()
|
||||||
|
|
||||||
|
if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) {
|
||||||
|
requestLocationPermission()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
@ -81,6 +95,16 @@ class MainActivity : AppCompatActivity(), TelegramListener {
|
||||||
paused = true
|
paused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
settings.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
telegramHelper.close()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTelegramStatusChanged(prevTelegramAuthorizationState: TelegramAuthorizationState,
|
override fun onTelegramStatusChanged(prevTelegramAuthorizationState: TelegramAuthorizationState,
|
||||||
newTelegramAuthorizationState: TelegramAuthorizationState) {
|
newTelegramAuthorizationState: TelegramAuthorizationState) {
|
||||||
runOnUi {
|
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() {
|
private fun updateChatsList() {
|
||||||
val chatList = telegramHelper.getChatList()
|
val chatList = telegramHelper.getChatList()
|
||||||
val chats: MutableList<TdApi.Chat> = mutableListOf()
|
val chats: MutableList<TdApi.Chat> = mutableListOf()
|
||||||
|
@ -237,9 +265,23 @@ class MainActivity : AppCompatActivity(), TelegramListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
private fun requestLocationPermission() {
|
||||||
super.onDestroy()
|
if (!AndroidUtils.isLocationPermissionAvailable(this)) {
|
||||||
telegramHelper.close()
|
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_REQUEST_LOCATION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, 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 :
|
inner class ChatsAdapter :
|
||||||
|
@ -258,16 +300,28 @@ class MainActivity : AppCompatActivity(), TelegramListener {
|
||||||
val showOnMapSwitch: SwitchCompat? = view.findViewById(R.id.show_on_map_switch)
|
val showOnMapSwitch: SwitchCompat? = view.findViewById(R.id.show_on_map_switch)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup,
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatsAdapter.ViewHolder {
|
||||||
viewType: Int): ChatsAdapter.ViewHolder {
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.chat_list_item, parent, false)
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.chat_list_item, parent, false)
|
|
||||||
|
|
||||||
return ViewHolder(view)
|
return ViewHolder(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val chatId = chats[position].id
|
||||||
holder.groupName?.text = chats[position].title
|
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
|
override fun getItemCount() = chats.size
|
||||||
|
|
|
@ -2,12 +2,26 @@ package net.osmand.telegram
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkInfo
|
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() {
|
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 val lastTimeInternetConnectionChecked: Long = 0
|
||||||
private var internetConnectionAvailable = true
|
private var internetConnectionAvailable = true
|
||||||
|
@ -15,6 +29,14 @@ class TelegramApplication : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
telegramHelper.appDir = filesDir.absolutePath
|
telegramHelper.appDir = filesDir.absolutePath
|
||||||
|
|
||||||
|
settings = TelegramSettings(this)
|
||||||
|
shareLocationHelper = ShareLocationHelper(this)
|
||||||
|
notificationHelper = NotificationHelper(this)
|
||||||
|
|
||||||
|
if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) {
|
||||||
|
shareLocationHelper.startSharingLocation()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isWifiConnected: Boolean
|
val isWifiConnected: Boolean
|
||||||
|
@ -47,4 +69,32 @@ class TelegramApplication : Application() {
|
||||||
}
|
}
|
||||||
return internetConnectionAvailable
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Long> = emptySet()
|
||||||
|
private var showOnMapChats: Set<Long> = 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<String>()
|
||||||
|
val shareLocationChats = ArrayList(shareLocationChats)
|
||||||
|
for (chatId in shareLocationChats) {
|
||||||
|
shareLocationChatsSet.add(chatId.toString())
|
||||||
|
}
|
||||||
|
edit.putStringSet(SHARE_LOCATION_CHATS_KEY, shareLocationChatsSet)
|
||||||
|
|
||||||
|
val showOnMapChatsSet = mutableSetOf<String>()
|
||||||
|
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<Long>()
|
||||||
|
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<Long>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
package net.osmand.telegram
|
package net.osmand.telegram.helpers
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import net.osmand.telegram.TelegramHelper.TelegramAuthenticationParameterType.*
|
import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.*
|
||||||
import net.osmand.telegram.utils.CancellableAsyncTask
|
import net.osmand.PlatformUtil
|
||||||
import net.osmand.telegram.utils.PlatformUtil
|
|
||||||
import org.drinkless.td.libcore.telegram.Client
|
import org.drinkless.td.libcore.telegram.Client
|
||||||
import org.drinkless.td.libcore.telegram.Client.ResultHandler
|
import org.drinkless.td.libcore.telegram.Client.ResultHandler
|
||||||
import org.drinkless.td.libcore.telegram.TdApi
|
import org.drinkless.td.libcore.telegram.TdApi
|
||||||
|
@ -104,6 +103,7 @@ class TelegramHelper private constructor() {
|
||||||
|
|
||||||
fun onTelegramChatsRead()
|
fun onTelegramChatsRead()
|
||||||
fun onTelegramError(code: Int, message: String)
|
fun onTelegramError(code: Int, message: String)
|
||||||
|
fun onSendLiveLicationError(code: Int, message: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TelegramAuthorizationRequestListener {
|
interface TelegramAuthorizationRequestListener {
|
||||||
|
@ -205,6 +205,77 @@ class TelegramHelper private constructor() {
|
||||||
listener?.onTelegramChatsRead()
|
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<Long>, 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<Long>()
|
||||||
|
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 {
|
fun logout(): Boolean {
|
||||||
return if (libraryLoaded) {
|
return if (libraryLoaded) {
|
||||||
isHaveAuthorization = false
|
isHaveAuthorization = false
|
||||||
|
@ -354,9 +425,7 @@ class TelegramHelper private constructor() {
|
||||||
chat.order = 0
|
chat.order = 0
|
||||||
setChatOrder(chat, order)
|
setChatOrder(chat, order)
|
||||||
}
|
}
|
||||||
CancellableAsyncTask.run("onTelegramChatsRead", 100, {
|
listener?.onTelegramChatsRead()
|
||||||
listener?.onTelegramChatsRead()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
TdApi.UpdateChatTitle.CONSTRUCTOR -> {
|
TdApi.UpdateChatTitle.CONSTRUCTOR -> {
|
||||||
val updateChat = obj as TdApi.UpdateChatTitle
|
val updateChat = obj as TdApi.UpdateChatTitle
|
||||||
|
@ -424,6 +493,11 @@ class TelegramHelper private constructor() {
|
||||||
chat.unreadMentionCount = updateChat.unreadMentionCount
|
chat.unreadMentionCount = updateChat.unreadMentionCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR -> {
|
||||||
|
val updateMessageSent = obj as TdApi.UpdateMessageSendSucceeded
|
||||||
|
}
|
||||||
|
|
||||||
TdApi.UpdateChatReplyMarkup.CONSTRUCTOR -> {
|
TdApi.UpdateChatReplyMarkup.CONSTRUCTOR -> {
|
||||||
val updateChat = obj as TdApi.UpdateChatReplyMarkup
|
val updateChat = obj as TdApi.UpdateChatReplyMarkup
|
||||||
val chat = chats[updateChat.chatId]
|
val chat = chats[updateChat.chatId]
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<TelegramNotification>()
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
package net.osmand.telegram.utils
|
package net.osmand.telegram.utils
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.support.v4.app.ActivityCompat
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAnd-telegram/src/main/res/drawable-hdpi/ic_pause.png
Normal file
After Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1 KiB |
BIN
OsmAnd-telegram/src/main/res/drawable-mdpi/ic_pause.png
Normal file
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAnd-telegram/src/main/res/drawable-xhdpi/ic_pause.png
Normal file
After Width: | Height: | Size: 193 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAnd-telegram/src/main/res/drawable-xxhdpi/ic_pause.png
Normal file
After Width: | Height: | Size: 215 B |
|
@ -9,4 +9,6 @@
|
||||||
<color name="icon_color_light">#ccc</color>
|
<color name="icon_color_light">#ccc</color>
|
||||||
<color name="icon_color_dark">#ff4f4f4f</color>
|
<color name="icon_color_dark">#ff4f4f4f</color>
|
||||||
|
|
||||||
|
<color name="osmand_orange">#ff8f00</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -12,4 +12,48 @@
|
||||||
<string name="closing">Closing</string>
|
<string name="closing">Closing</string>
|
||||||
<string name="shared_string_continue">Continue</string>
|
<string name="shared_string_continue">Continue</string>
|
||||||
<string name="shared_string_cancel">Cancel</string>
|
<string name="shared_string_cancel">Cancel</string>
|
||||||
|
<string name="gps_network_not_enabled">Location service not enabled. Turn it on?</string>
|
||||||
|
<string name="shared_string_settings">Settings</string>
|
||||||
|
<string name="no_location_permission">App has no permission to access location data.</string>
|
||||||
|
<string name="gps_not_available">Please enable GPS in the settings</string>
|
||||||
|
<string name="location_service_no_gps_available">The share location service requires a location provider to be turned on.</string>
|
||||||
|
<string name="osmand_service">Background mode</string>
|
||||||
|
<string name="osmand_service_descr">OsmAnd Telegram runs in the background with the screen off.</string>
|
||||||
|
<string name="shared_string_distance">Distance</string>
|
||||||
|
<string name="share_location">Share location</string>
|
||||||
|
<string name="sharing_location">Sharing location</string>
|
||||||
|
<string name="shared_string_paused">Paused</string>
|
||||||
|
<string name="shared_string_no_data">No data</string>
|
||||||
|
<string name="shared_string_pause">Pause</string>
|
||||||
|
<string name="shared_string_start">Start</string>
|
||||||
|
<string name="shared_string_stop">Stop</string>
|
||||||
|
<string name="process_location_service">OsmAnd Telegram location service</string>
|
||||||
|
|
||||||
|
<string name="yard">yd</string>
|
||||||
|
<string name="foot">ft</string>
|
||||||
|
<string name="mile">mi</string>
|
||||||
|
<string name="km">km</string>
|
||||||
|
<string name="m">m</string>
|
||||||
|
<string name="nm">nmi</string>
|
||||||
|
<string name="min_mile">min/m</string>
|
||||||
|
<string name="min_km">min/km</string>
|
||||||
|
<string name="nm_h">nmi/h</string>
|
||||||
|
<string name="m_s">m/s</string>
|
||||||
|
<string name="km_h">km/h</string>
|
||||||
|
<string name="mile_per_hour">mph</string>
|
||||||
|
<string name="si_kmh">Kilometers per hour</string>
|
||||||
|
<string name="si_mph">Miles per hour</string>
|
||||||
|
<string name="si_m_s">Meters per second</string>
|
||||||
|
<string name="si_min_km">Minutes per kilometer</string>
|
||||||
|
<string name="si_min_m">Minutes per mile</string>
|
||||||
|
<string name="si_nm_h">Nautical miles per hour (knot)</string>
|
||||||
|
<string name="si_mi_feet">Miles/feet</string>
|
||||||
|
<string name="si_mi_yard">Miles/yards</string>
|
||||||
|
<string name="si_km_m">Kilometers/meters</string>
|
||||||
|
<string name="si_nm">Nautical miles</string>
|
||||||
|
<string name="si_mi_meters">Miles/meters</string>
|
||||||
|
<string name="shared_string_hour_short">h</string>
|
||||||
|
<string name="shared_string_minute_short">min</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|