Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/bundler/cyclonedx-cocoapods-1.4.1
Browse files Browse the repository at this point in the history
  • Loading branch information
KaylaBrady authored Dec 17, 2024
2 parents ad983de + 673e762 commit a9b94ee
Show file tree
Hide file tree
Showing 189 changed files with 8,449 additions and 1,505 deletions.
3 changes: 2 additions & 1 deletion .envrc.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export GOOGLE_APP_ID=

# Sentry - needed only to record sentry events from local development
# Find DSN under the Client Keys settings of the mobile_app_ios project in Sentry
export SENTRY_DSN=
export SENTRY_DSN_IOS=
export SENTRY_DSN_ANDROID=
export SENTRY_ENVIRONMENT=debug
5 changes: 4 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ _Ticket:_ [Name](URL)

What is this PR for?

- [ ] If you added any user facing strings on iOS, are they included in Localizable.xcstrings?
iOS
- [ ] If you added any user-facing strings on iOS, are they included in Localizable.xcstrings?
- [ ] Add temporary machine translations, marked "Needs Review"
android
- [ ] All user-facing strings added to strings resource

### Testing

Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ jobs:
run: ./gradlew shared:check
env:
MAPBOX_SECRET_TOKEN: ${{ secrets.MAPBOX_SECRET_TOKEN }}
MAPBOX_PUBLIC_TOKEN: ${{ secrets.MAPBOX_PUBLIC_TOKEN }}
- uses: actions/upload-artifact@v4
if: failure()
with:
Expand All @@ -56,7 +55,6 @@ jobs:
run: ./gradlew androidApp:check
env:
MAPBOX_SECRET_TOKEN: ${{ secrets.MAPBOX_SECRET_TOKEN }}
MAPBOX_PUBLIC_TOKEN: ${{ secrets.MAPBOX_PUBLIC_TOKEN }}
- uses: actions/upload-artifact@v4
if: failure()
with:
Expand Down Expand Up @@ -102,7 +100,6 @@ jobs:
script: ./bin/android-instrumented-test-ci.sh
env:
MAPBOX_SECRET_TOKEN: ${{ secrets.MAPBOX_SECRET_TOKEN }}
MAPBOX_PUBLIC_TOKEN: ${{ secrets.MAPBOX_PUBLIC_TOKEN }}
- uses: actions/upload-artifact@v4
if: failure()
with:
Expand Down Expand Up @@ -147,7 +144,7 @@ jobs:
KEY_ALIAS: "upload"
KEY_PASSWORD: ${{ env.MOBILE_APP_ANDROID_UPLOAD_KEY_PASSPHRASE }}
MAPBOX_SECRET_TOKEN: ${{ secrets.MAPBOX_SECRET_TOKEN }}
MAPBOX_PUBLIC_TOKEN: ${{ secrets.MAPBOX_PUBLIC_TOKEN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
bundle exec fastlane android build flavor:Staging
- uses: actions/upload-artifact@v4
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The public key is fetched dynamically from the backend. Be sure to follow the Fi

Like on iOS, Mapbox for Android requires two keys. Follow the above keys link for instructions on how to configure the secret and public key.

**Note**: The property name in `~/.gradle/gradle.properties` is `MAPBOX_SECRET_TOKEN`, not `MAPBOX_DOWNLOADS_TOKEN` as used in the Mapbox documentation. Also, the public token should be stored in `androidApp/src/main/res/values/secrets.xml`, which will be created with a placeholder value if it does not already exist at build time.
**Note**: The property name in `~/.gradle/gradle.properties` is `MAPBOX_SECRET_TOKEN`, not `MAPBOX_DOWNLOADS_TOKEN` as used in the Mapbox documentation.

#### Sentry - [docs](https://docs.sentry.io/platforms/kotlin-multiplatform/) - [keys](https://mbtace.sentry.io/settings/projects/mobile_app_ios/keys/)

Expand All @@ -47,6 +47,12 @@ The recommendation for KMM projects is to use Android Studio for editing & runni

- Be sure to install the Android SDK Command-line Tools via Android Studio > Settings Android SDK > SDK Tool Tabs > Android SDK Command Line Tools.
- If you're seeing the error "undefined method 'map' for nil:NilClass" when running pod installs locally, you likely need to run `bundle exec gem uninstall ffi` then `bundle install` to get a cocoapods requirement to be installed properly on M1 Macs.
- If an Xcode build fails because of CocoaPods, try `bin/fix-cocoapods.sh`.
- If some piece of BOM generation fails in Xcode, try quitting Xcode and then running `open /Applications/Xcode.app` from a terminal with a good `PATH`.
- If iOS asset conversion fails in Android Studio, try quitting Android Studio and then running `open /Applications/Android\ Studio.app` from a terminal with a good `PATH`.
- If some piece of BOM generation still fails in Xcode even when Xcode was launched with a good `PATH`, try running `./gradlew :shared:bomCodegenIos` manually from a terminal with a good `PATH`.
- If `./gradlew :shared:bomCodegenIos` fails in a terminal with a good `PATH`, try `./gradlew --stop` to stop any daemons that have persisted a bad `PATH` and then try `./gradlew :shared:bomCodegenIos` again.
- If Android Studio can't find `rsvg-convert` even when Android Studio was launched with a good `PATH`, try `./gradlew --stop` to stop any daemons that have persisted a bad `PATH` and then try the build again.

## Running Locally

Expand All @@ -67,6 +73,10 @@ ios app:

To switch between the staging and prod app flavors, go to Build > Select Build Variant and then set the `:androidApp` Active Build Variant.

Populate any configuration needed in your the .envrc file. These will be read by a gradle build task
set as BuildConfig values so that they can be read by the application.


## Running Tests

### Unit Tests
Expand Down
82 changes: 72 additions & 10 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import com.mbta.tid.mbta_app.gradle.ConvertIosLocalizationTask
import com.mbta.tid.mbta_app.gradle.ConvertIosMapIconsTask
import java.io.BufferedReader
import java.io.StringReader
import java.util.Properties

plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.compose)
alias(libs.plugins.cycloneDx)
alias(libs.plugins.kotlinAndroid)
alias(libs.plugins.sentryGradle)
alias(libs.plugins.serialization)
id("check-mapbox-bridge")
}

sentry {
// Generates a JVM (Java, Kotlin, etc.) source bundle and uploads your source code to Sentry.
// This enables source context, allowing you to see your source
// code as part of your stack traces in Sentry.
includeSourceContext = true

org = "mbtace"
projectName = "mobile_app_android"
authToken = System.getenv("SENTRY_AUTH_TOKEN")
}

android {
namespace = "com.mbta.tid.mbta_app.android"
compileSdk = 34
Expand All @@ -21,7 +37,10 @@ android {
versionName = (findProperty("android.injected.version.name") ?: "0.1.0") as String
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures { compose = true }
buildFeatures {
buildConfig = true
compose = true
}
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
buildTypes { getByName("release") { isMinifyEnabled = false } }
flavorDimensions += "environment"
Expand All @@ -38,6 +57,10 @@ android {
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions { jvmTarget = "1.8" }
androidResources {
@Suppress("UnstableApiUsage")
generateLocaleConfig = true
}
}

dependencies {
Expand All @@ -58,13 +81,16 @@ dependencies {
implementation(libs.mapbox.compose)
implementation(libs.mapbox.turf)
implementation(libs.okhttp)
implementation(libs.playServices.location)
debugImplementation(platform(libs.compose.bom))
debugImplementation(libs.compose.ui.test.manifest)
debugImplementation(libs.compose.ui.tooling)
testImplementation(libs.junit)
testImplementation(libs.koin.test)
implementation(libs.koin.junit4)
androidTestImplementation(platform(libs.compose.bom))
androidTestImplementation(libs.androidx.test.monitor)
androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.compose.ui.test.junit4)
androidTestImplementation(libs.ktor.client.mock)
}
Expand All @@ -79,26 +105,62 @@ task<ConvertIosMapIconsTask>("convertIosIconsToAssets") {
assetsToReturnByName = listOf("alert-borderless-*")
}

task<ConvertIosLocalizationTask>("convertIosLocalization") {
androidEnglishStrings = layout.projectDirectory.file("src/main/res/values/strings.xml")
xcstrings = layout.projectDirectory.file("../iosApp/iosApp/Localizable.xcstrings")
resources = layout.projectDirectory.dir("src/main/res")
}

// https://github.com/mapbox/mapbox-gl-native-android/blob/7f03a710afbd714368084e4b514d3880bad11c27/gradle/gradle-config.gradle
task("accessToken") {
task("mapboxTempToken") {
val tokenFile = File("${projectDir}/src/main/res/values/secrets.xml")
if (!tokenFile.exists()) {
var mapboxAccessToken = System.getenv()["MAPBOX_PUBLIC_TOKEN"]
if (mapboxAccessToken == null) {
Logging.getLogger(this.javaClass)
.warn("You should set the MAPBOX_PUBLIC_TOKEN environment variable.")
mapboxAccessToken = ""
}
val tokenFileContents =
"""<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mapbox_access_token" translatable="false">$mapboxAccessToken</string>
<string name="mapbox_access_token" translatable="false">"temporary_mapbox_token"</string>
</resources>"""
tokenFile.writeText(tokenFileContents)
}
}

task("envVars") {
val envFile = File(".envrc")
val props = Properties()

if (envFile.exists()) {
val bufferedReader: BufferedReader = envFile.bufferedReader()
bufferedReader.use {
it.readLines()
.filter { line -> line.contains("export") }
.map { line ->
val cleanLine = line.replace("export", "")
props.load(StringReader(cleanLine))
}
}
} else {
println(".envrc file not configured, reading from system env instead")
}

android.defaultConfig.buildConfigField(
"String",
"SENTRY_DSN",
"\"${props.getProperty("SENTRY_DSN_ANDROID")
?: System.getenv("SENTRY_DSN_ANDROID") ?: ""}\""
)

android.defaultConfig.buildConfigField(
"String",
"SENTRY_ENVIRONMENT",
"\"${props.getProperty("SENTRY_ENVIRONMENT")
?: System.getenv("SENTRY_ENVIRONMENT") ?: ""}\""
)
}

gradle.projectsEvaluated {
tasks.getByPath("preBuild").dependsOn("accessToken", "convertIosIconsToAssets")
tasks
.getByPath("preBuild")
.dependsOn("mapboxTempToken", "convertIosIconsToAssets", "convertIosLocalization")
tasks.getByPath("spotlessKotlin").mustRunAfter("convertIosLocalization")
tasks.getByPath("check").dependsOn("checkMapboxBridge")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.mbta.tid.mbta_app.android

import android.app.Activity
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.rule.GrantPermissionRule
import com.mbta.tid.mbta_app.android.location.MockFusedLocationProviderClient
import com.mbta.tid.mbta_app.android.util.LocalActivity
import com.mbta.tid.mbta_app.android.util.LocalLocationClient
import com.mbta.tid.mbta_app.dependencyInjection.repositoriesModule
import com.mbta.tid.mbta_app.network.MockPhoenixSocket
import com.mbta.tid.mbta_app.network.PhoenixSocket
import org.junit.Rule
import org.junit.Test
import org.koin.compose.KoinContext
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import org.koin.test.KoinTest

class ContentViewTests : KoinTest {

@get:Rule
val runtimePermissionRule =
GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)
@get:Rule val composeTestRule = createComposeRule()

val koinApplication = koinApplication {
modules(
repositoriesModule(MockRepositories.buildWithDefaults()),
module { single<PhoenixSocket> { MockPhoenixSocket() } }
)
}

@Test
fun testSwitchingTabs() {
composeTestRule.setContent {
KoinContext(koinApplication.koin) {
CompositionLocalProvider(
LocalActivity provides (LocalContext.current as Activity),
LocalLocationClient provides MockFusedLocationProviderClient()
) {
ContentView()
}
}
}

composeTestRule.onNodeWithText("More").performClick()
composeTestRule.onNodeWithText("MBTA Go").assertIsDisplayed()

composeTestRule.onNodeWithText("Nearby").performClick()
composeTestRule.onNodeWithText("Nearby Transit").assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class DirectionPickerTest {
null,
stop,
patterns,
listOf(Direction("A", null, 0), Direction("B", null, 1))
listOf(Direction("North", null, 0), Direction("South", null, 1))
)
var filter: StopDetailsFilter? = StopDetailsFilter(routeId = route.id, directionId = 0)
composeTestRule.setContent {
Expand All @@ -57,8 +57,8 @@ class DirectionPickerTest {
}
}

composeTestRule.onNodeWithText("A").assertIsDisplayed()
composeTestRule.onNodeWithText("B").performClick()
composeTestRule.onNodeWithText("Northbound").assertIsDisplayed()
composeTestRule.onNodeWithText("Southbound").performClick()
assertTrue(filter?.routeId == route.id)
assertTrue(filter?.directionId == 1)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class HeadsignRowViewTest {
init("Somewhere", RealtimePatterns.Format.None(secondaryAlert = null))

composeTestRule.onNodeWithText("Somewhere").assertIsDisplayed()
composeTestRule.onNodeWithText("No Predictions").assertIsDisplayed()
composeTestRule.onNodeWithText("Predictions unavailable").assertIsDisplayed()
}

@Test
Expand All @@ -107,7 +107,7 @@ class HeadsignRowViewTest {
)

composeTestRule.onNodeWithText("Somewhere").assertIsDisplayed()
composeTestRule.onNodeWithText("No Predictions").assertIsDisplayed()
composeTestRule.onNodeWithText("Predictions unavailable").assertIsDisplayed()
composeTestRule.onNodeWithContentDescription("Alert").assertIsDisplayed()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PinButtonTest {
PinButton(pinned = false, color = Color.Unspecified) { wasTapped = true }
}

composeTestRule.onNodeWithContentDescription("pin route").performClick()
composeTestRule.onNodeWithContentDescription("Star route").performClick()
assertTrue(wasTapped)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.mbta.tid.mbta_app.android.component

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import com.mbta.tid.mbta_app.model.Route
import com.mbta.tid.mbta_app.model.RouteType
Expand Down Expand Up @@ -31,7 +30,6 @@ class RouteHeaderTest {
)
}
composeTestRule.onNodeWithText("Short name").assertIsDisplayed()
composeTestRule.onNodeWithContentDescription("Bus").assertIsDisplayed()
}

@Test
Expand All @@ -53,7 +51,6 @@ class RouteHeaderTest {
)
}
composeTestRule.onNodeWithText("Long name").assertIsDisplayed()
composeTestRule.onNodeWithContentDescription("Ferry").assertIsDisplayed()
}

@Test
Expand All @@ -75,7 +72,6 @@ class RouteHeaderTest {
)
}
composeTestRule.onNodeWithText("Long name").assertIsDisplayed()
composeTestRule.onNodeWithContentDescription("Commuter Rail").assertIsDisplayed()
}

@Test
Expand All @@ -97,6 +93,5 @@ class RouteHeaderTest {
)
}
composeTestRule.onNodeWithText("Long name").assertIsDisplayed()
composeTestRule.onNodeWithContentDescription("Subway").assertIsDisplayed()
}
}
Loading

0 comments on commit a9b94ee

Please sign in to comment.