Support F-Droid Android builds (#1768)

* fix(android): support source-based APK builds

Co-authored-by: Anton Lazarev <22821309+antonok-edm@users.noreply.github.com>

* fix(android): address fdroid review feedback

Co-authored-by: Anton Lazarev <22821309+antonok-edm@users.noreply.github.com>

* fix(android): complete the F-Droid build profile

* fix(app): isolate the F-Droid runtime flag

---------

Co-authored-by: Anton Lazarev <22821309+antonok-edm@users.noreply.github.com>
This commit is contained in:
Mohamed Boudra
2026-07-13 17:18:19 +08:00
committed by GitHub
co-authored by Anton Lazarev
parent 1f7efbd185
commit b6d49da4df
13 changed files with 328 additions and 67 deletions
@@ -4,6 +4,7 @@ import { Pressable, Text, View } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { QrCode, Link2, ClipboardPaste } from "lucide-react-native";
import { AdaptiveModalSheet, type SheetHeader } from "./adaptive-modal-sheet";
import { isFdroidBuild } from "@/constants/build-profile";
import { isNative } from "@/constants/platform";
const styles = StyleSheet.create((theme) => ({
@@ -86,7 +87,7 @@ export function AddHostMethodModal({
</View>
</Pressable>
{isNative ? (
{isNative && !isFdroidBuild ? (
<Pressable
style={styles.option}
onPress={handleScan}
+47 -45
View File
@@ -15,6 +15,7 @@ import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
import { buildOpenProjectRoute } from "@/utils/host-routes";
import { PaseoLogo } from "@/components/icons/paseo-logo";
import { openExternalUrl } from "@/utils/open-external-url";
import { isFdroidBuild } from "@/constants/build-profile";
import { isWeb, isNative } from "@/constants/platform";
interface WelcomeAction {
@@ -201,51 +202,52 @@ export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) {
[onHostAdded, finishOnboarding],
);
const actions: WelcomeAction[] = isWeb
? [
{
key: "direct-connection",
label: t("pairing.connectionMethods.direct.title"),
testID: "welcome-direct-connection",
primary: true,
icon: Link2,
onPress: handleOpenDirect,
},
{
key: "paste-pairing-link",
label: t("pairing.connectionMethods.pasteLink.title"),
testID: "welcome-paste-pairing-link",
primary: false,
icon: ClipboardPaste,
onPress: handleOpenPasteLink,
},
]
: [
{
key: "scan-qr",
label: t("pairing.connectionMethods.scanQr.title"),
testID: "welcome-scan-qr",
primary: true,
icon: QrCode,
onPress: handleScanQr,
},
{
key: "direct-connection",
label: t("pairing.connectionMethods.direct.title"),
testID: "welcome-direct-connection",
primary: false,
icon: Link2,
onPress: handleOpenDirect,
},
{
key: "paste-pairing-link",
label: t("pairing.connectionMethods.pasteLink.title"),
testID: "welcome-paste-pairing-link",
primary: false,
icon: ClipboardPaste,
onPress: handleOpenPasteLink,
},
];
const actions: WelcomeAction[] =
isWeb || isFdroidBuild
? [
{
key: "direct-connection",
label: t("pairing.connectionMethods.direct.title"),
testID: "welcome-direct-connection",
primary: true,
icon: Link2,
onPress: handleOpenDirect,
},
{
key: "paste-pairing-link",
label: t("pairing.connectionMethods.pasteLink.title"),
testID: "welcome-paste-pairing-link",
primary: false,
icon: ClipboardPaste,
onPress: handleOpenPasteLink,
},
]
: [
{
key: "scan-qr",
label: t("pairing.connectionMethods.scanQr.title"),
testID: "welcome-scan-qr",
primary: true,
icon: QrCode,
onPress: handleScanQr,
},
{
key: "direct-connection",
label: t("pairing.connectionMethods.direct.title"),
testID: "welcome-direct-connection",
primary: false,
icon: Link2,
onPress: handleOpenDirect,
},
{
key: "paste-pairing-link",
label: t("pairing.connectionMethods.pasteLink.title"),
testID: "welcome-paste-pairing-link",
primary: false,
icon: ClipboardPaste,
onPress: handleOpenPasteLink,
},
];
const scrollContentContainerStyle = useMemo(
() => [styles.container, { paddingBottom: theme.spacing[6] + insets.bottom }],
@@ -0,0 +1,4 @@
import Constants from "expo-constants";
/** F-Droid build without proprietary camera, notification, or OTA dependencies. */
export const isFdroidBuild = Constants.expoConfig?.extra?.fdroidBuild === true;
+18
View File
@@ -0,0 +1,18 @@
const DENIED_CAMERA_PERMISSION = {
canAskAgain: false,
expires: "never",
granted: false,
status: "denied",
} as const;
async function requestCameraPermission() {
return DENIED_CAMERA_PERMISSION;
}
export function useCameraPermissions() {
return [DENIED_CAMERA_PERMISSION, requestCameraPermission] as const;
}
export function CameraView(): null {
return null;
}
@@ -0,0 +1,40 @@
const DENIED_NOTIFICATION_PERMISSION = {
canAskAgain: false,
expires: "never",
granted: false,
status: "denied",
} as const;
export const PermissionStatus = {
DENIED: "denied",
GRANTED: "granted",
UNDETERMINED: "undetermined",
} as const;
export const AndroidImportance = { DEFAULT: 3 } as const;
export async function getPermissionsAsync() {
return DENIED_NOTIFICATION_PERMISSION;
}
export async function requestPermissionsAsync() {
return DENIED_NOTIFICATION_PERMISSION;
}
export async function getExpoPushTokenAsync() {
return { data: "" };
}
export async function setNotificationChannelAsync() {
return null;
}
export function setNotificationHandler(): void {}
export function addNotificationResponseReceivedListener() {
return { remove() {} };
}
export async function getLastNotificationResponseAsync() {
return null;
}