import { promises } from 'fs' ; import path from 'path' ; import { ExportedConfig , ModConfig } from '../Plugin.types' ; import { Colors , Manifest , Paths , Properties , Resources , Strings , Styles } from '../android' ; import { AndroidManifest } from '../android/Manifest' ; import { parseXMLAsync , writeXMLAsync } from '../utils/XML' ; import { reverseSortString , sortObject , sortObjWithOrder } from '../utils/sortObject' ; import { ForwardedBaseModOptions , provider , withGeneratedBaseMods } from './createBaseMod' ; const { readFile , writeFile } = promises ; type AndroidModName = keyof Required < ModConfig > [ 'android' ] ; function getAndroidManifestTemplate ( config ) { return parseXMLAsync ( `
  <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${ config . android ?. package ?? 'com.placeholder.appid' }">

    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- OPTIONAL PERMISSIONS, REMOVE WHATEVER YOU DO NOT NEED -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <!-- These require runtime permissions on M -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- END OPTIONAL PERMISSIONS -->

    <queries>
      <!-- Support checking for http(s) links via the Linking API -->
      <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
      </intent>
    </queries>

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme"
      android:usesCleartextTraffic="true"
    >
      <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="YOUR-APP-URL-HERE"/>
      <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="YOUR-APP-SDK-VERSION-HERE"/>
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:theme="@style/Theme.App.SplashScreen"
      >
        <intent-filter>
          <action android:name="android.intent.action.MAIN"/>
          <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
      </activity>
      <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
    </application>
  </manifest>
  ` ) as Promise < AndroidManifest > ; } export function sortAndroidManifest ( obj ) { if ( obj . manifest ) { obj . manifest = sortObject ( obj . manifest , reverseSortString ) ; if ( Array . isArray ( obj . manifest [ 'uses-permission' ] ) ) { obj . manifest [ 'uses-permission' ] . sort ( ( a , b ) => { if ( a . $ [ 'android:name' ] < b . $ [ 'android:name' ] ) return - 1 ; if ( a . $ [ 'android:name' ] > b . $ [ 'android:name' ] ) return 1 ; return 0 ; } ) ; } if ( Array . isArray ( obj . manifest . application ) ) { obj . manifest . application = obj . manifest . application . map ( application => { application = sortObjWithOrder ( application , [ 'meta-data' , 'service' , 'activity' ] ) ; if ( Array . isArray ( application [ 'meta-data' ] ) ) { application [ 'meta-data' ] . sort ( ( a , b ) => { if ( a . $ [ 'android:name' ] < b . $ [ 'android:name' ] ) return - 1 ; if ( a . $ [ 'android:name' ] > b . $ [ 'android:name' ] ) return 1 ; return 0 ; } ) ; } return application ; } ) ; } } return obj ; } const defaultProviders = { dangerous : provider < unknown > ( { getFilePath ( ) { return '' ; } , async read ( ) { return { filePath : '' , modResults : { } } ; } , async write ( ) { } } ) , manifest : provider < Manifest . AndroidManifest > ( { isIntrospective : true , getFilePath ( { modRequest : { platformProjectRoot } } ) { return path . join ( platformProjectRoot , 'app/src/main/AndroidManifest.xml' ) ; } , async read ( filePath , config ) { try { return await Manifest . readAndroidManifestAsync ( filePath ) ; } catch ( error ) { if ( ! config . modRequest . introspect ) { throw error ; } } return await getAndroidManifestTemplate ( config ) ; } , async write ( filePath , { modResults , modRequest : { introspect } } ) { if ( introspect ) return ; await Manifest . writeAndroidManifestAsync ( filePath , sortAndroidManifest ( modResults ) ) ; } } ) , gradleProperties : provider < Properties . PropertiesItem [ ] > ( { isIntrospective : true , getFilePath ( { modRequest : { platformProjectRoot } } ) { return path . join ( platformProjectRoot , 'gradle.properties' ) ; } , async read ( filePath , config ) { try { return await Properties . parsePropertiesFile ( await readFile ( filePath , 'utf8' ) ) ; } catch ( error ) { if ( ! config . modRequest . introspect ) { throw error ; } } return [ ] ; } , async write ( filePath , { modResults , modRequest : { introspect } } ) { if ( introspect ) return ; await writeFile ( filePath , Properties . propertiesListToString ( modResults ) ) ; } } ) , strings : provider < Resources . ResourceXML > ( { isIntrospective : true , async getFilePath ( { modRequest : { projectRoot , introspect } } ) { try { return await Strings . getProjectStringsXMLPathAsync ( projectRoot ) ; } catch ( error ) { if ( ! introspect ) { throw error ; } } return '' ; } , async read ( filePath , config ) { try { return await Resources . readResourcesXMLAsync ( { path : filePath } ) ; } catch ( error ) { if ( ! config . modRequest . introspect ) { throw error ; } } return { resources : { } } ; } , async write ( filePath , { modResults , modRequest : { introspect } } ) { if ( introspect ) return ; await writeXMLAsync ( { path : filePath , xml : modResults } ) ; } } ) , colors : provider < Resources . ResourceXML > ( { isIntrospective : true , async getFilePath ( { modRequest : { projectRoot , introspect } } ) { try { return await Colors . getProjectColorsXMLPathAsync ( projectRoot ) ; } catch ( error ) { if ( ! introspect ) { throw error ; } } return '' ; } , async read ( filePath , { modRequest : { introspect } } ) { try { return await Resources . readResourcesXMLAsync ( { path : filePath } ) ; } catch ( error ) { if ( ! introspect ) { throw error ; } } return { resources : { } } ; } , async write ( filePath , { modResults , modRequest : { introspect } } ) { if ( introspect ) return ; await writeXMLAsync ( { path : filePath , xml : modResults } ) ; } } ) , colorsNight : provider < Resources . ResourceXML > ( { isIntrospective : true , async getFilePath ( { modRequest : { projectRoot , introspect } } ) { try { return await Colors . getProjectColorsXMLPathAsync ( projectRoot , { kind : 'values-night' } ) ; } catch ( error ) { if ( ! introspect ) { throw error ; } } return '' ; } , async read ( filePath , config ) { try { return await Resources . readResourcesXMLAsync ( { path : filePath } ) ; } catch ( error ) { if ( ! config . modRequest . introspect ) { throw error ; } } return { resources : { } } ; } , async write ( filePath , { modResults , modRequest : { introspect } } ) { if ( introspect ) return ; await writeXMLAsync ( { path : filePath , xml : modResults } ) ; } } ) , styles : provider < Resources . ResourceXML > ( { isIntrospective : true , async getFilePath ( { modRequest : { projectRoot , introspect } } ) { try { return await Styles . getProjectStylesXMLPathAsync ( projectRoot ) ; } catch ( error ) { if ( ! introspect ) { throw error ; } } return '' ; } , async read ( filePath , config ) { let styles = { resources : { } } ; try { styles = await Resources . readResourcesXMLAsync ( { path : filePath , fallback : `<?xml version="1.0" encoding="utf-8"?><resources xmlns:tools="http://schemas.android.com/tools"></resources>` } ) ; } catch ( error ) { if ( ! config . modRequest . introspect ) { throw error ; } } if ( ! styles . resources . $ ) { styles . resources . $ = { } ; } if ( ! styles . resources . $ ?. [ 'xmlns:tools' ] ) { styles . resources . $ [ 'xmlns:tools' ] = 'http://schemas.android.com/tools' ; } return styles ; } , async write ( filePath , { modResults , modRequest : { introspect } } ) { if ( introspect ) return ; await writeXMLAsync ( { path : filePath , xml : modResults } ) ; } } ) , projectBuildGradle : provider < Paths . GradleProjectFile > ( { getFilePath ( { modRequest : { projectRoot } } ) { return Paths . getProjectBuildGradleFilePath ( projectRoot ) ; } , async read ( filePath ) { return Paths . getFileInfo ( filePath ) ; } , async write ( filePath , { modResults : { contents } } ) { await writeFile ( filePath , contents ) ; } } ) , settingsGradle : provider < Paths . GradleProjectFile > ( { getFilePath ( { modRequest : { projectRoot } } ) { return Paths . getSettingsGradleFilePath ( projectRoot ) ; } , async read ( filePath ) { return Paths . getFileInfo ( filePath ) ; } , async write ( filePath , { modResults : { contents } } ) { await writeFile ( filePath , contents ) ; } } ) , appBuildGradle : provider < Paths . GradleProjectFile > ( { getFilePath ( { modRequest : { projectRoot } } ) { return Paths . getAppBuildGradleFilePath ( projectRoot ) ; } , async read ( filePath ) { return Paths . getFileInfo ( filePath ) ; } , async write ( filePath , { modResults : { contents } } ) { await writeFile ( filePath , contents ) ; } } ) , mainActivity : provider < Paths . ApplicationProjectFile > ( { getFilePath ( { modRequest : { projectRoot } } ) { return Paths . getProjectFilePath ( projectRoot , 'MainActivity' ) ; } , async read ( filePath ) { return Paths . getFileInfo ( filePath ) ; } , async write ( filePath , { modResults : { contents } } ) { await writeFile ( filePath , contents ) ; } } ) , mainApplication : provider < Paths . ApplicationProjectFile > ( { getFilePath ( { modRequest : { projectRoot } } ) { return Paths . getProjectFilePath ( projectRoot , 'MainApplication' ) ; } , async read ( filePath ) { return Paths . getFileInfo ( filePath ) ; } , async write ( filePath , { modResults : { contents } } ) { await writeFile ( filePath , contents ) ; } } ) } ; type AndroidDefaultProviders = typeof defaultProviders ; export function withAndroidBaseMods ( config , { providers , ... props } = { } ) { return withGeneratedBaseMods < AndroidModName > ( config , { ... props , platform : 'android' , providers : providers ?? getAndroidModFileProviders ( ) } ) ; } export function getAndroidModFileProviders ( ) { return defaultProviders ; }