Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ class LatestNewsCardTest : BaseAndroidComposeTest() {
composeTestRule
.onNodeWithTag(LatestNewsTestTags.LINK, useUnmergedTree = true)
.assertIsDisplayed()
.assert(hasText("read"))

// Click the right button to go to the next hazard
composeTestRule
Expand All @@ -139,7 +138,6 @@ class LatestNewsCardTest : BaseAndroidComposeTest() {
composeTestRule
.onNodeWithTag(LatestNewsTestTags.LINK, useUnmergedTree = true)
.assertIsDisplayed()
.assert(hasText("read"))
}

@Test
Expand Down Expand Up @@ -169,7 +167,6 @@ class LatestNewsCardTest : BaseAndroidComposeTest() {
composeTestRule
.onNodeWithTag(LatestNewsTestTags.LINK, useUnmergedTree = true)
.assertIsDisplayed()
.assert(hasText("read"))
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.github.warnastrophy.core.util.BaseAndroidComposeTest
import com.github.warnastrophy.core.util.formatDate
import com.google.android.gms.maps.model.LatLng
import junit.framework.TestCase
import org.junit.Assert.assertEquals
import org.junit.Test

class MapIconTest : BaseAndroidComposeTest() {
Expand Down Expand Up @@ -68,10 +69,12 @@ class MapIconTest : BaseAndroidComposeTest() {
hazard.value?.let {
HazardMarker(
it,
markerIconProvider = { _, _, _, _ -> null },
polygonContent = {},
markerInfoWindowContent = { _, _, _, _, content -> Box { content() } },
iconContent = { icon, tint -> icon(tint) })
testInjectables =
HazardMarkerTestInjectables(
markerIconProvider = { _, _, _, _ -> null },
polygonContent = {},
markerInfoWindowContent = { _, _, _, _, content -> Box { content() } },
iconContent = { icon, tint -> icon(tint) }))
}
}

Expand Down Expand Up @@ -205,10 +208,12 @@ class MapIconTest : BaseAndroidComposeTest() {
composeTestRule.setContent {
HazardMarker(
hazard = hazard,
markerIconProvider = { _, _, _, _ -> null },
polygonContent = {},
markerInfoWindowContent = { _, _, _, _, content -> Box { content() } },
iconContent = { _, _ -> })
testInjectables =
HazardMarkerTestInjectables(
markerIconProvider = { _, _, _, _ -> null },
polygonContent = {},
markerInfoWindowContent = { _, _, _, _, content -> Box { content() } },
iconContent = { _, _ -> }))
}

// Title is visible
Expand Down Expand Up @@ -241,14 +246,16 @@ class MapIconTest : BaseAndroidComposeTest() {
composeTestRule.setContent {
HazardMarker(
hazard = hazard,
markerIconProvider = { _, _, _, _ -> null },
polygonContent = {},
markerInfoWindowContent = { _, _, _, _, content -> Box { content() } },
iconContent = { icon, tint ->
receivedIcon = icon
receivedTint = tint
icon(tint)
})
testInjectables =
HazardMarkerTestInjectables(
markerIconProvider = { _, _, _, _ -> null },
polygonContent = {},
markerInfoWindowContent = { _, _, _, _, content -> Box { content() } },
iconContent = { icon, tint ->
receivedIcon = icon
receivedTint = tint
icon(tint)
}))
}

composeTestRule.waitForIdle()
Expand Down Expand Up @@ -356,10 +363,12 @@ class MapIconTest : BaseAndroidComposeTest() {
hazard = hazard,
selectedMarkerId = selectedMarkerId,
onMarkerSelected = { selectedMarkerId = it },
markerIconProvider = { _, _, _, _ -> null },
polygonContent = { coords -> polygonCoordsReceived = coords },
markerInfoWindowContent = { _, _, _, _, content -> Box { content() } },
iconContent = { _, _ -> })
testInjectables =
HazardMarkerTestInjectables(
markerIconProvider = { _, _, _, _ -> null },
polygonContent = { coords -> polygonCoordsReceived = coords },
markerInfoWindowContent = { _, _, _, _, content -> Box { content() } },
iconContent = { _, _ -> }))
}

composeTestRule.waitForIdle()
Expand Down Expand Up @@ -556,28 +565,6 @@ class MapIconTest : BaseAndroidComposeTest() {
composeTestRule.onNodeWithText("Hazard").assertIsDisplayed()
}

@Test
fun hazardMarker_invokesOnInfoWindowClickCallback() {
var onInfoWindowClickCalled = false
val hazard = hazardBasedOnType("FL").copy(articleUrl = "https://example.com/article")

setUpHazardMarker(hazard = hazard, onInfoWindowClick = { onInfoWindowClickCalled = true })

composeTestRule.waitForIdle()
TestCase.assertTrue("onInfoWindowClick should have been called", onInfoWindowClickCalled)
}

@Test
fun hazardMarker_invokesOnMarkerClickCallback() {
var markerClickId: Int? = null
val hazard = hazardBasedOnType("FL").copy(id = 999)

setUpHazardMarker(hazard = hazard, onMarkerSelected = { markerClickId = it })

composeTestRule.waitForIdle()
TestCase.assertEquals(999, markerClickId)
}

@Test
fun formatSeveritySnippet_handlesWholeNumbers() {
val hazard = hazardBasedOnType("FL").copy(severity = 100.0, severityUnit = "km")
Expand All @@ -601,25 +588,4 @@ class MapIconTest : BaseAndroidComposeTest() {
val hazard = hazardBasedOnType("FL").copy(severity = 10.0, severityUnit = " ha ")
TestCase.assertEquals("10 ha", formatSeveritySnippet(hazard))
}

private fun setUpHazardMarker(
hazard: Hazard,
onMarkerSelected: ((Int?) -> Unit)? = null,
onInfoWindowClick: (() -> Unit)? = null
) {
composeTestRule.setContent {
HazardMarker(
hazard = hazard,
onMarkerSelected = onMarkerSelected ?: { _ -> },
onInfoWindowClick = onInfoWindowClick ?: {},
markerIconProvider = { _, _, _, _ -> null },
polygonContent = {},
markerInfoWindowContent = { _, _, onMarkerClick, onInfoWindowClickLambda, content ->
onMarkerClick()
onInfoWindowClickLambda()
Box { content() }
},
iconContent = { _, _ -> })
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ class AuthRepositoryFirebase(
auth.signInWithCredential(firebaseCred).await().user
?: return Result.failure(
IllegalStateException("Login failed: Could not retrieve user information"))
return Result.success(user)

Result.success(user)
} else {
return Result.failure(
IllegalStateException("Login failed: Credential is not of type GitHub"))
Result.failure(IllegalStateException("Login failed: Credential is not of type GitHub"))
}
} catch (e: Exception) {
val errorMessage =
Expand All @@ -112,8 +112,7 @@ class AuthRepositoryFirebase(
"GitHub login failed: ${e.localizedMessage ?: UNEXPECTED_ERROR_MESSAGE}"
}
}

return Result.failure(IllegalStateException(errorMessage, e))
Result.failure(IllegalStateException(errorMessage, e))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel
import com.github.warnastrophy.core.util.ErrorDisplayManager
import kotlinx.coroutines.flow.StateFlow

// @HiltViewModel TODO: uncomment in next PR
class GlobalErrorViewModel(private val errorHandler: ErrorDisplayManager) : ViewModel() {
class GlobalErrorViewModel(errorHandler: ErrorDisplayManager) : ViewModel() {
val errorState: StateFlow<ErrorState> = errorHandler.errorState
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ object DashboardEmergencyContactsTestTags {
*
* Defines the visual appearance with a light yellow background and gray text variants.
*/
// TODO remove hardcoded colors
object DashboardEmergencyContactsCardColors {
fun getColors(colorScheme: ColorScheme, isDarkTheme: Boolean): Colors {
return Colors(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private fun CardHeader(date: String?, extendedColors: ExtendedColorScheme) {
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(id = R.string.latest_news),
text = stringResource(id = R.string.latest_news_title),
modifier = Modifier.testTag(LatestNewsTestTags.HEADER_TITLE),
color = extendedColors.newsCard.headerText,
fontWeight = FontWeight.Bold,
Expand Down Expand Up @@ -229,9 +229,9 @@ private fun RowScope.HazardInfo(

if (fetcherState.hazards.isNotEmpty() && currentHazard.articleUrl != null) {
Text(
text = "read",
text = stringResource(R.string.latest_news_read_article),
color = extendedColors.newsCard.readArticleText,
fontSize = 16.sp,
fontSize = 13.sp,
textDecoration = TextDecoration.Underline,
modifier =
Modifier.clickable { openWebPage(context, currentHazard.articleUrl) }
Expand Down Expand Up @@ -274,7 +274,7 @@ private fun HazardImage(eventType: String?) {
val imageRes = getImageForEvent(eventType ?: "default")
Image(
painter = painterResource(id = imageRes),
contentDescription = "Event Image",
contentDescription = stringResource(R.string.latest_news_event_image_description),
contentScale = ContentScale.Crop,
modifier = Modifier.size(80.dp).clip(RoundedCornerShape(8.dp)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import com.github.warnastrophy.core.data.service.HazardsDataService
import kotlinx.coroutines.flow.StateFlow

/** ViewModel responsible for providing the latest news and hazard data state to the UI. */
// @HiltViewModel TODO: uncomment in next PR
class LatestNewsViewModel(private val hazardsService: HazardsDataService) : ViewModel() {
class LatestNewsViewModel(hazardsService: HazardsDataService) : ViewModel() {
/** Exposes the fetcherState flow from the service for the UI to collect */
val fetcherState: StateFlow<FetcherState> = hazardsService.fetcherState
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,33 +138,20 @@ fun getSeverityColor(hazard: Hazard): Color {
}

/**
* Composable function to display a hazard marker on the map.
* Data class to hold injectable dependencies for [HazardMarker]. This simplifies the function
* signature and is primarily used for testing purposes.
*
* @param hazard The hazard data to be displayed.
* @param selectedMarkerId The ID of the currently selected marker (if any), used to show/hide
* polygon.
* @param onMarkerSelected Callback invoked when the marker is selected.
* @param onInfoWindowClick Callback invoked when the info window is clicked. If null (default),
* opens the article URL.
* @param markerIconProvider Injectable function for creating the marker icon, allows testing
* without GoogleMap. If null (default), uses bitmapDescriptorFromVector.
* @param polygonContent Injectable composable for rendering the polygon, allows testing without
* GoogleMap.
* @param markerInfoWindowContent Injectable composable for the marker info window, allows testing
* without GoogleMap.
* @param iconContent Injectable composable for the icon content, allows testing without GoogleMap.
* @param markerIconProvider Custom function to create a [BitmapDescriptor] for the marker.
* @param polygonContent Composable for rendering the hazard's affected area polygon.
* @param markerInfoWindowContent Composable for the entire marker and its info window.
* @param iconContent Composable for rendering the icon inside the info window.
*/
@Composable
fun HazardMarker(
hazard: Hazard,
selectedMarkerId: Int? = null,
onMarkerSelected: (Int?) -> Unit = {},
onInfoWindowClick: (() -> Unit)? = null,
markerIconProvider: ((Context, Int, Float, Color) -> BitmapDescriptor?)? = null,
polygonContent: @Composable (polygonCoords: List<LatLng>) -> Unit = { coords ->
data class HazardMarkerTestInjectables(
val markerIconProvider: ((Context, Int, Float, Color) -> BitmapDescriptor?)? = null,
val polygonContent: @Composable (polygonCoords: List<LatLng>) -> Unit = { coords ->
PolygonWrapper(coords)
},
markerInfoWindowContent:
val markerInfoWindowContent:
@Composable
(
state: MarkerState,
Expand All @@ -177,13 +164,33 @@ fun HazardMarker(
state = state,
onClick = {
onMarkerClick()
false // keep default behaviour
false
},
icon = icon,
onInfoWindowClick = { infoWindowClick() },
content = { content() })
},
iconContent: @Composable (icon: MapIcon, tint: Color) -> Unit = { icon, tint -> icon(tint) }
val iconContent: @Composable (icon: MapIcon, tint: Color) -> Unit = { icon, tint -> icon(tint) }
)

/**
* Composable function to display a hazard marker on the map.
*
* @param hazard The hazard data to be displayed.
* @param selectedMarkerId The ID of the currently selected marker (if any), used to show/hide
* polygon.
* @param onMarkerSelected Callback invoked when the marker is selected.
* @param onInfoWindowClick Callback invoked when the info window is clicked. If null (default),
* opens the article URL.
* @param testInjectables A set of injectable dependencies, primarily for testing.
*/
@Composable
fun HazardMarker(
hazard: Hazard,
selectedMarkerId: Int? = null,
onMarkerSelected: (Int?) -> Unit = {},
onInfoWindowClick: (() -> Unit)? = null,
testInjectables: HazardMarkerTestInjectables = HazardMarkerTestInjectables()
) {
val ctx = LocalContext.current

Expand All @@ -194,7 +201,7 @@ fun HazardMarker(
// Only create markerState when not in test mode (markerIconProvider null means production)
// In test mode, we'll create a fake MarkerState that won't be used
val markerState =
if (markerIconProvider == null) {
if (testInjectables.markerIconProvider == null) {
rememberMarkerState(position = Location.toLatLng(markerLocation))
} else {
// Test mode: create a minimal MarkerState without Google Maps
Expand All @@ -218,8 +225,8 @@ fun HazardMarker(
// bitmapDescriptorFromVector
val markerIcon: BitmapDescriptor? =
iconRes?.let { res ->
if (markerIconProvider != null) {
markerIconProvider(ctx, res, 32f, severityTint)
if (testInjectables.markerIconProvider != null) {
testInjectables.markerIconProvider.invoke(ctx, res, 32f, severityTint)
} else {
bitmapDescriptorFromVector(
context = ctx, vectorResId = res, sizeDp = 32f, tintColor = severityTint)
Expand All @@ -236,18 +243,18 @@ fun HazardMarker(
affectedZone?.let { locations ->
if (locations.size > 1) {
val polygonCoords = locations.map { location -> Location.toLatLng(location) }
polygonContent(polygonCoords)
testInjectables.polygonContent(polygonCoords)
}
}
}

// Determine the callback for info window click: use custom if provided, otherwise open web page
val infoWindowClickCallback = onInfoWindowClick ?: { openWebPage(ctx, hazard.articleUrl) }

markerInfoWindowContent(
testInjectables.markerInfoWindowContent(
markerState, markerIcon, { onMarkerSelected(hazard.id) }, infoWindowClickCallback) {
HazardInfoWindowContent(hazard = hazard, title = hazard.description, snippet = snippet)
iconContent(hazardTypeToMapIcon(hazard.type), severityTint)
testInjectables.iconContent(hazardTypeToMapIcon(hazard.type), severityTint)
}
}

Expand Down
1 change: 0 additions & 1 deletion app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="light_green">#FFE8F5E9</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
Expand Down
Loading
Loading