Skip to content

Commit

Permalink
Feature/path-activities (#213)
Browse files Browse the repository at this point in the history
* refactor: inject API key through DirectionsRepository constructor

* test: update tests to accommodate API key in DirectionsRepository

* refactor: modify factory to include API key injection

* test: update tests to adapt for the API key in DirectionsViewModelTest

* feature: add DirectionsViewModel in MainActivity

* build: add MAPS_API_KEY to BuildConfig for DirectionsViewModel

* feat: add path between activities

* test: update tests in ActivitiesMapScreenTest to handle paths between Activities

* test: add new legs and activity

* format: format to ktfmtFormat

* fix: filter out activities with invalid locations
  • Loading branch information
Unsaved2 authored Nov 21, 2024
1 parent 4817024 commit fcf9336
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 68 deletions.
8 changes: 8 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ android {
vectorDrawables {
useSupportLibrary = true
}

// Declare the API key as a BuildConfig field
buildConfigField("String", "MAPS_API_KEY", "\"$mapsApiKey\"")

// Enable BuildConfig functionality
buildFeatures {
buildConfig = true
}
}

buildTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.github.se.travelpouch.model.activity.Activity
import com.github.se.travelpouch.model.activity.ActivityRepository
import com.github.se.travelpouch.model.activity.ActivityViewModel
import com.github.se.travelpouch.model.activity.map.DirectionsRepositoryInterface
import com.github.se.travelpouch.model.activity.map.DirectionsResponse
import com.github.se.travelpouch.model.activity.map.DirectionsViewModel
import com.github.se.travelpouch.model.activity.map.Leg
import com.github.se.travelpouch.model.activity.map.OverviewPolyline
import com.github.se.travelpouch.model.activity.map.Route
import com.github.se.travelpouch.model.travels.Location
import com.github.se.travelpouch.ui.navigation.NavigationActions
import com.google.android.gms.maps.model.LatLng
import com.google.firebase.Timestamp
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
Expand All @@ -28,8 +36,10 @@ class ActivitiesMapScreenTest {
private lateinit var mockActivityRepositoryFirebase: ActivityRepository
private lateinit var mockActivityModelView: ActivityViewModel
private lateinit var mockNavigationActions: NavigationActions
private lateinit var mockkDirectionsViewModel: DirectionsViewModel
private lateinit var mockkDirectionsRepository: DirectionsRepositoryInterface

val listOfActivities =
private val listOfActivities =
listOf(
Activity(
uid = "1",
Expand All @@ -44,7 +54,43 @@ class ActivitiesMapScreenTest {
description = "Presentation to showcase the project to the client.",
location = Location(40.0, -122.4194, Timestamp.now(), "Paris"),
date = Timestamp(Timestamp.now().seconds + 3600, Timestamp.now().nanoseconds),
documentsNeeded = null))
documentsNeeded = null),
Activity(
uid = "3",
title = "Workshop",
description = "Workshop on team building and skill development.",
location = Location(51.5074, -0.1278, Timestamp.now(), "London"),
date = Timestamp(Timestamp.now().seconds + 7200, Timestamp.now().nanoseconds),
documentsNeeded = mapOf("Workshop Material" to 1)))

private val mockLeg =
Leg(
distanceText = "3.4 km",
distanceValue = 3400,
durationText = "15 mins",
durationValue = 900,
startAddress = "Start Address",
endAddress = "End Address",
startLocation = LatLng(37.7749, -122.4194),
endLocation = LatLng(34.0522, -118.2437),
overviewPolyline = OverviewPolyline("u{~vFvyys@fC_y@"))

private val mockLeg2 =
Leg(
distanceText = "5.2 km",
distanceValue = 5200,
durationText = "25 mins",
durationValue = 1500,
startAddress = "End Address",
endAddress = "Next Address",
startLocation = LatLng(34.0522, -118.2437),
endLocation = LatLng(36.1699, -115.1398),
overviewPolyline = OverviewPolyline("a~bcFghij@dE_g@"))

private val mockResponse =
DirectionsResponse(
routes =
listOf(Route(OverviewPolyline("u{~vFvyys@fC_y@"), legs = listOf(mockLeg, mockLeg2))))

@get:Rule val composeTestRule = createComposeRule()

Expand All @@ -53,6 +99,23 @@ class ActivitiesMapScreenTest {
mockNavigationActions = mock(NavigationActions::class.java)
mockActivityRepositoryFirebase = mock(ActivityRepository::class.java)
mockActivityModelView = ActivityViewModel(mockActivityRepositoryFirebase)

mockkDirectionsRepository = mock(DirectionsRepositoryInterface::class.java)
mockkDirectionsViewModel = DirectionsViewModel(mockkDirectionsRepository)

doAnswer { invocation ->
val onSuccess = invocation.getArgument(4) as (DirectionsResponse) -> Unit
onSuccess(mockResponse)
null
}
.whenever(mockkDirectionsRepository)
.getDirections(
origin = anyString(),
destination = anyString(),
mode = anyString(),
waypoints = anyOrNull(),
onSuccess = any(),
onFailure = any())
}

@Test
Expand All @@ -68,7 +131,9 @@ class ActivitiesMapScreenTest {

mockActivityModelView.getAllActivities()

composeTestRule.setContent { ActivitiesMapScreen(mockActivityModelView, mockNavigationActions) }
composeTestRule.setContent {
ActivitiesMapScreen(mockActivityModelView, mockNavigationActions, mockkDirectionsViewModel)
}

composeTestRule.onNodeWithTag("Map").assertExists()
}
Expand All @@ -79,26 +144,26 @@ class ActivitiesMapScreenTest {
`when`(mockActivityRepositoryFirebase.getAllActivities(any(), any())).then {
it.getArgument<(List<Activity>) -> Unit>(0)(listOf())
}
composeTestRule.setContent { ActivitiesMapScreen(mockActivityModelView, mockNavigationActions) }
composeTestRule.setContent {
ActivitiesMapScreen(mockActivityModelView, mockNavigationActions, mockkDirectionsViewModel)
}

composeTestRule.onNodeWithTag("Map").assertExists()
}

@Test
fun testGoBackButton() {
// Configurer le contenu de la règle Compose
composeTestRule.setContent {
ActivitiesMapScreen(
activityViewModel = mockActivityModelView, navigationActions = mockNavigationActions)
activityViewModel = mockActivityModelView,
navigationActions = mockNavigationActions,
directionsViewModel = mockkDirectionsViewModel)
}

// Attendre que l'interface utilisateur soit prête
composeTestRule.waitForIdle()

// Simuler le clic sur le bouton "Go Back"
composeTestRule.onNodeWithTag("GoBackButton").performClick()

// Vérifier que la méthode goBack() a été appelée
verify(mockNavigationActions).goBack()
}
}
9 changes: 8 additions & 1 deletion app/src/main/java/com/github/se/travelpouch/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import com.github.se.travelpouch.model.activity.ActivityViewModel
import com.github.se.travelpouch.model.activity.map.DirectionsViewModel
import com.github.se.travelpouch.model.authentication.AuthenticationService
import com.github.se.travelpouch.model.dashboard.CalendarViewModel
import com.github.se.travelpouch.model.documents.DocumentViewModel
Expand Down Expand Up @@ -82,6 +83,12 @@ class MainActivity : ComponentActivity() {
val calendarViewModel: CalendarViewModel =
viewModel(factory = CalendarViewModel.Factory(activityModelView))

val directionsViewModel: DirectionsViewModel =
viewModel(
factory =
DirectionsViewModel.provideFactory(
BuildConfig.MAPS_API_KEY) // Inject the API key for the DirectionsViewModel
)
NavHost(navController = navController, startDestination = Route.DEFAULT) {
navigation(
startDestination = Screen.AUTH,
Expand Down Expand Up @@ -117,7 +124,7 @@ class MainActivity : ComponentActivity() {
}

composable(Screen.ACTIVITIES_MAP) {
ActivitiesMapScreen(activityModelView, navigationActions)
ActivitiesMapScreen(activityModelView, navigationActions, directionsViewModel)
}

composable(Screen.PARTICIPANT_LIST) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import okhttp3.Request
import org.json.JSONObject

/** Repository for fetching directions using the Google Maps Directions API. */
class DirectionsRepository(client: OkHttpClient) : DirectionsRepositoryInterface {
class DirectionsRepository(client: OkHttpClient, private val apiKey: String) :
DirectionsRepositoryInterface {

private val networkManager: NetworkManager = NetworkManager(client)

Expand Down Expand Up @@ -120,7 +121,6 @@ class DirectionsRepository(client: OkHttpClient) : DirectionsRepositoryInterface
* @param origin The starting point of the route, formatted as "latitude,longitude".
* @param destination The ending point of the route, formatted as "latitude,longitude".
* @param mode The travel mode ("driving", "walking", "bicycling", or "transit").
* @param apiKey The API key used to authenticate the request.
* @param waypoints A string of waypoints formatted as "latitude,longitude|latitude,longitude"
* (optional).
* @param onSuccess Callback that is called when the request is successful.
Expand All @@ -131,7 +131,6 @@ class DirectionsRepository(client: OkHttpClient) : DirectionsRepositoryInterface
origin: String,
destination: String,
mode: String,
apiKey: String,
waypoints: String?,
onSuccess: (DirectionsResponse) -> Unit,
onFailure: (Exception) -> Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ interface DirectionsRepositoryInterface {
origin: String,
destination: String,
mode: String,
apiKey: String,
waypoints: String? = null,
onSuccess: (DirectionsResponse) -> Unit,
onFailure: (Exception) -> Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ data class RouteDetails(
}
}

/** ViewModel for fetching and managing directions data from the Google Maps Directions API. */
/**
* ViewModel for fetching directions between activities using the Google Maps Directions API.
*
* @param repository The repository to fetch directions from.
*/
class DirectionsViewModel(private val repository: DirectionsRepositoryInterface) : ViewModel() {

// StateFlow to hold the fetched route details for activities
Expand All @@ -45,17 +49,26 @@ class DirectionsViewModel(private val repository: DirectionsRepositoryInterface)
/** Factory class for creating DirectionsViewModel instances. */
// create factory
companion object {
val Factory: ViewModelProvider.Factory =
fun provideFactory(apiKey: String): ViewModelProvider.Factory =
object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return DirectionsViewModel(DirectionsRepository(OkHttpClient())) as T
return DirectionsViewModel(
DirectionsRepository(
client = OkHttpClient(), apiKey = apiKey // Inject API key dynamically
))
as T
}
}
}

/** Function to fetch directions between activities sequentially. */
fun fetchDirectionsForActivities(activities: List<Activity>, mode: String, apiKey: String) {
/**
* Fetches directions for a list of activities using the specified travel mode.
*
* @param activities The list of activities to create a route for.
* @param mode The travel mode ("driving", "walking", "bicycling", or "transit").
*/
fun fetchDirectionsForActivities(activities: List<Activity>, mode: String) {
if (activities.size < 2) {
Log.e("DirectionsViewModel", "Not enough activities to create a route")
return
Expand Down Expand Up @@ -84,7 +97,7 @@ class DirectionsViewModel(private val repository: DirectionsRepositoryInterface)
}

// Fetch directions using the extracted origin, destination, and waypoints
fetchDirections(origin, destination, mode, apiKey, waypoints)
fetchDirections(origin, destination, mode, waypoints)
}

/**
Expand All @@ -93,13 +106,11 @@ class DirectionsViewModel(private val repository: DirectionsRepositoryInterface)
* @param origin The starting point of the route as a LatLng object.
* @param destination The ending point of the route as a LatLng object.
* @param mode The travel mode ("driving", "walking", "bicycling", or "transit").
* @param apiKey The API key for the Google Maps Directions API.
*/
fun fetchDirections(
origin: LatLng,
destination: LatLng,
mode: String,
apiKey: String,
waypoints: List<LatLng>? = null
) {

Expand All @@ -113,7 +124,6 @@ class DirectionsViewModel(private val repository: DirectionsRepositoryInterface)
origin = originStr,
destination = destinationStr,
mode = mode,
apiKey = apiKey,
waypoints = waypointsStr,
onSuccess = { directionsResponse ->
// Use extractRouteDetails to extract the route details
Expand Down
Loading

0 comments on commit fcf9336

Please sign in to comment.