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