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