Files
react-native-omni/plugin/src/withPip.ts
T
2026-04-24 20:47:24 +02:00

130 lines
4.7 KiB
TypeScript

import { AndroidConfig, withAndroidManifest, withMainActivity, type ConfigPlugin } from '@expo/config-plugins';
import { addImports, appendContentsInsideDeclarationBlock } from '@expo/config-plugins/build/android/codeMod';
const MODE_MARKER = 'OmniView.onActivityPipModeChanged(';
const UI_STATE_MARKER = 'OmniView.onActivityPipTransitionToPip(';
function patchMainActivityForPip(mainActivity: string, language: 'java' | 'kt'): string {
const isJava = language === 'java';
if (mainActivity.includes(MODE_MARKER) && mainActivity.includes(UI_STATE_MARKER)) {
return mainActivity;
}
const withRequiredImports = addImports(
mainActivity,
['android.app.PictureInPictureUiState', 'android.os.Build', 'dev.zoriya.omni.OmniView'],
isJava
);
let output = withRequiredImports;
if (!output.includes(MODE_MARKER)) {
const modeChangedBlock = isJava
? [
'\n @Override',
' public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {',
' super.onPictureInPictureModeChanged(isInPictureInPictureMode);',
' OmniView.onActivityPipModeChanged(this, isInPictureInPictureMode);',
' }\n',
]
: [
'\n override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {',
' super.onPictureInPictureModeChanged(isInPictureInPictureMode)',
' OmniView.onActivityPipModeChanged(this, isInPictureInPictureMode)',
' }\n',
];
output = appendContentsInsideDeclarationBlock(output, 'class MainActivity', modeChangedBlock.join('\n'));
}
if (!output.includes(UI_STATE_MARKER)) {
const uiStateChangedBlock = isJava
? [
'\n @Override',
' public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {',
' super.onPictureInPictureUiStateChanged(pipState);',
' if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && pipState.isTransitioningToPip()) {',
' OmniView.onActivityPipTransitionToPip(this);',
' }',
' }\n',
]
: [
'\n override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {',
' super.onPictureInPictureUiStateChanged(pipState)',
' if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && pipState.isTransitioningToPip) {',
' OmniView.onActivityPipTransitionToPip(this)',
' }',
' }\n',
];
output = appendContentsInsideDeclarationBlock(output, 'class MainActivity', uiStateChangedBlock.join('\n'));
}
return output;
}
function ensureUsesFeature(
androidManifest: AndroidConfig.Manifest.AndroidManifest,
featureName: string,
required: 'true' | 'false'
): void {
const usesFeature = androidManifest.manifest['uses-feature'] ?? [];
const existing = usesFeature.find((feature) => feature.$['android:name'] === featureName);
if (existing) {
existing.$['android:required'] = required;
} else {
usesFeature.push({
$: {
'android:name': featureName,
'android:required': required,
},
});
}
androidManifest.manifest['uses-feature'] = usesFeature;
}
function ensureConfigChanges(mainActivity: AndroidConfig.Manifest.ManifestActivity): void {
const requiredChanges = ['screenSize', 'smallestScreenSize', 'screenLayout', 'orientation'];
const existing = mainActivity.$['android:configChanges'] ?? '';
const existingSet = new Set(
existing
.split('|')
.map((item) => item.trim())
.filter(Boolean)
);
requiredChanges.forEach((item) => existingSet.add(item));
mainActivity.$['android:configChanges'] = Array.from(existingSet).join('|');
}
function patchManifestForPip(
androidManifest: AndroidConfig.Manifest.AndroidManifest
): AndroidConfig.Manifest.AndroidManifest {
ensureUsesFeature(androidManifest, 'android.software.picture_in_picture', 'false');
const mainActivity = AndroidConfig.Manifest.getMainActivityOrThrow(androidManifest);
mainActivity.$['android:supportsPictureInPicture'] = 'true';
mainActivity.$['android:resizeableActivity'] = 'true';
ensureConfigChanges(mainActivity);
return androidManifest;
}
export const withPip: ConfigPlugin = (config) => {
const withMainActivityPatched = withMainActivity(config, (activityConfig) => {
activityConfig.modResults.contents = patchMainActivityForPip(
activityConfig.modResults.contents,
activityConfig.modResults.language
);
return activityConfig;
});
return withAndroidManifest(withMainActivityPatched, (manifestConfig) => {
manifestConfig.modResults = patchManifestForPip(manifestConfig.modResults);
return manifestConfig;
});
};