@applicaster/zapp-react-native-utils 14.0.0-alpha.1661204539 → 14.0.0-alpha.1740013076

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +11 -6
  2. package/actionsExecutor/ScreenActions.ts +91 -17
  3. package/actionsExecutor/feedDecorator.ts +171 -0
  4. package/actionsExecutor/screenResolver.ts +6 -3
  5. package/analyticsUtils/AnalyticsEvents/helper.ts +81 -0
  6. package/analyticsUtils/AnalyticsEvents/sendHeaderClickEvent.ts +1 -1
  7. package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -1
  8. package/analyticsUtils/AnalyticsEvents/sendOnClickEvent.ts +14 -4
  9. package/analyticsUtils/__tests__/analyticsUtils.test.js +3 -0
  10. package/analyticsUtils/events.ts +8 -0
  11. package/analyticsUtils/index.tsx +3 -4
  12. package/analyticsUtils/manager.ts +1 -1
  13. package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
  14. package/appUtils/HooksManager/Hook.ts +4 -4
  15. package/appUtils/HooksManager/index.ts +11 -1
  16. package/appUtils/accessibilityManager/const.ts +13 -0
  17. package/appUtils/accessibilityManager/hooks.ts +35 -1
  18. package/appUtils/accessibilityManager/index.ts +154 -30
  19. package/appUtils/accessibilityManager/utils.ts +24 -0
  20. package/appUtils/contextKeysManager/contextResolver.ts +30 -3
  21. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +5 -0
  22. package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
  23. package/appUtils/focusManager/index.ios.ts +10 -0
  24. package/appUtils/focusManager/index.ts +82 -11
  25. package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
  26. package/appUtils/focusManagerAux/utils/index.ts +106 -3
  27. package/appUtils/platform/platformUtils.ts +31 -1
  28. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +0 -15
  29. package/appUtils/playerManager/useChapterMarker.tsx +0 -1
  30. package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -0
  31. package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
  32. package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
  33. package/arrayUtils/index.ts +8 -3
  34. package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
  35. package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
  36. package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
  37. package/audioPlayerUtils/assets/index.ts +2 -0
  38. package/audioPlayerUtils/index.ts +242 -0
  39. package/componentsUtils/__tests__/isTabsScreen.test.ts +38 -0
  40. package/componentsUtils/index.ts +4 -1
  41. package/conf/player/__tests__/selectors.test.ts +34 -0
  42. package/conf/player/selectors.ts +10 -0
  43. package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
  44. package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
  45. package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
  46. package/configurationUtils/__tests__/manifestKeyParser.test.ts +546 -0
  47. package/configurationUtils/index.ts +64 -35
  48. package/configurationUtils/manifestKeyParser.ts +57 -32
  49. package/focusManager/FocusManager.ts +26 -16
  50. package/focusManager/Tree.ts +25 -21
  51. package/focusManager/__tests__/FocusManager.test.ts +50 -8
  52. package/index.d.ts +1 -10
  53. package/manifestUtils/_internals/getDefaultConfiguration.js +28 -0
  54. package/manifestUtils/{_internals.js → _internals/index.js} +2 -25
  55. package/manifestUtils/createConfig.js +4 -1
  56. package/manifestUtils/defaultManifestConfigurations/player.js +1253 -200
  57. package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
  58. package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +1 -2
  59. package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
  60. package/navigationUtils/index.ts +7 -5
  61. package/package.json +2 -3
  62. package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
  63. package/playerUtils/PlayerTTS/index.ts +1 -0
  64. package/playerUtils/__tests__/configurationUtils.test.ts +1 -65
  65. package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
  66. package/playerUtils/_internals/__tests__/utils.test.ts +71 -0
  67. package/playerUtils/_internals/index.ts +1 -0
  68. package/playerUtils/_internals/utils.ts +31 -0
  69. package/playerUtils/configurationUtils.ts +0 -44
  70. package/playerUtils/getPlayerActionButtons.ts +17 -0
  71. package/playerUtils/index.ts +59 -0
  72. package/playerUtils/usePlayerTTS.ts +21 -0
  73. package/playerUtils/useValidatePlayerConfig.tsx +22 -19
  74. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
  75. package/reactHooks/cell-click/__tests__/index.test.js +3 -0
  76. package/reactHooks/cell-click/index.ts +3 -0
  77. package/reactHooks/debugging/__tests__/index.test.js +0 -1
  78. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +47 -90
  79. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
  80. package/reactHooks/feed/index.ts +2 -0
  81. package/reactHooks/feed/useBatchLoading.ts +17 -10
  82. package/reactHooks/feed/useFeedLoader.tsx +39 -44
  83. package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
  84. package/reactHooks/feed/usePipesCacheReset.ts +3 -3
  85. package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
  86. package/reactHooks/layout/__tests__/index.test.tsx +3 -1
  87. package/reactHooks/layout/isTablet/index.ts +12 -5
  88. package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
  89. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
  90. package/reactHooks/layout/useLayoutVersion.ts +5 -5
  91. package/reactHooks/navigation/index.ts +7 -5
  92. package/reactHooks/navigation/useIsScreenActive.ts +9 -5
  93. package/reactHooks/navigation/useRoute.ts +7 -2
  94. package/reactHooks/navigation/useScreenStateStore.ts +8 -0
  95. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
  96. package/reactHooks/screen/useScreenContext.ts +1 -1
  97. package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +2 -1
  98. package/reactHooks/state/index.ts +1 -1
  99. package/reactHooks/state/useHomeRiver.ts +4 -2
  100. package/reactHooks/state/useRivers.ts +7 -8
  101. package/riverComponetsMeasurementProvider/index.tsx +1 -1
  102. package/screenPickerUtils/index.ts +7 -0
  103. package/services/js2native.ts +1 -0
  104. package/storage/ScreenSingleValueProvider.ts +138 -26
  105. package/storage/ScreenStateMultiSelectProvider.ts +210 -36
  106. package/testUtils/index.tsx +7 -8
  107. package/time/BackgroundTimer.ts +6 -4
  108. package/utils/__tests__/find.test.ts +36 -0
  109. package/utils/__tests__/pathOr.test.ts +37 -0
  110. package/utils/__tests__/startsWith.test.ts +30 -0
  111. package/utils/find.ts +3 -0
  112. package/utils/index.ts +24 -1
  113. package/utils/pathOr.ts +5 -0
  114. package/utils/startsWith.ts +9 -0
  115. package/playerUtils/configurationGenerator.ts +0 -2572
@@ -1,22 +1,29 @@
1
1
  import { BehaviorSubject } from "rxjs";
2
- import { log_debug, SingleValueProvider } from "./StorageSingleSelectProvider";
2
+ import { SingleValueProvider } from "./StorageSingleSelectProvider";
3
+ import { createLogger } from "../logger";
4
+ import { bridgeLogger } from "../../zapp-react-native-bridge/logger";
5
+ import { useScreenStateStore } from "../reactHooks/navigation/useScreenStateStore";
3
6
 
4
- export const screenStates: Record<string, Record<string, string>> = {};
7
+ export const { log_debug, log_error } = createLogger({
8
+ category: "ScreenSingleValueProvider",
9
+ subsystem: "zapp-react-native-bridge",
10
+ parent: bridgeLogger,
11
+ });
5
12
 
6
13
  export class ScreenSingleValueProvider implements SingleValueProvider {
14
+ // @ts-ignore
15
+ private valueSubject: BehaviorSubject<string | null>;
16
+ private screenStateStoreUnsubscribe: (() => void) | null = null;
17
+
7
18
  private static providers: Record<
8
19
  string,
9
20
  Record<string, SingleValueProvider>
10
21
  > = {};
11
22
 
12
- // TODO: Screen State should be stored outside of this class and should be shared with screen multi-select provider
13
- public static getState(screenRoute: string): Record<string, string> {
14
- return screenStates[screenRoute] || {};
15
- }
16
-
17
23
  public static getProvider(
18
24
  key: string,
19
- screenRoute: string
25
+ screenRoute: string,
26
+ screenStateStore: ReturnType<typeof useScreenStateStore>
20
27
  ): SingleValueProvider {
21
28
  if (!key) {
22
29
  throw new Error("ScreenSingleValueProvider: Key is required");
@@ -26,48 +33,119 @@ export class ScreenSingleValueProvider implements SingleValueProvider {
26
33
  throw new Error("ScreenSingleValueProvider: Screen route is required");
27
34
  }
28
35
 
29
- if (!this.providers[screenRoute]) {
30
- this.providers[screenRoute] = {};
36
+ if (!screenStateStore) {
37
+ throw new Error(
38
+ "ScreenSingleValueProvider: screen state store is required"
39
+ );
31
40
  }
32
41
 
33
- if (!this.providers[screenRoute][key]) {
34
- this.providers[screenRoute][key] = new ScreenSingleValueProvider(
35
- key,
36
- screenRoute
37
- );
42
+ if (!this.providers[screenRoute]) {
43
+ this.providers[screenRoute] = {};
38
44
  }
39
45
 
40
- if (!screenStates[screenRoute]) {
41
- screenStates[screenRoute] = {};
46
+ try {
47
+ if (!this.providers[screenRoute][key]) {
48
+ this.providers[screenRoute][key] = new ScreenSingleValueProvider(
49
+ key,
50
+ screenRoute,
51
+ screenStateStore
52
+ );
53
+ } else {
54
+ (
55
+ this.providers[screenRoute][key] as ScreenSingleValueProvider
56
+ ).updateStore(screenStateStore);
57
+ }
58
+ } catch (error) {
59
+ log_error("Failed to create provider", { key, screenRoute, error });
60
+ throw error;
42
61
  }
43
62
 
44
63
  return this.providers[screenRoute][key];
45
64
  }
46
65
 
47
- // @ts-ignore
48
- private valueSubject: BehaviorSubject<string | null>;
49
- p;
50
66
  // @ts-ignore
51
67
  private constructor(
52
68
  private key: string,
53
- private route: string
69
+ route: string,
70
+ private screenStateStore: ReturnType<typeof useScreenStateStore>
54
71
  ) {
55
72
  if (!key) {
56
73
  throw new Error("ScreenSingleValueProvider: Key is required");
57
74
  }
58
75
 
59
- // localStorage.addListener?.({ key, namespace }, this.reloadValue);
76
+ if (!screenStateStore) {
77
+ throw new Error(
78
+ "ScreenSingleValueProvider: screen state store is required"
79
+ );
80
+ }
60
81
 
82
+ this.key = key;
61
83
  this.valueSubject = new BehaviorSubject<string | null>(null);
62
84
 
85
+ this.setupScreenStateSubscription();
86
+
63
87
  void this.getValueAsync();
64
- log_debug("ScreenSingleValueProvider: Initializing");
88
+
89
+ log_debug("ScreenSingleValueProvider: Initializing", { key, route });
90
+ }
91
+
92
+ private updateStore(
93
+ screenStateStore: ReturnType<typeof useScreenStateStore>
94
+ ): void {
95
+ this.cleanup();
96
+ this.screenStateStore = screenStateStore;
97
+ this.setupScreenStateSubscription();
98
+ void this.getValueAsync();
99
+ }
100
+
101
+ private setupScreenStateSubscription(): void {
102
+ this.screenStateStoreUnsubscribe = this.screenStateStore.subscribe(
103
+ (state) => ({
104
+ value: state.data[this.key],
105
+ exists: this.key in state.data,
106
+ })
107
+ // (current, previous) => {
108
+ // if (!current.exists && previous.exists) {
109
+ // log_debug(
110
+ // `ScreenSingleValueProvider: Key deleted from store: ${this.key}`
111
+ // );
112
+
113
+ // // TODO: If we need to handle deletion, we can do it here
114
+ // }
115
+
116
+ // if (current.value !== previous.value) {
117
+ // this.valueSubject.next(current.value || null);
118
+
119
+ // log_debug(`ScreenSingleValueProvider: Key updated: ${this.key}`, {
120
+ // previous: previous.value,
121
+ // current: current.value,
122
+ // });
123
+ // }
124
+ // }
125
+ );
126
+
127
+ log_debug(
128
+ `ScreenSingleValueProvider: screen state store subscription setup for key: ${this.key}`
129
+ );
130
+ }
131
+
132
+ private cleanup(): void {
133
+ if (this.screenStateStoreUnsubscribe) {
134
+ this.screenStateStoreUnsubscribe();
135
+ this.screenStateStoreUnsubscribe = null;
136
+
137
+ log_debug(
138
+ `ScreenSingleValueProvider: Unsubscribed from screen state store for key: ${this.key}`
139
+ );
140
+ }
65
141
  }
66
142
 
67
143
  clearValue(): Promise<void> {
68
- delete [this.route][this.key];
144
+ this.screenStateStore.getState().removeValue(this.key);
69
145
  this.valueSubject.next(null);
70
146
 
147
+ log_debug(`clearValue: value cleared for key: ${this.key}`);
148
+
71
149
  return Promise.resolve();
72
150
  }
73
151
 
@@ -80,13 +158,47 @@ export class ScreenSingleValueProvider implements SingleValueProvider {
80
158
  }
81
159
 
82
160
  getValueAsync(): Promise<string | null> {
83
- return Promise.resolve(this.valueSubject.value);
161
+ const currentValue = this.screenStateStore.getState().data[this.key];
162
+ const value = currentValue || null;
163
+ const selected = this.getValue();
164
+ const valuesAreEqual = value === selected;
165
+
166
+ const bothEmpty =
167
+ (value === null || value === "") &&
168
+ (selected === null || selected === "");
169
+
170
+ if (!this.valueSubject.closed && !valuesAreEqual && !bothEmpty) {
171
+ this.valueSubject.next(value);
172
+ }
173
+
174
+ return Promise.resolve(value);
84
175
  }
85
176
 
86
177
  setValue(value: string): Promise<void> {
87
- screenStates[this.route][this.key] = value;
178
+ this.screenStateStore.getState().setValue(this.key, value);
88
179
  this.valueSubject.next(value);
89
180
 
181
+ log_debug(`setValue: value set for key: ${this.key}`, { value });
182
+
90
183
  return Promise.resolve();
91
184
  }
185
+
186
+ // TODO: remove if not needed
187
+
188
+ public static cleanupRoute(screenRoute: string): void {
189
+ if (this.providers[screenRoute]) {
190
+ Object.values(this.providers[screenRoute]).forEach((provider) => {
191
+ (provider as ScreenSingleValueProvider).destroy();
192
+ });
193
+
194
+ delete this.providers[screenRoute];
195
+ }
196
+ }
197
+
198
+ // TODO: remove if not needed
199
+
200
+ public destroy(): void {
201
+ this.valueSubject.complete();
202
+ this.cleanup();
203
+ }
92
204
  }
@@ -1,10 +1,19 @@
1
- import { log_debug, MultiSelectProvider } from "./StorageMultiSelectProvider";
1
+ import { MultiSelectProvider } from "./StorageMultiSelectProvider";
2
2
  import { BehaviorSubject } from "rxjs";
3
- import { screenStates } from "./ScreenSingleValueProvider";
3
+ import { createLogger } from "../logger";
4
+ import { bridgeLogger } from "../../zapp-react-native-bridge/logger";
5
+ import { useScreenStateStore } from "../reactHooks/navigation/useScreenStateStore";
6
+
7
+ export const { log_debug, log_error } = createLogger({
8
+ category: "ScreenMultiSelectProvider",
9
+ subsystem: "zapp-react-native-bridge",
10
+ parent: bridgeLogger,
11
+ });
4
12
 
5
13
  export class ScreenMultiSelectProvider implements MultiSelectProvider {
6
14
  // @ts-ignore
7
15
  private itemSubject: BehaviorSubject<string[] | null>;
16
+ private screenStateStoreUnsubscribe: (() => void) | null = null;
8
17
 
9
18
  private static providers: Record<
10
19
  string,
@@ -13,29 +22,42 @@ export class ScreenMultiSelectProvider implements MultiSelectProvider {
13
22
 
14
23
  public static getProvider(
15
24
  key: string,
16
- screenRoute: string
25
+ screenRoute: string,
26
+ screenStateStore: ReturnType<typeof useScreenStateStore>
17
27
  ): MultiSelectProvider {
18
28
  if (!key) {
19
- throw new Error("ScreenSingleValueProvider: Key is required");
29
+ throw new Error("ScreenMultiSelectProvider: Key is required");
20
30
  }
21
31
 
22
32
  if (!screenRoute) {
23
- throw new Error("ScreenSingleValueProvider: Screen route is required");
33
+ throw new Error("ScreenMultiSelectProvider: Screen route is required");
24
34
  }
25
35
 
26
- if (!this.providers[screenRoute]) {
27
- this.providers[screenRoute] = {};
36
+ if (!screenStateStore) {
37
+ throw new Error(
38
+ "ScreenMultiSelectProvider: screen state store is required"
39
+ );
28
40
  }
29
41
 
30
- if (!this.providers[screenRoute][key]) {
31
- this.providers[screenRoute][key] = new ScreenMultiSelectProvider(
32
- key,
33
- screenRoute
34
- );
42
+ if (!this.providers[screenRoute]) {
43
+ this.providers[screenRoute] = {};
35
44
  }
36
45
 
37
- if (!screenStates[screenRoute]) {
38
- screenStates[screenRoute] = {};
46
+ try {
47
+ if (!this.providers[screenRoute][key]) {
48
+ this.providers[screenRoute][key] = new ScreenMultiSelectProvider(
49
+ key,
50
+ screenRoute,
51
+ screenStateStore
52
+ );
53
+ } else {
54
+ (
55
+ this.providers[screenRoute][key] as ScreenMultiSelectProvider
56
+ ).updateStore(screenStateStore);
57
+ }
58
+ } catch (error) {
59
+ log_error("Failed to create provider", { key, screenRoute, error });
60
+ throw error;
39
61
  }
40
62
 
41
63
  return this.providers[screenRoute][key];
@@ -44,76 +66,228 @@ export class ScreenMultiSelectProvider implements MultiSelectProvider {
44
66
  // @ts-ignore
45
67
  private constructor(
46
68
  private key: string,
47
- private route: string
69
+ route: string,
70
+ private screenStateStore: ReturnType<typeof useScreenStateStore>
48
71
  ) {
49
72
  if (!key) {
50
- throw new Error("StorageMultiSelectProvider: Key is required");
73
+ throw new Error("ScreenMultiSelectProvider: Key is required");
74
+ }
75
+
76
+ if (!screenStateStore) {
77
+ throw new Error(
78
+ "ScreenMultiSelectProvider: screen state store is required"
79
+ );
51
80
  }
52
81
 
53
82
  this.key = key;
54
- // this.storage.addListener?.({ key, namespace }, this.reloadItems);
83
+ this.itemSubject = new BehaviorSubject<string[]>([]);
55
84
 
56
- this.itemSubject = new BehaviorSubject([]);
85
+ this.setupScreenStateSubscription();
57
86
 
58
87
  void this.getSelectedAsync();
59
- log_debug("StorageMultiSelectProvider: Initializing");
88
+
89
+ log_debug("ScreenMultiSelectProvider: Initializing", { key, route });
90
+ }
91
+
92
+ private updateStore(
93
+ screenStateStore: ReturnType<typeof useScreenStateStore>
94
+ ): void {
95
+ if (screenStateStore === this.screenStateStore) {
96
+ return;
97
+ }
98
+
99
+ this.cleanup();
100
+ this.screenStateStore = screenStateStore;
101
+ this.setupScreenStateSubscription();
102
+ void this.getSelectedAsync();
103
+ }
104
+
105
+ private setupScreenStateSubscription(): void {
106
+ this.screenStateStoreUnsubscribe = this.screenStateStore.subscribe(
107
+ (state) => ({
108
+ value: state.data[this.key],
109
+ exists: this.key in state.data,
110
+ })
111
+ // (current, previous) => {
112
+ // if (!current.exists && previous.exists) {
113
+ // log_debug(
114
+ // `ScreenMultiSelectProvider: Key deleted from store: ${this.key}`
115
+ // );
116
+
117
+ // // TODO: If we need to handle deletion, we can do it here
118
+ // }
119
+
120
+ // if (current.value !== previous.value) {
121
+ // const items = this.parseStoredValue(current.value);
122
+ // this.itemSubject.next(items);
123
+
124
+ // log_debug(`ScreenMultiSelectProvider: Key updated: ${this.key}`, {
125
+ // previous: this.parseStoredValue(previous.value),
126
+ // current: items,
127
+ // });
128
+ // }
129
+ // }
130
+ );
131
+
132
+ log_debug(
133
+ `ScreenMultiSelectProvider: screen state store subscription setup for key: ${this.key}`
134
+ );
135
+ }
136
+
137
+ private parseStoredValue(value: string): string[] {
138
+ if (!value) return [];
139
+
140
+ return typeof value === "string"
141
+ ? value.split(",").filter((item) => item.length > 0)
142
+ : [];
143
+ }
144
+
145
+ private formatValueForStorage(items: string[]): string {
146
+ return items.join(",");
147
+ }
148
+
149
+ private getCurrentItems(): Set<string> {
150
+ const currentValue = this.screenStateStore.getState().data[this.key];
151
+ const items = this.parseStoredValue(currentValue);
152
+
153
+ return new Set(items);
154
+ }
155
+
156
+ private updateScreenState(items: Set<string>): void {
157
+ const itemsArray = Array.from(items);
158
+ const formattedValue = this.formatValueForStorage(itemsArray);
159
+
160
+ this.screenStateStore.getState().setValue(this.key, formattedValue);
161
+ const itemsToUpdate = Array.from(items) || [];
162
+ this.itemSubject.next(itemsToUpdate);
60
163
  }
61
164
 
62
- private getSetOfCurrentItems() {
63
- const currentResult: string = screenStates[this.route][this.key];
165
+ private cleanup(): void {
166
+ if (this.screenStateStoreUnsubscribe) {
167
+ this.screenStateStoreUnsubscribe();
168
+ this.screenStateStoreUnsubscribe = null;
64
169
 
65
- return new Set(currentResult ? currentResult.split(",") : []);
170
+ log_debug(
171
+ `ScreenMultiSelectProvider: Unsubscribed from screen state store for key: ${this.key}`
172
+ );
173
+ }
66
174
  }
67
175
 
68
176
  addItem(item: string): Promise<void> {
69
- const items = this.getSetOfCurrentItems();
177
+ const items = this.getCurrentItems();
70
178
  items.add(item);
71
- screenStates[this.route][this.key] = Array.from(items).join(",");
72
- this.itemSubject.next(Array.from(items) || []);
179
+ this.updateScreenState(items);
73
180
 
74
181
  log_debug(
75
- `addItem: item added: ${item}, current items: ${Array.from(items)}`
182
+ `addItem: item added: ${item}, current items: [${Array.from(items).join(", ")}]`
76
183
  );
77
184
 
78
185
  return Promise.resolve();
79
186
  }
80
187
 
81
- addItems(_items: string[]): Promise<void> {
188
+ addItems(itemsToAdd: string[]): Promise<void> {
189
+ const items = this.getCurrentItems();
190
+
191
+ for (const item of itemsToAdd) {
192
+ items.add(item);
193
+ }
194
+
195
+ this.updateScreenState(items);
196
+
197
+ log_debug(
198
+ `addItems: items added: [${itemsToAdd.join(", ")}], current items: [${Array.from(items).join(", ")}]`
199
+ );
200
+
82
201
  return Promise.resolve();
83
202
  }
84
203
 
85
204
  public getObservable = (): BehaviorSubject<string[]> => this.itemSubject;
86
205
 
87
206
  getSelectedAsync(): Promise<string[]> {
88
- const values = screenStates[this.route][this.key]?.split(",");
89
- this.itemSubject.next(values || []);
207
+ const currentValue = this.screenStateStore.getState().data[this.key] || [];
208
+ const values = this.parseStoredValue(currentValue);
209
+ const selected = this.getSelectedItems();
210
+
211
+ const arraysAreEqual =
212
+ Array.isArray(values) &&
213
+ Array.isArray(selected) &&
214
+ values.length === selected.length &&
215
+ values.every((val, index) => val === selected[index]);
216
+
217
+ const bothEmpty = values.length === 0 && selected.length === 0;
218
+
219
+ if (!this.itemSubject.closed && !arraysAreEqual && !bothEmpty) {
220
+ this.itemSubject.next(values);
221
+ }
90
222
 
91
- return Promise.resolve(values || []);
223
+ return Promise.resolve(values);
92
224
  }
93
225
 
94
226
  getSelectedItems(): string[] {
95
- return this.itemSubject.value;
227
+ return this.itemSubject.value || [];
96
228
  }
97
229
 
98
230
  removeAllItems(): Promise<void> {
231
+ this.screenStateStore.getState().removeValue(this.key);
232
+ this.itemSubject.next([]);
233
+
234
+ log_debug(`removeAllItems: all items removed for key: ${this.key}`);
235
+
99
236
  return Promise.resolve();
100
237
  }
101
238
 
102
239
  removeItem(item: string): Promise<void> {
103
- const items = this.getSetOfCurrentItems();
240
+ const items = this.getCurrentItems();
104
241
  items.delete(item);
105
242
 
106
- screenStates[this.route][this.key] = Array.from(items).join(",");
107
- this.itemSubject.next(Array.from(items) || []);
243
+ this.updateScreenState(items);
244
+
245
+ log_debug(
246
+ `removeItem: item removed: ${item}, remaining items: ${Array.from(items).join(", ")}`
247
+ );
108
248
 
109
249
  return Promise.resolve();
110
250
  }
111
251
 
112
- removeItems(_items: string[]): Promise<void> {
252
+ removeItems(itemsToRemove: string[]): Promise<void> {
253
+ const items = this.getCurrentItems();
254
+
255
+ for (const item of itemsToRemove) {
256
+ items.delete(item);
257
+ }
258
+
259
+ this.updateScreenState(items);
260
+
261
+ log_debug(
262
+ `removeItems: items removed: [${itemsToRemove.join(", ")}], remaining items: [${Array.from(items).join(", ")}]`
263
+ );
264
+
113
265
  return Promise.resolve();
114
266
  }
115
267
 
116
- setSelectedItems(_items: string[]): Promise<void> {
268
+ setSelectedItems(items: string[]): Promise<void> {
269
+ this.updateScreenState(new Set(items));
270
+ log_debug(`setSelectedItems: items set to: [${items.join(", ")}]`);
271
+
117
272
  return Promise.resolve();
118
273
  }
274
+
275
+ // TODO: remove if not needed
276
+
277
+ public static cleanupRoute(screenRoute: string): void {
278
+ if (this.providers[screenRoute]) {
279
+ Object.values(this.providers[screenRoute]).forEach((provider) => {
280
+ (provider as ScreenMultiSelectProvider).destroy();
281
+ });
282
+
283
+ delete this.providers[screenRoute];
284
+ }
285
+ }
286
+
287
+ // TODO: remove if not needed
288
+
289
+ public destroy(): void {
290
+ this.itemSubject.complete();
291
+ this.cleanup();
292
+ }
119
293
  }
@@ -1,17 +1,16 @@
1
- import * as R from "ramda";
2
-
1
+ import { SafeAreaProvider } from "react-native-safe-area-context";
2
+ import { render } from "@testing-library/react-native";
3
3
  import React, { PropsWithChildren } from "react";
4
- import { View } from "react-native";
5
-
4
+ import configureStore from "redux-mock-store";
6
5
  import { Provider } from "react-redux";
6
+ import { View } from "react-native";
7
7
  import thunk from "redux-thunk";
8
- import configureStore from "redux-mock-store";
9
- import { SafeAreaProvider } from "react-native-safe-area-context";
8
+ import * as R from "ramda";
9
+
10
10
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
11
11
 
12
- import { render } from "@testing-library/react-native";
13
- import { AnalyticsProvider } from "../analyticsUtils";
14
12
  import { ThemeContext } from "../theme";
13
+ import { AnalyticsProvider } from "../analyticsUtils";
15
14
 
16
15
  export { getByTestId } from "./getByTestId";
17
16
 
@@ -11,15 +11,17 @@ class BackgroundTimer {
11
11
  this.uniqueId = 0;
12
12
  this.callbacks = {};
13
13
 
14
- const EventEmitter = platformSelect({
14
+ const EventEmitter: typeof DeviceEventEmitter | undefined = platformSelect({
15
15
  android: DeviceEventEmitter,
16
- ios: undefined,
16
+ android_tv: DeviceEventEmitter,
17
+ amazon: DeviceEventEmitter, // probably does not exist and uses android_tv
17
18
  default: undefined,
18
19
  });
19
20
 
20
21
  EventEmitter?.addListener("BackgroundTimer.timer.fired", (id: number) => {
21
- if (this.callbacks[id]) {
22
- const callback = this.callbacks[id];
22
+ const callback = this.callbacks[id];
23
+
24
+ if (callback) {
23
25
  delete this.callbacks[id];
24
26
  callback();
25
27
  }
@@ -0,0 +1,36 @@
1
+ import { find } from "../find";
2
+
3
+ test("example 1", () => {
4
+ const predicate = <T>(_: T, index: number): boolean => index === 0;
5
+ const xs = ["1", "2", "2", "3", "4"];
6
+
7
+ expect(find(predicate, xs)).toBe("1");
8
+ });
9
+
10
+ test("example 2", () => {
11
+ const predicate = <T>(_: T, index: number): boolean => index === 0;
12
+ const xs: string[] = [];
13
+
14
+ expect(find(predicate, xs)).toBe(undefined);
15
+ });
16
+
17
+ test("example 3", () => {
18
+ const predicate = () => false;
19
+ const xs = ["1", "2", "2", "3"];
20
+
21
+ expect(find(predicate, xs)).toBe(undefined);
22
+ });
23
+
24
+ test("example 4", () => {
25
+ const predicate = <T>(_: T, index: number): boolean => index === 1;
26
+ const xs = ["1", "2", "2", "3"];
27
+
28
+ expect(find(predicate, xs)).toBe("2");
29
+ });
30
+
31
+ test("example 5", () => {
32
+ const predicate = <T>(_: T, index: number): boolean => index === 2;
33
+ const xs = ["1", "2.1", "2", "3", "2", "4"];
34
+
35
+ expect(find(predicate, xs)).toBe("2");
36
+ });
@@ -0,0 +1,37 @@
1
+ import { pathOr } from "../pathOr";
2
+
3
+ test("example 1", () => {
4
+ const defaultValue = "defaultValue";
5
+ const path = ["a", "b", "c"];
6
+ const xs = { a: { b: { c: 1 } } };
7
+
8
+ const output = 1;
9
+
10
+ expect(pathOr(defaultValue, path, xs)).toEqual(output);
11
+ });
12
+
13
+ test("example 2", () => {
14
+ const defaultValue = "defaultValue";
15
+ const path = ["a", "b"];
16
+ const xs = { a: { b: { c: 1 } } };
17
+
18
+ const output = { c: 1 };
19
+
20
+ expect(pathOr(defaultValue, path, xs)).toEqual(output);
21
+ });
22
+
23
+ test("example 3", () => {
24
+ const defaultValue = "defaultValue";
25
+ const path = ["a", "b", "x"];
26
+ const xs = { a: { b: { c: 1 } } };
27
+
28
+ expect(pathOr(defaultValue, path, xs)).toBe(defaultValue);
29
+ });
30
+
31
+ test("example 4", () => {
32
+ const defaultValue = "defaultValue";
33
+ const path = ["a", "b", "c"];
34
+ const xs = undefined;
35
+
36
+ expect(pathOr(defaultValue, path, xs)).toBe(defaultValue);
37
+ });