Files
paseo/packages/app/app.config.js
T
paseo-bot[bot] b63a325be3 Let Android devices without a camera install Paseo from Google Play (#6339)
* fix(android): declare the camera optional so camera-less devices can install

Requesting CAMERA makes Android treat android.hardware.camera as a required
feature, so Google Play marks devices without a camera as incompatible.
Pairing works without QR scanning, so declare the camera features optional.

Fixes #6303

* test(app): resolve the manifest with the Expo CLI's prebuild packages

Load @expo/prebuild-config and @expo/config-plugins from @expo/cli instead
of the older hoisted copies, and set the F-Droid flag explicitly for each
build profile case.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-10 09:54:15 +02:00

201 lines
6.0 KiB
JavaScript

const fs = require("node:fs");
const path = require("node:path");
const pkg = require("./package.json");
const withAndroidAsyncStorageSize = require("./plugins/with-android-async-storage-size");
const withAndroidOptionalCamera = require("./plugins/with-android-optional-camera");
const withAndroidProfileable = require("./plugins/with-android-profileable");
const withFdroidAutolinking = require("./plugins/with-fdroid-autolinking");
const withPasteInput = require("./plugins/with-paste-input");
const withAndroidScroll = require("./modules/paseo-scroll/app.plugin");
const { getNativeReleaseVersion } = require("./native-release-version");
const appVariant = process.env.APP_VARIANT ?? "production";
const isFdroidBuild = process.env.PASEO_FDROID_BUILD === "1";
const isProfileBuild = process.env.PASEO_PROFILE_BUILD === "1";
const buildProfile = isFdroidBuild
? {
androidPermissions: [
"RECORD_AUDIO",
"android.permission.RECORD_AUDIO",
"android.permission.MODIFY_AUDIO_SETTINGS",
],
cameraPlugins: [],
fdroidPlugins: [withFdroidAutolinking],
notificationPlugins: [],
}
: {
androidPermissions: [
"RECORD_AUDIO",
"android.permission.RECORD_AUDIO",
"android.permission.MODIFY_AUDIO_SETTINGS",
"CAMERA",
"android.permission.CAMERA",
],
cameraPlugins: [
[
"expo-camera",
{
cameraPermission:
"Allow $(PRODUCT_NAME) to access your camera to scan pairing QR codes.",
},
],
],
fdroidPlugins: [],
notificationPlugins: [
[
"expo-notifications",
{
icon: "./assets/images/notification-icon.png",
color: "#20744A",
},
],
],
};
function resolveSecretFile(params) {
const fromEnv = process.env[params.envKey];
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
return fromEnv.trim();
}
const fallbackAbsolutePath = path.resolve(__dirname, params.fallbackRelativePath);
if (fs.existsSync(fallbackAbsolutePath)) {
return params.fallbackRelativePath;
}
return undefined;
}
const variants = {
production: {
name: "Paseo",
packageId: "sh.paseo",
googleServicesFile: resolveSecretFile({
envKey: "GOOGLE_SERVICES_FILE_PROD",
fallbackRelativePath: "./.secrets/google-services.prod.json",
}),
googleServiceInfoPlist: resolveSecretFile({
envKey: "GOOGLE_SERVICE_INFO_PLIST_PROD",
fallbackRelativePath: "./.secrets/GoogleService-Info.prod.plist",
}),
},
development: {
name: "Paseo Debug",
packageId: "sh.paseo.debug",
googleServicesFile: resolveSecretFile({
envKey: "GOOGLE_SERVICES_FILE_DEBUG",
fallbackRelativePath: "./.secrets/google-services.debug.json",
}),
googleServiceInfoPlist: resolveSecretFile({
envKey: "GOOGLE_SERVICE_INFO_PLIST_DEBUG",
fallbackRelativePath: "./.secrets/GoogleService-Info.debug.plist",
}),
},
};
const variant = variants[appVariant] ?? variants.production;
const nativeReleaseVersion = getNativeReleaseVersion(pkg.version);
export default {
expo: {
name: variant.name,
slug: "voice-mobile",
version: nativeReleaseVersion.appVersion,
orientation: "portrait",
icon: "./assets/images/icon.png",
scheme: "paseo",
userInterfaceStyle: "automatic",
newArchEnabled: true,
ios: {
supportsTablet: true,
infoPlist: {
NSMicrophoneUsageDescription: "This app needs access to the microphone for voice commands.",
ITSAppUsesNonExemptEncryption: false,
},
bundleIdentifier: variant.packageId,
...(variant.googleServiceInfoPlist
? { googleServicesFile: variant.googleServiceInfoPlist }
: {}),
buildNumber: nativeReleaseVersion.iosBuildNumber,
},
android: {
adaptiveIcon: {
backgroundColor: "#000000",
foregroundImage: "./assets/images/android-icon-foreground.png",
},
edgeToEdgeEnabled: true,
predictiveBackGestureEnabled: false,
softwareKeyboardLayoutMode: "resize",
// Allow HTTP connections for local network hosts (required for release builds)
usesCleartextTraffic: true,
permissions: buildProfile.androidPermissions,
package: variant.packageId,
versionCode: nativeReleaseVersion.androidVersionCode,
...(variant.googleServicesFile ? { googleServicesFile: variant.googleServicesFile } : {}),
},
web: {
output: "single",
favicon: "./assets/images/favicon.png",
},
autolinking: {
searchPaths: ["../../node_modules", "./node_modules"],
},
plugins: [
"expo-router",
withPasteInput,
withAndroidScroll,
[withAndroidAsyncStorageSize, 64],
...buildProfile.cameraPlugins,
withAndroidOptionalCamera,
[
"expo-splash-screen",
{
image: "./assets/images/splash-icon.png",
imageWidth: 200,
resizeMode: "contain",
backgroundColor: "#ffffff",
dark: {
backgroundColor: "#000000",
},
},
],
...buildProfile.notificationPlugins,
"expo-audio",
[
"expo-gradle-jvmargs",
{
xmx: "4096m",
maxMetaspace: "1024m",
},
],
[
"expo-build-properties",
{
android: {
minSdkVersion: 29,
kotlinVersion: "2.1.20",
// Allow HTTP connections for local network hosts in release builds
usesCleartextTraffic: true,
},
},
],
...buildProfile.fdroidPlugins,
...(isProfileBuild ? [withAndroidProfileable] : []),
],
experiments: {
typedRoutes: true,
reactCompiler: true,
autolinkingModuleResolution: true,
},
extra: {
fdroidBuild: isFdroidBuild,
profileBuild: isProfileBuild,
router: {},
eas: {
projectId: "0e7f65ce-0367-46c8-a238-2b65963d235a",
},
},
owner: "getpaseo",
},
};