diff --git a/.gitignore b/.gitignore index aa724b7..2cf9b31 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ .externalNativeBuild .cxx local.properties +/app/release diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ae388c2..2437a51 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -12,6 +12,7 @@ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 51bc3be..a07a278 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -151,6 +151,10 @@ dependencies { // DataStore implementation("androidx.datastore:datastore-core:1.0.0") + // Maps + implementation(project(":maps")) + + // SystemUIController val accompanistVersion = "0.30.1" implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion") diff --git a/app/src/main/java/ai/travel/app/home/HomeViewModel.kt b/app/src/main/java/ai/travel/app/home/HomeViewModel.kt index 8259197..645ec4c 100644 --- a/app/src/main/java/ai/travel/app/home/HomeViewModel.kt +++ b/app/src/main/java/ai/travel/app/home/HomeViewModel.kt @@ -3,6 +3,7 @@ package ai.travel.app.home import ai.travel.app.database.ArrayListConverter import ai.travel.app.database.DatabaseRepo import ai.travel.app.database.travel.TripsEntity +import ai.travel.app.datastore.UserDatastore import ai.travel.app.dto.ApiPrompt import ai.travel.app.dto.PalmApi import ai.travel.app.dto.Prompt @@ -18,12 +19,16 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import javax.inject.Inject @HiltViewModel @@ -54,8 +59,11 @@ class HomeViewModel @Inject constructor( dbRepository.getMoreInfo(destination) val allTrips: Flow> = dbRepository.allTrips - fun getCurrentTrip(destination: String): Flow> = dbRepository.getCurrentTrip(destination) - fun uniqueDays(destination: String): Flow> = dbRepository.distinctDays(destination) + fun getCurrentTrip(destination: String): Flow> = + dbRepository.getCurrentTrip(destination) + + fun uniqueDays(destination: String): Flow> = + dbRepository.distinctDays(destination) val tripName = mutableStateOf(TextFieldValue("")) val tripBudget = mutableStateOf(TextFieldValue("")) @@ -86,11 +94,20 @@ class HomeViewModel @Inject constructor( private val _loginStatus = MutableStateFlow(false) val loginStatus: StateFlow = _loginStatus.asStateFlow() + init { + viewModelScope.launch { + val dataStore = UserDatastore(application.applicationContext) + dataStore.getLoginStatus.collectLatest { + _loginStatus.value = it + } + } + } + fun updateUserDetails( userName: String, gender: String, userPhoneNumber: String, - loginStatus: Boolean + loginStatus: Boolean, ) { _userName.value = userName _userPhoneNumber.value = userPhoneNumber @@ -104,108 +121,158 @@ class HomeViewModel @Inject constructor( } - fun getApiData() { viewModelScope.launch { - delay(1000) - val apiData = - repository.getApiData( - ApiPrompt( - prompt = Prompt( - text = _message.value + withContext(Dispatchers.IO) { + val apiData = + repository.getApiData( + ApiPrompt( + prompt = Prompt( + text = _message.value + ) ) ) - ) - _imageState.value = ApiState.Loaded(apiData) - extractTourDetails(apiData.candidates?.get(0)?.output ?: "") - getGeoCodes() - } - } + _imageState.value = ApiState.Loaded(apiData) + extractTourDetails(apiData.candidates?.get(0)?.output ?: "") + _data.value.forEachIndexed { index, location -> + val geoCodes = mutableMapOf() + val day = location.getOrDefault("Day", "-2") + if (day != "-2") { + val locationName = location.getOrDefault("Name", "") + if (locationName != "") { + val apiData = + repository.getGeocodingData( + query = "$locationName, ${_location.value}", + ) + geoCodes["latitude"] = + apiData.items?.get(0)?.position?.lat?.toString() ?: "" + geoCodes["longitude"] = + apiData.items?.get(0)?.position?.lng?.toString() ?: "" + _geoCodesData.value[index].geoCode = GeoCode( + latitude = geoCodes["latitude"] ?: "", + longitude = geoCodes["longitude"] ?: "" + ) + } + } - private fun getGeoCodes() { - viewModelScope.launch { - delay(1000) - _data.value.forEachIndexed { index, location -> - val geoCodes = mutableMapOf() - val day = location.getOrDefault("Day", "-2") - if (day != "-2") { - val locationName = location.getOrDefault("Name", "") - if (locationName != "") { - val apiData = - repository.getGeocodingData( - query = "$locationName, ${_location.value}", + } + _imageState.value = ApiState.ReceivedGeoCodes + _geoCodesData.value.forEachIndexed { index, location -> + val apiData = + repository.getPlaceIdData( + PlaceIdBody( + textQuery = location.name ) - geoCodes["latitude"] = - apiData.items?.get(0)?.position?.lat?.toString() ?: "" - geoCodes["longitude"] = - apiData.items?.get(0)?.position?.lng?.toString() ?: "" - _geoCodesData.value[index].geoCode = GeoCode( - latitude = geoCodes["latitude"] ?: "", - longitude = geoCodes["longitude"] ?: "" ) - } + _geoCodesData.value[index].placeId = apiData.places?.get(0)?.id ?: "" + println("placeIddddd: ${_geoCodesData.value[index].placeId}") } - + _imageState.value = ApiState.ReceivedPlaceId + _geoCodesData.value.forEachIndexed { index, location -> + val apiData = + repository.getPhotoId( + photoId = _geoCodesData.value[index].placeId ?: "" + ) + _geoCodesData.value[index].photoID = + apiData.result?.photos?.get(0)?.photo_reference ?: "" + println("photoIddddd: ${apiData.result}") + println("photoIddddd 111: ${_geoCodesData.value[index].placeId}") + } + _imageState.value = ApiState.ReceivedPhotoId + _geoCodesData.value.forEachIndexed { index, location -> + val apiData = + repository.getPhoto( + photoReference = _geoCodesData.value[index].photoID ?: "", + maxWidth = 1200, + ) + _geoCodesData.value[index].photo = apiData + } + dbRepository.insertAllTrips(_geoCodesData.value.take(8).map { + TripsEntity( + day = it.day, + timeOfDay = it.timeOfDay, + name = it.name, + budget = it.budget, + latitude = it.geoCode?.latitude?.toDouble(), + longitude = it.geoCode?.longitude?.toDouble(), + photoBase64 = byteArrayToBase64(it.photo ?: ByteArray(0)), + source = source.value.text, + destination = destination.value.text, + travelActivity = "", + ) + }) + _imageState.value = ApiState.ReceivedPhoto } - _imageState.value = ApiState.ReceivedGeoCodes - getPlaceId() } } - private fun getPlaceId() { - viewModelScope.launch { - delay(1000) - _geoCodesData.value.forEachIndexed { index, location -> - val apiData = - repository.getPlaceIdData( - PlaceIdBody( - textQuery = location.name + private suspend fun getGeoCodes() { + _data.value.forEachIndexed { index, location -> + val geoCodes = mutableMapOf() + val day = location.getOrDefault("Day", "-2") + if (day != "-2") { + val locationName = location.getOrDefault("Name", "") + if (locationName != "") { + val apiData = + repository.getGeocodingData( + query = "$locationName, ${_location.value}", ) + geoCodes["latitude"] = + apiData.items?.get(0)?.position?.lat?.toString() ?: "" + geoCodes["longitude"] = + apiData.items?.get(0)?.position?.lng?.toString() ?: "" + _geoCodesData.value[index].geoCode = GeoCode( + latitude = geoCodes["latitude"] ?: "", + longitude = geoCodes["longitude"] ?: "" ) - _geoCodesData.value[index].placeId = apiData.places?.get(0)?.id ?: "" - println("placeIddddd: ${_geoCodesData.value[index].placeId}") + } } - _imageState.value = ApiState.ReceivedPlaceId - getPhotoId() - } + } + _imageState.value = ApiState.ReceivedGeoCodes } - private fun getPhoto() { - viewModelScope.launch { - delay(1000) - _geoCodesData.value.forEachIndexed { index, location -> - val apiData = - repository.getPhoto( - photoReference = _geoCodesData.value[index].photoID ?: "", - maxWidth = 1200, + private suspend fun getPlaceId() { + _geoCodesData.value.forEachIndexed { index, location -> + val apiData = + repository.getPlaceIdData( + PlaceIdBody( + textQuery = location.name ) - _geoCodesData.value[index].photo = apiData - } - addTripToDatabase() - _imageState.value = ApiState.ReceivedPhoto - + ) + _geoCodesData.value[index].placeId = apiData.places?.get(0)?.id ?: "" + println("placeIddddd: ${_geoCodesData.value[index].placeId}") } - + _imageState.value = ApiState.ReceivedPlaceId } - private fun addTripToDatabase() { - viewModelScope.launch { - println("Adding to databasesssssssssss") - dbRepository.insertAllTrips(_geoCodesData.value.map { - TripsEntity( - day = it.day, - timeOfDay = it.timeOfDay, - name = it.name, - budget = it.budget, - latitude = it.geoCode?.latitude?.toDouble(), - longitude = it.geoCode?.longitude?.toDouble(), - photoBase64 = byteArrayToBase64(it.photo ?: ByteArray(0)), - source = source.value.text, - destination = destination.value.text, - travelActivity = "", + private suspend fun getPhoto() { + _geoCodesData.value.forEachIndexed { index, location -> + val apiData = + repository.getPhoto( + photoReference = _geoCodesData.value[index].photoID ?: "", + maxWidth = 1200, ) - }) + _geoCodesData.value[index].photo = apiData + } + } + + private suspend fun addTripToDatabase() { + println("Adding to databasesssssssssss") + dbRepository.insertAllTrips(_geoCodesData.value.map { + TripsEntity( + day = it.day, + timeOfDay = it.timeOfDay, + name = it.name, + budget = it.budget, + latitude = it.geoCode?.latitude?.toDouble(), + longitude = it.geoCode?.longitude?.toDouble(), + photoBase64 = byteArrayToBase64(it.photo ?: ByteArray(0)), + source = source.value.text, + destination = destination.value.text, + travelActivity = "", + ) + }) // _geoCodesData.value.forEachIndexed { _, location -> // dbRepository.insertTrip( @@ -223,32 +290,24 @@ class HomeViewModel @Inject constructor( // ) // ) // } - - } - } private fun byteArrayToBase64(byteArray: ByteArray): String { return Base64.encodeToString(byteArray, Base64.DEFAULT) } - private fun getPhotoId() { - viewModelScope.launch { - delay(1000) - _geoCodesData.value.forEachIndexed { index, location -> - val apiData = - repository.getPhotoId( - photoId = _geoCodesData.value[index].placeId ?: "" - ) - _geoCodesData.value[index].photoID = - apiData.result?.photos?.get(0)?.photo_reference ?: "" - println("photoIddddd: ${apiData.result}") - println("photoIddddd 111: ${_geoCodesData.value[index].placeId}") - } - _imageState.value = ApiState.ReceivedPhotoId - getPhoto() + private suspend fun getPhotoId() { + _geoCodesData.value.forEachIndexed { index, location -> + val apiData = + repository.getPhotoId( + photoId = _geoCodesData.value[index].placeId ?: "" + ) + _geoCodesData.value[index].photoID = + apiData.result?.photos?.get(0)?.photo_reference ?: "" + println("photoIddddd: ${apiData.result}") + println("photoIddddd 111: ${_geoCodesData.value[index].placeId}") } - + _imageState.value = ApiState.ReceivedPhotoId } fun updateMessage(message: String, location: String, noOfDays: String) { diff --git a/app/src/main/java/ai/travel/app/home/ui/CollapsedTopBar.kt b/app/src/main/java/ai/travel/app/home/ui/CollapsedTopBar.kt index 82dd1da..ed11df0 100644 --- a/app/src/main/java/ai/travel/app/home/ui/CollapsedTopBar.kt +++ b/app/src/main/java/ai/travel/app/home/ui/CollapsedTopBar.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border 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.Row import androidx.compose.foundation.layout.Spacer @@ -39,11 +38,15 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBackIos +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Divider +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -57,17 +60,19 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.navigation.NavController @Composable fun CollapsedTopBarHomeScreen( @@ -130,3 +135,51 @@ fun CollapsedTopBarHomeScreen( } } +@Composable +fun CollapsedTopBarDetailsScreen( + text: String, + isCollapsed: Boolean, + navController: NavController +) { + AnimatedVisibility( + visible = isCollapsed, + enter = fadeIn(), + exit = fadeOut() + ) { + Column(modifier = Modifier) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(appGradient) + .padding(vertical = 10.dp, horizontal = 20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Filled.ArrowBackIos, + contentDescription = "Arrow Back", + tint = textColor, + modifier = Modifier + .padding(start = 0.dp) + .size(25.dp) + .clickable { + navController.popBackStack() + } + ) + Spacer(modifier = Modifier.width(15.dp)) + + Text(text = buildAnnotatedString { + withStyle( + style = SpanStyle( + color = textColor, + fontSize = 32.sp, + fontWeight = FontWeight.Black + ) + ) { + append(text) + } + }, modifier = Modifier.padding(end = 10.dp)) + } + Divider(thickness = 1.dp, color = textColor.copy(0.5f)) + } + } +} diff --git a/app/src/main/java/ai/travel/app/home/ui/HomeScreenMain.kt b/app/src/main/java/ai/travel/app/home/ui/HomeScreenMain.kt index 6a37688..7d5d30c 100644 --- a/app/src/main/java/ai/travel/app/home/ui/HomeScreenMain.kt +++ b/app/src/main/java/ai/travel/app/home/ui/HomeScreenMain.kt @@ -2,6 +2,7 @@ package ai.travel.app.home.ui import ai.travel.app.R import ai.travel.app.bottomBar.items +import ai.travel.app.home.ApiState import ai.travel.app.home.HomeViewModel import ai.travel.app.newTrip.NewTripScreen import ai.travel.app.newTrip.NewTripViewModel @@ -59,17 +60,22 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.rememberLottieComposition @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable @@ -286,6 +292,106 @@ fun HomeScreenMain( } } + if (viewModel.isAnimationVisible.value) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + when (state.value) { + is ApiState.Error -> { + + } + + is ApiState.Loaded -> { + val currenanim by rememberLottieComposition( + spec = LottieCompositionSpec.Asset("location.json") + ) + LottieAnimation( + composition = currenanim, + iterations = Int.MAX_VALUE, + contentScale = ContentScale.Crop, + speed = 1f, + modifier = Modifier + .size(250.dp) + ) + + Text(text = "Fetching your location", color = textColor, fontSize = 18.sp) + } + + ApiState.Loading -> { + val currenanim by rememberLottieComposition( + spec = LottieCompositionSpec.Asset("location.json") + ) + LottieAnimation( + composition = currenanim, + iterations = Int.MAX_VALUE, + contentScale = ContentScale.Crop, + speed = 1f, + modifier = Modifier + .size(250.dp) + ) + + Text(text = "Fetching your location", color = textColor, fontSize = 18.sp) + } + + ApiState.NotStarted -> { + + } + + ApiState.ReceivedGeoCodes -> { + val currenanim by rememberLottieComposition( + spec = LottieCompositionSpec.Asset("itineary.json") + ) + LottieAnimation( + composition = currenanim, + iterations = Int.MAX_VALUE, + contentScale = ContentScale.Crop, + speed = 1f, + modifier = Modifier + .size(250.dp) + ) + Text(text = "Planning Itineary", color = textColor, fontSize = 18.sp) + } + + ApiState.ReceivedPhoto -> { + viewModel.isAnimationVisible.value = false + + } + + ApiState.ReceivedPhotoId -> { + val currenanim by rememberLottieComposition( + spec = LottieCompositionSpec.Asset("getset.json") + ) + LottieAnimation( + composition = currenanim, + iterations = Int.MAX_VALUE, + contentScale = ContentScale.Crop, + speed = 1f, + modifier = Modifier + .size(250.dp) + ) + Text(text = "Get Set Go", color = textColor, fontSize = 18.sp) + } + + ApiState.ReceivedPlaceId -> { + val currenanim by rememberLottieComposition( + spec = LottieCompositionSpec.Asset("onyourmark.json") + ) + LottieAnimation( + composition = currenanim, + iterations = Int.MAX_VALUE, + contentScale = ContentScale.Crop, + speed = 1f, + modifier = Modifier + .size(250.dp) + ) + Text(text = "ON your Mark", color = textColor, fontSize = 18.sp) + } + } + } + } + } } } } diff --git a/app/src/main/java/ai/travel/app/navigation/NavController.kt b/app/src/main/java/ai/travel/app/navigation/NavController.kt index 217d7a3..121baaa 100644 --- a/app/src/main/java/ai/travel/app/navigation/NavController.kt +++ b/app/src/main/java/ai/travel/app/navigation/NavController.kt @@ -32,7 +32,7 @@ fun NavController( val newTripViewModel: NewTripViewModel = hiltViewModel() val loginStatus = homeViewModel.loginStatus.collectAsState() - NavHost(navController = navHostController, startDestination = if (!loginStatus.value) Screens.Home.route else Screens.Login.route) { + NavHost(navController = navHostController, startDestination = if (loginStatus.value) Screens.Home.route else Screens.Login.route) { composable(Screens.Home.route) { HomeScreenMain( viewModel = homeViewModel, diff --git a/app/src/main/java/ai/travel/app/riveanimation/LoginUI.kt b/app/src/main/java/ai/travel/app/riveanimation/LoginUI.kt index 5a19ed5..3f02a14 100644 --- a/app/src/main/java/ai/travel/app/riveanimation/LoginUI.kt +++ b/app/src/main/java/ai/travel/app/riveanimation/LoginUI.kt @@ -280,8 +280,8 @@ fun LoginUI( exit = slideOutHorizontally() ) { TextFieldWithIconsLogin( - textValue = "Password", - placeholder = "Enter Your Password", + textValue = "OTP", + placeholder = "Enter OTP Sent to your Phone Number", icon = Icons.Filled.Lock, mutableText = password, onValueChanged = { diff --git a/app/src/main/java/ai/travel/app/tripDetails/MoreInfoTrips.kt b/app/src/main/java/ai/travel/app/tripDetails/MoreInfoTrips.kt index fb8e122..aafd064 100644 --- a/app/src/main/java/ai/travel/app/tripDetails/MoreInfoTrips.kt +++ b/app/src/main/java/ai/travel/app/tripDetails/MoreInfoTrips.kt @@ -104,7 +104,7 @@ fun MoreInfoTrips(viewModel: HomeViewModel, paddingValues: PaddingValues) { Card( modifier = Modifier .fillMaxWidth() - .fillMaxHeight(0.24f), + .fillMaxHeight(0.34f), shape = RoundedCornerShape(0.dp), elevation = CardDefaults.cardElevation(7.dp), colors = CardDefaults.cardColors( @@ -136,7 +136,7 @@ fun MoreInfoTrips(viewModel: HomeViewModel, paddingValues: PaddingValues) { ) } }, - contentScale = ContentScale.FillWidth + contentScale = ContentScale.Crop ) } } diff --git a/app/src/main/java/ai/travel/app/tripDetails/TripDetails.kt b/app/src/main/java/ai/travel/app/tripDetails/TripDetails.kt index 6f30d59..5a4eaa8 100644 --- a/app/src/main/java/ai/travel/app/tripDetails/TripDetails.kt +++ b/app/src/main/java/ai/travel/app/tripDetails/TripDetails.kt @@ -1,8 +1,11 @@ package ai.travel.app.tripDetails +import ai.travel.app.R import ai.travel.app.database.travel.TripsEntity import ai.travel.app.home.HomeViewModel import ai.travel.app.home.base64ToByteArray +import ai.travel.app.home.ui.CollapsedTopBarDetailsScreen +import ai.travel.app.home.ui.CollapsedTopBarHomeScreen import ai.travel.app.home.ui.convertImageByteArrayToBitmap import ai.travel.app.ui.theme.CardBackground import ai.travel.app.ui.theme.appGradient @@ -10,8 +13,11 @@ import ai.travel.app.ui.theme.bottomBarBackground import ai.travel.app.ui.theme.bottomBarBorder import ai.travel.app.ui.theme.lightText import ai.travel.app.ui.theme.textColor +import android.graphics.Point import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image +import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -34,11 +40,13 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CalendarToday import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.LocationOn +import androidx.compose.material.icons.filled.Map import androidx.compose.material.icons.filled.Public import androidx.compose.material.icons.filled.Wallet import androidx.compose.material.icons.filled.WbSunny @@ -50,20 +58,27 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold import androidx.compose.material3.SheetState import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color @@ -73,16 +88,19 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.example.mapbox_map.MapBoxMap +import com.example.mapbox_map.MapBoxPoint +import com.example.mapbox_map.MapItem import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun TripDetailsScreen( viewModel: HomeViewModel, paddingValues: PaddingValues, - navController: NavController + navController: NavController, ) { val cardData1 = listOf( GridCardData( @@ -115,6 +133,22 @@ fun TripDetailsScreen( skipPartiallyExpanded = false ) ) + var currentPoint = remember { mutableStateOf(null) } + val collapseThreshold = remember { + mutableFloatStateOf(0.25f) + } + val listState = rememberLazyListState() + val isCollapsed = remember(listState) { + derivedStateOf { + val firstVisibleItemOffset = listState.firstVisibleItemScrollOffset + val firstItemHeight = listState.layoutInfo.visibleItemsInfo.firstOrNull()?.size ?: 0 + val totalOffset = + (firstVisibleItemOffset.toFloat() / firstItemHeight.toFloat()) + + totalOffset > collapseThreshold.floatValue + } + } + BottomSheetScaffold( sheetContent = { MoreInfoTrips(viewModel = viewModel, paddingValues = paddingValues) @@ -124,206 +158,313 @@ fun TripDetailsScreen( sheetShape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp), sheetPeekHeight = 0.dp, ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(appGradient) - .padding(paddingValues) - ) { - - val trips = viewModel.getCurrentTrip(viewModel.currentDestination.value).collectAsState(initial = emptyList()) - val days = viewModel.uniqueDays(viewModel.currentDestination.value).collectAsState(initial = emptyList()) - val currentDay = remember { mutableStateOf("1") } - var dayTrips = - viewModel.getTrips(currentDay.value, viewModel.currentDestination.value).collectAsState(initial = emptyList()) + Scaffold( + topBar = { + CollapsedTopBarDetailsScreen( + isCollapsed = isCollapsed.value, + text = viewModel.currentDestination.value, + navController = navController + ) + }, + ) { padding -> + println(padding) + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + Column( + modifier = Modifier + .fillMaxSize() + .background(appGradient) + .padding(paddingValues) + ) { + + val trips = viewModel.getCurrentTrip(viewModel.currentDestination.value) + .collectAsState(initial = emptyList()) + val days = viewModel.uniqueDays(viewModel.currentDestination.value) + .collectAsState(initial = emptyList()) + val currentDay = remember { mutableStateOf("1") } + var point: Point? by remember { + mutableStateOf(null) + } + var dayTrips = + viewModel.getTrips(currentDay.value, viewModel.currentDestination.value) + .collectAsState(initial = emptyList()) - val newItems = remember { - mutableStateListOf() - } - LaunchedEffect(key1 = viewModel.getCurrentTrip(viewModel.currentDestination.value)) { - viewModel.getCurrentTrip(viewModel.currentDestination.value).collectLatest { - if (it.isNotEmpty() && viewModel.currentDestination.value != "") { - newItems.clear() - newItems.addAll( - extractTripsByDestination( - it, - viewModel.currentDestination.value - ) - ) + val newItems = remember { + mutableStateListOf() + } + LaunchedEffect(key1 = viewModel.getCurrentTrip(viewModel.currentDestination.value)) { + viewModel.getCurrentTrip(viewModel.currentDestination.value).collectLatest { + if (it.isNotEmpty() && viewModel.currentDestination.value != "") { + newItems.clear() + newItems.addAll( + extractTripsByDestination( + it, + viewModel.currentDestination.value + ) + ) + } + } } - } - } - if (trips.value.isEmpty()) { - CircularProgressIndicator() - } else { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopStart) { - trips.value[0]?.photoBase64?.let { - Card( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.24f), - shape = RoundedCornerShape(0.dp), - elevation = CardDefaults.cardElevation(7.dp), - colors = CardDefaults.cardColors( - containerColor = Color.Transparent - ) + if (trips.value.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center ) { - convertImageByteArrayToBitmap(base64ToByteArray(it))?.asImageBitmap() - ?.let { it1 -> - Image( - bitmap = it1, - contentDescription = "some useful description", - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .drawWithCache { - val gradient = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Black.copy(0.8f) - ), - startY = size.height / 5.5f, - endY = size.height - ) - onDrawWithContent { - drawContent() - drawRect( - gradient, - blendMode = BlendMode.Multiply + CircularProgressIndicator(color = lightText) + } + } else { + trips.value[0]?.photoBase64?.let { + LazyColumn( + modifier = Modifier + .fillMaxSize(), + state = listState + ) { + item { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.TopStart + ) { + Card( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.34f), + shape = RoundedCornerShape(0.dp), + elevation = CardDefaults.cardElevation(7.dp), + colors = CardDefaults.cardColors( + containerColor = Color.Transparent + ) + ) { + convertImageByteArrayToBitmap(base64ToByteArray(it))?.asImageBitmap() + ?.let { it1 -> + Image( + bitmap = it1, + contentDescription = "some useful description", + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .drawWithCache { + val gradient = + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black.copy( + 0.8f + ) + ), + startY = size.height / 5.5f, + endY = size.height + ) + onDrawWithContent { + drawContent() + drawRect( + gradient, + blendMode = BlendMode.Multiply + ) + } + }, + contentScale = ContentScale.FillWidth ) } - }, - contentScale = ContentScale.FillWidth - ) - } - } + } - Column { - Icon( - imageVector = Icons.Filled.Close, - contentDescription = "Arrow Back", - tint = Color.Black, - modifier = Modifier - .padding(start = 15.dp, top=10.dp) - .size(25.dp) - .clickable { - navController.popBackStack() - } - ) - Spacer(modifier = Modifier.fillMaxHeight(0.13f)) - Text( - text = viewModel.currentDestination.value, - color = textColor, - fontSize = 35.sp, - modifier = Modifier.padding( - start = 20.dp, - top = 20.dp, - bottom = 8.dp - ) - ) + Column(modifier = Modifier.fillMaxSize()) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = "Arrow Back", + tint = lightText, + modifier = Modifier + .padding(start = 15.dp, top = 10.dp) + .size(25.dp) + .clickable { + navController.popBackStack() + } + ) + Spacer(modifier = Modifier.height(70.dp)) + Text( + text = viewModel.currentDestination.value, + color = textColor, + fontSize = 35.sp, + modifier = Modifier.padding( + start = 20.dp, + top = 20.dp, + bottom = 8.dp + ) + ) - LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier.height(200.dp) - ) { - items(cardData1) { - GridCard( - topText = it.topText, - bottomText = it.bottomText, - icon = it.icon - ) - } - items(cardData2) { - GridCard( - topText = it.topText, - bottomText = it.bottomText, - icon = it.icon - ) - } + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = Modifier.height(200.dp) + ) { + items(cardData1) { + GridCard( + topText = it.topText, + bottomText = it.bottomText, + icon = it.icon + ) + } - } + items(cardData2) { + GridCard( + topText = it.topText, + bottomText = it.bottomText, + icon = it.icon + ) + } - Row( - verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .padding( - start = 12.dp, - top = 0.dp, - bottom = 0.dp, - end = 12.dp - ) - ) { - Icon( - imageVector = Icons.Filled.LocationOn, - contentDescription = "topText", - tint = lightText, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(7.dp)) - Text( - text = "Your Schedule", - color = textColor, - fontSize = 12.sp, - ) - } + } - Spacer(modifier = Modifier.height(10.dp)) - - LazyRow(contentPadding = PaddingValues(7.dp)) { - items(days.value) { day -> - Card( - colors = CardDefaults.cardColors( - containerColor = if (day == currentDay.value) lightText else bottomBarBackground, - ), - border = BorderStroke(1.dp, bottomBarBorder), - shape = RoundedCornerShape(20.dp), - elevation = CardDefaults.cardElevation(0.dp), - modifier = Modifier - .width(120.dp) - .padding( - start = 12.dp, - top = 0.dp, - bottom = 12.dp, - end = 12.dp - ) - .clickable( - interactionSource = MutableInteractionSource(), - indication = null + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding( + start = 12.dp, + top = 0.dp, + bottom = 0.dp, + end = 12.dp + ) ) { - coroutineScope.launch { - currentDay.value = day ?: "1" - } + Icon( + imageVector = Icons.Filled.Map, + contentDescription = "topText", + tint = lightText, + modifier = Modifier.size(30.dp) + ) + Spacer(modifier = Modifier.width(7.dp)) + Text( + text = "Map View", + color = textColor, + fontSize = 25.sp, + ) } - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - .padding( - start = 0.dp, - top = 10.dp, - bottom = 10.dp + Spacer(modifier = Modifier.height(20.dp)) + + Card( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = 12.dp) + .height(200.dp) + .shadow(10.dp), + shape = RoundedCornerShape(10.dp), + elevation = CardDefaults.cardElevation(40.dp), + colors = CardDefaults.cardColors( + containerColor = Color.Transparent + ), + ) { + MapBoxMap( + onPointChange = { point -> + + }, + points = dayTrips.value.map { + MapItem( + image = R.drawable.app_icon, + latitude = it?.latitude ?: 0.0, + longitude = it?.longitude ?: 0.0, + location = it?.destination ?: "", + time = it?.timeOfDay ?: "", + ) + }, + currentPoint = currentPoint.apply { + value = MapBoxPoint( + latitude = dayTrips.value[0]?.latitude + ?: 0.0, + longitude = dayTrips.value[0]?.longitude + ?: 0.0, + zoom = 15.0 + ) + }, + latitude = dayTrips.value[0]?.latitude + ?: 0.0, + longitude = dayTrips.value[0]?.longitude + ?: 0.0, ) - ) { - Text( - text = "Day $day", - color = textColor, - fontSize = 18.sp, - modifier = Modifier.padding(start = 2.dp) - ) + } + + Spacer(modifier = Modifier.height(20.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding( + start = 12.dp, + top = 0.dp, + bottom = 0.dp, + end = 12.dp + ) + ) { + Icon( + imageVector = Icons.Filled.LocationOn, + contentDescription = "topText", + tint = lightText, + modifier = Modifier.size(30.dp) + ) + Spacer(modifier = Modifier.width(7.dp)) + Text( + text = "Your Schedule", + color = textColor, + fontSize = 25.sp, + ) + } + + Spacer(modifier = Modifier.height(20.dp)) } } + } + item { + LazyRow { + items(days.value) { day -> + Card( + colors = CardDefaults.cardColors( + containerColor = if (day == currentDay.value) lightText else bottomBarBackground, + ), + border = BorderStroke(1.dp, bottomBarBorder), + shape = RoundedCornerShape(20.dp), + elevation = CardDefaults.cardElevation(0.dp), + modifier = Modifier + .width(120.dp) + .padding( + start = 12.dp, + top = 0.dp, + bottom = 12.dp, + end = 12.dp + ) + .clickable( + interactionSource = MutableInteractionSource(), + indication = null + ) { + coroutineScope.launch { + currentDay.value = day ?: "1" + } + } - } - } + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .padding( + start = 0.dp, + top = 10.dp, + bottom = 10.dp + ) + ) { + Text( + text = "Day $day", + color = textColor, + fontSize = 18.sp, + modifier = Modifier.padding(start = 2.dp) + ) + } + } - Spacer(modifier = Modifier.height(10.dp)) - LazyColumn() { + } + } + } + items(dayTrips.value) { Row( modifier = Modifier @@ -397,9 +538,12 @@ fun TripDetailsScreen( Row(verticalAlignment = Alignment.CenterVertically) { Button( onClick = { - viewModel.currentDay.value = currentDay.value - viewModel.currentTimeOfDay.value = it?.timeOfDay ?: "" - viewModel.currentNewDestination.value = it?.name ?: "" + viewModel.currentDay.value = + currentDay.value + viewModel.currentTimeOfDay.value = + it?.timeOfDay ?: "" + viewModel.currentNewDestination.value = + it?.name ?: "" coroutineScope.launch { modalSheetStates.bottomSheetState.expand() } @@ -433,16 +577,14 @@ fun TripDetailsScreen( } } } - - } } } + } } - } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0bbd5aa..00bc7cc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,5 @@ Tripify BaseActivity + pk.eyJ1IjoidGhla2FhaWxhc2hzaGFybWEiLCJhIjoiY2xtNm9sbW00MnY4bTNlbzl2c2Y2Z3g3byJ9.HZmK-5aP8z6hVA8IIU0q6A \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 09ebc54..2684dd4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,7 @@ buildscript { plugins { id("com.android.application") version "8.1.1" apply false + id("com.android.library") version "8.1.0" apply false id("org.jetbrains.kotlin.android") version "1.8.10" apply false id("com.google.dagger.hilt.android") version "2.48" apply false id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false diff --git a/gradle.properties b/gradle.properties index 3c5031e..cc3196d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,5 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +MAPBOX_DOWNLOADS_TOKEN=sk.eyJ1IjoidGhla2FhaWxhc2hzaGFybWEiLCJhIjoiY2xtNnBxNmFxMG1qeTNkbW1oazhjMHhreSJ9.3AKIgPKr9iqO8JeCTDtbDw \ No newline at end of file diff --git a/maps/.gitignore b/maps/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/maps/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/maps/build.gradle b/maps/build.gradle new file mode 100644 index 0000000..8bd33c8 --- /dev/null +++ b/maps/build.gradle @@ -0,0 +1,75 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'com.example.requestlocation' + compileSdk 33 + + defaultConfig { + minSdk 25 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.4.3' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.8.0' + implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0') + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' + implementation 'androidx.activity:activity-compose:1.5.1' + implementation platform('androidx.compose:compose-bom:2022.10.00') + implementation 'androidx.compose.ui:ui' + implementation 'androidx.compose.ui:ui-graphics' + implementation 'androidx.compose.ui:ui-tooling-preview' + implementation 'androidx.compose.material3:material3' + implementation 'androidx.appcompat:appcompat-resources:1.6.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00') + androidTestImplementation 'androidx.compose.ui:ui-test-junit4' + debugImplementation 'androidx.compose.ui:ui-tooling' + debugImplementation 'androidx.compose.ui:ui-test-manifest' + + implementation 'com.google.android.gms:play-services-location:21.0.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4' + implementation 'com.mapbox.maps:android:10.14.0' + implementation 'com.mapbox.mapboxsdk:mapbox-android-gestures:0.8.0' + + implementation("com.mapbox.navigation:ui-dropin:2.14.2") + +} \ No newline at end of file diff --git a/maps/proguard-rules.pro b/maps/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/maps/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/maps/src/androidTest/java/com/example/mapbox_map/ExampleInstrumentedTest.kt b/maps/src/androidTest/java/com/example/mapbox_map/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..c2d4604 --- /dev/null +++ b/maps/src/androidTest/java/com/example/mapbox_map/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.mapbox_map + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppConText() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.requestlocation", appContext.packageName) + } +} \ No newline at end of file diff --git a/maps/src/main/AndroidManifest.xml b/maps/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8c3de62 --- /dev/null +++ b/maps/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/maps/src/main/java/com/example/mapbox_map/MainActivity.kt b/maps/src/main/java/com/example/mapbox_map/MainActivity.kt new file mode 100644 index 0000000..97135a5 --- /dev/null +++ b/maps/src/main/java/com/example/mapbox_map/MainActivity.kt @@ -0,0 +1,400 @@ +package com.example.mapbox_map + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.Image +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.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +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.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.LocationOn +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.example.mapbox_map.ui.LocationService +import com.example.requestlocation.R +import com.mapbox.geojson.Point +import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp + +class MainActivity : ComponentActivity() { + + init { + lifecycle.addObserver(object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { + MapboxNavigationApp.attach(owner) + } + }) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MapScreen() + } + } + +} + +@Composable +fun MapScreen() { + + var point: Point? by remember { + mutableStateOf(null) + } + var relaunch by remember { + mutableStateOf(false) + } + + val context = LocalContext.current + + + val permissionRequest = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestMultiplePermissions(), + onResult = { permissions -> + if (!permissions.values.all { it }) { + //handle permission denied + } else { + relaunch = !relaunch + } + } + ) + var isClicked = remember { mutableStateOf(false) } + var isReset = remember { mutableStateOf(false) } + var currentPoint = remember { mutableStateOf(null) } + + + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier.fillMaxSize(), + ) { + MapBoxMap( + onPointChange = { point = it }, + modifier = Modifier + .fillMaxSize(), + isClicked = isClicked, + currentPoint = currentPoint, + isReset = isReset, + latitude = 0.0, + longitude = 0.0, + points = listOf() + ) + } + AnimatedVisibility( + visible = !isClicked.value, + enter = slideInVertically(tween(1000), initialOffsetY = { + it + }), + exit = slideOutVertically(tween(1000), targetOffsetY = { + it + }) + ) { +// Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) { +// LazyRow( +// modifier = Modifier.fillMaxWidth(), +// contentPadding = PaddingValues(30.dp) +// ) { +// items(items) { item -> +// Card( +// modifier = Modifier +// .width(300.dp) +// .height(200.dp) +// .padding(end = 10.dp), +// shape = RoundedCornerShape(10.dp), +// elevation = CardDefaults.cardElevation(10.dp) +// ) { +// Row( +// modifier = Modifier.fillMaxSize(), +// verticalAlignment = Alignment.CenterVertically, +// horizontalArrangement = Arrangement.Center +// ) { +// Image( +// painter = painterResource(id = item.image), +// contentDescription = null, +// contentScale = ContentScale.Crop, +// modifier = Modifier +// .fillMaxWidth(0.5f) +// .fillMaxHeight() +// ) +// Spacer(modifier = Modifier.width(10.dp)) +// Column(modifier = Modifier.fillMaxWidth()) { +// Spacer(modifier = Modifier.height(10.dp)) +// Text( +// text = item.location, +// color = Color.Black, +// fontSize = 20.sp, +// fontWeight = FontWeight.Bold, +// softWrap = true +// ) +// Spacer(modifier = Modifier.height(10.dp)) +// Text( +// text = item.time, +// color = Color.Black, +// fontSize = 10.sp, +// fontWeight = FontWeight.Normal +// ) +// Spacer(modifier = Modifier.height(10.dp)) +// OutlinedButton( +// onClick = { +// isReset.value = false +// isClicked.value = true +//// currentPoint.value = item.point +// }, +// shape = RoundedCornerShape(10.dp) +// ) { +// Row(verticalAlignment = Alignment.CenterVertically) { +// Icon( +// imageVector = Icons.Filled.LocationOn, +// contentDescription = null, +// modifier = Modifier.size(20.dp), +// tint = Color.Black +// ) +// Spacer(modifier = Modifier.width(10.dp)) +// Text( +// text = "Navigate", +// color = Color.Black, +// fontSize = 10.sp, +// fontWeight = FontWeight.Normal +// ) +// } +// } +// } +// Spacer(modifier = Modifier.width(10.dp)) +// } +// } +// } +// +// } + } +// } + } + Box( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 10.dp, vertical = 25.dp), + contentAlignment = Alignment.TopEnd + ) { + Card( + modifier = Modifier + .padding(end = 10.dp), + shape = RoundedCornerShape(10.dp), + elevation = CardDefaults.cardElevation(10.dp) + ) { + Button( + onClick = { + isReset.value = true + isClicked.value = false + + }, + shape = RoundedCornerShape(10.dp) + ) { + Text( + text = "Reset", + color = Color.White, + fontSize = 10.sp, + fontWeight = FontWeight.Normal, + + ) + } + + } + } + + LaunchedEffect(key1 = relaunch) { + try { + val location = LocationService().getCurrentLocation(context) + point = Point.fromLngLat(location.longitude, location.latitude) + + } catch (e: LocationService.LocationServiceException) { + when (e) { + is LocationService.LocationServiceException.LocationDisabledException -> { + //handle location disabled, show dialog or a snack-bar to enable location + } + + is LocationService.LocationServiceException.MissingPermissionException -> { + permissionRequest.launch( + arrayOf( + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) + } + + is LocationService.LocationServiceException.NoNetworkEnabledException -> { + //handle no network enabled, show dialog or a snack-bar to enable network + } + + is LocationService.LocationServiceException.UnknownException -> { + //handle unknown exception + } + } + } + } +} + +//val items = listOf( +// MapItem( +// image = R.drawable.one, +// location = "Taj Mahal, Agra", +// time = "2 hours ago", +// point = Point.fromLngLat(78.0421, 27.1750) // Taj Mahal's latitude and longitude +// ), +// MapItem( +// image = R.drawable.two, +// location = "Jaipur Palace, Jaipur", +// time = "1 day ago", +// point = Point.fromLngLat(75.8376, 26.9124) // Jaipur Palace's latitude and longitude +// ), +// MapItem( +// image = R.drawable.three, +// location = "Kerala Backwaters, Alappuzha", +// time = "3 days ago", +// point = Point.fromLngLat(76.3300, 9.4981) // Kerala Backwaters' latitude and longitude +// ), +// MapItem( +// image = R.drawable.two, +// location = "SIES GST NERUL", +// time = "3 days ago", +// point = Point.fromLngLat(73.0231, 19.0428) // Kerala Backwaters' latitude and longitude +// ), +// MapItem( +// image = R.drawable.one, // Repeating image +// location = "Hampi, Karnataka", +// time = "4 days ago", +// point = Point.fromLngLat(76.4741, 15.3350) // Hampi's latitude and longitude +// ), +// MapItem( +// image = R.drawable.two, // Repeating image +// location = "Lotus Temple, Delhi", +// time = "5 days ago", +// point = Point.fromLngLat(77.2647, 28.5535) // Lotus Temple's latitude and longitude +// ), +// MapItem( +// image = R.drawable.three, // Repeating image +// location = "Goa Beach, Goa", +// time = "6 days ago", +// point = Point.fromLngLat(73.7319, 15.2993) // Goa Beach's latitude and longitude +// ), +// MapItem( +// image = R.drawable.one, // Repeating image +// location = "Mysore Palace, Mysore", +// time = "7 days ago", +// point = Point.fromLngLat(76.6550, 12.3051) // Mysore Palace's latitude and longitude +// ), +// MapItem( +// image = R.drawable.two, // Repeating image +// location = "Jaisalmer Fort, Jaisalmer", +// time = "8 days ago", +// point = Point.fromLngLat(70.9100, 26.9124) // Jaisalmer Fort's latitude and longitude +// ), +// MapItem( +// image = R.drawable.three, // Repeating image +// location = "Kanchipuram Temples, Tamil Nadu", +// time = "9 days ago", +// point = Point.fromLngLat(79.7006, 12.8431) // Kanchipuram Temples' latitude and longitude +// ), +// MapItem( +// image = R.drawable.one, // Repeating image +// location = "Ajanta Caves, Maharashtra", +// time = "10 days ago", +// point = Point.fromLngLat(75.7050, 20.5513) // Ajanta Caves' latitude and longitude +// ), +// MapItem( +// image = R.drawable.two, // Repeating image +// location = "Sundarbans, West Bengal", +// time = "11 days ago", +// point = Point.fromLngLat(88.9763, 21.9497) // Sundarbans' latitude and longitude +// ), +// MapItem( +// image = R.drawable.three, // Repeating image +// location = "Dudhsagar Falls, Goa", +// time = "12 days ago", +// point = Point.fromLngLat(74.3192, 15.3145) // Dudhsagar Falls' latitude and longitude +// ), +// MapItem( +// image = R.drawable.one, // Repeating image +// location = "Shimla, Himachal Pradesh", +// time = "13 days ago", +// point = Point.fromLngLat(77.1734, 31.1048) // Shimla's latitude and longitude +// ), +// MapItem( +// image = R.drawable.two, // Repeating image +// location = "Kaziranga National Park, Assam", +// time = "14 days ago", +// point = Point.fromLngLat(93.2277, 26.6340) // Kaziranga National Park's latitude and longitude +// ), +// MapItem( +// image = R.drawable.three, // Repeating image +// location = "Puducherry Beach, Puducherry", +// time = "15 days ago", +// point = Point.fromLngLat(79.8083, 11.9416) // Puducherry Beach's latitude and longitude +// ), +// MapItem( +// image = R.drawable.one, // Repeating image +// location = "Mount Abu, Rajasthan", +// time = "16 days ago", +// point = Point.fromLngLat(72.8277, 24.5925) // Mount Abu's latitude and longitude +// ), +// MapItem( +// image = R.drawable.two, // Repeating image +// location = "Rann of Kutch, Gujarat", +// time = "17 days ago", +// point = Point.fromLngLat(69.8597, 23.7337) // Rann of Kutch's latitude and longitude +// ), +// MapItem( +// image = R.drawable.three, // Repeating image +// location = "Tawang Monastery, Arunachal Pradesh", +// time = "18 days ago", +// point = Point.fromLngLat(91.6274, 27.5860) // Tawang Monastery's latitude and longitude +// ), +// // Repeat the cycle of images as needed +//) + + + +data class MapItem( + val image: Int, + val location: String, + val time: String, + val latitude: Double = 0.0, + val longitude: Double = 0.0, +) \ No newline at end of file diff --git a/maps/src/main/java/com/example/mapbox_map/MapBoxMap.kt b/maps/src/main/java/com/example/mapbox_map/MapBoxMap.kt new file mode 100644 index 0000000..c8b0771 --- /dev/null +++ b/maps/src/main/java/com/example/mapbox_map/MapBoxMap.kt @@ -0,0 +1,288 @@ +package com.example.mapbox_map + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import androidx.annotation.DrawableRes +import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.graphics.drawable.toBitmap +import com.example.requestlocation.R +import com.mapbox.android.gestures.StandardScaleGestureDetector +import com.mapbox.geojson.Point +import com.mapbox.maps.CameraOptions +import com.mapbox.maps.MapView +import com.mapbox.maps.Style +import com.mapbox.maps.extension.style.atmosphere.generated.atmosphere +import com.mapbox.maps.extension.style.expressions.dsl.generated.rgb +import com.mapbox.maps.extension.style.expressions.dsl.generated.zoom +import com.mapbox.maps.extension.style.layers.properties.generated.ProjectionName +import com.mapbox.maps.extension.style.projection.generated.projection +import com.mapbox.maps.extension.style.sources.generated.rasterDemSource +import com.mapbox.maps.extension.style.style +import com.mapbox.maps.extension.style.terrain.generated.terrain +import com.mapbox.maps.plugin.animation.MapAnimationOptions +import com.mapbox.maps.plugin.animation.flyTo +import com.mapbox.maps.plugin.annotation.annotations +import com.mapbox.maps.plugin.annotation.generated.PointAnnotationManager +import com.mapbox.maps.plugin.annotation.generated.PointAnnotationOptions +import com.mapbox.maps.plugin.annotation.generated.createPointAnnotationManager +import com.mapbox.maps.plugin.gestures.OnScaleListener +import com.mapbox.maps.plugin.gestures.gestures +import com.mapbox.navigation.dropin.NavigationView + +@Composable +fun MapBoxMap( + modifier: Modifier = Modifier, + onPointChange: (Point) -> Unit, + isClicked: MutableState = mutableStateOf(false), + isReset: MutableState = mutableStateOf(false), + points: List, + latitude: Double, + longitude: Double, + currentPoint: MutableState, +) { + + val context = LocalContext.current + + val marker = remember(context) { + context.getDrawable(R.drawable.marker)!!.toBitmap() + } + + var pointAnnotationManager: PointAnnotationManager? by remember { + mutableStateOf(null) + } + + + + AndroidView( + factory = { context -> + val cameraOptions = CameraOptions.Builder() + .center(Point.fromLngLat(points[0].longitude, points[0].latitude)) + .zoom(13.0) + .pitch(45.0) + .bearing(-17.6) + .build() + MapView(context).also { mapView -> + mapView.getMapboxMap().loadStyle( + style(Style.SATELLITE_STREETS) { + val zoom = zoom() + Log.i("Zoommmmmmmm", "${zoom.literalValue}") + +terrain("terrain-enable") + +projection(ProjectionName.MERCATOR) + +atmosphere { + color(rgb(220.0, 159.0, 159.0)) // Pink fog / lower atmosphere + highColor(rgb(220.0, 159.0, 159.0)) // Blue sky / upper atmosphere + horizonBlend(0.4) // Exaggerate atmosphere (default is .1) + } + +rasterDemSource("raster-dem") { + url("mapbox://mapbox.terrain-rgb") + } + +terrain("raster-dem") { + exaggeration(1.5) + } + } + ) + isScalingOut(mapView) { + isClicked.value = false + } + mapView.getMapboxMap().flyTo( + cameraOptions, + MapAnimationOptions.mapAnimationOptions { + duration(5000L) + } + ) + + +// val annotationApi = mapView.annotations +// pointAnnotationManager = annotationApi.createPointAnnotationManager() +// +// mapView.getMapboxMap().addOnMapClickListener { p -> +// onPointChange(p) +// true +// } + } + }, + update = { mapView -> + isScalingOut(mapView) { + isClicked.value = false + isReset.value = true + } + val cameraOptions = CameraOptions.Builder() + .center(Point.fromLngLat(longitude, latitude)) + .zoom(currentPoint.value?.zoom ?: 3.0) + .build() + mapView.annotations.cleanup() + points.forEach { mapItem -> + addAnnotationToMap( + context = context, + mapView = mapView, + point = Point.fromLngLat(mapItem.longitude, mapItem.latitude), + icon = mapItem.image + ) + } + if (isClicked.value) { + mapView.getMapboxMap().flyTo( + cameraOptions, + MapAnimationOptions.mapAnimationOptions { + duration(5000L) + } + ) + } + if (isReset.value){ + mapView.getMapboxMap().flyTo( + cameraOptions, + MapAnimationOptions.mapAnimationOptions { + duration(5000L) + } + ) + } + + +// if (point != null) { +// pointAnnotationManager?.let { +// it.deleteAll() +// val pointAnnotationOptions = PointAnnotationOptions() +// .withPoint(point) +// .withIconImage(marker) +// +// it.create(pointAnnotationOptions) +// mapView.getMapboxMap() +// .flyTo(CameraOptions.Builder().zoom(16.0).center(point).build()) +// } +// } +// NoOpUpdate + }, + modifier = modifier + ) +} + +fun addAnnotationToMap( + icon: Int, + context: Context, + mapView: MapView, + point: Point +) { + bitmapFromDrawableRes( + context, + icon + )?.let { + val annotationApi = mapView.annotations + val pointAnnotationManager = annotationApi.createPointAnnotationManager() + val pointAnnotationOptions: PointAnnotationOptions = PointAnnotationOptions() + .withPoint(point) + .withIconImage(it) + pointAnnotationManager.create(pointAnnotationOptions) + } +} + +private fun bitmapFromDrawableRes(context: Context, @DrawableRes resourceId: Int) = + convertDrawableToBitmap(AppCompatResources.getDrawable(context, resourceId)) + +private fun convertDrawableToBitmap(sourceDrawable: Drawable?): Bitmap? { + if (sourceDrawable == null) { + return null + } + return if (sourceDrawable is BitmapDrawable) { + sourceDrawable.bitmap + } else { + val constantState = sourceDrawable.constantState ?: return null + val drawable = constantState.newDrawable().mutate() + val bitmap: Bitmap = Bitmap.createBitmap( + drawable.intrinsicWidth, drawable.intrinsicHeight, + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + bitmap + } +} + +fun isScalingOut( + mapView: MapView, + onClick: () -> Unit = {} +) { + mapView.gestures.addOnScaleListener(object : OnScaleListener { + override fun onScale(detector: StandardScaleGestureDetector) { + if (detector.isScalingOut) { + onClick() + } + } + + override fun onScaleBegin(detector: StandardScaleGestureDetector) { + if (detector.isScalingOut) { + onClick() + } + } + + override fun onScaleEnd(detector: StandardScaleGestureDetector) { + if (detector.isScalingOut) { + onClick() + } + } + + }) +} + + +@Composable +fun MapBoxNavigation( + modifier: Modifier = Modifier, + onPointChange: (Point) -> Unit, + point: Point?, +) { + + val context = LocalContext.current + + val marker = remember(context) { + context.getDrawable(R.drawable.marker)!!.toBitmap() + } + + var pointAnnotationManager: PointAnnotationManager? by remember { + mutableStateOf(null) + } + val accessToken = stringResource(id = R.string.mapbox_access_token) + + + + + + AndroidView( + factory = { + NavigationView( + context, + accessToken = accessToken, + ).also { navigationView -> + + navigationView.api.routeReplayEnabled(true) + navigationView.api.startArrival() + + } + }, + update = { mapView -> + + }, + modifier = modifier + ) +} + +data class MapBoxPoint( + val latitude: Double, + val longitude: Double, + val zoom: Double = 15.0, + val pitch: Double = 10.0, + val bearing: Double = 1.0 +) \ No newline at end of file diff --git a/maps/src/main/java/com/example/mapbox_map/MyApp.kt b/maps/src/main/java/com/example/mapbox_map/MyApp.kt new file mode 100644 index 0000000..c526791 --- /dev/null +++ b/maps/src/main/java/com/example/mapbox_map/MyApp.kt @@ -0,0 +1,21 @@ +package com.example.mapbox_map + +import android.app.Application +import com.example.requestlocation.R +import com.mapbox.navigation.base.options.NavigationOptions +import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp + +class MyApp : Application() { + override fun onCreate() { + super.onCreate() + if (!MapboxNavigationApp.isSetup()) { + MapboxNavigationApp.setup { + NavigationOptions.Builder(applicationContext = applicationContext) + .accessToken(R.string.mapbox_access_token.toString()) + // additional options + .build() + } + } + + } +} \ No newline at end of file diff --git a/maps/src/main/java/com/example/mapbox_map/PreviewMap.kt b/maps/src/main/java/com/example/mapbox_map/PreviewMap.kt new file mode 100644 index 0000000..cf9bec6 --- /dev/null +++ b/maps/src/main/java/com/example/mapbox_map/PreviewMap.kt @@ -0,0 +1,156 @@ +package com.example.mapbox_map + +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.graphics.drawable.toBitmap +import com.example.requestlocation.R +import com.mapbox.geojson.Point +import com.mapbox.maps.CameraOptions +import com.mapbox.maps.MapView +import com.mapbox.maps.Style +import com.mapbox.maps.extension.style.atmosphere.generated.atmosphere +import com.mapbox.maps.extension.style.expressions.dsl.generated.rgb +import com.mapbox.maps.extension.style.expressions.dsl.generated.zoom +import com.mapbox.maps.extension.style.layers.properties.generated.ProjectionName +import com.mapbox.maps.extension.style.projection.generated.projection +import com.mapbox.maps.extension.style.sources.generated.rasterDemSource +import com.mapbox.maps.extension.style.style +import com.mapbox.maps.extension.style.terrain.generated.terrain +import com.mapbox.maps.plugin.animation.MapAnimationOptions +import com.mapbox.maps.plugin.animation.flyTo +import com.mapbox.maps.plugin.annotation.annotations +import com.mapbox.maps.plugin.annotation.generated.PointAnnotationManager + +@Composable +fun PreviewMap( + modifier: Modifier = Modifier, + onPointChange: (Point) -> Unit, + isClicked: MutableState = mutableStateOf(false), + isReset: MutableState = mutableStateOf(false), + points: List, + latitude: Double, + longitude: Double, + currentPoint: MutableState, +) { + + val context = LocalContext.current + + val marker = remember(context) { + context.getDrawable(R.drawable.marker)!!.toBitmap() + } + + var pointAnnotationManager: PointAnnotationManager? by remember { + mutableStateOf(null) + } + + + + AndroidView( + factory = { context -> + val cameraOptions = CameraOptions.Builder() + .center(Point.fromLngLat(points[0].longitude, points[0].latitude)) + .zoom(13.0) + .pitch(45.0) + .bearing(-17.6) + .build() + MapView(context).also { mapView -> + mapView.getMapboxMap().loadStyle( + style(Style.SATELLITE_STREETS) { + val zoom = zoom() + Log.i("Zoommmmmmmm", "${zoom.literalValue}") + +terrain("terrain-enable") + +projection(ProjectionName.MERCATOR) + +atmosphere { + color(rgb(220.0, 159.0, 159.0)) // Pink fog / lower atmosphere + highColor(rgb(220.0, 159.0, 159.0)) // Blue sky / upper atmosphere + horizonBlend(0.4) // Exaggerate atmosphere (default is .1) + } + +rasterDemSource("raster-dem") { + url("mapbox://mapbox.terrain-rgb") + } + +terrain("raster-dem") { + exaggeration(1.5) + } + } + ) + isScalingOut(mapView) { + isClicked.value = false + } + mapView.getMapboxMap().flyTo( + cameraOptions, + MapAnimationOptions.mapAnimationOptions { + duration(5000L) + } + ) + + +// val annotationApi = mapView.annotations +// pointAnnotationManager = annotationApi.createPointAnnotationManager() +// +// mapView.getMapboxMap().addOnMapClickListener { p -> +// onPointChange(p) +// true +// } + } + }, + update = { mapView -> + isScalingOut(mapView) { + isClicked.value = false + isReset.value = true + } + val cameraOptions = CameraOptions.Builder() + .center(Point.fromLngLat(longitude, latitude)) + .zoom(currentPoint.value?.zoom ?: 3.0) + .build() + mapView.annotations.cleanup() + points.forEach { mapItem -> + addAnnotationToMap( + context = context, + mapView = mapView, + point = Point.fromLngLat(mapItem.longitude, mapItem.latitude), + icon = mapItem.image + ) + } + if (isClicked.value) { + mapView.getMapboxMap().flyTo( + cameraOptions, + MapAnimationOptions.mapAnimationOptions { + duration(5000L) + } + ) + } + if (isReset.value){ + mapView.getMapboxMap().flyTo( + cameraOptions, + MapAnimationOptions.mapAnimationOptions { + duration(5000L) + } + ) + } + + +// if (point != null) { +// pointAnnotationManager?.let { +// it.deleteAll() +// val pointAnnotationOptions = PointAnnotationOptions() +// .withPoint(point) +// .withIconImage(marker) +// +// it.create(pointAnnotationOptions) +// mapView.getMapboxMap() +// .flyTo(CameraOptions.Builder().zoom(16.0).center(point).build()) +// } +// } +// NoOpUpdate + }, + modifier = modifier + ) +} \ No newline at end of file diff --git a/maps/src/main/java/com/example/mapbox_map/ui/LocationService.kt b/maps/src/main/java/com/example/mapbox_map/ui/LocationService.kt new file mode 100644 index 0000000..ea5ef48 --- /dev/null +++ b/maps/src/main/java/com/example/mapbox_map/ui/LocationService.kt @@ -0,0 +1,69 @@ +package com.example.mapbox_map.ui + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.pm.PackageManager +import android.location.Location +import android.location.LocationManager +import androidx.core.app.ActivityCompat +import com.google.android.gms.location.CurrentLocationRequest +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.Priority +import kotlinx.coroutines.tasks.await + +class LocationService { + + @SuppressLint("MissingPermission") + suspend fun getCurrentLocation(context: Context): Location { + + if (!context.hasPermissions( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) { + throw throw LocationServiceException.MissingPermissionException() + } + + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + + if (!isGpsEnabled) { + throw LocationServiceException.LocationDisabledException() + } + if (!isNetworkEnabled) { + throw LocationServiceException.NoNetworkEnabledException() + } + + val locationProvider = LocationServices.getFusedLocationProviderClient(context) + val request = CurrentLocationRequest.Builder() + .setPriority(Priority.PRIORITY_HIGH_ACCURACY) + .build() + + + try { + val location = locationProvider.getCurrentLocation(request, null).await() + + return location + + } catch (e: Exception) { + throw LocationServiceException.UnknownException(e) + } + + } + + fun Context.hasPermissions(vararg permissions: String) = + permissions.all { + ActivityCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED + } + + + sealed class LocationServiceException : Exception() { + class MissingPermissionException : LocationServiceException() + class LocationDisabledException : LocationServiceException() + class NoNetworkEnabledException : LocationServiceException() + class UnknownException(val exception: Exception) : LocationServiceException() + } + +} \ No newline at end of file diff --git a/maps/src/main/java/com/example/mapbox_map/ui/theme/Color.kt b/maps/src/main/java/com/example/mapbox_map/ui/theme/Color.kt new file mode 100644 index 0000000..d5192f8 --- /dev/null +++ b/maps/src/main/java/com/example/mapbox_map/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.example.mapbox_map.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/maps/src/main/java/com/example/mapbox_map/ui/theme/Theme.kt b/maps/src/main/java/com/example/mapbox_map/ui/theme/Theme.kt new file mode 100644 index 0000000..2adf272 --- /dev/null +++ b/maps/src/main/java/com/example/mapbox_map/ui/theme/Theme.kt @@ -0,0 +1,70 @@ +package com.example.mapbox_map.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun RequestLocationTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/maps/src/main/java/com/example/mapbox_map/ui/theme/Type.kt b/maps/src/main/java/com/example/mapbox_map/ui/theme/Type.kt new file mode 100644 index 0000000..a1fb729 --- /dev/null +++ b/maps/src/main/java/com/example/mapbox_map/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.mapbox_map.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/maps/src/main/res/drawable-v24/ic_launcher_foreground.xml b/maps/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/maps/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/maps/src/main/res/drawable/app_icon.png b/maps/src/main/res/drawable/app_icon.png new file mode 100644 index 0000000..e3d3ce3 Binary files /dev/null and b/maps/src/main/res/drawable/app_icon.png differ diff --git a/maps/src/main/res/drawable/appicon.png b/maps/src/main/res/drawable/appicon.png new file mode 100644 index 0000000..c8b2acf Binary files /dev/null and b/maps/src/main/res/drawable/appicon.png differ diff --git a/maps/src/main/res/drawable/appicons.jpg b/maps/src/main/res/drawable/appicons.jpg new file mode 100644 index 0000000..05431c6 Binary files /dev/null and b/maps/src/main/res/drawable/appicons.jpg differ diff --git a/maps/src/main/res/drawable/ic_appicon.png b/maps/src/main/res/drawable/ic_appicon.png new file mode 100644 index 0000000..a6cca46 Binary files /dev/null and b/maps/src/main/res/drawable/ic_appicon.png differ diff --git a/maps/src/main/res/drawable/ic_launcher_background.xml b/maps/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/maps/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/maps/src/main/res/drawable/marker.xml b/maps/src/main/res/drawable/marker.xml new file mode 100644 index 0000000..596040d --- /dev/null +++ b/maps/src/main/res/drawable/marker.xml @@ -0,0 +1,5 @@ + + + diff --git a/maps/src/main/res/drawable/one.jpg b/maps/src/main/res/drawable/one.jpg new file mode 100644 index 0000000..d6a2b46 Binary files /dev/null and b/maps/src/main/res/drawable/one.jpg differ diff --git a/maps/src/main/res/drawable/three.jpg b/maps/src/main/res/drawable/three.jpg new file mode 100644 index 0000000..48e758a Binary files /dev/null and b/maps/src/main/res/drawable/three.jpg differ diff --git a/maps/src/main/res/drawable/two.jpg b/maps/src/main/res/drawable/two.jpg new file mode 100644 index 0000000..758fc4c Binary files /dev/null and b/maps/src/main/res/drawable/two.jpg differ diff --git a/maps/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/maps/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/maps/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/maps/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/maps/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/maps/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/maps/src/main/res/mipmap-hdpi/ic_launcher.webp b/maps/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/maps/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/maps/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/maps/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/maps/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/maps/src/main/res/mipmap-mdpi/ic_launcher.webp b/maps/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/maps/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/maps/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/maps/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/maps/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/maps/src/main/res/mipmap-xhdpi/ic_launcher.webp b/maps/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/maps/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/maps/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/maps/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/maps/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/maps/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/maps/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/maps/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/maps/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/maps/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/maps/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/maps/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/maps/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/maps/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/maps/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/maps/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/maps/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/maps/src/main/res/values/colors.xml b/maps/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/maps/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/maps/src/main/res/values/strings.xml b/maps/src/main/res/values/strings.xml new file mode 100644 index 0000000..fc50dbd --- /dev/null +++ b/maps/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + pk.eyJ1IjoidGhla2FhaWxhc2hzaGFybWEiLCJhIjoiY2xtNm9sbW00MnY4bTNlbzl2c2Y2Z3g3byJ9.HZmK-5aP8z6hVA8IIU0q6A + MapBoxMap + \ No newline at end of file diff --git a/maps/src/main/res/values/themes.xml b/maps/src/main/res/values/themes.xml new file mode 100644 index 0000000..3da3284 --- /dev/null +++ b/maps/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +