Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4b41349
Migrate HomeScreen to Jetpack Compose
gino-m Jan 28, 2026
92d84d9
Fix PR comments and tests for home screen migration
gino-m Jan 29, 2026
9463f94
Apply ktfmt formatting
gino-m Jan 29, 2026
73b7b48
Unblock checkCode: fix Java 25 compatibility and suppress SwallowedEx…
gino-m Jan 29, 2026
001a38e
Revert gradle.properties change
gino-m Jan 29, 2026
5023b23
Merge branch 'master' into migrate-home-screen
gino-m Jan 29, 2026
6b897b7
Update copyright to 2026
gino-m Jan 29, 2026
7c6931e
Move fetching PR comments to a skill
gino-m Jan 30, 2026
8fd1624
Update agent skills and guidelines
gino-m Jan 30, 2026
c25e3ff
Add missing copyright headers
gino-m Jan 30, 2026
7851d98
Merge branch 'master' of https://github.com/google/ground-android int…
gino-m Jan 30, 2026
9243884
Update fetch_pr_comments skill to support code comments and improve f…
gino-m Jan 30, 2026
d99a9d1
Merge branch 'master' of https://github.com/google/ground-android int…
gino-m Jan 30, 2026
f3de0ba
Merge branch 'gino-m/patch/gemini-skills' into migrate-home-screen
gino-m Jan 30, 2026
caa72c9
chore: suppress false positive unused resource warning
gino-m Jan 30, 2026
6d2e79d
Merge branch 'master' of https://github.com/google/ground-android int…
gino-m Feb 3, 2026
b5d155d
Refactor Home drawer icons and fix nullable LOI ID
gino-m Feb 3, 2026
980455f
test: add tests for sign out dialog display and onBack method behavior
gino-m Feb 3, 2026
2956620
Tweak contribution file
gino-m Feb 3, 2026
a3da85e
Update fetch PR comments skill and fix HomeScreenFragmentTest
gino-m Feb 3, 2026
c200389
Update fetch PR comments skill and fix HomeScreenFragmentTest
gino-m Feb 3, 2026
b757c31
Merge branch 'migrate-home-screen' of https://github.com/google/groun…
gino-m Feb 4, 2026
9804ca9
Fix map drag conflict by disabling drawer gestures when closed
gino-m Feb 4, 2026
1e7a297
Local fixes
gino-m Feb 5, 2026
437281e
Merge branch 'migrate-home-screen' of https://github.com/google/groun…
gino-m Feb 14, 2026
24823ab
Merge branch 'master' of https://github.com/google/ground-android int…
gino-m Feb 14, 2026
f1a6ed3
Move data sharing terms logic to ViewModel
gino-m Feb 14, 2026
5bf082d
Integrate BasemapSelectorScreen into HomeScreenMapContainerScreen
gino-m Feb 14, 2026
e1c279d
Skip partially expanded state in BasemapSelector
gino-m Feb 14, 2026
043ab63
Fix invalid data sharing terms handling and test initialization
gino-m Feb 14, 2026
48dc755
refactor: extract sub-components in HomeDrawer and add map type selec…
gino-m Feb 14, 2026
4d9b363
Fix HomeScreenMapContainerViewModel job filtering and test failure
gino-m Feb 15, 2026
81b629d
Fix: Guard `onTermsConsentGiven` for unshown terms and reduce the 'Ad…
gino-m Feb 15, 2026
71f43f4
refactor: migrate HomeScreen navigation drawer from Compose ModalNavi…
gino-m Feb 15, 2026
71adb7c
Fix map crash and transparent drawer
gino-m Feb 15, 2026
8e2fb0d
Refactor HomeScreen to layer MapFragment and ComposeView
gino-m Feb 16, 2026
85422c7
style: remove extraneous blank lines.
gino-m Feb 16, 2026
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
2 changes: 1 addition & 1 deletion .agent/skills/fetch_pr_comments/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The script outputs Markdown text to stdout, containing:
- PR Title and URL
- Reviews (Approved/Changes Requested)
- General Comments (Pull Request level)
- Code Comments (Grouped by file and line number)
- Code Comments (Grouped by file and line number, unresolved threads only)

## Dependencies

Expand Down
105 changes: 93 additions & 12 deletions .agent/skills/fetch_pr_comments/scripts/fetch_comments.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,103 @@ def get_repo_owner_name(pr_url):
return match.group(1), match.group(2)
return None, None

def get_code_comments(owner, repo, pr_number):
cmd = f"gh api repos/{owner}/{repo}/pulls/{pr_number}/comments --paginate"
GRAPHQL_QUERY = """
query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 50, after: $cursor) {
pageInfo {
hasNextPage
endCursor
}
nodes {
isResolved
path
comments(first: 50) {
nodes {
author { login }
body
path
line
originalLine
createdAt
url
}
}
}
}
}
}
}
"""

def run_graphql_query(query, variables):
try:
return json.loads(run_command(cmd))
except Exception as e:
print(f"Warning: Failed to fetch code comments: {e}", file=sys.stderr)
return []
result = subprocess.run(
["gh", "api", "graphql", "--input", "-"],
input=json.dumps({"query": query, "variables": variables}),
capture_output=True,
text=True,
check=True
)
return json.loads(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Error running GraphQL query: {e}", file=sys.stderr)
print(f"Stderr: {e.stderr}", file=sys.stderr)
return None

def get_code_comments(owner, repo, pr_number):
comments = []
cursor = None
has_next = True

while has_next:
variables = {
"owner": owner,
"repo": repo,
"pr": int(pr_number),
"cursor": cursor
}

data = run_graphql_query(GRAPHQL_QUERY, variables)
if not data:
break

pr_data = data.get("data", {}).get("repository", {}).get("pullRequest", {})
if not pr_data:
break

threads = pr_data.get("reviewThreads", {})
page_info = threads.get("pageInfo", {})
has_next = page_info.get("hasNextPage", False)
cursor = page_info.get("endCursor")

for thread in threads.get("nodes", []):
if thread.get("isResolved"):
continue

for comment in thread.get("comments", {}).get("nodes", []):
mapped = {
"path": comment.get("path"),
"line": comment.get("line"),
"original_line": comment.get("originalLine"),
"body": comment.get("body"),
"user": {"login": comment.get("author", {}).get("login") if comment.get("author") else "Unknown"},
"created_at": comment.get("createdAt"),
"html_url": comment.get("url")
}
comments.append(mapped)

return comments

def main():
# Allow optional PR argument (number or URL)
pr_arg = ""
if len(sys.argv) > 1:
pr_arg = f" {sys.argv[1]}"

cmd = f"gh pr view{pr_arg} --json number,title,url,state,comments,reviews,latestReviews"

try:
json_output = run_command(cmd)
pr_data = json.loads(json_output)
Expand All @@ -80,7 +161,7 @@ def main():
title = pr_data.get('title')
url = pr_data.get('url')
state = pr_data.get('state')

owner, repo = get_repo_owner_name(url)
code_comments = []
if owner and repo:
Expand Down Expand Up @@ -134,19 +215,19 @@ def main():
if path not in comments_by_file:
comments_by_file[path] = []
comments_by_file[path].append(cc)

for path, comments in comments_by_file.items():
print(f"### File: `{path}`\n")
# Sort by line number (or position if line is None)
comments.sort(key=lambda x: (x.get('line') or x.get('original_line') or 0))

for cc in comments:
author = cc.get('user', {}).get('login', 'Unknown')
body = cc.get('body', '').strip()
date = format_date(cc.get('created_at', ''))
line = cc.get('line') or cc.get('original_line') or "Outdated"
html_url = cc.get('html_url', '')

print(f"#### Line {line} - {author} ({date})")
print(f"[Link]({html_url})\n")
print(body)
Expand Down
17 changes: 9 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ to the base repository using a pull request.

## Initial build configuration

### Add Google Maps API Key(s)

### Set up Firebase

Expand All @@ -157,15 +156,17 @@ to the base repository using a pull request.

4. Download the config file for the Android app to `app/src/debug/google-services.json`

5. Create a file named `secrets.properties` in the root of the project with the following contents:
### Add Google Maps API Key(s)

```
MAPS_API_KEY=<Your Maps SDK API key>
```
Create a file named `secrets.properties` in the root of the project with the following contents:

```
MAPS_API_KEY=<Your Maps SDK API key>
```

You can find the Maps SDK key for your Firebase project at
http://console.cloud.google.com/google/maps-apis/credentials under
"Android key (auto created by Firebase)".
You can find the Maps SDK key for your Firebase project at
http://console.cloud.google.com/google/maps-apis/credentials under
"Android key (auto created by Firebase)".

### Troubleshooting

Expand Down
4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ android {
buildConfigField "String", "SIGNUP_FORM_LINK", "\"\""
manifestPlaceholders.usesCleartextTraffic = true

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "org.groundplatform.android.CustomTestRunner"
}

// Use flag -PtestBuildType with desired variant to change default behavior.
Expand Down Expand Up @@ -213,6 +213,7 @@ dependencies {
implementation libs.androidx.ui.tooling.preview.android
stagingImplementation libs.androidx.ui.test.manifest
testImplementation libs.androidx.ui.test.junit4
androidTestImplementation libs.androidx.ui.test.junit4
implementation libs.androidx.navigation.compose
implementation libs.androidx.hilt.navigation.compose

Expand Down Expand Up @@ -384,3 +385,4 @@ secrets {
// checked in version control.
defaultPropertiesFileName = "local.defaults.properties"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.groundplatform.android

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication

class CustomTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?,
): Application = super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.groundplatform.android.ui.compose

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.groundplatform.android.R
import org.groundplatform.android.ui.main.MainActivity
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@HiltAndroidTest
class ComposeE2ETest {

@get:Rule(order = 0) var hiltRule = HiltAndroidRule(this)

@get:Rule(order = 1) val composeTestRule = createAndroidComposeRule<MainActivity>()

@Before
fun setup() {
hiltRule.inject()
}

@Test
fun testNavigationToSyncStatus() {
// 1. Open Drawer
composeTestRule.onNodeWithTag("open_nav_drawer").performClick()

// 2. Click "Sync Status"
val syncStatusText = composeTestRule.activity.getString(R.string.sync_status)
composeTestRule.onNodeWithText(syncStatusText).performClick()

// 3. Verify Sync Status Screen is shown (Title in TopAppBar)
val syncStatusTitle = composeTestRule.activity.getString(R.string.data_sync_status)
composeTestRule.onNodeWithText(syncStatusTitle).assertIsDisplayed()
}

@Test
fun testNavigationToOfflineAreas() {
// 1. Open Drawer
composeTestRule.onNodeWithTag("open_nav_drawer").performClick()

// 2. Click "Offline Map Imagery"
val offlineMapText = composeTestRule.activity.getString(R.string.offline_map_imagery)
composeTestRule.onNodeWithText(offlineMapText).performClick()

// 3. Verify Offline Areas Screen is shown (Title in TopAppBar)
// Note: OfflineAreasFragment label is @string/offline_map_imagery
val offlineMapTitle = composeTestRule.activity.getString(R.string.offline_map_imagery)
composeTestRule.onNodeWithText(offlineMapTitle).assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ fun BasemapSelectorScreen(
) {
val currentMapType by viewModel.currentMapType.collectAsStateWithLifecycle()
val isOfflineImageryEnabled by viewModel.isOfflineImageryEnabled.collectAsStateWithLifecycle()
val sheetState = rememberModalBottomSheetState()
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val scope = rememberCoroutineScope()

fun onMapTypeSelected(mapType: MapType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ abstract class AbstractMapContainerFragment : AbstractFragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(childFragmentManager.findFragmentById(R.id.map) as? MapFragment)?.let { map = it }
map.attachToParent(this, R.id.map) { onMapAttached(it) }

if (view is ViewGroup) {
Expand All @@ -68,7 +69,7 @@ abstract class AbstractMapContainerFragment : AbstractFragment() {
)
}

private fun onMapAttached(map: MapFragment) {
protected fun onMapAttached(map: MapFragment) {
val viewModel = getMapViewModel()

// Removes all markers, overlays, polylines and polygons from the map.
Expand Down Expand Up @@ -189,6 +190,11 @@ abstract class AbstractMapContainerFragment : AbstractFragment() {
/** Configuration to enable/disable base map features. */
open fun getMapConfig() = DEFAULT_MAP_CONFIG

override fun onDestroyView() {
map.disableCurrentLocationIndicator()
super.onDestroyView()
}

companion object {
private val DEFAULT_MAP_CONFIG: MapConfig = MapConfig(showOfflineImagery = true)
}
Expand Down
Loading
Loading