CarInfo and CarSensors Osrm
This commit is contained in:
@@ -19,7 +19,6 @@ import androidx.car.app.hardware.common.OnCarDataAvailableListener
|
||||
import androidx.car.app.hardware.info.CarHardwareLocation
|
||||
import androidx.car.app.hardware.info.CarSensors
|
||||
import androidx.car.app.hardware.info.Speed
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
@@ -33,10 +32,13 @@ import com.kouros.navigation.car.screen.SearchScreen
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
|
||||
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
||||
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||
import com.kouros.navigation.model.BaseStyleModel
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
@@ -52,7 +54,10 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
lateinit var surfaceRenderer: SurfaceRenderer
|
||||
|
||||
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
|
||||
updateLocation(location!!)
|
||||
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
|
||||
if (routingEngine == RouteEngine.VALHALLA.ordinal) {
|
||||
updateLocation(location!!)
|
||||
}
|
||||
}
|
||||
|
||||
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
|
||||
@@ -75,8 +80,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
|
||||
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
||||
carSensors.removeCarHardwareLocationListener(locationListener)
|
||||
carInfo.removeSpeedListener(speedListener)
|
||||
carSensors.removeCarHardwareLocationListener(carLocationListener)
|
||||
carInfo.removeSpeedListener(carSpeedListener)
|
||||
Log.i(TAG, "In onDestroy()")
|
||||
val locationManager =
|
||||
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
@@ -88,7 +93,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
|
||||
lateinit var baseStyle: BaseStyle.Json
|
||||
|
||||
val locationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
|
||||
val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
|
||||
OnCarDataAvailableListener { data ->
|
||||
if (data.location.status == CarValue.STATUS_SUCCESS) {
|
||||
val location = data.location.value
|
||||
@@ -96,7 +101,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
val speedListener = OnCarDataAvailableListener<Speed> { data ->
|
||||
val carSpeedListener = OnCarDataAvailableListener<Speed> { data ->
|
||||
if (data.displaySpeedMetersPerSecond.status == CarValue.STATUS_SUCCESS) {
|
||||
val speed = data.displaySpeedMetersPerSecond.value
|
||||
surfaceRenderer.updateCarSpeed(speed!!)
|
||||
@@ -106,9 +111,19 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
val lifecycle: Lifecycle = lifecycle
|
||||
lifecycle.addObserver(mLifeCycleObserver)
|
||||
}
|
||||
|
||||
fun onRoutingEngineStateUpdated(routeEngine : Int) {
|
||||
navigationViewModel = when (routeEngine) {
|
||||
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
|
||||
else -> ViewModel(OsrmRepository())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
|
||||
navigationViewModel = getRouteEngine(carContext)
|
||||
|
||||
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
|
||||
routeModel = RouteCarModel()
|
||||
|
||||
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
|
||||
@@ -155,9 +170,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
carSensors.addCarHardwareLocationListener(
|
||||
CarSensors.UPDATE_RATE_NORMAL,
|
||||
carContext.mainExecutor,
|
||||
locationListener
|
||||
carLocationListener
|
||||
)
|
||||
carInfo.addSpeedListener(carContext.mainExecutor, speedListener)
|
||||
carInfo.addSpeedListener(carContext.mainExecutor, carSpeedListener)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
@@ -221,15 +236,15 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
|
||||
fun updateLocation(location: Location) {
|
||||
if (routeModel.isNavigating()) {
|
||||
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
||||
val distance = location.distanceTo(snapedLocation)
|
||||
navigationScreen.updateTrip(location)
|
||||
val wayPointLocation = routeModel.route.currentStep().wayPointLocation
|
||||
val distance = location.distanceTo(wayPointLocation)
|
||||
if (distance > MAXIMAL_ROUTE_DEVIATION) {
|
||||
navigationScreen.calculateNewRoute(routeModel.routeState.destination)
|
||||
return
|
||||
}
|
||||
navigationScreen.updateTrip(location)
|
||||
if (distance < MAXIMAL_SNAP_CORRECTION) {
|
||||
surfaceRenderer.updateLocation(snapedLocation)
|
||||
surfaceRenderer.updateLocation(wayPointLocation)
|
||||
} else {
|
||||
surfaceRenderer.updateLocation(location)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import android.graphics.Rect
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.hardware.display.VirtualDisplay
|
||||
import android.location.Location
|
||||
import android.os.CountDownTimer
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import androidx.car.app.AppManager
|
||||
import androidx.car.app.CarContext
|
||||
@@ -31,9 +29,12 @@ import com.kouros.navigation.car.map.cameraState
|
||||
import com.kouros.navigation.car.map.getPaddingValues
|
||||
import com.kouros.navigation.car.map.rememberBaseStyle
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.Constants.homeLocation
|
||||
import com.kouros.navigation.data.ObjectBox
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.utils.bearing
|
||||
import com.kouros.navigation.utils.calculateTilt
|
||||
import com.kouros.navigation.utils.calculateZoom
|
||||
@@ -161,7 +162,9 @@ class SurfaceRenderer(
|
||||
|
||||
fun onConnectionStateUpdated(connectionState: Int) {
|
||||
when (connectionState) {
|
||||
CarConnection.CONNECTION_TYPE_NATIVE -> ObjectBox.init(carContext)
|
||||
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
|
||||
CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
|
||||
CarConnection.CONNECTION_TYPE_NATIVE -> ObjectBox.init(carContext) // Automotive OS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,44 +259,22 @@ class SurfaceRenderer(
|
||||
)
|
||||
lastBearing = cameraPosition.value!!.bearing
|
||||
lastLocation = location
|
||||
//speed.value = location.speed
|
||||
if (!countDownTimerActive) {
|
||||
countDownTimerActive = true
|
||||
val mainThreadHandler = Handler(carContext.mainLooper)
|
||||
val lastLocationTimer = lastLocation
|
||||
checkUpdate(mainThreadHandler, lastLocationTimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkUpdate(
|
||||
mainThreadHandler: Handler,
|
||||
lastLocationTimer: Location
|
||||
) {
|
||||
mainThreadHandler.post {
|
||||
object : CountDownTimer(3000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {}
|
||||
override fun onFinish() {
|
||||
countDownTimerActive = false
|
||||
if (lastLocation.time - lastLocationTimer.time < 1500) {
|
||||
speed.postValue(0F)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
bearing = bearing,
|
||||
zoom = zoom,
|
||||
tilt = tilt,
|
||||
padding = getPaddingValues(height, viewStyle),
|
||||
target = target
|
||||
synchronized(this) {
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
bearing = bearing,
|
||||
zoom = zoom,
|
||||
tilt = tilt,
|
||||
padding = getPaddingValues(height, viewStyle),
|
||||
target = target
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setRouteData() {
|
||||
@@ -316,17 +297,22 @@ class SurfaceRenderer(
|
||||
}
|
||||
|
||||
fun setCategories(location: Location, route: String) {
|
||||
viewStyle = ViewStyle.AMENITY_VIEW
|
||||
routeData.value = route
|
||||
updateCameraPosition(
|
||||
0.0,
|
||||
12.0,
|
||||
target = Position(location.longitude, location.latitude)
|
||||
)
|
||||
synchronized(this) {
|
||||
viewStyle = ViewStyle.AMENITY_VIEW
|
||||
routeData.value = route
|
||||
updateCameraPosition(
|
||||
0.0,
|
||||
12.0,
|
||||
target = Position(location.longitude, location.latitude)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCarLocation(location: Location) {
|
||||
// updateLocation(location)
|
||||
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
|
||||
if (routingEngine == RouteEngine.OSRM.ordinal) {
|
||||
updateLocation(location)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCarSpeed(newSpeed: Float) {
|
||||
|
||||
@@ -177,12 +177,12 @@ fun AmenityLayer(routeData: String?) {
|
||||
@Composable
|
||||
fun SpeedCameraLayer(speedCameras: String?) {
|
||||
if (speedCameras != null && speedCameras.isNotEmpty()) {
|
||||
val color = const(Color.DarkGray)
|
||||
val color = const(Color.Red)
|
||||
val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras))
|
||||
SymbolLayer(
|
||||
id = "speed-camera-layer",
|
||||
source = cameraSource,
|
||||
iconImage = image(painterResource(R.drawable.speed_camera_48px), drawAsSdf = true),
|
||||
iconImage = image(painterResource(R.drawable.speed_camera_24px), drawAsSdf = true),
|
||||
iconColor = color,
|
||||
iconSize =
|
||||
interpolate(
|
||||
|
||||
@@ -48,13 +48,10 @@ class RouteCarModel() : RouteModel() {
|
||||
val stepData = currentStep()
|
||||
val currentStepCueWithImage: SpannableString =
|
||||
createString(stepData.instruction)
|
||||
|
||||
|
||||
val straightNormal =
|
||||
Lane.Builder()
|
||||
.addDirection(LaneDirection.create(LaneDirection.SHAPE_STRAIGHT, false))
|
||||
.build()
|
||||
|
||||
val step =
|
||||
Step.Builder(currentStepCueWithImage)
|
||||
.setManeuver(
|
||||
@@ -65,7 +62,7 @@ class RouteCarModel() : RouteModel() {
|
||||
.setRoad(routeState.destination.street!!)
|
||||
stepData.lane.forEach {
|
||||
if (it.indications.isNotEmpty() ) {
|
||||
step.setLanesImage(createCarIcon(carContext, R.drawable.lanes))
|
||||
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
|
||||
step.addLane(straightNormal)
|
||||
}
|
||||
}
|
||||
@@ -139,6 +136,10 @@ class RouteCarModel() : RouteModel() {
|
||||
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
||||
}
|
||||
|
||||
fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
||||
return CarIcon.Builder(iconCompat).build()
|
||||
}
|
||||
|
||||
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
|
||||
carContext.getCarService<AppManager?>(AppManager::class.java)
|
||||
.showAlert(createAlert(carContext, distance, maxSpeed))
|
||||
|
||||
@@ -15,8 +15,6 @@ import androidx.car.app.model.Distance
|
||||
import androidx.car.app.model.Header
|
||||
import androidx.car.app.model.MessageTemplate
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.car.app.navigation.model.Lane
|
||||
import androidx.car.app.navigation.model.LaneDirection
|
||||
import androidx.car.app.navigation.model.Maneuver
|
||||
import androidx.car.app.navigation.model.MapWithContentTemplate
|
||||
import androidx.car.app.navigation.model.MessageInfo
|
||||
@@ -92,12 +90,8 @@ class NavigationScreen(
|
||||
var speedCameras = listOf<Elements>()
|
||||
val speedObserver = Observer<List<Elements>> { cameras ->
|
||||
speedCameras = cameras
|
||||
|
||||
val coordinates = mutableListOf<List<Double>>()
|
||||
val loc = location(0.0, 0.0)
|
||||
cameras.forEach {
|
||||
val loc =
|
||||
location(longitude = it.lon!!, latitude = it.lat!!)
|
||||
coordinates.add(listOf(it.lon!!, it.lat!!))
|
||||
}
|
||||
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
||||
@@ -238,22 +232,13 @@ class NavigationScreen(
|
||||
Distance.UNIT_METERS
|
||||
}
|
||||
val nextStep = routeModel.nextStep(carContext = carContext)
|
||||
if (nextStep != null) {
|
||||
return RoutingInfo.Builder()
|
||||
.setCurrentStep(
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
Distance.create(currentDistance, displayUnit)
|
||||
)
|
||||
.setNextStep(nextStep)
|
||||
.build()
|
||||
} else {
|
||||
return RoutingInfo.Builder()
|
||||
.setCurrentStep(
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
Distance.create(currentDistance, displayUnit)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
return RoutingInfo.Builder()
|
||||
.setCurrentStep(
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
Distance.create(currentDistance, displayUnit)
|
||||
)
|
||||
.setNextStep(nextStep)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createActionStripBuilder(): ActionStrip.Builder {
|
||||
@@ -350,7 +335,7 @@ class NavigationScreen(
|
||||
return Action.Builder()
|
||||
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
|
||||
.setOnClickListener {
|
||||
screenManager.push(SettingsScreen(carContext))
|
||||
screenManager.push(SettingsScreen(carContext, viewModel))
|
||||
}
|
||||
.build()
|
||||
}
|
||||
@@ -466,7 +451,7 @@ class NavigationScreen(
|
||||
}
|
||||
|
||||
fun updateTrip(location: Location) {
|
||||
updateSpeedCamera(location)
|
||||
updateSpeedCamera(surfaceRenderer.lastLocation)
|
||||
with(routeModel) {
|
||||
updateLocation(location, viewModel)
|
||||
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||
|
||||
@@ -12,11 +12,12 @@ import androidx.car.app.model.Toggle
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Constants.AVOID_MOTORWAY
|
||||
import com.kouros.navigation.data.Constants.AVOID_TOLLWAY
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
|
||||
|
||||
|
||||
class NavigationSettings(private val carContext: CarContext) : Screen(carContext) {
|
||||
class NavigationSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
|
||||
|
||||
private var motorWayToggleState = false
|
||||
|
||||
@@ -52,7 +53,12 @@ class NavigationSettings(private val carContext: CarContext) : Screen(carContext
|
||||
tollWayToggleState = !tollWayToggleState
|
||||
}.setChecked(tollWayToggleState).build()
|
||||
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle))
|
||||
|
||||
listBuilder.addItem(
|
||||
buildRowForScreenTemplate(
|
||||
RoutingSettings(carContext, viewModel),
|
||||
R.string.routing_engine
|
||||
)
|
||||
)
|
||||
return ListTemplate.Builder()
|
||||
.setSingleList(listBuilder.build())
|
||||
.setHeader(
|
||||
@@ -70,4 +76,12 @@ class NavigationSettings(private val carContext: CarContext) : Screen(carContext
|
||||
.setToggle(toggle)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun buildRowForScreenTemplate(screen: Screen, title: Int): Row {
|
||||
return Row.Builder()
|
||||
.setTitle(carContext.getString(title))
|
||||
.setOnClickListener { screenManager.push(screen) }
|
||||
.setBrowsable(true)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,6 @@ class RoutePreviewScreen(
|
||||
val header = Header.Builder()
|
||||
.setStartHeaderAction(Action.BACK)
|
||||
.setTitle(carContext.getString(R.string.route_preview))
|
||||
//.addEndHeaderAction(navigateAction)
|
||||
.addEndHeaderAction(
|
||||
favoriteAction()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.kouros.navigation.car.screen
|
||||
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.Header
|
||||
import androidx.car.app.model.ItemList
|
||||
import androidx.car.app.model.ListTemplate
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.SectionedItemList
|
||||
import androidx.car.app.model.Template
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
|
||||
|
||||
class RoutingSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
|
||||
private var routingEngine = RouteEngine.VALHALLA.ordinal
|
||||
|
||||
init {
|
||||
routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
val templateBuilder = ListTemplate.Builder()
|
||||
val radioList =
|
||||
ItemList.Builder()
|
||||
.addItem(
|
||||
buildRowForTemplate(
|
||||
R.string.valhalla,
|
||||
)
|
||||
)
|
||||
.addItem(
|
||||
buildRowForTemplate(
|
||||
R.string.osrm,
|
||||
)
|
||||
)
|
||||
.setOnSelectedListener { index: Int ->
|
||||
this.onSelected(index)
|
||||
}
|
||||
.setSelectedIndex(routingEngine)
|
||||
.build()
|
||||
|
||||
return templateBuilder
|
||||
.addSectionedList(SectionedItemList.create(
|
||||
radioList,
|
||||
carContext.getString(R.string.routing_engine)
|
||||
))
|
||||
.setHeader(
|
||||
Header.Builder()
|
||||
.setTitle(carContext.getString(R.string.routing_engine))
|
||||
.setStartHeaderAction(Action.BACK)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
private fun onSelected(index: Int) {
|
||||
setIntKeyValue(carContext, index, ROUTING_ENGINE)
|
||||
viewModel.routingEngine.value = index
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
(carContext
|
||||
.getString(R.string.routing_engine)
|
||||
+ ":"
|
||||
+ " " + index), CarToast.LENGTH_LONG
|
||||
)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun buildRowForTemplate(title: Int): Row {
|
||||
return Row.Builder()
|
||||
.setTitle(carContext.getString(title))
|
||||
.build()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,10 +24,12 @@ import androidx.car.app.model.ListTemplate
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.Template
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
|
||||
/** A screen demonstrating selectable lists. */
|
||||
class SettingsScreen(
|
||||
carContext: CarContext,
|
||||
private var viewModel: ViewModel,
|
||||
) : Screen(carContext) {
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
@@ -40,7 +42,7 @@ class SettingsScreen(
|
||||
)
|
||||
listBuilder.addItem(
|
||||
buildRowForTemplate(
|
||||
NavigationSettings(carContext),
|
||||
NavigationSettings(carContext, viewModel),
|
||||
R.string.navigation_settings
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user