1*082815dcSEvan Baconimport { getResourceXMLPathAsync } from './Paths'; 2*082815dcSEvan Baconimport { 3*082815dcSEvan Bacon buildResourceGroup, 4*082815dcSEvan Bacon buildResourceItem, 5*082815dcSEvan Bacon ensureDefaultResourceXML, 6*082815dcSEvan Bacon findResourceGroup, 7*082815dcSEvan Bacon getResourceItemsAsObject, 8*082815dcSEvan Bacon readResourcesXMLAsync, 9*082815dcSEvan Bacon ResourceGroupXML, 10*082815dcSEvan Bacon ResourceItemXML, 11*082815dcSEvan Bacon ResourceKind, 12*082815dcSEvan Bacon ResourceXML, 13*082815dcSEvan Bacon} from './Resources'; 14*082815dcSEvan Bacon 15*082815dcSEvan Bacon// Adds support for `tools:x` 16*082815dcSEvan Baconconst fallbackResourceString = `<?xml version="1.0" encoding="utf-8"?><resources xmlns:tools="http://schemas.android.com/tools"></resources>`; 17*082815dcSEvan Bacon 18*082815dcSEvan Baconexport async function readStylesXMLAsync({ 19*082815dcSEvan Bacon path, 20*082815dcSEvan Bacon fallback = fallbackResourceString, 21*082815dcSEvan Bacon}: { 22*082815dcSEvan Bacon path: string; 23*082815dcSEvan Bacon fallback?: string | null; 24*082815dcSEvan Bacon}): Promise<ResourceXML> { 25*082815dcSEvan Bacon return readResourcesXMLAsync({ path, fallback }); 26*082815dcSEvan Bacon} 27*082815dcSEvan Bacon 28*082815dcSEvan Baconexport async function getProjectStylesXMLPathAsync( 29*082815dcSEvan Bacon projectRoot: string, 30*082815dcSEvan Bacon { kind }: { kind?: ResourceKind } = {} 31*082815dcSEvan Bacon): Promise<string> { 32*082815dcSEvan Bacon return getResourceXMLPathAsync(projectRoot, { kind, name: 'styles' }); 33*082815dcSEvan Bacon} 34*082815dcSEvan Bacon 35*082815dcSEvan Baconfunction ensureDefaultStyleResourceXML(xml: ResourceXML): ResourceXML { 36*082815dcSEvan Bacon xml = ensureDefaultResourceXML(xml); 37*082815dcSEvan Bacon if (!Array.isArray(xml?.resources?.style)) { 38*082815dcSEvan Bacon xml.resources.style = []; 39*082815dcSEvan Bacon } 40*082815dcSEvan Bacon return xml; 41*082815dcSEvan Bacon} 42*082815dcSEvan Bacon 43*082815dcSEvan Baconexport function getStyleParent( 44*082815dcSEvan Bacon xml: ResourceXML, 45*082815dcSEvan Bacon group: { name: string; parent?: string } 46*082815dcSEvan Bacon): ResourceGroupXML | null { 47*082815dcSEvan Bacon return findResourceGroup(xml.resources.style, group); 48*082815dcSEvan Bacon} 49*082815dcSEvan Bacon 50*082815dcSEvan Baconexport function getStylesItem({ 51*082815dcSEvan Bacon name, 52*082815dcSEvan Bacon xml, 53*082815dcSEvan Bacon parent, 54*082815dcSEvan Bacon}: { 55*082815dcSEvan Bacon name: string; 56*082815dcSEvan Bacon xml: ResourceXML; 57*082815dcSEvan Bacon parent: { name: string; parent?: string }; 58*082815dcSEvan Bacon}): ResourceItemXML | null { 59*082815dcSEvan Bacon xml = ensureDefaultStyleResourceXML(xml); 60*082815dcSEvan Bacon 61*082815dcSEvan Bacon const appTheme = getStyleParent(xml, parent); 62*082815dcSEvan Bacon 63*082815dcSEvan Bacon if (!appTheme) { 64*082815dcSEvan Bacon return null; 65*082815dcSEvan Bacon } 66*082815dcSEvan Bacon 67*082815dcSEvan Bacon if (appTheme.item) { 68*082815dcSEvan Bacon const existingItem = appTheme.item.filter(({ $: head }) => head.name === name)[0]; 69*082815dcSEvan Bacon 70*082815dcSEvan Bacon // Don't want to 2 of the same item, so if one exists, we overwrite it 71*082815dcSEvan Bacon if (existingItem) { 72*082815dcSEvan Bacon return existingItem; 73*082815dcSEvan Bacon } 74*082815dcSEvan Bacon } 75*082815dcSEvan Bacon return null; 76*082815dcSEvan Bacon} 77*082815dcSEvan Bacon 78*082815dcSEvan Baconexport function setStylesItem({ 79*082815dcSEvan Bacon item, 80*082815dcSEvan Bacon xml, 81*082815dcSEvan Bacon parent, 82*082815dcSEvan Bacon}: { 83*082815dcSEvan Bacon item: ResourceItemXML; 84*082815dcSEvan Bacon xml: ResourceXML; 85*082815dcSEvan Bacon parent: { name: string; parent: string }; 86*082815dcSEvan Bacon}): ResourceXML { 87*082815dcSEvan Bacon xml = ensureDefaultStyleResourceXML(xml); 88*082815dcSEvan Bacon 89*082815dcSEvan Bacon let appTheme = getStyleParent(xml, parent); 90*082815dcSEvan Bacon 91*082815dcSEvan Bacon if (!appTheme) { 92*082815dcSEvan Bacon appTheme = buildResourceGroup(parent); 93*082815dcSEvan Bacon xml.resources!.style!.push(appTheme); 94*082815dcSEvan Bacon } 95*082815dcSEvan Bacon 96*082815dcSEvan Bacon if (appTheme.item) { 97*082815dcSEvan Bacon const existingItem = appTheme.item.filter(({ $: head }) => head.name === item.$.name)[0]; 98*082815dcSEvan Bacon 99*082815dcSEvan Bacon // Don't want to 2 of the same item, so if one exists, we overwrite it 100*082815dcSEvan Bacon if (existingItem) { 101*082815dcSEvan Bacon existingItem._ = item._; 102*082815dcSEvan Bacon existingItem.$ = item.$; 103*082815dcSEvan Bacon } else { 104*082815dcSEvan Bacon appTheme.item.push(item); 105*082815dcSEvan Bacon } 106*082815dcSEvan Bacon } else { 107*082815dcSEvan Bacon appTheme.item = [item]; 108*082815dcSEvan Bacon } 109*082815dcSEvan Bacon return xml; 110*082815dcSEvan Bacon} 111*082815dcSEvan Bacon 112*082815dcSEvan Baconexport function removeStylesItem({ 113*082815dcSEvan Bacon name, 114*082815dcSEvan Bacon xml, 115*082815dcSEvan Bacon parent, 116*082815dcSEvan Bacon}: { 117*082815dcSEvan Bacon name: string; 118*082815dcSEvan Bacon xml: ResourceXML; 119*082815dcSEvan Bacon parent: { name: string; parent: string }; 120*082815dcSEvan Bacon}): ResourceXML { 121*082815dcSEvan Bacon xml = ensureDefaultStyleResourceXML(xml); 122*082815dcSEvan Bacon const appTheme = getStyleParent(xml, parent); 123*082815dcSEvan Bacon if (appTheme?.item) { 124*082815dcSEvan Bacon const index = appTheme.item.findIndex(({ $: head }: ResourceItemXML) => head.name === name); 125*082815dcSEvan Bacon if (index > -1) { 126*082815dcSEvan Bacon appTheme.item.splice(index, 1); 127*082815dcSEvan Bacon } 128*082815dcSEvan Bacon } 129*082815dcSEvan Bacon return xml; 130*082815dcSEvan Bacon} 131*082815dcSEvan Bacon 132*082815dcSEvan Bacon// This is a very common theme so make it reusable. 133*082815dcSEvan Baconexport function getAppThemeLightNoActionBarGroup() { 134*082815dcSEvan Bacon return { name: 'AppTheme', parent: 'Theme.AppCompat.Light.NoActionBar' }; 135*082815dcSEvan Bacon} 136*082815dcSEvan Bacon 137*082815dcSEvan Baconexport function assignStylesValue( 138*082815dcSEvan Bacon xml: ResourceXML, 139*082815dcSEvan Bacon { 140*082815dcSEvan Bacon add, 141*082815dcSEvan Bacon value, 142*082815dcSEvan Bacon targetApi, 143*082815dcSEvan Bacon name, 144*082815dcSEvan Bacon parent, 145*082815dcSEvan Bacon }: { 146*082815dcSEvan Bacon add: boolean; 147*082815dcSEvan Bacon value: string; 148*082815dcSEvan Bacon targetApi?: string; 149*082815dcSEvan Bacon name: string; 150*082815dcSEvan Bacon parent: { name: string; parent: string }; 151*082815dcSEvan Bacon } 152*082815dcSEvan Bacon): ResourceXML { 153*082815dcSEvan Bacon if (add) { 154*082815dcSEvan Bacon return setStylesItem({ 155*082815dcSEvan Bacon xml, 156*082815dcSEvan Bacon parent, 157*082815dcSEvan Bacon item: buildResourceItem({ 158*082815dcSEvan Bacon name, 159*082815dcSEvan Bacon targetApi, 160*082815dcSEvan Bacon value, 161*082815dcSEvan Bacon }), 162*082815dcSEvan Bacon }); 163*082815dcSEvan Bacon } 164*082815dcSEvan Bacon return removeStylesItem({ 165*082815dcSEvan Bacon xml, 166*082815dcSEvan Bacon parent, 167*082815dcSEvan Bacon name, 168*082815dcSEvan Bacon }); 169*082815dcSEvan Bacon} 170*082815dcSEvan Bacon 171*082815dcSEvan Bacon/** 172*082815dcSEvan Bacon * Helper to convert a styles.xml parent's children into a simple k/v pair. 173*082815dcSEvan Bacon * Added for testing purposes. 174*082815dcSEvan Bacon * 175*082815dcSEvan Bacon * @param xml 176*082815dcSEvan Bacon * @returns 177*082815dcSEvan Bacon */ 178*082815dcSEvan Baconexport function getStylesGroupAsObject( 179*082815dcSEvan Bacon xml: ResourceXML, 180*082815dcSEvan Bacon group: { name: string; parent?: string } 181*082815dcSEvan Bacon): Record<string, string> | null { 182*082815dcSEvan Bacon const xmlGroup = getStyleParent(xml, group); 183*082815dcSEvan Bacon return xmlGroup?.item ? getResourceItemsAsObject(xmlGroup.item) : null; 184*082815dcSEvan Bacon} 185