diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 1993aaac3..ddbf6b5ea 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -21,7 +21,7 @@ android {
localProperties.load(FileInputStream(localPropertiesFile))
}
- //val mapsApiKey: String = localProperties.getProperty("MAPS_API_KEY") ?: ""
+ val mapsApiKey: String = localProperties.getProperty("MAPS_API_KEY") ?: ""
defaultConfig {
applicationId = "com.github.lookupgroup27.lookup"
@@ -34,6 +34,8 @@ android {
vectorDrawables {
useSupportLibrary = true
}
+ manifestPlaceholders["MAPS_API_KEY"] = mapsApiKey
+
}
signingConfigs {
@@ -188,6 +190,14 @@ dependencies {
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
+ // Google Service and Maps
+ implementation(libs.play.services.maps)
+ implementation(libs.maps.compose)
+ implementation(libs.maps.compose.utils)
+ implementation(libs.play.services.auth)
+ implementation(libs.play.services.location)
+
+
// Unit Testing
testImplementation(libs.junit)
androidTestImplementation(libs.mockk)
diff --git a/app/src/androidTest/java/com/github/lookupgroup27/lookup/ui/googlemap/GoogleMapKtTest.kt b/app/src/androidTest/java/com/github/lookupgroup27/lookup/ui/googlemap/GoogleMapKtTest.kt
new file mode 100644
index 000000000..100aee5a8
--- /dev/null
+++ b/app/src/androidTest/java/com/github/lookupgroup27/lookup/ui/googlemap/GoogleMapKtTest.kt
@@ -0,0 +1,77 @@
+package com.github.lookupgroup27.lookup.ui.googlemap
+
+import android.Manifest
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.rule.GrantPermissionRule
+import com.github.lookupgroup27.lookup.ui.navigation.NavigationActions
+import com.github.lookupgroup27.lookup.ui.navigation.Screen
+import com.google.android.gms.maps.model.LatLng
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+class GoogleMapScreenTest {
+
+ private lateinit var navigationActions: NavigationActions
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val permissionRule: GrantPermissionRule =
+ GrantPermissionRule.grant(Manifest.permission.ACCESS_FINE_LOCATION)
+
+ @Before
+ fun setUp() {
+ // Mock NavigationActions
+ navigationActions = mock(NavigationActions::class.java)
+ // Setup to return the map route as current
+ `when`(navigationActions.currentRoute()).thenReturn(Screen.GOOGLE_MAP)
+
+ // Set the Compose content to GoogleMapScreen
+ composeTestRule.setContent { GoogleMapScreen(navigationActions) }
+ }
+
+ @Test
+ fun mapScreenDisplaysCorrectly() {
+
+ // Verify that the GoogleMapScreen is displayed
+ composeTestRule.onNodeWithTag("googleMapScreen").assertIsDisplayed()
+
+ // Ensure the bottom navigation is set up correctly
+ composeTestRule.onNodeWithTag("bottomNavigationMenu").assertIsDisplayed()
+ }
+
+ @Test
+ fun mapIsCenteredOnCurrentLocation() {
+ val fakeLocation = LatLng(37.7749, -122.4194) // Example coordinates for San Francisco
+
+ // Simulate location update
+ composeTestRule.runOnIdle {
+ // Update the locationProvider's currentLocation value
+ // This part depends on how you can access and update the locationProvider in your test
+ }
+
+ // Verify if the map is centered on the current location
+ composeTestRule.onNodeWithTag("googleMapScreen").assertIsDisplayed()
+ // Add more assertions to verify the map's camera position if possible
+ }
+
+ @Test
+ fun markerIsDisplayedOnCurrentLocation() {
+ val fakeLocation = LatLng(37.7749, -122.4194) // Example coordinates for San Francisco
+
+ // Simulate location update
+ composeTestRule.runOnIdle {
+ // Update the locationProvider's currentLocation value
+ // This part depends on how you can access and update the locationProvider in your test
+ }
+
+ // Verify if the marker is displayed on the current location
+ composeTestRule.onNodeWithTag("googleMapScreen").assertIsDisplayed()
+ // Add more assertions to verify the marker's position if possible
+ }
+}
diff --git a/app/src/androidTest/java/com/github/lookupgroup27/lookup/ui/overview/MenuKtTest.kt b/app/src/androidTest/java/com/github/lookupgroup27/lookup/ui/overview/MenuKtTest.kt
index 81827f454..d566e69a4 100644
--- a/app/src/androidTest/java/com/github/lookupgroup27/lookup/ui/overview/MenuKtTest.kt
+++ b/app/src/androidTest/java/com/github/lookupgroup27/lookup/ui/overview/MenuKtTest.kt
@@ -65,7 +65,7 @@ class MenuKtTest {
// Check that all buttons are displayed
composeTestRule.onNodeWithText("Quizzes").assertIsDisplayed()
composeTestRule.onNodeWithText("Calendar").assertIsDisplayed()
- composeTestRule.onNodeWithText("Sky Tracker").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Google Map").assertIsDisplayed()
composeTestRule.onNodeWithTag("profile_button").assertIsDisplayed()
}
@@ -115,9 +115,9 @@ class MenuKtTest {
composeTestRule.setContent { MenuScreen(navigationActions = mockNavigationActions) }
// Perform click on "Sky Tracker" button
- composeTestRule.onNodeWithText("Sky Tracker").performClick()
+ composeTestRule.onNodeWithText("Google Map").performClick()
// Verify navigation to Sky Tracker screen is triggered
- verify(mockNavigationActions).navigateTo(Screen.SKY_TRACKER)
+ verify(mockNavigationActions).navigateTo(Screen.GOOGLE_MAP)
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ad038ba8a..0e88e87f7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/MainActivity.kt b/app/src/main/java/com/github/lookupgroup27/lookup/MainActivity.kt
index 94d26ec79..5d9be0139 100644
--- a/app/src/main/java/com/github/lookupgroup27/lookup/MainActivity.kt
+++ b/app/src/main/java/com/github/lookupgroup27/lookup/MainActivity.kt
@@ -17,6 +17,7 @@ import com.github.lookupgroup27.lookup.model.profile.ProfileViewModel
import com.github.lookupgroup27.lookup.model.quiz.QuizViewModel
import com.github.lookupgroup27.lookup.ui.authentication.SignInScreen
import com.github.lookupgroup27.lookup.ui.calendar.CalendarScreen
+import com.github.lookupgroup27.lookup.ui.googlemap.GoogleMapScreen
import com.github.lookupgroup27.lookup.ui.map.MapScreen
import com.github.lookupgroup27.lookup.ui.navigation.NavigationActions
import com.github.lookupgroup27.lookup.ui.navigation.Route
@@ -28,7 +29,6 @@ import com.github.lookupgroup27.lookup.ui.profile.ProfileInformationScreen
import com.github.lookupgroup27.lookup.ui.profile.ProfileScreen
import com.github.lookupgroup27.lookup.ui.quiz.QuizPlayScreen
import com.github.lookupgroup27.lookup.ui.quiz.QuizScreen
-import com.github.lookupgroup27.lookup.ui.skytracker.SkyTrackerScreen
import com.github.lookupgroup27.lookup.ui.theme.LookUpTheme
import com.google.firebase.auth.FirebaseAuth
@@ -79,7 +79,7 @@ fun LookUpApp() {
composable(Screen.MENU) { MenuScreen(navigationActions) }
composable(Screen.PROFILE) { ProfileScreen(navigationActions) }
composable(Screen.CALENDAR) { CalendarScreen(calendarViewModel, navigationActions) }
- composable(Screen.SKY_TRACKER) { SkyTrackerScreen(navigationActions) }
+ composable(Screen.GOOGLE_MAP) { GoogleMapScreen(navigationActions) }
composable(Screen.QUIZ) { QuizScreen(quizViewModel, navigationActions) }
}
diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/model/location/LocationProvider.kt b/app/src/main/java/com/github/lookupgroup27/lookup/model/location/LocationProvider.kt
new file mode 100644
index 000000000..775b19d23
--- /dev/null
+++ b/app/src/main/java/com/github/lookupgroup27/lookup/model/location/LocationProvider.kt
@@ -0,0 +1,25 @@
+package com.github.lookupgroup27.lookup.model.location
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.location.Location
+import androidx.compose.runtime.mutableStateOf
+import androidx.core.app.ActivityCompat
+import com.google.android.gms.location.*
+
+class LocationProvider(private val context: Context) {
+ private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
+ var currentLocation = mutableStateOf(null)
+
+ fun requestLocationUpdates() {
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) !=
+ PackageManager.PERMISSION_GRANTED) {
+ // Handle permission request
+ return
+ }
+ fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? ->
+ currentLocation.value = location
+ }
+ }
+}
diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/ui/googlemap/GoogleMap.kt b/app/src/main/java/com/github/lookupgroup27/lookup/ui/googlemap/GoogleMap.kt
new file mode 100644
index 000000000..7ed4768f2
--- /dev/null
+++ b/app/src/main/java/com/github/lookupgroup27/lookup/ui/googlemap/GoogleMap.kt
@@ -0,0 +1,104 @@
+package com.github.lookupgroup27.lookup.ui.googlemap
+
+import android.Manifest
+import android.app.Activity
+import android.content.pm.PackageManager
+import android.location.Location
+import android.widget.Toast
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+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.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import com.github.lookupgroup27.lookup.model.location.LocationProvider
+import com.github.lookupgroup27.lookup.ui.navigation.BottomNavigationMenu
+import com.github.lookupgroup27.lookup.ui.navigation.LIST_TOP_LEVEL_DESTINATION
+import com.github.lookupgroup27.lookup.ui.navigation.NavigationActions
+import com.google.android.gms.maps.model.CameraPosition
+import com.google.android.gms.maps.model.LatLng
+import com.google.maps.android.compose.GoogleMap
+import com.google.maps.android.compose.MapProperties
+import com.google.maps.android.compose.MapType
+import com.google.maps.android.compose.MapUiSettings
+import com.google.maps.android.compose.Marker
+import com.google.maps.android.compose.MarkerState
+import com.google.maps.android.compose.rememberCameraPositionState
+
+private const val LOCATION_PERMISSION_REQUEST_CODE = 1001
+
+@Composable
+fun GoogleMapScreen(navigationActions: NavigationActions) {
+ val context = LocalContext.current
+ var hasLocationPermission by remember { mutableStateOf(false) }
+ val locationProvider = remember { LocationProvider(context) }
+
+ LaunchedEffect(Unit) {
+ hasLocationPermission =
+ ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
+ PackageManager.PERMISSION_GRANTED
+ if (hasLocationPermission) {
+ locationProvider.requestLocationUpdates()
+ } else {
+ // Request permission
+ ActivityCompat.requestPermissions(
+ context as Activity,
+ arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
+ LOCATION_PERMISSION_REQUEST_CODE)
+ Toast.makeText(
+ context, "Location permission is required to access the map.", Toast.LENGTH_LONG)
+ .show()
+ }
+ }
+
+ Scaffold(
+ modifier = Modifier.testTag("googleMapScreen"),
+ bottomBar = {
+ BottomNavigationMenu(
+ onTabSelect = { route -> navigationActions.navigateTo(route) },
+ tabList = LIST_TOP_LEVEL_DESTINATION,
+ selectedItem = navigationActions.currentRoute())
+ },
+ content = { padding ->
+ MapView(
+ padding,
+ hasLocationPermission,
+ locationProvider.currentLocation.value) // Pass current location to MapView
+ })
+}
+
+@Composable
+fun MapView(padding: PaddingValues, hasLocationPermission: Boolean, location: Location?) {
+ var mapProperties by remember { mutableStateOf(MapProperties(mapType = MapType.NORMAL)) }
+ var mapUiSettings by remember { mutableStateOf(MapUiSettings(zoomControlsEnabled = false)) }
+ val cameraPositionState = rememberCameraPositionState()
+
+ LaunchedEffect(location) {
+ if (hasLocationPermission && location != null) {
+ val latLng = LatLng(location.latitude, location.longitude)
+ cameraPositionState.position = CameraPosition.fromLatLngZoom(latLng, 5f)
+ }
+ }
+
+ GoogleMap(
+ modifier = Modifier.fillMaxSize().padding(padding),
+ properties = mapProperties,
+ uiSettings = mapUiSettings,
+ cameraPositionState = cameraPositionState) {
+ if (hasLocationPermission && location != null) {
+ val latLng = LatLng(location.latitude, location.longitude)
+ Marker(state = MarkerState(position = latLng), title = "You are here")
+ } else {
+ // case where location is not available
+ }
+ }
+}
diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/ui/map/Map.kt b/app/src/main/java/com/github/lookupgroup27/lookup/ui/map/Map.kt
index 8f6815ae2..bc1fb434e 100644
--- a/app/src/main/java/com/github/lookupgroup27/lookup/ui/map/Map.kt
+++ b/app/src/main/java/com/github/lookupgroup27/lookup/ui/map/Map.kt
@@ -21,7 +21,7 @@ fun MapScreen(navigationActions: NavigationActions) {
BottomNavigationMenu(
onTabSelect = { destination -> navigationActions.navigateTo(destination) },
tabList = LIST_TOP_LEVEL_DESTINATION,
- selectedItem = Route.MENU)
+ selectedItem = Route.MAP)
}) { innerPadding ->
Box(modifier = Modifier.fillMaxSize().padding(innerPadding).testTag("map_screen")) {
Image(
diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActions.kt b/app/src/main/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActions.kt
index b1dd3c86f..901382bba 100644
--- a/app/src/main/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActions.kt
+++ b/app/src/main/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActions.kt
@@ -12,7 +12,7 @@ object Route {
const val LANDING = "Landing"
const val MAP = "Map"
const val CALENDAR = "Calendar"
- const val SKY_TRACKER = "SkyTracker"
+ const val GOOGLE_MAP = "Google Map"
const val QUIZ = "Quiz"
const val QUIZ_PLAY = "QuizPlay"
const val PROFILE = "Profile"
@@ -26,7 +26,7 @@ object Screen {
const val LANDING = "Landing Screen"
const val MAP = "Map Screen"
const val CALENDAR = "Calendar Screen"
- const val SKY_TRACKER = "Sky Tracker Screen"
+ const val GOOGLE_MAP = "Google Map Screen"
const val QUIZ = "Quiz Screen"
const val QUIZ_PLAY = "Quiz Play Screen"
const val PROFILE = "Profile Screen"
diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/ui/overview/Menu.kt b/app/src/main/java/com/github/lookupgroup27/lookup/ui/overview/Menu.kt
index 37e249a69..c8ce2dc3d 100644
--- a/app/src/main/java/com/github/lookupgroup27/lookup/ui/overview/Menu.kt
+++ b/app/src/main/java/com/github/lookupgroup27/lookup/ui/overview/Menu.kt
@@ -82,8 +82,8 @@ fun MenuScreen(navigationActions: NavigationActions) {
Button(onClick = { navigationActions.navigateTo(Screen.CALENDAR) }) {
Text(text = "Calendar", style = MaterialTheme.typography.headlineSmall)
}
- Button(onClick = { navigationActions.navigateTo(Screen.SKY_TRACKER) }) {
- Text(text = "Sky Tracker", style = MaterialTheme.typography.headlineSmall)
+ Button(onClick = { navigationActions.navigateTo(Screen.GOOGLE_MAP) }) {
+ Text(text = "Google Map", style = MaterialTheme.typography.headlineSmall)
}
}
}
diff --git a/app/src/test/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActionsTest.kt b/app/src/test/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActionsTest.kt
index 2d8c4fa17..919c276d6 100644
--- a/app/src/test/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActionsTest.kt
+++ b/app/src/test/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActionsTest.kt
@@ -44,8 +44,8 @@ class NavigationActionsTest {
verify(navHostController).navigate(eq(Route.MENU), any Unit>())
// Test navigating to specific screens
- navigationActions.navigateTo(Screen.SKY_TRACKER)
- verify(navHostController).navigate(Screen.SKY_TRACKER)
+ navigationActions.navigateTo(Screen.GOOGLE_MAP)
+ verify(navHostController).navigate(Screen.GOOGLE_MAP)
navigationActions.navigateTo(Screen.QUIZ)
verify(navHostController).navigate(Screen.QUIZ)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index d1ce711cb..b9a893c6e 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -65,6 +65,13 @@ firebaseFirestore = "25.1.0"
firebaseUiAuth = "8.0.0"
navigationRuntimeKtx = "2.8.2"
+# Google Service and Maps
+playServicesAuth = "21.2.0"
+playServicesMaps = "19.0.0"
+playServicesLocation = "21.3.0"
+mapsCompose = "4.3.3"
+mapsComposeUtils = "4.3.0"
+
# Calendar Libraries
ical4j = "3.0.21"
compose = "1.5.1"
@@ -150,6 +157,14 @@ ical4j = { module = "org.mnode.ical4j:ical4j", version.ref = "ical4j" }
compose = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "compose" }
androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "navigationTesting" }
+maps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "mapsCompose" }
+maps-compose-utils = { module = "com.google.maps.android:maps-compose-utils", version.ref = "mapsComposeUtils" }
+play-services-auth = { module = "com.google.android.gms:play-services-auth", version.ref = "playServicesAuth" }
+play-services-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "playServicesMaps" }
+
+play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" }
+
+
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }