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
4 changes: 2 additions & 2 deletions .env → .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=
EXPO_PUBLIC_FIREBASE_APP_ID=
EXPO_PUBLIC_GRAPHQL_ENDPOINT=
EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT=
EXPO_PUBLIC_MANAGER_DASHBOARD_URL=
EXPO_PUBLIC_REFERRER_ENDPOINT=
EXPO_PUBLIC_COMMUNITY_DASHBOARD_URL=
EXPO_PUBLIC_REFERRER_ENDPOINT=
241 changes: 184 additions & 57 deletions .github/workflows/release-android.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
name: Build and Release Android APK
name: Build and Release Android

on:
push:
# branches:
# - 'feat/android-release-workflow'
tags:
- 'v*.*.*' # Triggers on version tags like v1.0.0, v2.1.3, etc.

env:
EXPO_PUBLIC_FIREBASE_API_KEY: ${{ vars.EXPO_PUBLIC_FIREBASE_API_KEY }}
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ vars.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN }}
EXPO_PUBLIC_FIREBASE_DATABASE_URL: ${{ vars.EXPO_PUBLIC_FIREBASE_DATABASE_URL }}
EXPO_PUBLIC_FIREBASE_PROJECT_ID: ${{ vars.EXPO_PUBLIC_FIREBASE_PROJECT_ID }}
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ vars.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET }}
EXPO_PUBLIC_FIREBASE_APP_ID: ${{ vars.EXPO_PUBLIC_FIREBASE_APP_ID }}
EXPO_PUBLIC_GRAPHQL_ENDPOINT: ${{ vars.EXPO_PUBLIC_GRAPHQL_ENDPOINT }}
EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT: ${{ vars.EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT }}
- 'v*.*.*'

jobs:
build-and-release:
name: Build Android APK and Create Release
environment: 'alpha-2'
# ─── JOB 1: STAGING BUILD ───────────────────────────────────────────
build-staging:
name: Build and Deploy Staging APK
# FIXME: Update this to staging later on
environment: alpha-2
runs-on: ubuntu-latest

permissions:
contents: write

env:
EXPO_PUBLIC_FIREBASE_API_KEY: ${{ vars.EXPO_PUBLIC_FIREBASE_API_KEY }}
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ vars.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN }}
EXPO_PUBLIC_FIREBASE_DATABASE_URL: ${{ vars.EXPO_PUBLIC_FIREBASE_DATABASE_URL }}
EXPO_PUBLIC_FIREBASE_PROJECT_ID: ${{ vars.EXPO_PUBLIC_FIREBASE_PROJECT_ID }}
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ vars.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET }}
EXPO_PUBLIC_FIREBASE_APP_ID: ${{ vars.EXPO_PUBLIC_FIREBASE_APP_ID }}
EXPO_PUBLIC_GRAPHQL_ENDPOINT: ${{ vars.EXPO_PUBLIC_GRAPHQL_ENDPOINT }}
EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT: ${{ vars.EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT }}

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -39,14 +42,135 @@ jobs:
node-version: '22'
cache: 'pnpm'

- name: Get pnpm store directory
shell: bash
- name: Install dependencies
run: pnpm install

- name: Create .env.local for codegen
run: |
echo "EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT=${{ vars.EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT }}" > .env.local

- name: Generate GraphQL types
run: pnpm generate:type

- name: Setup JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Setup Android SDK
uses: android-actions/setup-android@v3

- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Decode staging keystore
run: |
echo "${{ secrets.ANDROID_STAGING_KEYSTORE_BASE64 }}" | base64 --decode > ${{ runner.temp }}/mapswipe-staging.keystore

- name: Expo prebuild (staging)
run: APP_ENV=staging npx expo prebuild --platform android --clean

- name: Build staging APK
env:
KEYSTORE_PATH: ${{ runner.temp }}/mapswipe-staging.keystore
STORE_PASSWORD: ${{ secrets.ANDROID_STAGING_KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.ANDROID_STAGING_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.ANDROID_STAGING_KEY_PASSWORD }}
run: |
cd android && ./gradlew assembleRelease \
-Pandroid.injected.signing.store.file=$KEYSTORE_PATH \
-Pandroid.injected.signing.store.password=$STORE_PASSWORD \
-Pandroid.injected.signing.key.alias=$KEY_ALIAS \
-Pandroid.injected.signing.key.password=$KEY_PASSWORD

- name: Get tag name
id: tag
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

- name: Rename APK
run: |
mkdir -p generated
cp android/app/build/outputs/apk/release/app-release.apk \
generated/mapswipe-staging-${{ steps.tag.outputs.tag }}.apk

- name: Create staging pre-release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
gh release create ${{ steps.tag.outputs.tag }}-beta \
--title "MapSwipe Mobile ${{ steps.tag.outputs.tag }} (Staging)" \
--prerelease \
--notes "## Staging build for ${{ steps.tag.outputs.tag }}

This is a **staging pre-release** for internal testing only.

### 📱 Install
1. Download the APK below
2. Enable 'Install from unknown sources' in Android settings
3. Install and test against the **staging database**

### 🔧 Build info
- Commit: \`${{ github.sha }}\`
- Build: #${{ github.run_number }}
- Date: $(date +'%Y-%m-%d %H:%M:%S UTC')
- Package: \`org.missingmaps.mapswipe.staging\`

---
Once testing is complete, approve the production deployment in GitHub Actions." \
generated/mapswipe-staging-${{ steps.tag.outputs.tag }}.apk

# ─── JOB 2: PRODUCTION BUILD (requires manual approval) ─────────────
build-production:
name: Build and Deploy Production APK
environment: production
runs-on: ubuntu-latest
needs: build-staging
permissions:
contents: write

env:
EXPO_PUBLIC_FIREBASE_API_KEY: ${{ vars.EXPO_PUBLIC_FIREBASE_API_KEY }}
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ vars.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN }}
EXPO_PUBLIC_FIREBASE_DATABASE_URL: ${{ vars.EXPO_PUBLIC_FIREBASE_DATABASE_URL }}
EXPO_PUBLIC_FIREBASE_PROJECT_ID: ${{ vars.EXPO_PUBLIC_FIREBASE_PROJECT_ID }}
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ vars.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET }}
EXPO_PUBLIC_FIREBASE_APP_ID: ${{ vars.EXPO_PUBLIC_FIREBASE_APP_ID }}
EXPO_PUBLIC_GRAPHQL_ENDPOINT: ${{ vars.EXPO_PUBLIC_GRAPHQL_ENDPOINT }}
EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT: ${{ vars.EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT }}

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: true

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install

- name: Create .env.local for codegen
run: |
echo "EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT=${{ vars.EXPO_PUBLIC_GRAPHQL_CODEGEN_ENDPOINT }}" > .env.local

- name: Generate GraphQL types
run: pnpm generate:type

- name: Setup JDK 17
uses: actions/setup-java@v4
with:
Expand All @@ -66,49 +190,52 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-

- name: Build Release APK
run: pnpm build:release
- name: Decode production keystore
run: |
echo "${{ secrets.ANDROID_PROD_KEYSTORE_BASE64 }}" | base64 --decode > ${{ runner.temp }}/mapswipe-prod.keystore

- name: Expo prebuild (production)
run: npx expo prebuild --platform android --clean

- name: Get release info
id: release-info
- name: Build production APK
env:
KEYSTORE_PATH: ${{ runner.temp }}/mapswipe-prod.keystore
STORE_PASSWORD: ${{ secrets.ANDROID_PROD_KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.ANDROID_PROD_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.ANDROID_PROD_KEY_PASSWORD }}
run: |
APK_FILE=$(ls generated/app-release-*.apk | head -n 1)
APK_NAME=$(basename "$APK_FILE")
TAG_NAME=${GITHUB_REF#refs/tags/}
FILE_SIZE=$(du -h "$APK_FILE" | cut -f1)
cd android && ./gradlew assembleRelease \
-Pandroid.injected.signing.store.file=$KEYSTORE_PATH \
-Pandroid.injected.signing.store.password=$STORE_PASSWORD \
-Pandroid.injected.signing.key.alias=$KEY_ALIAS \
-Pandroid.injected.signing.key.password=$KEY_PASSWORD

echo "apk_file=$APK_FILE" >> $GITHUB_OUTPUT
echo "apk_name=$APK_NAME" >> $GITHUB_OUTPUT
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "file_size=$FILE_SIZE" >> $GITHUB_OUTPUT
- name: Get tag name
id: tag
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

echo "📦 APK: $APK_NAME ($FILE_SIZE)"
- name: Rename APK
run: |
mkdir -p generated
cp android/app/build/outputs/apk/release/app-release.apk \
generated/mapswipe-${{ steps.tag.outputs.tag }}.apk

- name: Create Release with gh CLI
- name: Publish production release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create ${{ steps.release-info.outputs.tag_name }} \
--title "MapSwipe Mobile ${{ steps.release-info.outputs.tag_name }}" \
--notes "## MapSwipe Mobile ${{ steps.release-info.outputs.tag_name }}

### 📱 Download APK
- **File**: \`${{ steps.release-info.outputs.apk_name }}\`
- **Size**: ${{ steps.release-info.outputs.file_size }}

### 📦 Installation
1. Download the APK file below
2. Enable \"Install from unknown sources\" in your Android settings
3. Open the downloaded APK to install

### 🔧 Build Information
- **Commit**: \`${{ github.sha }}\`
- **Build**: #${{ github.run_number }}
- **Date**: $(date +'%Y-%m-%d %H:%M:%S UTC')

### ⚙️ Technical Details
- Built with Expo SDK 54
- React Native 0.81.5
- Node.js 22" \
${{ steps.release-info.outputs.apk_file }}
gh release create ${{ steps.tag.outputs.tag }} \
--title "MapSwipe Mobile ${{ steps.tag.outputs.tag }}" \
--notes "## MapSwipe Mobile ${{ steps.tag.outputs.tag }}

### 📱 Install
1. Download the APK below
2. Enable 'Install from unknown sources' in Android settings
3. Install

### 🔧 Build info
- Commit: \`${{ github.sha }}\`
- Build: #${{ github.run_number }}
- Date: $(date +'%Y-%m-%d %H:%M:%S UTC')
- Package: \`org.missingmaps.mapswipe\`" \
generated/mapswipe-${{ steps.tag.outputs.tag }}.apk
12 changes: 12 additions & 0 deletions app.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* eslint-env node */

const isStaging = process.env.APP_ENV === 'staging';

const stagingConfig = require('./app.staging.json').expo;
const prodConfig = require('./app.prod.json').expo;

const config = isStaging ? stagingConfig : prodConfig;

module.exports = {
expo: config,
};
File renamed without changes.
46 changes: 46 additions & 0 deletions app.staging.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"expo": {
"name": "MapSwipe Staging",
"slug": "mapswipe-mobile-staging",
"version": "0.0.1",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "mapswipemobilestaging",
"newArchEnabled": true,
"backgroundColor": "#ffffff",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/images/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"bundleIdentifier": "org.missingmaps.mapswipe.staging",
"backgroundColor": "#ffffff",
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffaa00"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false,
"package": "org.missingmaps.mapswipe.staging",
"userInterfaceStyle": "light"
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
"expo-system-ui",
"@maplibre/maplibre-react-native"
],
"experiments": {
"typedRoutes": true
}
}
}
4 changes: 2 additions & 2 deletions app/(auth)/exploreGroup/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import Page from '@/components/Page';
import Text from '@/components/Text';
import { showAlert } from '@/components/Toast';
import {
managerDashboardUrl,
communityDashboardUrl,
supportedLanguages,
} from '@/constants/common';
import { SPACING_XS } from '@/constants/dimensions';
Expand Down Expand Up @@ -186,7 +186,7 @@ function ExploreGroup() {

const handleMoreStatsClick = useCallback(() => {
if (userGroupId) {
Linking.openURL(`${managerDashboardUrl}/user-group/${userGroupId}/`);
Linking.openURL(`${communityDashboardUrl}/user-group/${userGroupId}/`);
}
}, [userGroupId]);

Expand Down
Binary file modified assets/images/adaptive-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions components/ProfileStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { useRouter } from 'expo-router';

import {
managerDashboardUrl,
communityDashboardUrl,
supportedLanguages,
} from '@/constants/common';
import { UserStatsQuery } from '@/generated/types/graphql';
Expand Down Expand Up @@ -125,7 +125,7 @@ function ProfileStats({ userStats }: {userStats: UserStatsQuery | undefined}) {

const handleMoreStatsClick = useCallback(() => {
if (user?.uid) {
Linking.openURL(`${managerDashboardUrl}/user/${user?.uid}/`);
Linking.openURL(`${communityDashboardUrl}/user/${user?.uid}/`);
}
}, [user]);

Expand Down
2 changes: 1 addition & 1 deletion constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const SUPPORTED_PROJECT_TYPES = [
PROJECT_TYPE_VALIDATE_IMAGE,
];

export const managerDashboardUrl = process.env.EXPO_PUBLIC_MANAGER_DASHBOARD_URL;
export const communityDashboardUrl = process.env.EXPO_PUBLIC_COMMUNITY_DASHBOARD_URL;
export const missingMapUrl = 'https://www.missingmaps.org';
export const mapSwipeWebUrl = 'https://mapswipe.org/';
export const gqlEndpoint = `${process.env.EXPO_PUBLIC_GRAPHQL_ENDPOINT}/graphql/`;
Expand Down
Loading
Loading