Categories
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
||||
applicationId = "com.kouros.navigation"
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionCode = 61
|
||||
versionName = "0.2.0.61"
|
||||
versionCode = 64
|
||||
versionName = "0.2.0.64"
|
||||
base.archivesName = "navi-$versionName"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -6,39 +6,43 @@ import android.app.AppOpsManager
|
||||
import android.location.LocationManager
|
||||
import android.os.Bundle
|
||||
import android.os.Process
|
||||
import android.speech.tts.TextToSpeech
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.compose.animation.core.animateIntAsState
|
||||
import androidx.compose.foundation.gestures.detectVerticalDragGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.BottomSheetScaffold
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
@@ -46,7 +50,9 @@ import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
||||
import com.kouros.navigation.car.TextToSpeechManager
|
||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.TILT
|
||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||
import com.kouros.navigation.data.StepData
|
||||
@@ -77,7 +83,6 @@ import org.maplibre.compose.location.Location
|
||||
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
||||
import org.maplibre.compose.location.rememberUserLocationState
|
||||
import org.maplibre.spatialk.geojson.Position
|
||||
import java.util.Locale
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
||||
@@ -94,7 +99,6 @@ class MainActivity : ComponentActivity() {
|
||||
val nextStepData: MutableLiveData<StepData> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
var lastStepIndex = -1
|
||||
var lastLocation = location(0.0, 0.0)
|
||||
val observer = Observer<String> { newRoute ->
|
||||
@@ -108,7 +112,6 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var textToSpeech: TextToSpeech
|
||||
|
||||
private fun checkMock() {
|
||||
if (useMock) {
|
||||
@@ -118,7 +121,6 @@ class MainActivity : ComponentActivity() {
|
||||
SimulationType.GPX -> gpx(
|
||||
context = applicationContext, mock
|
||||
)
|
||||
|
||||
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
|
||||
}
|
||||
}
|
||||
@@ -135,6 +137,10 @@ class MainActivity : ComponentActivity() {
|
||||
private lateinit var mock: MockLocation
|
||||
private var loadRecentPlaces = false
|
||||
|
||||
lateinit var textToSpeechManager: TextToSpeechManager
|
||||
|
||||
var guidanceAudio = 0
|
||||
|
||||
override fun onDestroy() {
|
||||
if (simulationJob != null) {
|
||||
simulationJob?.cancel()
|
||||
@@ -146,11 +152,11 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
textToSpeech = TextToSpeech(applicationContext) { status ->
|
||||
if (status == TextToSpeech.SUCCESS) {
|
||||
textToSpeech.language = Locale.getDefault()
|
||||
}
|
||||
}
|
||||
textToSpeechManager = TextToSpeechManager(applicationContext)
|
||||
val repository = getSettingsRepository(applicationContext)
|
||||
repository.guidanceAudioFlow.asLiveData().observe(this, Observer {
|
||||
guidanceAudio = it
|
||||
})
|
||||
|
||||
if (useMock) {
|
||||
checkMockLocationEnabled()
|
||||
@@ -158,12 +164,12 @@ class MainActivity : ComponentActivity() {
|
||||
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
fusedLocationClient.lastLocation.addOnSuccessListener { _: android.location.Location? ->
|
||||
navigationViewModel.route.observe(this, observer)
|
||||
if (useMock) {
|
||||
mock = MockLocation(locationManager)
|
||||
mock.setMockLocation(
|
||||
homeVogelhart.latitude, homeVogelhart.longitude, 0F
|
||||
)
|
||||
navigationViewModel.route.observe(this, observer)
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
@@ -195,19 +201,11 @@ class MainActivity : ComponentActivity() {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun StartScreen(
|
||||
navController: NavHostController
|
||||
) {
|
||||
navController: NavHostController) {
|
||||
|
||||
val appViewModel: AppViewModel = appViewModel()
|
||||
val darkMode by appViewModel.darkMode.collectAsState()
|
||||
|
||||
val baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1)
|
||||
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
val sheetPeekHeight = 180.dp
|
||||
val sheetPeekHeightState = remember { mutableStateOf(sheetPeekHeight) }
|
||||
|
||||
val locationProvider = rememberDefaultLocationProvider(
|
||||
updateInterval = 0.5.seconds, desiredAccuracy = DesiredAccuracy.Highest
|
||||
)
|
||||
@@ -218,40 +216,98 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
val step: StepData? by stepData.observeAsState()
|
||||
val nextStep: StepData? by nextStepData.observeAsState()
|
||||
|
||||
var collapsedHeight by remember {
|
||||
mutableIntStateOf(300)
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
fun closeSheet() {
|
||||
scope.launch {
|
||||
scaffoldState.bottomSheetState.partialExpand()
|
||||
sheetPeekHeightState.value = 50.dp
|
||||
collapsedHeight = if (routeModel.isNavigating()) {
|
||||
150
|
||||
} else {
|
||||
300
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenHeight = configuration.screenHeightDp
|
||||
var expandedType by remember {
|
||||
mutableStateOf(ExpandedType.COLLAPSED)
|
||||
}
|
||||
|
||||
val height by animateIntAsState(
|
||||
when (expandedType) {
|
||||
ExpandedType.HALF -> screenHeight / 2
|
||||
ExpandedType.FULL -> screenHeight
|
||||
ExpandedType.COLLAPSED -> collapsedHeight
|
||||
}
|
||||
)
|
||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState()
|
||||
NavigationTheme(useDarkTheme = darkMode == 1) {
|
||||
BottomSheetScaffold(
|
||||
snackbarHost = {
|
||||
SnackbarHost(hostState = snackbarHostState)
|
||||
},
|
||||
scaffoldState = scaffoldState,
|
||||
sheetPeekHeight = sheetPeekHeightState.value,
|
||||
scaffoldState = bottomSheetScaffoldState,
|
||||
sheetShape = RoundedCornerShape(
|
||||
bottomStart = 0.dp,
|
||||
bottomEnd = 0.dp,
|
||||
topStart = 12.dp,
|
||||
topEnd = 12.dp
|
||||
),
|
||||
sheetContent = {
|
||||
SheetContent(step, nextStep) { closeSheet() }
|
||||
var isUpdated = false
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(height.dp)
|
||||
.pointerInput(Unit) {
|
||||
detectVerticalDragGestures(
|
||||
onVerticalDrag = { change, dragAmount ->
|
||||
change.consume()
|
||||
if (!isUpdated) {
|
||||
expandedType = when {
|
||||
dragAmount < 0 && expandedType == ExpandedType.COLLAPSED -> {
|
||||
ExpandedType.HALF
|
||||
}
|
||||
dragAmount < 0 && expandedType == ExpandedType.HALF -> {
|
||||
ExpandedType.FULL
|
||||
}
|
||||
|
||||
dragAmount > 0 && expandedType == ExpandedType.FULL -> {
|
||||
ExpandedType.HALF
|
||||
}
|
||||
|
||||
dragAmount > 0 && expandedType == ExpandedType.HALF -> {
|
||||
ExpandedType.COLLAPSED
|
||||
}
|
||||
else -> {
|
||||
ExpandedType.FULL
|
||||
}
|
||||
}
|
||||
isUpdated = true
|
||||
}
|
||||
},
|
||||
onDragEnd = {
|
||||
isUpdated = false
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
SheetContent(step, nextStep) { closeSheet() }
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
MapView(
|
||||
applicationContext,
|
||||
userLocationState,
|
||||
step,
|
||||
nextStep,
|
||||
cameraPosition,
|
||||
routeData,
|
||||
tilt,
|
||||
baseStyle,
|
||||
)
|
||||
}
|
||||
sheetPeekHeight = height.dp
|
||||
) {
|
||||
MapView(
|
||||
applicationContext,
|
||||
userLocationState,
|
||||
step,
|
||||
nextStep,
|
||||
cameraPosition,
|
||||
routeData,
|
||||
tilt,
|
||||
baseStyle,
|
||||
)
|
||||
if (!routeModel.isNavigating()) {
|
||||
Settings(navController, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
@@ -338,7 +394,9 @@ class MainActivity : ComponentActivity() {
|
||||
if (isNavigating()) {
|
||||
updateLocation(currentLocation, navigationViewModel)
|
||||
stepData.value = currentStep()
|
||||
//textToSpeech(stepData.value!!.instruction)
|
||||
if (guidanceAudio == 1) {
|
||||
textToSpeech()
|
||||
}
|
||||
if (navState.nextStep) {
|
||||
nextStepData.value = nextStep()
|
||||
}
|
||||
@@ -377,19 +435,11 @@ class MainActivity : ComponentActivity() {
|
||||
stepData.value = StepData("", "", 0.0, 0, 0, 0, 0.0)
|
||||
}
|
||||
|
||||
fun textToSpeech(text: String) {
|
||||
fun textToSpeech() {
|
||||
val currentStep = routeModel.route.currentStep()
|
||||
val stepData = routeModel.currentStep()
|
||||
if (currentStep.index > lastStepIndex && stepData.leftStepDistance < 50) {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val cs: CharSequence = stepData.instruction
|
||||
textToSpeech.speak(cs, TextToSpeech.QUEUE_FLUSH, null, "1233455")
|
||||
Log.d("TTS", "speak $cs")
|
||||
} catch (e: Throwable) {
|
||||
Log.d("TTS", "speak error", e)
|
||||
}
|
||||
}
|
||||
if (currentStep.index > lastStepIndex && stepData.leftStepDistance < INSTRUCTION_DISTANCE) {
|
||||
textToSpeechManager.speak(stepData.message)
|
||||
lastStepIndex = currentStep.index
|
||||
}
|
||||
}
|
||||
@@ -420,5 +470,11 @@ class MainActivity : ComponentActivity() {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum class ExpandedType {
|
||||
HALF, FULL, COLLAPSED
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,14 @@ import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.captionBar
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -42,6 +45,7 @@ import com.kouros.navigation.data.nominatim.SearchResult
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.utils.location
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SearchSheet(
|
||||
applicationContext: Context,
|
||||
@@ -61,7 +65,6 @@ fun SearchSheet(
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
// Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
|
||||
SearchBar(
|
||||
textFieldState = textFieldState,
|
||||
searchPlaces = emptyList(),
|
||||
@@ -72,7 +75,7 @@ fun SearchSheet(
|
||||
closeSheet = { closeSheet() }
|
||||
|
||||
)
|
||||
|
||||
Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
|
||||
if (recentPlaces.value != null) {
|
||||
val items = listOf(recentPlaces)
|
||||
if (items.isNotEmpty()) {
|
||||
@@ -121,29 +124,33 @@ fun Home(
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchPlaces(viewModel: NavigationViewModel, location: Location, it: String) {
|
||||
viewModel.searchPlaces(it, location)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SearchBar(
|
||||
textFieldState: TextFieldState,
|
||||
searchPlaces: List<Place>,
|
||||
searchResults: List<SearchResult>,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: NavigationViewModel,
|
||||
context: Context,
|
||||
location: Location,
|
||||
closeSheet: () -> Unit
|
||||
) {
|
||||
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
SearchBar(
|
||||
windowInsets = WindowInsets.captionBar,
|
||||
colors = SearchBarDefaults.colors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
),
|
||||
modifier = modifier,
|
||||
inputField = {
|
||||
SearchBarDefaults.InputField(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.speed_camera_24px),
|
||||
painter = painterResource(id = R.drawable.search_48px),
|
||||
"Search",
|
||||
modifier = Modifier.size(24.dp, 24.dp),
|
||||
)
|
||||
@@ -160,23 +167,19 @@ fun SearchBar(
|
||||
)
|
||||
},
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = it },
|
||||
onExpandedChange = { },
|
||||
) {
|
||||
if (searchPlaces.isNotEmpty()) {
|
||||
Text(context.getString(R.string.recent_destinations))
|
||||
RecentPlaces(searchPlaces, viewModel, context, location, closeSheet)
|
||||
}
|
||||
if (searchResults.isNotEmpty()) {
|
||||
Text("Search places")
|
||||
SearchPlaces( searchResults, viewModel, context, location, closeSheet)
|
||||
Text("Search places")
|
||||
SearchPlaces( searchResults, viewModel, context, location, closeSheet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchPlaces(viewModel: NavigationViewModel, location: Location, it: String) {
|
||||
viewModel.searchPlaces(it, location)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchPlaces(
|
||||
searchResults: List<SearchResult>,
|
||||
|
||||
Reference in New Issue
Block a user