Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fddf518
chore: cleanup and tooling updates
gino-m Feb 16, 2026
655775a
feat: add HomeDrawer component and ViewModel logic
gino-m Feb 16, 2026
d886942
chore: remove build output file
gino-m Feb 16, 2026
da3f1ae
Clean up PR
gino-m Feb 16, 2026
4ea3ae3
refactor(home): migrate drawer to Compose and remove XML
gino-m Feb 16, 2026
8748d6d
Restore deleted files
gino-m Feb 16, 2026
6f4f819
fix(home): add system bars padding to drawer
gino-m Feb 16, 2026
c151380
refactor: clean up unused imports and reformat Compose state observat…
gino-m Feb 16, 2026
6cf23b4
feat: move version text display from a clickable drawer item to a sta…
gino-m Feb 16, 2026
8cd3af2
Update app/src/main/java/org/groundplatform/android/ui/home/HomeDrawe…
gino-m Feb 16, 2026
fa98dbf
Update app/src/main/java/org/groundplatform/android/ui/home/HomeDrawe…
gino-m Feb 16, 2026
67c1ff6
Fix checkCode issues: Refactor HomeDrawer/HomeScreenFragment, fix for…
gino-m Feb 16, 2026
98f7551
Refactor HomeDrawer and fix HomeScreenFragmentTest
gino-m Feb 16, 2026
562c1ab
Fix checkCode issues and test regressions
gino-m Feb 17, 2026
067e9a4
Fix ktfmt formatting issues
gino-m Feb 17, 2026
fc623c2
Refactor HomeDrawer to use HomeDrawerAction interface
gino-m Feb 22, 2026
09cd0e5
Make user parameter non-null in HomeDrawer
gino-m Feb 22, 2026
a1d4e16
refactor: remove unnecessary comments from `openSignOutWarningDialog`…
gino-m Feb 22, 2026
ae5a8f9
Refactor HomeDrawerState into ViewModel
gino-m Feb 22, 2026
5f7c642
test: assert build version string with BuildConfig.VERSION_NAME in Ho…
gino-m Feb 22, 2026
96da66d
Replace Glide with Coil AsyncImage in HomeDrawer
gino-m Feb 22, 2026
1bc876c
Fix duplicate coil-compose in libs.versions.toml
gino-m Feb 22, 2026
5640cae
Address PR #3554 feedback
gino-m Mar 9, 2026
8bef94a
Merge master into refactor/home-drawer and resolve conflicts
gino-m Mar 9, 2026
d2421c4
Merge branch 'master' into refactor/home-drawer
gino-m Mar 9, 2026
c9a5878
Fix UI Home Drawer regressions
gino-m Mar 10, 2026
e55ec8b
Fix UI Home Drawer dividers
gino-m Mar 10, 2026
60fc4ef
Remove unused style
gino-m Mar 10, 2026
9fbf637
Refactor version constant
gino-m Mar 10, 2026
a365307
Merge branch 'master' of https://github.com/google/ground-android int…
gino-m Mar 10, 2026
b8a3c66
Fix UI Home Drawer typography and padding
gino-m Mar 10, 2026
14a663f
Fix exact CSS layout spacings and typography
gino-m Mar 10, 2026
0924cff
Complete typography with explicit Google Sans and Manrope fonts per CSS
gino-m Mar 10, 2026
ea707c0
Suppress LongMethod detekt check on SurveySelector
gino-m Mar 10, 2026
e7a9587
Restore user photo size to 32dp
gino-m Mar 10, 2026
bdd1b36
Restore Font Weights for Survey Selector and Nav Items per original CSS
gino-m Mar 10, 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
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
3 changes: 1 addition & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ android {
buildConfigField "int", "AUTH_EMULATOR_PORT", "9099"
buildConfigField "String", "SIGNUP_FORM_LINK", "\"\""
manifestPlaceholders.usesCleartextTraffic = true

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

// Use flag -PtestBuildType with desired variant to change default behavior.
Expand Down Expand Up @@ -385,3 +383,4 @@ secrets {
// checked in version control.
defaultPropertiesFileName = "local.defaults.properties"
}

333 changes: 333 additions & 0 deletions app/src/main/java/org/groundplatform/android/ui/home/HomeDrawer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
/*
* 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.home

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ExitToApp
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.groundplatform.android.R
import org.groundplatform.android.model.Survey
import org.groundplatform.android.model.User
import org.groundplatform.ui.theme.AppTheme

@Composable
fun HomeDrawer(
user: User,
survey: Survey?,
versionText: String,
onAction: (HomeDrawerAction) -> Unit,
) {
Column(
modifier =
Modifier.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.systemBarsPadding()
.verticalScroll(rememberScrollState())
) {
AppInfoHeader(user = user, onAction = onAction)
SurveySelector(survey = survey, onSwitchSurvey = { onAction(HomeDrawerAction.OnSwitchSurvey) })
HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp))
DrawerItems(onAction, versionText)
}
}

private val NAV_ITEMS =
listOf(
DrawerItem(
labelId = R.string.sync_status,
icon = IconSource.Drawable(R.drawable.ic_history),
action = HomeDrawerAction.OnNavigateToSyncStatus,
),
DrawerItem(
labelId = R.string.offline_map_imagery,
icon = IconSource.Drawable(R.drawable.cloud_off),
action = HomeDrawerAction.OnNavigateToOfflineAreas,
),
DrawerItem(
labelId = R.string.settings,
icon = IconSource.Vector(Icons.Default.Settings),
action = HomeDrawerAction.OnNavigateToSettings,
),
DrawerItem(
labelId = R.string.about,
icon = IconSource.Drawable(R.drawable.info_outline),
action = HomeDrawerAction.OnNavigateToAbout,
),
DrawerItem(
labelId = R.string.terms_of_service,
icon = IconSource.Drawable(R.drawable.feed),
action = HomeDrawerAction.OnNavigateToTerms,
),
DrawerItem(
labelId = R.string.sign_out,
icon = IconSource.Vector(Icons.AutoMirrored.Filled.ExitToApp),
action = HomeDrawerAction.OnSignOut,
),
)

@Composable
private fun DrawerItems(onAction: (HomeDrawerAction) -> Unit, versionText: String) {
NAV_ITEMS.forEach { item -> DrawerNavigationItem(item, onAction) }

DrawerVersionFooter(versionText)
}

@Composable
private fun DrawerNavigationItem(item: DrawerItem, onAction: (HomeDrawerAction) -> Unit) {
val label = stringResource(item.labelId)
NavigationDrawerItem(
label = {
Text(
text = label,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
fontFamily =
androidx.compose.ui.text.font.FontFamily(
androidx.compose.ui.text.font.Font(R.font.manrope_bold, FontWeight.SemiBold),
androidx.compose.ui.text.font.Font(R.font.manrope_medium, FontWeight.Medium),
),
lineHeight = 20.sp,
)
},
selected = false,
onClick = { onAction(item.action) },
icon = {
val description = null
when (item.icon) {
is IconSource.Vector -> Icon(item.icon.imageVector, contentDescription = description)
is IconSource.Drawable ->
Icon(painterResource(item.icon.id), contentDescription = description)
}
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding).testTag(label),
)
}

@Composable
private fun DrawerVersionFooter(versionText: String) {
Row(
modifier =
Modifier.fillMaxWidth()
.padding(NavigationDrawerItemDefaults.ItemPadding)
.padding(start = 16.dp, end = 24.dp, top = 12.dp, bottom = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = Icons.Default.Build,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.width(12.dp))
Text(
text = versionText,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
fontFamily =
androidx.compose.ui.text.font.FontFamily(
androidx.compose.ui.text.font.Font(R.font.manrope_bold, FontWeight.SemiBold),
androidx.compose.ui.text.font.Font(R.font.manrope_medium, FontWeight.Medium),
),
lineHeight = 20.sp,
Comment on lines +170 to +177
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you might want to use the existing Typography styles when possible. For example, most of this can be replaced by just: style = MaterialTheme.typography.titleSmall
This helps to keep consistency and avoids redefining fonts/sizes/etc

color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}

private data class DrawerItem(
@androidx.annotation.StringRes val labelId: Int,
val icon: IconSource,
val action: HomeDrawerAction,
)

@Composable
private fun AppInfoHeader(user: User, onAction: (HomeDrawerAction) -> Unit) {
Column(
modifier =
Modifier.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(vertical = 24.dp, horizontal = 16.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Image(
painter = painterResource(R.drawable.ground_logo),
contentDescription = null,
modifier = Modifier.size(24.dp),
)
Spacer(Modifier.width(8.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = stringResource(R.string.app_name),
fontSize = 18.sp,
fontFamily =
androidx.compose.ui.text.font.FontFamily(
androidx.compose.ui.text.font.Font(R.font.google_sans)
),
fontWeight = FontWeight.Normal,
lineHeight = 24.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
if (user.photoUrl != null) {
coil.compose.AsyncImage(
model = user.photoUrl,
contentDescription = null,
modifier =
Modifier.size(32.dp).clip(CircleShape).clickable {
onAction(HomeDrawerAction.OnUserDetails)
},
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
)
}
}
}
}

@Suppress("LongMethod")
@Composable
private fun SurveySelector(survey: Survey?, onSwitchSurvey: () -> Unit) {
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(R.drawable.ic_content_paste),
contentDescription = stringResource(R.string.current_survey),
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(Modifier.width(4.dp))
Text(
text = stringResource(R.string.current_survey),
fontSize = 12.sp,
fontWeight = FontWeight.SemiBold,
fontFamily =
androidx.compose.ui.text.font.FontFamily(
androidx.compose.ui.text.font.Font(R.font.google_sans)
),
lineHeight = 16.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Spacer(Modifier.height(16.dp))

if (survey == null) {
Text(stringResource(R.string.no_survey_selected))
} else {
Text(
text = survey.title,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
fontFamily =
androidx.compose.ui.text.font.FontFamily(
androidx.compose.ui.text.font.Font(R.font.google_sans)
),
lineHeight = 24.sp,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
if (survey.description.isNotEmpty()) {
Text(
text = survey.description,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
fontFamily =
androidx.compose.ui.text.font.FontFamily(
androidx.compose.ui.text.font.Font(R.font.google_sans)
),
lineHeight = 20.sp,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 4,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(top = 8.dp),
)
}
}

Spacer(Modifier.height(16.dp))

Text(
text = stringResource(R.string.switch_survey),
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
fontFamily =
androidx.compose.ui.text.font.FontFamily(
androidx.compose.ui.text.font.Font(R.font.manrope_bold, FontWeight.SemiBold),
androidx.compose.ui.text.font.Font(R.font.manrope_medium, FontWeight.Medium),
),
lineHeight = 20.sp,
color = MaterialTheme.colorScheme.primary,
modifier =
Modifier.clip(CircleShape).clickable(onClick = onSwitchSurvey).padding(vertical = 10.dp),
)
}
}

private sealed interface IconSource {
data class Vector(val imageVector: androidx.compose.ui.graphics.vector.ImageVector) : IconSource

data class Drawable(@androidx.annotation.DrawableRes val id: Int) : IconSource
}

@Preview(showBackground = true)
@Composable
private fun HomeDrawerPreview() {
val mockUser =
User(id = "1", email = "test@example.com", displayName = "Jane Doe", photoUrl = null)
val mockSurvey =
Survey(
id = "1",
title = "Tree Survey",
description = "A comprehensive survey for mapping urban tree canopy and assessing health.",
jobMap = emptyMap(),
generalAccess = org.groundplatform.android.proto.Survey.GeneralAccess.PUBLIC,
)
AppTheme {
HomeDrawer(user = mockUser, survey = mockSurvey, versionText = "1.0.0-preview", onAction = {})
}
}
Loading
Loading