Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c43b913dde | |||
| 256b338ae3 | |||
| ec95406305 | |||
| 6bd1dc8358 | |||
| d3d73c255e | |||
| 72cfbf41f3 | |||
| 86303e5c84 | |||
| c414ddc4fa | |||
| f0fda1947d | |||
| 8a75cffbd1 | |||
| 0b0226368b | |||
| 8f42971187 |
@@ -1,9 +1,9 @@
|
|||||||
name: Build Android APK
|
name: Release APK
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*.*.*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -12,7 +12,9 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: github.server_url == 'https://github.com'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
@@ -24,10 +26,14 @@ jobs:
|
|||||||
sudo rm -rf /usr/share/swift /usr/local/julia*
|
sudo rm -rf /usr/share/swift /usr/local/julia*
|
||||||
sudo apt-get autoremove -y > /dev/null 2>&1
|
sudo apt-get autoremove -y > /dev/null 2>&1
|
||||||
sudo apt-get clean
|
sudo apt-get clean
|
||||||
df -h
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '17'
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20'
|
||||||
@@ -36,10 +42,15 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- uses: actions/setup-java@v4
|
- name: Set up Android SDK
|
||||||
with:
|
uses: android-actions/setup-android@v3
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
- name: Accept Android SDK licenses
|
||||||
|
run: yes | sdkmanager --licenses || true
|
||||||
|
|
||||||
|
- name: Install SDK components
|
||||||
|
run: |
|
||||||
|
sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0"
|
||||||
|
|
||||||
- name: Cache Gradle
|
- name: Cache Gradle
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -47,31 +58,128 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
~/.gradle/wrapper
|
~/.gradle/wrapper
|
||||||
key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
key: ${{ runner.os }}-gradle-${{ hashFiles('package.json') }}
|
||||||
restore-keys: gradle-
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
|
||||||
- name: Generate Android native project
|
- name: Decode keystore
|
||||||
run: npx expo prebuild --platform android --clean
|
env:
|
||||||
|
KEYSTORE_B64: ${{ secrets.KEYSTORE_B64 }}
|
||||||
- name: Build debug APK
|
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
|
||||||
working-directory: android
|
KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }}
|
||||||
|
KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
chmod +x gradlew
|
mkdir -p ~/.config/sheethappens
|
||||||
./gradlew assembleDebug --no-daemon
|
echo "$KEYSTORE_B64" | base64 -d > ~/.config/sheethappens/sheethappens.jks
|
||||||
|
{
|
||||||
|
printf 'KEYSTORE_PATH=%s\n' "$HOME/.config/sheethappens/sheethappens.jks"
|
||||||
|
printf 'KEYSTORE_ALIAS=%q\n' "$KEYSTORE_ALIAS"
|
||||||
|
printf 'KEYSTORE_STORE_PASSWORD=%q\n' "$KEYSTORE_STORE_PASSWORD"
|
||||||
|
printf 'KEYSTORE_KEY_PASSWORD=%q\n' "$KEYSTORE_KEY_PASSWORD"
|
||||||
|
} > ~/.config/sheethappens/signing.env
|
||||||
|
chmod 600 ~/.config/sheethappens/signing.env ~/.config/sheethappens/sheethappens.jks
|
||||||
|
|
||||||
- name: Upload APK artifact
|
- name: Verify signing.env
|
||||||
uses: actions/upload-artifact@v4
|
run: |
|
||||||
with:
|
echo "Keys present in signing.env:"
|
||||||
name: SheetHappens-debug-${{ github.ref_name }}
|
grep -o '^[^=]*' ~/.config/sheethappens/signing.env
|
||||||
path: android/app/build/outputs/apk/debug/app-debug.apk
|
source ~/.config/sheethappens/signing.env
|
||||||
retention-days: 30
|
[ -n "$KEYSTORE_PATH" ] && echo "KEYSTORE_PATH: set" || echo "KEYSTORE_PATH: EMPTY"
|
||||||
|
[ -n "$KEYSTORE_ALIAS" ] && echo "KEYSTORE_ALIAS: set" || echo "KEYSTORE_ALIAS: EMPTY"
|
||||||
|
[ -n "$KEYSTORE_STORE_PASSWORD" ] && echo "KEYSTORE_STORE_PASSWORD: set" || echo "KEYSTORE_STORE_PASSWORD: EMPTY"
|
||||||
|
[ -n "$KEYSTORE_KEY_PASSWORD" ] && echo "KEYSTORE_KEY_PASSWORD: set" || echo "KEYSTORE_KEY_PASSWORD: EMPTY"
|
||||||
|
|
||||||
|
- name: Patch native modules for Android 15 16KB page size
|
||||||
|
run: |
|
||||||
|
WORKLETS="node_modules/react-native-worklets/android/CMakeLists.txt"
|
||||||
|
REANIMATED="node_modules/react-native-reanimated/android/CMakeLists.txt"
|
||||||
|
if [ -f "$WORKLETS" ] && ! grep -q "max-page-size" "$WORKLETS"; then
|
||||||
|
printf '\ntarget_link_options(worklets PRIVATE "-Wl,-z,max-page-size=16384")\n' >> "$WORKLETS"
|
||||||
|
fi
|
||||||
|
if [ -f "$REANIMATED" ] && ! grep -q "max-page-size" "$REANIMATED"; then
|
||||||
|
printf '\ntarget_link_options(reanimated PRIVATE "-Wl,-z,max-page-size=16384")\n' >> "$REANIMATED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build signed APK
|
||||||
|
run: ./build-apk.sh
|
||||||
|
|
||||||
|
- name: Find built APK
|
||||||
|
id: apk
|
||||||
|
run: |
|
||||||
|
APK=$(ls dist/*.apk | sort | tail -1)
|
||||||
|
echo "path=$APK" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Generate changelog
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${{ github.ref_name }}$" | head -1)
|
||||||
|
echo "Previous tag: $PREV_TAG"
|
||||||
|
echo "prev_tag=$PREV_TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
FEATS=$(git log "${PREV_TAG}..HEAD" --pretty=format:"%s" --no-merges \
|
||||||
|
| grep -E "^feat(\([^)]+\))?: " \
|
||||||
|
| sed -E 's/^feat(\([^)]+\))?: //' \
|
||||||
|
| sed 's/^/- /')
|
||||||
|
|
||||||
|
FIXES=$(git log "${PREV_TAG}..HEAD" --pretty=format:"%s" --no-merges \
|
||||||
|
| grep -E "^fix(\([^)]+\))?: " \
|
||||||
|
| sed -E 's/^fix(\([^)]+\))?: //' \
|
||||||
|
| sed 's/^/- /')
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "changelog<<CEOF"
|
||||||
|
[ -n "$FEATS" ] && printf "### What's New\n%s\n\n" "$FEATS"
|
||||||
|
[ -n "$FIXES" ] && printf "### Bug Fixes\n%s\n\n" "$FIXES"
|
||||||
|
echo "CEOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
name: "SheetHappens ${{ github.ref_name }}"
|
||||||
|
body: |
|
||||||
|
## SheetHappens ${{ github.ref_name }}
|
||||||
|
|
||||||
|
${{ steps.changelog.outputs.changelog }}
|
||||||
|
[Full changelog](https://github.com/${{ github.repository }}/compare/${{ steps.changelog.outputs.prev_tag }}...${{ github.ref_name }}) · [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
1. Enable "Unknown sources" on the device
|
||||||
|
2. Transfer the APK to the device and open it to install
|
||||||
|
files: ${{ steps.apk.outputs.path }}
|
||||||
|
draft: false
|
||||||
|
prerelease: ${{ contains(github.ref_name, '-') }}
|
||||||
|
|
||||||
|
- name: Create Gitea Release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
TAG: ${{ github.ref_name }}
|
||||||
|
PREV_TAG: ${{ steps.changelog.outputs.prev_tag }}
|
||||||
|
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
run: |
|
run: |
|
||||||
gh release create ${{ github.ref_name }} \
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
android/app/build/outputs/apk/debug/app-debug.apk#SheetHappens-${{ github.ref_name }}.apk \
|
echo "GITEA_TOKEN not set — skipping Gitea release"
|
||||||
--title "SheetHappens ${{ github.ref_name }}" \
|
exit 0
|
||||||
--generate-notes
|
fi
|
||||||
|
|
||||||
|
BODY="## SheetHappens ${TAG}
|
||||||
|
|
||||||
|
${CHANGELOG}
|
||||||
|
|
||||||
|
[Full changelog](https://homegit.gyozamancave.fr/billisdead/SheetHappens/compare/${PREV_TAG}...${TAG}) · [CHANGELOG.md](https://homegit.gyozamancave.fr/billisdead/SheetHappens/raw/branch/main/CHANGELOG.md)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
Download the APK from the [GitHub release](https://github.com/${GH_REPO}/releases/tag/${TAG}), enable \"Unknown sources\" on the device and install."
|
||||||
|
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"https://homegit.gyozamancave.fr/api/v1/repos/billisdead/SheetHappens/releases" \
|
||||||
|
-d "{
|
||||||
|
\"tag_name\": \"${TAG}\",
|
||||||
|
\"name\": \"SheetHappens ${TAG}\",
|
||||||
|
\"body\": $(printf '%s' "$BODY" | jq -Rs .),
|
||||||
|
\"draft\": false,
|
||||||
|
\"prerelease\": $([ "${TAG#*-}" != "${TAG}" ] && echo true || echo false)
|
||||||
|
}"
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to SheetHappens are documented here.
|
||||||
|
Each release links to the full diff for detailed context.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [v0.1.7] — 2026-06-22
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Revert splash screen overrides that caused Android crash-to-desktop — expo-router already manages the splash lifecycle internally
|
||||||
|
- Remove redundant `SafeAreaProvider` wrapper (expo-router's `ExpoRoot` already injects it)
|
||||||
|
|
||||||
|
[View changes](https://github.com/pirona/SheetHappens/compare/v0.1.6...v0.1.7)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [v0.1.6] — 2026-06-22
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Prevent black screen on launch by holding splash until SecureStore settings load
|
||||||
|
- Add `splash.backgroundColor` to `app.json` to match app theme on Android
|
||||||
|
|
||||||
|
[View changes](https://github.com/pirona/SheetHappens/compare/v0.1.5...v0.1.6)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [v0.1.5] — 2026-06-22
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Fix signed APK release: inject signing secrets via env vars, write `signing.env` with `printf %q` to handle special characters
|
||||||
|
- Add `signing.env` verification step in CI
|
||||||
|
|
||||||
|
[View changes](https://github.com/pirona/SheetHappens/compare/v0.1.0...v0.1.5)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [v0.1.0] — Initial release
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Incident log with SQLite persistence
|
||||||
|
- Voice input via `expo-speech-recognition`
|
||||||
|
- Edit mode, service autocomplete
|
||||||
|
- Dark theme (NativeWind + expo-router)
|
||||||
|
- Onboarding flow
|
||||||
|
- Signed APK via GitHub Actions CI
|
||||||
+60
-1
@@ -1,13 +1,55 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useState, Component } from "react";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
import { Stack, router } from "expo-router";
|
import { Stack, router } from "expo-router";
|
||||||
import { StatusBar } from "expo-status-bar";
|
import { StatusBar } from "expo-status-bar";
|
||||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||||
|
import { View, Text, ScrollView } from "react-native";
|
||||||
import "../global.css";
|
import "../global.css";
|
||||||
import { Colors } from "@/constants/theme";
|
import { Colors } from "@/constants/theme";
|
||||||
import { useSettings } from "@/hooks/useSettings";
|
import { useSettings } from "@/hooks/useSettings";
|
||||||
|
|
||||||
|
// Catch JS errors in release mode and show them on-screen instead of
|
||||||
|
// silently crashing to desktop. Remove once the crash is identified.
|
||||||
|
class CrashBoundary extends Component<
|
||||||
|
{ children: ReactNode },
|
||||||
|
{ error: Error | null }
|
||||||
|
> {
|
||||||
|
state = { error: null };
|
||||||
|
static getDerivedStateFromError(e: Error) {
|
||||||
|
return { error: e };
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const { error } = this.state;
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, backgroundColor: "#0f1117", padding: 20, paddingTop: 60 }}>
|
||||||
|
<Text style={{ color: "#ef4444", fontSize: 16, fontWeight: "700", marginBottom: 12 }}>
|
||||||
|
CRASH — copie ce texte
|
||||||
|
</Text>
|
||||||
|
<ScrollView>
|
||||||
|
<Text style={{ color: "#fff", fontSize: 11, fontFamily: "monospace" }}>
|
||||||
|
{String(error)}{"\n\n"}{(error as any).stack}
|
||||||
|
</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const { settings, ready } = useSettings();
|
const { settings, ready } = useSettings();
|
||||||
|
const [jsError, setJsError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const prev = (ErrorUtils as any).getGlobalHandler();
|
||||||
|
(ErrorUtils as any).setGlobalHandler((e: Error, isFatal: boolean) => {
|
||||||
|
if (isFatal) setJsError(`${String(e)}\n\n${(e as any).stack ?? ""}`);
|
||||||
|
prev?.(e, isFatal);
|
||||||
|
});
|
||||||
|
return () => (ErrorUtils as any).setGlobalHandler(prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ready) return;
|
if (!ready) return;
|
||||||
@@ -16,7 +58,23 @@ export default function RootLayout() {
|
|||||||
}
|
}
|
||||||
}, [ready, settings.onboardingDone]);
|
}, [ready, settings.onboardingDone]);
|
||||||
|
|
||||||
|
if (jsError) {
|
||||||
return (
|
return (
|
||||||
|
<View style={{ flex: 1, backgroundColor: "#0f1117", padding: 20, paddingTop: 60 }}>
|
||||||
|
<Text style={{ color: "#ef4444", fontSize: 16, fontWeight: "700", marginBottom: 12 }}>
|
||||||
|
FATAL JS ERROR — copie ce texte
|
||||||
|
</Text>
|
||||||
|
<ScrollView>
|
||||||
|
<Text style={{ color: "#fff", fontSize: 11, fontFamily: "monospace" }}>
|
||||||
|
{jsError}
|
||||||
|
</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CrashBoundary>
|
||||||
<GestureHandlerRootView style={{ flex: 1, backgroundColor: Colors.bg }}>
|
<GestureHandlerRootView style={{ flex: 1, backgroundColor: Colors.bg }}>
|
||||||
<StatusBar style="light" />
|
<StatusBar style="light" />
|
||||||
<Stack
|
<Stack
|
||||||
@@ -35,5 +93,6 @@ export default function RootLayout() {
|
|||||||
<Stack.Screen name="onboarding" options={{ headerShown: false }} />
|
<Stack.Screen name="onboarding" options={{ headerShown: false }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</GestureHandlerRootView>
|
</GestureHandlerRootView>
|
||||||
|
</CrashBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+189
@@ -0,0 +1,189 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# build-apk.sh — Build a signed release APK locally without expo.dev / EAS.
|
||||||
|
#
|
||||||
|
# Prerequisites (first time only):
|
||||||
|
# 1. Generate a keystore:
|
||||||
|
# keytool -genkey -v -keystore ~/.config/sheethappens/sheethappens.jks \
|
||||||
|
# -alias sheethappens -keyalg RSA -keysize 2048 -validity 10000
|
||||||
|
# 2. Fill in signing credentials:
|
||||||
|
# mkdir -p ~/.config/sheethappens
|
||||||
|
# cat > ~/.config/sheethappens/signing.env <<EOF
|
||||||
|
# KEYSTORE_PATH=~/.config/sheethappens/sheethappens.jks
|
||||||
|
# KEYSTORE_ALIAS=sheethappens
|
||||||
|
# KEYSTORE_STORE_PASSWORD=your_store_password
|
||||||
|
# KEYSTORE_KEY_PASSWORD=your_key_password
|
||||||
|
# EOF
|
||||||
|
# chmod 600 ~/.config/sheethappens/signing.env
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./build-apk.sh # → dist/sheethappens-YYYYMMDD-HHMM.apk
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
SIGNING_ENV="$HOME/.config/sheethappens/signing.env"
|
||||||
|
DIST_DIR="$SCRIPT_DIR/dist"
|
||||||
|
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||||
|
info() { echo -e "${GREEN}[build]${NC} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
|
||||||
|
abort() { echo -e "${RED}[error]${NC} $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ─── 1. Signing credentials ────────────────────────────────────────────────
|
||||||
|
if [ ! -f "$SIGNING_ENV" ]; then
|
||||||
|
abort "Missing signing.env — see comments at top of this script."
|
||||||
|
fi
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$SIGNING_ENV"
|
||||||
|
|
||||||
|
[ -z "${KEYSTORE_PATH:-}" ] && abort "KEYSTORE_PATH not set in signing.env"
|
||||||
|
[ -z "${KEYSTORE_ALIAS:-}" ] && abort "KEYSTORE_ALIAS not set in signing.env"
|
||||||
|
[ -z "${KEYSTORE_STORE_PASSWORD:-}" ] && abort "KEYSTORE_STORE_PASSWORD not set in signing.env"
|
||||||
|
[ -z "${KEYSTORE_KEY_PASSWORD:-}" ] && abort "KEYSTORE_KEY_PASSWORD not set in signing.env"
|
||||||
|
|
||||||
|
KEYSTORE_PATH_EXPANDED="${KEYSTORE_PATH/#\$HOME/$HOME}"
|
||||||
|
KEYSTORE_PATH_EXPANDED="${KEYSTORE_PATH_EXPANDED/#~/$HOME}"
|
||||||
|
|
||||||
|
[ ! -f "$KEYSTORE_PATH_EXPANDED" ] && \
|
||||||
|
abort "Keystore not found: $KEYSTORE_PATH_EXPANDED"
|
||||||
|
|
||||||
|
# ─── 2. Java check ─────────────────────────────────────────────────────────
|
||||||
|
if [ -z "${JAVA_HOME:-}" ] || "$JAVA_HOME/bin/java" -version 2>&1 | grep -qE '"(2[5-9]|[3-9][0-9])\.'; then
|
||||||
|
if [ -d "$HOME/jdk21" ]; then
|
||||||
|
export JAVA_HOME="$HOME/jdk21"
|
||||||
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
|
info "Using local JDK 21: $JAVA_HOME"
|
||||||
|
else
|
||||||
|
abort "Java 25+ detected but Gradle only supports ≤ Java 24.\nInstall JDK 21 and set JAVA_HOME."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
info "Java: $("$JAVA_HOME/bin/java" -version 2>&1 | head -1)"
|
||||||
|
|
||||||
|
# ─── 3. Android SDK ────────────────────────────────────────────────────────
|
||||||
|
if [ -z "${ANDROID_HOME:-}" ]; then
|
||||||
|
for candidate in "$HOME/android-sdk" "$HOME/Android/Sdk" "/opt/android-sdk"; do
|
||||||
|
if [ -d "$candidate/platform-tools" ]; then
|
||||||
|
export ANDROID_HOME="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
[ -z "${ANDROID_HOME:-}" ] && abort "Android SDK not found. Set ANDROID_HOME."
|
||||||
|
export PATH="$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin"
|
||||||
|
info "Android SDK: $ANDROID_HOME"
|
||||||
|
|
||||||
|
# ─── 4. expo prebuild ──────────────────────────────────────────────────────
|
||||||
|
info "Running expo prebuild (Android)…"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
npx expo prebuild --platform android --clean --no-install
|
||||||
|
|
||||||
|
# ─── 4.5. Patch native modules — 16KB page size (Android 15) ──────────────
|
||||||
|
info "Patching native modules for 16KB page size support (Android 15)…"
|
||||||
|
|
||||||
|
WORKLETS_CMAKE="$SCRIPT_DIR/node_modules/react-native-worklets/android/CMakeLists.txt"
|
||||||
|
REANIMATED_CMAKE="$SCRIPT_DIR/node_modules/react-native-reanimated/android/CMakeLists.txt"
|
||||||
|
|
||||||
|
if [ -f "$WORKLETS_CMAKE" ] && ! grep -q "max-page-size" "$WORKLETS_CMAKE"; then
|
||||||
|
printf '\ntarget_link_options(worklets PRIVATE "-Wl,-z,max-page-size=16384")\n' >> "$WORKLETS_CMAKE"
|
||||||
|
info "Patched react-native-worklets for 16KB pages"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$REANIMATED_CMAKE" ] && ! grep -q "max-page-size" "$REANIMATED_CMAKE"; then
|
||||||
|
printf '\ntarget_link_options(reanimated PRIVATE "-Wl,-z,max-page-size=16384")\n' >> "$REANIMATED_CMAKE"
|
||||||
|
info "Patched react-native-reanimated for 16KB pages"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── 4.6. Disable New Architecture (Android 15 compat) ────────────────────
|
||||||
|
GRADLE_PROPS_NEWARCH="$SCRIPT_DIR/android/gradle.properties"
|
||||||
|
if [ -f "$GRADLE_PROPS_NEWARCH" ]; then
|
||||||
|
sed -i 's/^newArchEnabled=true/newArchEnabled=false/' "$GRADLE_PROPS_NEWARCH"
|
||||||
|
if ! grep -q "^newArchEnabled=" "$GRADLE_PROPS_NEWARCH"; then
|
||||||
|
echo "newArchEnabled=false" >> "$GRADLE_PROPS_NEWARCH"
|
||||||
|
fi
|
||||||
|
info "New Architecture disabled in gradle.properties"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── 5. Patch build.gradle — inject release signingConfig ──────────────────
|
||||||
|
info "Patching android/app/build.gradle for release signing…"
|
||||||
|
|
||||||
|
python3 - "$SCRIPT_DIR/android/app/build.gradle" << 'PYEOF'
|
||||||
|
import sys, re
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
if 'MYAPP_UPLOAD_STORE_FILE' in content:
|
||||||
|
print('[patch] build.gradle already patched, skipping.')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
release_signing = """\n release {
|
||||||
|
if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
|
||||||
|
storeFile file(MYAPP_UPLOAD_STORE_FILE)
|
||||||
|
storePassword MYAPP_UPLOAD_STORE_PASSWORD
|
||||||
|
keyAlias MYAPP_UPLOAD_KEY_ALIAS
|
||||||
|
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
content = re.sub(
|
||||||
|
r'(signingConfigs\s*\{[\s\S]*?debug\s*\{[\s\S]*?\})',
|
||||||
|
r'\1' + release_signing,
|
||||||
|
content,
|
||||||
|
count=1
|
||||||
|
)
|
||||||
|
|
||||||
|
def replace_release_signing(m):
|
||||||
|
return m.group(0).replace('signingConfig signingConfigs.debug',
|
||||||
|
'signingConfig signingConfigs.release', 1)
|
||||||
|
|
||||||
|
content = re.sub(
|
||||||
|
r'release\s*\{[^}]*signingConfig\s+signingConfigs\.debug',
|
||||||
|
replace_release_signing,
|
||||||
|
content,
|
||||||
|
count=1,
|
||||||
|
flags=re.DOTALL
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
print('[patch] build.gradle patched for release signing.')
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
# ─── 6. Inject signing props into gradle.properties ────────────────────────
|
||||||
|
info "Injecting signing credentials into gradle.properties…"
|
||||||
|
GRADLE_PROPS="$SCRIPT_DIR/android/gradle.properties"
|
||||||
|
|
||||||
|
sed -i '/^# --- sheethappens release signing/,/^# --- end signing/d' "$GRADLE_PROPS" 2>/dev/null || true
|
||||||
|
|
||||||
|
cat >> "$GRADLE_PROPS" << EOF
|
||||||
|
|
||||||
|
# --- sheethappens release signing (injected by build-apk.sh — wiped after build)
|
||||||
|
MYAPP_UPLOAD_STORE_FILE=$KEYSTORE_PATH_EXPANDED
|
||||||
|
MYAPP_UPLOAD_STORE_PASSWORD=$KEYSTORE_STORE_PASSWORD
|
||||||
|
MYAPP_UPLOAD_KEY_ALIAS=$KEYSTORE_ALIAS
|
||||||
|
MYAPP_UPLOAD_KEY_PASSWORD=$KEYSTORE_KEY_PASSWORD
|
||||||
|
# --- end signing
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ─── 7. Gradle build ───────────────────────────────────────────────────────
|
||||||
|
cd "$SCRIPT_DIR/android"
|
||||||
|
chmod +x gradlew
|
||||||
|
info "Building APK (release)…"
|
||||||
|
./gradlew assembleRelease --no-daemon
|
||||||
|
|
||||||
|
# ─── 8. Wipe credentials from gradle.properties ───────────────────────────
|
||||||
|
sed -i '/^# --- sheethappens release signing/,/^# --- end signing/d' "$GRADLE_PROPS"
|
||||||
|
info "Signing credentials wiped from gradle.properties."
|
||||||
|
|
||||||
|
# ─── 9. Copy to dist/ ─────────────────────────────────────────────────────
|
||||||
|
mkdir -p "$DIST_DIR"
|
||||||
|
TIMESTAMP="$(date +%Y%m%d-%H%M)"
|
||||||
|
OUTPUT="$DIST_DIR/sheethappens-$TIMESTAMP.apk"
|
||||||
|
cp "app/build/outputs/apk/release/app-release.apk" "$OUTPUT"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
info "Build complete!"
|
||||||
|
echo -e " ${GREEN}→ $OUTPUT${NC}"
|
||||||
|
echo ""
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Exclude native build for packages not used in this project.
|
||||||
|
// react-native-gesture-handler detects their absence and falls back to its
|
||||||
|
// noreanimated codepath, so GestureHandlerRootView continues to work.
|
||||||
|
module.exports = {
|
||||||
|
dependencies: {
|
||||||
|
'react-native-reanimated': {
|
||||||
|
platforms: { android: null },
|
||||||
|
},
|
||||||
|
'react-native-worklets': {
|
||||||
|
platforms: { android: null },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user