diff --git a/.github/workflows/lint-report.yml b/.github/workflows/lint-report.yml index c92c2c8d..e5748066 100644 --- a/.github/workflows/lint-report.yml +++ b/.github/workflows/lint-report.yml @@ -35,11 +35,26 @@ jobs: - name: Run Android Lint run: ./gradlew lint - - name: Merge SARIF files - run: | - jq -s '{ "$schema": "https://json.schemastore.org/sarif-2.1.0", "version": "2.1.0", "runs": map(.runs) | add }' maps-compose/build/reports/lint-results.sarif maps-compose-utils/build/reports/lint-results.sarif maps-compose-widgets/build/reports/lint-results.sarif maps-app/build/reports/lint-results.sarif > merged.sarif + - name: Upload SARIF from maps-compose + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: maps-compose/build/reports/lint-results.sarif + category: maps-compose + + - name: Upload SARIF from maps-compose-utils + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: maps-compose-utils/build/reports/lint-results.sarif + category: maps-compose-utils + + - name: Upload SARIF from maps-compose-widgets + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: maps-compose-widgets/build/reports/lint-results.sarif + category: maps-compose-widgets - - name: Upload SARIF file + - name: Upload SARIF from maps-app uses: github/codeql-action/upload-sarif@v3 with: - sarif_file: merged.sarif + sarif_file: maps-app/build/reports/lint-results.sarif + category: maps-app diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fa02bfbe..22e698a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,8 @@ mapsecrets = "2.0.1" mapsktx = "5.2.0" org-jacoco-core = "0.8.12" screenshot = "0.0.1-alpha10" +constraintlayout = "2.2.1" +material = "1.12.0" [libraries] android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } @@ -48,6 +50,8 @@ maps-ktx-utils = { module = "com.google.maps.android:maps-utils-ktx", version.re maps-secrets-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "mapsecrets" } org-jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "org-jacoco-core" } test-junit = { module = "junit:junit", version.ref = "junit" } +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/maps-app/build.gradle.kts b/maps-app/build.gradle.kts index 7af6545d..53fa4232 100644 --- a/maps-app/build.gradle.kts +++ b/maps-app/build.gradle.kts @@ -65,6 +65,8 @@ dependencies { implementation(libs.kotlin) implementation(libs.kotlinx.coroutines.android) implementation(libs.androidx.compose.ui.preview.tooling) + implementation(libs.androidx.constraintlayout) + implementation(libs.material) debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.leakcanary.android) diff --git a/maps-app/src/main/AndroidManifest.xml b/maps-app/src/main/AndroidManifest.xml index 0063f72d..484beb71 100644 --- a/maps-app/src/main/AndroidManifest.xml +++ b/maps-app/src/main/AndroidManifest.xml @@ -72,6 +72,9 @@ + diff --git a/maps-app/src/main/java/com/google/maps/android/compose/FragmentDemoActivity.kt b/maps-app/src/main/java/com/google/maps/android/compose/FragmentDemoActivity.kt new file mode 100644 index 00000000..0caaf178 --- /dev/null +++ b/maps-app/src/main/java/com/google/maps/android/compose/FragmentDemoActivity.kt @@ -0,0 +1,23 @@ +package com.google.maps.android.compose + + +import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.widget.ViewPager2 + +class FragmentDemoActivity : FragmentActivity() { + + private lateinit var viewPager: ViewPager2 + private lateinit var pagerAdapter: MapFragmentPagerAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_fragment_demo) + + viewPager = findViewById(R.id.view_pager) + + pagerAdapter = MapFragmentPagerAdapter(this) + viewPager.adapter = pagerAdapter + + } +} diff --git a/maps-app/src/main/java/com/google/maps/android/compose/GoogleMapComposeFragment.kt b/maps-app/src/main/java/com/google/maps/android/compose/GoogleMapComposeFragment.kt new file mode 100644 index 00000000..fde1cdc8 --- /dev/null +++ b/maps-app/src/main/java/com/google/maps/android/compose/GoogleMapComposeFragment.kt @@ -0,0 +1,142 @@ +package com.google.maps.android.compose + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.geometry.Offset // For MarkerComposable anchor +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng + + +enum class MarkerType { + CUSTOM_CONTENT_MARKER, // To use com.google.maps.android.compose.MarkerComposable with your Text + STANDARD_MARKER_WITH_SNIPPET // To use the standard com.google.maps.android.compose.Marker +} + +data class MapConfig( + val initialLatLng: LatLng, + val initialZoom: Float, + val title: String, + val markerType: MarkerType = MarkerType.CUSTOM_CONTENT_MARKER, // Default + val standardMarkerSnippet: String? = null +) + +class GoogleMapComposeFragment : Fragment() { + + private var mapConfig: MapConfig? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + val lat = it.getDouble(ARG_LAT, Double.NaN) + val lng = it.getDouble(ARG_LNG, Double.NaN) + val zoom = it.getFloat(ARG_ZOOM, 10f) + val title = it.getString(ARG_TITLE, "Map") + + val markerTypeName = it.getString(ARG_MARKER_TYPE) + val markerType = markerTypeName?.let { name -> + try { + MarkerType.valueOf(name) + } catch (e: IllegalArgumentException) { + MarkerType.CUSTOM_CONTENT_MARKER + } + } ?: MarkerType.CUSTOM_CONTENT_MARKER + + val snippet = it.getString(ARG_STANDARD_MARKER_SNIPPET) + + if (!lat.isNaN() && !lng.isNaN()) { + mapConfig = MapConfig( + initialLatLng = LatLng(lat, lng), + initialZoom = zoom, + title = title, + markerType = markerType, + standardMarkerSnippet = snippet + ) + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme { + val currentConfig = mapConfig ?: MapConfig( + initialLatLng = LatLng(0.0, 0.0), + initialZoom = 2f, + title = "Default Map", + markerType = MarkerType.CUSTOM_CONTENT_MARKER + ) + MapContent(config = currentConfig) + } + } + } + + @Composable + fun MapContent(config: MapConfig) { + val cameraPositionState = rememberCameraPositionState { + position = CameraPosition.fromLatLngZoom(config.initialLatLng, config.initialZoom) + } + + GoogleMap( + cameraPositionState = cameraPositionState + ) { + when (config.markerType) { + MarkerType.CUSTOM_CONTENT_MARKER -> { + val markerState = rememberUpdatedMarkerState(position = config.initialLatLng) + markerState.position = config.initialLatLng + + MarkerComposable( + state = markerState, + anchor = Offset(0.5f, 1.0f) + ){ + Text(text = "Hello, World! (from ${config.title})") + } + } + MarkerType.STANDARD_MARKER_WITH_SNIPPET -> { + val markerState = rememberUpdatedMarkerState(position = config.initialLatLng) + markerState.position = config.initialLatLng + + Marker( + state = markerState, + title = config.title, + snippet = config.standardMarkerSnippet ?: "Standard Marker Snippet" // Snippet for the info window + ) + } + } + } + } + + companion object { + private const val ARG_LAT = "arg_lat" + private const val ARG_LNG = "arg_lng" + private const val ARG_ZOOM = "arg_zoom" + private const val ARG_TITLE = "arg_title" + private const val ARG_MARKER_TYPE = "arg_marker_type" + private const val ARG_STANDARD_MARKER_SNIPPET = "arg_standard_marker_snippet" + + @JvmStatic + fun newInstance(config: MapConfig): GoogleMapComposeFragment { + return GoogleMapComposeFragment().apply { + arguments = Bundle().apply { + putDouble(ARG_LAT, config.initialLatLng.latitude) + putDouble(ARG_LNG, config.initialLatLng.longitude) + putFloat(ARG_ZOOM, config.initialZoom) + putString(ARG_TITLE, config.title) + putString(ARG_MARKER_TYPE, config.markerType.name) + config.standardMarkerSnippet?.let { putString(ARG_STANDARD_MARKER_SNIPPET, it) } + } + } + } + } +} diff --git a/maps-app/src/main/java/com/google/maps/android/compose/MainActivity.kt b/maps-app/src/main/java/com/google/maps/android/compose/MainActivity.kt index c7dbd8af..96c47b8f 100644 --- a/maps-app/src/main/java/com/google/maps/android/compose/MainActivity.kt +++ b/maps-app/src/main/java/com/google/maps/android/compose/MainActivity.kt @@ -199,6 +199,13 @@ class MainActivity : ComponentActivity() { }) { Text(getString(R.string.draggable_markers_collection_with_polygon)) } + Spacer(modifier = Modifier.padding(5.dp)) + Button( + onClick = { + context.startActivity(Intent(context, FragmentDemoActivity::class.java)) + }) { + Text(getString(R.string.fragment_demo_activity)) + } } } } diff --git a/maps-app/src/main/java/com/google/maps/android/compose/MapFragmentPagerAdapter.kt b/maps-app/src/main/java/com/google/maps/android/compose/MapFragmentPagerAdapter.kt new file mode 100644 index 00000000..cb2cd092 --- /dev/null +++ b/maps-app/src/main/java/com/google/maps/android/compose/MapFragmentPagerAdapter.kt @@ -0,0 +1,33 @@ +package com.google.maps.android.compose + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.gms.maps.model.LatLng + +class MapFragmentPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) { + + private val mapConfigs = listOf( + MapConfig( // First map - Los Angeles + initialLatLng = LatLng(34.0522, -118.2437), + initialZoom = 10f, + title = "Los Angeles", + // LA gets the custom content marker + markerType = MarkerType.CUSTOM_CONTENT_MARKER + ), + MapConfig( // Second map - New York City + initialLatLng = LatLng(40.7128, -74.0060), + initialZoom = 10f, + title = "New York City", + // NYC gets the standard marker + markerType = MarkerType.STANDARD_MARKER_WITH_SNIPPET, + standardMarkerSnippet = "The Big Apple!" + ) + ) + + override fun getItemCount(): Int = mapConfigs.size + + override fun createFragment(position: Int): Fragment { + return GoogleMapComposeFragment.newInstance(mapConfigs[position]) + } +} diff --git a/maps-app/src/main/res/layout/activity_fragment_demo.xml b/maps-app/src/main/res/layout/activity_fragment_demo.xml new file mode 100644 index 00000000..5620f3cd --- /dev/null +++ b/maps-app/src/main/res/layout/activity_fragment_demo.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/maps-app/src/main/res/values/strings.xml b/maps-app/src/main/res/values/strings.xml index 3993bcba..ea0381f6 100644 --- a/maps-app/src/main/res/values/strings.xml +++ b/maps-app/src/main/res/values/strings.xml @@ -28,6 +28,7 @@ Syncing Draggable Marker With Model Updating Non-Draggable Marker With Model Polygon around draggable markers + Fragment Demo Activity Recomposition Map Street View Custom Location Button