ignite-parse-auth-kit 1.0.0

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 (241) hide show
  1. package/CONTRIBUTING.md +0 -0
  2. package/LICENSE +21 -0
  3. package/README.md +492 -0
  4. package/app/app.tsx +116 -0
  5. package/app/components/AlertTongle.tsx +105 -0
  6. package/app/components/AutoImage.tsx +89 -0
  7. package/app/components/Button.tsx +248 -0
  8. package/app/components/Card.tsx +314 -0
  9. package/app/components/EmptyState.tsx +248 -0
  10. package/app/components/Header.tsx +332 -0
  11. package/app/components/Icon.tsx +140 -0
  12. package/app/components/ListItem.tsx +243 -0
  13. package/app/components/ListView.tsx +42 -0
  14. package/app/components/Screen.tsx +305 -0
  15. package/app/components/Text.test.tsx +23 -0
  16. package/app/components/Text.tsx +116 -0
  17. package/app/components/TextField.tsx +292 -0
  18. package/app/components/Toggle/Checkbox.tsx +123 -0
  19. package/app/components/Toggle/Radio.tsx +106 -0
  20. package/app/components/Toggle/Switch.tsx +264 -0
  21. package/app/components/Toggle/Toggle.tsx +285 -0
  22. package/app/components/index copy.ts +15 -0
  23. package/app/components/index.ts +18 -0
  24. package/app/config/config.base.ts +26 -0
  25. package/app/config/config.dev.ts +10 -0
  26. package/app/config/config.prod.ts +10 -0
  27. package/app/config/index.ts +28 -0
  28. package/app/context/AuthContext.tsx +14 -0
  29. package/app/context/EpisodeContext.tsx +136 -0
  30. package/app/context/auth/AuthProvider.tsx +340 -0
  31. package/app/context/auth/hooks.ts +29 -0
  32. package/app/context/auth/index.ts +38 -0
  33. package/app/context/auth/reducer.ts +68 -0
  34. package/app/context/auth/services.ts +394 -0
  35. package/app/context/auth/types.ts +99 -0
  36. package/app/context/auth/validation.ts +45 -0
  37. package/app/devtools/ReactotronClient.ts +9 -0
  38. package/app/devtools/ReactotronClient.web.ts +12 -0
  39. package/app/devtools/ReactotronConfig.ts +139 -0
  40. package/app/i18n/ar.ts +126 -0
  41. package/app/i18n/demo-ar.ts +464 -0
  42. package/app/i18n/demo-en.ts +462 -0
  43. package/app/i18n/demo-es.ts +469 -0
  44. package/app/i18n/demo-fr.ts +471 -0
  45. package/app/i18n/demo-hi.ts +468 -0
  46. package/app/i18n/demo-ja.ts +464 -0
  47. package/app/i18n/demo-ko.ts +457 -0
  48. package/app/i18n/en.ts +146 -0
  49. package/app/i18n/es.ts +132 -0
  50. package/app/i18n/fr.ts +132 -0
  51. package/app/i18n/hi.ts +131 -0
  52. package/app/i18n/index.ts +86 -0
  53. package/app/i18n/ja.ts +130 -0
  54. package/app/i18n/ko.ts +129 -0
  55. package/app/i18n/translate.ts +33 -0
  56. package/app/lib/Parse/index.ts +2 -0
  57. package/app/lib/Parse/parse.ts +62 -0
  58. package/app/navigators/AppNavigator.tsx +145 -0
  59. package/app/navigators/DemoNavigator.tsx +137 -0
  60. package/app/navigators/navigationUtilities.ts +208 -0
  61. package/app/screens/ChooseAuthScreen.tsx +224 -0
  62. package/app/screens/DemoCommunityScreen.tsx +141 -0
  63. package/app/screens/DemoDebugScreen.tsx +192 -0
  64. package/app/screens/DemoPodcastListScreen.tsx +387 -0
  65. package/app/screens/DemoShowroomScreen/DemoDivider.tsx +66 -0
  66. package/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx +313 -0
  67. package/app/screens/DemoShowroomScreen/DemoUseCase.tsx +52 -0
  68. package/app/screens/DemoShowroomScreen/DrawerIconButton.tsx +120 -0
  69. package/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx +59 -0
  70. package/app/screens/DemoShowroomScreen/demos/DemoAutoImage.tsx +230 -0
  71. package/app/screens/DemoShowroomScreen/demos/DemoButton.tsx +234 -0
  72. package/app/screens/DemoShowroomScreen/demos/DemoCard.tsx +181 -0
  73. package/app/screens/DemoShowroomScreen/demos/DemoEmptyState.tsx +78 -0
  74. package/app/screens/DemoShowroomScreen/demos/DemoHeader.tsx +151 -0
  75. package/app/screens/DemoShowroomScreen/demos/DemoIcon.tsx +115 -0
  76. package/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx +218 -0
  77. package/app/screens/DemoShowroomScreen/demos/DemoText.tsx +144 -0
  78. package/app/screens/DemoShowroomScreen/demos/DemoTextField.tsx +233 -0
  79. package/app/screens/DemoShowroomScreen/demos/DemoToggle.tsx +354 -0
  80. package/app/screens/DemoShowroomScreen/demos/index.ts +12 -0
  81. package/app/screens/ErrorScreen/ErrorBoundary.tsx +76 -0
  82. package/app/screens/ErrorScreen/ErrorDetails.tsx +98 -0
  83. package/app/screens/ForgetPasswordScreen.tsx +180 -0
  84. package/app/screens/LoginScreen.tsx +260 -0
  85. package/app/screens/RegisterScreen.tsx +395 -0
  86. package/app/screens/WelcomeScreen.tsx +114 -0
  87. package/app/services/api/apiProblem.test.ts +73 -0
  88. package/app/services/api/apiProblem.ts +74 -0
  89. package/app/services/api/index.ts +91 -0
  90. package/app/services/api/types.ts +50 -0
  91. package/app/theme/colors.ts +85 -0
  92. package/app/theme/colorsDark.ts +50 -0
  93. package/app/theme/context.tsx +145 -0
  94. package/app/theme/context.utils.ts +25 -0
  95. package/app/theme/spacing.ts +14 -0
  96. package/app/theme/spacingDark.ts +14 -0
  97. package/app/theme/styles.ts +24 -0
  98. package/app/theme/theme.ts +23 -0
  99. package/app/theme/timing.ts +6 -0
  100. package/app/theme/types.ts +64 -0
  101. package/app/theme/typography.ts +71 -0
  102. package/app/utils/crashReporting.ts +62 -0
  103. package/app/utils/delay.ts +6 -0
  104. package/app/utils/formatDate.ts +49 -0
  105. package/app/utils/gestureHandler.native.ts +3 -0
  106. package/app/utils/gestureHandler.ts +6 -0
  107. package/app/utils/hasValidStringProp.ts +11 -0
  108. package/app/utils/openLinkInBrowser.ts +8 -0
  109. package/app/utils/storage/index.ts +82 -0
  110. package/app/utils/storage/storage.test.ts +61 -0
  111. package/app/utils/useHeader.tsx +37 -0
  112. package/app/utils/useIsMounted.ts +18 -0
  113. package/app/utils/useSafeAreaInsetsStyle.ts +46 -0
  114. package/app.config.ts +39 -0
  115. package/app.json +67 -0
  116. package/assets/icons/back.png +0 -0
  117. package/assets/icons/back@2x.png +0 -0
  118. package/assets/icons/back@3x.png +0 -0
  119. package/assets/icons/bell.png +0 -0
  120. package/assets/icons/bell@2x.png +0 -0
  121. package/assets/icons/bell@3x.png +0 -0
  122. package/assets/icons/caretLeft.png +0 -0
  123. package/assets/icons/caretLeft@2x.png +0 -0
  124. package/assets/icons/caretLeft@3x.png +0 -0
  125. package/assets/icons/caretRight.png +0 -0
  126. package/assets/icons/caretRight@2x.png +0 -0
  127. package/assets/icons/caretRight@3x.png +0 -0
  128. package/assets/icons/check.png +0 -0
  129. package/assets/icons/check@2x.png +0 -0
  130. package/assets/icons/check@3x.png +0 -0
  131. package/assets/icons/demo/clap.png +0 -0
  132. package/assets/icons/demo/clap@2x.png +0 -0
  133. package/assets/icons/demo/clap@3x.png +0 -0
  134. package/assets/icons/demo/community.png +0 -0
  135. package/assets/icons/demo/community@2x.png +0 -0
  136. package/assets/icons/demo/community@3x.png +0 -0
  137. package/assets/icons/demo/components.png +0 -0
  138. package/assets/icons/demo/components@2x.png +0 -0
  139. package/assets/icons/demo/components@3x.png +0 -0
  140. package/assets/icons/demo/debug.png +0 -0
  141. package/assets/icons/demo/debug@2x.png +0 -0
  142. package/assets/icons/demo/debug@3x.png +0 -0
  143. package/assets/icons/demo/github.png +0 -0
  144. package/assets/icons/demo/github@2x.png +0 -0
  145. package/assets/icons/demo/github@3x.png +0 -0
  146. package/assets/icons/demo/heart.png +0 -0
  147. package/assets/icons/demo/heart@2x.png +0 -0
  148. package/assets/icons/demo/heart@3x.png +0 -0
  149. package/assets/icons/demo/pin.png +0 -0
  150. package/assets/icons/demo/pin@2x.png +0 -0
  151. package/assets/icons/demo/pin@3x.png +0 -0
  152. package/assets/icons/demo/podcast.png +0 -0
  153. package/assets/icons/demo/podcast@2x.png +0 -0
  154. package/assets/icons/demo/podcast@3x.png +0 -0
  155. package/assets/icons/demo/slack.png +0 -0
  156. package/assets/icons/demo/slack@2x.png +0 -0
  157. package/assets/icons/demo/slack@3x.png +0 -0
  158. package/assets/icons/google.png +0 -0
  159. package/assets/icons/hidden.png +0 -0
  160. package/assets/icons/hidden@2x.png +0 -0
  161. package/assets/icons/hidden@3x.png +0 -0
  162. package/assets/icons/ladybug.png +0 -0
  163. package/assets/icons/ladybug@2x.png +0 -0
  164. package/assets/icons/ladybug@3x.png +0 -0
  165. package/assets/icons/lock.png +0 -0
  166. package/assets/icons/lock@2x.png +0 -0
  167. package/assets/icons/lock@3x.png +0 -0
  168. package/assets/icons/menu.png +0 -0
  169. package/assets/icons/menu@2x.png +0 -0
  170. package/assets/icons/menu@3x.png +0 -0
  171. package/assets/icons/more.png +0 -0
  172. package/assets/icons/more@2x.png +0 -0
  173. package/assets/icons/more@3x.png +0 -0
  174. package/assets/icons/settings.png +0 -0
  175. package/assets/icons/settings@2x.png +0 -0
  176. package/assets/icons/settings@3x.png +0 -0
  177. package/assets/icons/view.png +0 -0
  178. package/assets/icons/view@2x.png +0 -0
  179. package/assets/icons/view@3x.png +0 -0
  180. package/assets/icons/x.png +0 -0
  181. package/assets/icons/x@2x.png +0 -0
  182. package/assets/icons/x@3x.png +0 -0
  183. package/assets/images/app-icon-all.png +0 -0
  184. package/assets/images/app-icon-android-adaptive-background.png +0 -0
  185. package/assets/images/app-icon-android-adaptive-foreground.png +0 -0
  186. package/assets/images/app-icon-android-legacy.png +0 -0
  187. package/assets/images/app-icon-ios.png +0 -0
  188. package/assets/images/app-icon-web-favicon.png +0 -0
  189. package/assets/images/demo/cr-logo.png +0 -0
  190. package/assets/images/demo/cr-logo@2x.png +0 -0
  191. package/assets/images/demo/cr-logo@3x.png +0 -0
  192. package/assets/images/demo/rnl-logo.png +0 -0
  193. package/assets/images/demo/rnl-logo@2x.png +0 -0
  194. package/assets/images/demo/rnl-logo@3x.png +0 -0
  195. package/assets/images/demo/rnn-logo.png +0 -0
  196. package/assets/images/demo/rnn-logo@2x.png +0 -0
  197. package/assets/images/demo/rnn-logo@3x.png +0 -0
  198. package/assets/images/demo/rnr-image-1.png +0 -0
  199. package/assets/images/demo/rnr-image-1@2x.png +0 -0
  200. package/assets/images/demo/rnr-image-1@3x.png +0 -0
  201. package/assets/images/demo/rnr-image-2.png +0 -0
  202. package/assets/images/demo/rnr-image-2@2x.png +0 -0
  203. package/assets/images/demo/rnr-image-2@3x.png +0 -0
  204. package/assets/images/demo/rnr-image-3.png +0 -0
  205. package/assets/images/demo/rnr-image-3@2x.png +0 -0
  206. package/assets/images/demo/rnr-image-3@3x.png +0 -0
  207. package/assets/images/demo/rnr-logo.png +0 -0
  208. package/assets/images/demo/rnr-logo@2x.png +0 -0
  209. package/assets/images/demo/rnr-logo@3x.png +0 -0
  210. package/assets/images/logo.png +0 -0
  211. package/assets/images/logo@2x.png +0 -0
  212. package/assets/images/logo@3x.png +0 -0
  213. package/assets/images/sad-face.png +0 -0
  214. package/assets/images/sad-face@2x.png +0 -0
  215. package/assets/images/sad-face@3x.png +0 -0
  216. package/assets/images/welcome-face.png +0 -0
  217. package/assets/images/welcome-face@2x.png +0 -0
  218. package/assets/images/welcome-face@3x.png +0 -0
  219. package/babel.config.js +7 -0
  220. package/bin/cli.js +196 -0
  221. package/ignite/templates/app-icon/android-adaptive-background.png +0 -0
  222. package/ignite/templates/app-icon/android-adaptive-foreground.png +0 -0
  223. package/ignite/templates/app-icon/android-legacy.png +0 -0
  224. package/ignite/templates/app-icon/ios-universal.png +0 -0
  225. package/ignite/templates/component/NAME.tsx.ejs +39 -0
  226. package/ignite/templates/navigator/NAMENavigator.tsx.ejs +18 -0
  227. package/ignite/templates/screen/NAMEScreen.tsx.ejs +29 -0
  228. package/ignite/templates/splash-screen/logo.png +0 -0
  229. package/index.tsx +9 -0
  230. package/jest.config.js +5 -0
  231. package/metro.config.js +31 -0
  232. package/package.json +166 -0
  233. package/plugins/withSplashScreen.ts +69 -0
  234. package/src/app/_layout.tsx +58 -0
  235. package/src/app/index.tsx +5 -0
  236. package/test/i18n.test.ts +75 -0
  237. package/test/mockFile.ts +6 -0
  238. package/test/setup.ts +58 -0
  239. package/test/test-tsconfig.json +8 -0
  240. package/tsconfig.json +52 -0
  241. package/types/lib.es5.d.ts +25 -0
@@ -0,0 +1,192 @@
1
+ import { FC, useCallback, useMemo } from "react"
2
+ import {
3
+ LayoutAnimation,
4
+ Linking,
5
+ Platform,
6
+ TextStyle,
7
+ useColorScheme,
8
+ View,
9
+ ViewStyle,
10
+ } from "react-native"
11
+ import * as Application from "expo-application"
12
+
13
+ import { Button } from "@/components/Button"
14
+ import { ListItem } from "@/components/ListItem"
15
+ import { Screen } from "@/components/Screen"
16
+ import { Text } from "@/components/Text"
17
+ import { useAuth } from "@/context/AuthContext"
18
+ import { isRTL } from "@/i18n"
19
+ import { DemoTabScreenProps } from "@/navigators/DemoNavigator"
20
+ import type { ThemedStyle } from "@/theme/types"
21
+ import { useAppTheme } from "@/theme/context"
22
+ import { $styles } from "@/theme/styles"
23
+
24
+ /**
25
+ * @param {string} url - The URL to open in the browser.
26
+ * @returns {void} - No return value.
27
+ */
28
+ function openLinkInBrowser(url: string) {
29
+ Linking.canOpenURL(url).then((canOpen) => canOpen && Linking.openURL(url))
30
+ }
31
+
32
+ const usingHermes = typeof HermesInternal === "object" && HermesInternal !== null
33
+
34
+ export const DemoDebugScreen: FC<DemoTabScreenProps<"DemoDebug">> = function DemoDebugScreen(
35
+ _props,
36
+ ) {
37
+ const { setThemeContextOverride, themeContext, themed } = useAppTheme()
38
+ const { logout } = useAuth()
39
+
40
+ // @ts-expect-error
41
+ const usingFabric = global.nativeFabricUIManager != null
42
+
43
+ const demoReactotron = useMemo(
44
+ () => async () => {
45
+ if (__DEV__) {
46
+ console.tron.display({
47
+ name: "DISPLAY",
48
+ value: {
49
+ appId: Application.applicationId,
50
+ appName: Application.applicationName,
51
+ appVersion: Application.nativeApplicationVersion,
52
+ appBuildVersion: Application.nativeBuildVersion,
53
+ hermesEnabled: usingHermes,
54
+ },
55
+ important: true,
56
+ })
57
+ }
58
+ },
59
+ [],
60
+ )
61
+
62
+ const toggleTheme = useCallback(() => {
63
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) // Animate the transition
64
+ setThemeContextOverride(themeContext === "dark" ? "light" : "dark")
65
+ }, [themeContext, setThemeContextOverride])
66
+
67
+ // Resets the theme to the system theme
68
+ const colorScheme = useColorScheme()
69
+ const resetTheme = useCallback(() => {
70
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
71
+ setThemeContextOverride(undefined)
72
+ }, [setThemeContextOverride])
73
+
74
+ return (
75
+ <Screen
76
+ preset="scroll"
77
+ safeAreaEdges={["top"]}
78
+ contentContainerStyle={[$styles.container, themed($container)]}
79
+ >
80
+ <Text
81
+ style={themed($reportBugsLink)}
82
+ tx="demoDebugScreen:reportBugs"
83
+ onPress={() => openLinkInBrowser("https://github.com/infinitered/ignite/issues")}
84
+ />
85
+
86
+ <Text style={themed($title)} preset="heading" tx="demoDebugScreen:title" />
87
+ <Text preset="bold">Current system theme: {colorScheme}</Text>
88
+ <Text preset="bold">Current app theme: {themeContext}</Text>
89
+ <Button onPress={resetTheme} text={`Reset`} />
90
+
91
+ <View style={themed($itemsContainer)}>
92
+ <Button onPress={toggleTheme} text={`Toggle Theme: ${themeContext}`} />
93
+ </View>
94
+ <View style={themed($itemsContainer)}>
95
+ <ListItem
96
+ LeftComponent={
97
+ <View style={themed($item)}>
98
+ <Text preset="bold">App Id</Text>
99
+ <Text>{Application.applicationId}</Text>
100
+ </View>
101
+ }
102
+ />
103
+ <ListItem
104
+ LeftComponent={
105
+ <View style={themed($item)}>
106
+ <Text preset="bold">App Name</Text>
107
+ <Text>{Application.applicationName}</Text>
108
+ </View>
109
+ }
110
+ />
111
+ <ListItem
112
+ LeftComponent={
113
+ <View style={themed($item)}>
114
+ <Text preset="bold">App Version</Text>
115
+ <Text>{Application.nativeApplicationVersion}</Text>
116
+ </View>
117
+ }
118
+ />
119
+ <ListItem
120
+ LeftComponent={
121
+ <View style={themed($item)}>
122
+ <Text preset="bold">App Build Version</Text>
123
+ <Text>{Application.nativeBuildVersion}</Text>
124
+ </View>
125
+ }
126
+ />
127
+ <ListItem
128
+ LeftComponent={
129
+ <View style={themed($item)}>
130
+ <Text preset="bold">Hermes Enabled</Text>
131
+ <Text>{String(usingHermes)}</Text>
132
+ </View>
133
+ }
134
+ />
135
+ <ListItem
136
+ LeftComponent={
137
+ <View style={themed($item)}>
138
+ <Text preset="bold">Fabric Enabled</Text>
139
+ <Text>{String(usingFabric)}</Text>
140
+ </View>
141
+ }
142
+ />
143
+ </View>
144
+ <View style={themed($buttonContainer)}>
145
+ <Button style={themed($button)} tx="demoDebugScreen:reactotron" onPress={demoReactotron} />
146
+ <Text style={themed($hint)} tx={`demoDebugScreen:${Platform.OS}ReactotronHint` as const} />
147
+ </View>
148
+ <View style={themed($buttonContainer)}>
149
+ <Button style={themed($button)} tx="common:logOut" onPress={logout} />
150
+ </View>
151
+ </Screen>
152
+ )
153
+ }
154
+
155
+ const $container: ThemedStyle<ViewStyle> = ({ spacing }) => ({
156
+ paddingBottom: spacing.xxl,
157
+ })
158
+
159
+ const $title: ThemedStyle<TextStyle> = ({ spacing }) => ({
160
+ marginBottom: spacing.xxl,
161
+ })
162
+
163
+ const $reportBugsLink: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
164
+ color: colors.tint,
165
+ marginBottom: spacing.lg,
166
+ alignSelf: isRTL ? "flex-start" : "flex-end",
167
+ })
168
+
169
+ const $item: ThemedStyle<ViewStyle> = ({ spacing }) => ({
170
+ marginBottom: spacing.md,
171
+ })
172
+
173
+ const $itemsContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
174
+ marginVertical: spacing.xl,
175
+ })
176
+
177
+ const $button: ThemedStyle<ViewStyle> = ({ spacing }) => ({
178
+ marginBottom: spacing.xs,
179
+ })
180
+
181
+ const $buttonContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
182
+ marginBottom: spacing.md,
183
+ })
184
+
185
+ const $hint: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
186
+ color: colors.palette.neutral600,
187
+ fontSize: 12,
188
+ lineHeight: 15,
189
+ paddingBottom: spacing.lg,
190
+ })
191
+
192
+ // @demo remove-file
@@ -0,0 +1,387 @@
1
+ import { ComponentType, FC, useCallback, useEffect, useMemo, useState } from "react"
2
+ import {
3
+ AccessibilityProps,
4
+ ActivityIndicator,
5
+ Image,
6
+ ImageSourcePropType,
7
+ ImageStyle,
8
+ Platform,
9
+ StyleSheet,
10
+ TextStyle,
11
+ View,
12
+ ViewStyle,
13
+ } from "react-native"
14
+ import { type ContentStyle } from "@shopify/flash-list"
15
+ import Animated, {
16
+ Extrapolation,
17
+ interpolate,
18
+ useAnimatedStyle,
19
+ useSharedValue,
20
+ withSpring,
21
+ } from "react-native-reanimated"
22
+
23
+ import { Button, type ButtonAccessoryProps } from "@/components/Button"
24
+ import { Card } from "@/components/Card"
25
+ import { EmptyState } from "@/components/EmptyState"
26
+ import { Icon } from "@/components/Icon"
27
+ import { ListView } from "@/components/ListView"
28
+ import { Screen } from "@/components/Screen"
29
+ import { Text } from "@/components/Text"
30
+ import { Switch } from "@/components/Toggle/Switch"
31
+ import { useEpisodes, useEpisode } from "@/context/EpisodeContext"
32
+ import { isRTL } from "@/i18n"
33
+ import { translate } from "@/i18n/translate"
34
+ import { DemoTabScreenProps } from "@/navigators/DemoNavigator"
35
+ import type { EpisodeItem } from "@/services/api/types"
36
+ import type { ThemedStyle } from "@/theme/types"
37
+ import { useAppTheme } from "@/theme/context"
38
+ import { $styles } from "@/theme/styles"
39
+ import { delay } from "@/utils/delay"
40
+ import { openLinkInBrowser } from "@/utils/openLinkInBrowser"
41
+
42
+ const ICON_SIZE = 14
43
+
44
+ const rnrImage1 = require("@assets/images/demo/rnr-image-1.png")
45
+ const rnrImage2 = require("@assets/images/demo/rnr-image-2.png")
46
+ const rnrImage3 = require("@assets/images/demo/rnr-image-3.png")
47
+
48
+ const rnrImages = [rnrImage1, rnrImage2, rnrImage3]
49
+
50
+ export const DemoPodcastListScreen: FC<DemoTabScreenProps<"DemoPodcastList">> = (_props) => {
51
+ const { themed } = useAppTheme()
52
+ const {
53
+ totalEpisodes,
54
+ totalFavorites,
55
+
56
+ episodesForList,
57
+ fetchEpisodes,
58
+ favoritesOnly,
59
+ toggleFavoritesOnly,
60
+ toggleFavorite,
61
+ } = useEpisodes()
62
+
63
+ const [refreshing, setRefreshing] = useState(false)
64
+ const [isLoading, setIsLoading] = useState(false)
65
+
66
+ // initially, kick off a background refresh without the refreshing UI
67
+ useEffect(() => {
68
+ ;(async function load() {
69
+ setIsLoading(true)
70
+ await fetchEpisodes()
71
+ setIsLoading(false)
72
+ })()
73
+ }, [fetchEpisodes])
74
+
75
+ // simulate a longer refresh, if the refresh is too fast for UX
76
+ async function manualRefresh() {
77
+ setRefreshing(true)
78
+ await Promise.allSettled([fetchEpisodes(), delay(750)])
79
+ setRefreshing(false)
80
+ }
81
+
82
+ return (
83
+ <Screen preset="fixed" safeAreaEdges={["top"]} contentContainerStyle={$styles.flex1}>
84
+ <ListView<EpisodeItem>
85
+ contentContainerStyle={themed([$styles.container, $listContentContainer])}
86
+ data={episodesForList}
87
+ extraData={totalEpisodes + totalFavorites}
88
+ refreshing={refreshing}
89
+ estimatedItemSize={177}
90
+ onRefresh={manualRefresh}
91
+ ListEmptyComponent={
92
+ isLoading ? (
93
+ <ActivityIndicator />
94
+ ) : (
95
+ <EmptyState
96
+ preset="generic"
97
+ style={themed($emptyState)}
98
+ headingTx={
99
+ favoritesOnly ? "demoPodcastListScreen:noFavoritesEmptyState.heading" : undefined
100
+ }
101
+ contentTx={
102
+ favoritesOnly ? "demoPodcastListScreen:noFavoritesEmptyState.content" : undefined
103
+ }
104
+ button={favoritesOnly ? "" : undefined}
105
+ buttonOnPress={manualRefresh}
106
+ imageStyle={$emptyStateImage}
107
+ ImageProps={{ resizeMode: "contain" }}
108
+ />
109
+ )
110
+ }
111
+ ListHeaderComponent={
112
+ <View style={themed($heading)}>
113
+ <Text preset="heading" tx="demoPodcastListScreen:title" />
114
+ {(favoritesOnly || episodesForList.length > 0) && (
115
+ <View style={themed($toggle)}>
116
+ <Switch
117
+ value={favoritesOnly}
118
+ onValueChange={() => toggleFavoritesOnly()}
119
+ labelTx="demoPodcastListScreen:onlyFavorites"
120
+ labelPosition="left"
121
+ labelStyle={$labelStyle}
122
+ accessibilityLabel={translate("demoPodcastListScreen:accessibility.switch")}
123
+ />
124
+ </View>
125
+ )}
126
+ </View>
127
+ }
128
+ renderItem={({ item }) => (
129
+ <EpisodeCard episode={item} onPressFavorite={() => toggleFavorite(item)} />
130
+ )}
131
+ />
132
+ </Screen>
133
+ )
134
+ }
135
+
136
+ const EpisodeCard = ({
137
+ episode,
138
+ onPressFavorite,
139
+ }: {
140
+ episode: EpisodeItem
141
+ onPressFavorite: () => void
142
+ }) => {
143
+ const {
144
+ theme: { colors },
145
+ themed,
146
+ } = useAppTheme()
147
+ const { isFavorite, datePublished, duration, parsedTitleAndSubtitle } = useEpisode(episode)
148
+
149
+ const liked = useSharedValue(isFavorite ? 1 : 0)
150
+ const imageUri = useMemo<ImageSourcePropType>(() => {
151
+ return rnrImages[Math.floor(Math.random() * rnrImages.length)]
152
+ }, [])
153
+
154
+ // Grey heart
155
+ const animatedLikeButtonStyles = useAnimatedStyle(() => {
156
+ return {
157
+ transform: [
158
+ {
159
+ scale: interpolate(liked.value, [0, 1], [1, 0], Extrapolation.EXTEND),
160
+ },
161
+ ],
162
+ opacity: interpolate(liked.value, [0, 1], [1, 0], Extrapolation.CLAMP),
163
+ }
164
+ })
165
+
166
+ // Pink heart
167
+ const animatedUnlikeButtonStyles = useAnimatedStyle(() => {
168
+ return {
169
+ transform: [
170
+ {
171
+ scale: liked.value,
172
+ },
173
+ ],
174
+ opacity: liked.value,
175
+ }
176
+ })
177
+
178
+ const handlePressFavorite = useCallback(() => {
179
+ onPressFavorite()
180
+ liked.value = withSpring(liked.value ? 0 : 1)
181
+ }, [liked, onPressFavorite])
182
+
183
+ /**
184
+ * Android has a "longpress" accessibility action. iOS does not, so we just have to use a hint.
185
+ * @see https://reactnative.dev/docs/accessibility#accessibilityactions
186
+ */
187
+ const accessibilityHintProps = useMemo(
188
+ () =>
189
+ Platform.select<AccessibilityProps>({
190
+ ios: {
191
+ accessibilityLabel: episode.title,
192
+ accessibilityHint: translate("demoPodcastListScreen:accessibility.cardHint", {
193
+ action: isFavorite ? "unfavorite" : "favorite",
194
+ }),
195
+ },
196
+ android: {
197
+ accessibilityLabel: episode.title,
198
+ accessibilityActions: [
199
+ {
200
+ name: "longpress",
201
+ label: translate("demoPodcastListScreen:accessibility.favoriteAction"),
202
+ },
203
+ ],
204
+ onAccessibilityAction: ({ nativeEvent }) => {
205
+ if (nativeEvent.actionName === "longpress") {
206
+ handlePressFavorite()
207
+ }
208
+ },
209
+ },
210
+ }),
211
+ [episode.title, handlePressFavorite, isFavorite],
212
+ )
213
+
214
+ const handlePressCard = () => {
215
+ openLinkInBrowser(episode.enclosure.link)
216
+ }
217
+
218
+ const ButtonLeftAccessory: ComponentType<ButtonAccessoryProps> = useMemo(
219
+ () =>
220
+ function ButtonLeftAccessory() {
221
+ return (
222
+ <View>
223
+ <Animated.View
224
+ style={[
225
+ $styles.row,
226
+ themed($iconContainer),
227
+ StyleSheet.absoluteFill,
228
+ animatedLikeButtonStyles,
229
+ ]}
230
+ >
231
+ <Icon
232
+ icon="heart"
233
+ size={ICON_SIZE}
234
+ color={colors.palette.neutral800} // dark grey
235
+ />
236
+ </Animated.View>
237
+ <Animated.View
238
+ style={[$styles.row, themed($iconContainer), animatedUnlikeButtonStyles]}
239
+ >
240
+ <Icon
241
+ icon="heart"
242
+ size={ICON_SIZE}
243
+ color={colors.palette.primary400} // pink
244
+ />
245
+ </Animated.View>
246
+ </View>
247
+ )
248
+ },
249
+ [animatedLikeButtonStyles, animatedUnlikeButtonStyles, colors, themed],
250
+ )
251
+
252
+ return (
253
+ <Card
254
+ style={themed($item)}
255
+ verticalAlignment="force-footer-bottom"
256
+ onPress={handlePressCard}
257
+ onLongPress={handlePressFavorite}
258
+ HeadingComponent={
259
+ <View style={[$styles.row, themed($metadata)]}>
260
+ <Text
261
+ style={themed($metadataText)}
262
+ size="xxs"
263
+ accessibilityLabel={datePublished.accessibilityLabel}
264
+ >
265
+ {datePublished.textLabel}
266
+ </Text>
267
+ <Text
268
+ style={themed($metadataText)}
269
+ size="xxs"
270
+ accessibilityLabel={duration.accessibilityLabel}
271
+ >
272
+ {duration.textLabel}
273
+ </Text>
274
+ </View>
275
+ }
276
+ content={
277
+ parsedTitleAndSubtitle.subtitle
278
+ ? `${parsedTitleAndSubtitle.title} - ${parsedTitleAndSubtitle.subtitle}`
279
+ : parsedTitleAndSubtitle.title
280
+ }
281
+ {...accessibilityHintProps}
282
+ RightComponent={<Image source={imageUri} style={themed($itemThumbnail)} />}
283
+ FooterComponent={
284
+ <Button
285
+ onPress={handlePressFavorite}
286
+ onLongPress={handlePressFavorite}
287
+ style={themed([$favoriteButton, isFavorite && $unFavoriteButton])}
288
+ accessibilityLabel={
289
+ isFavorite
290
+ ? translate("demoPodcastListScreen:accessibility.unfavoriteIcon")
291
+ : translate("demoPodcastListScreen:accessibility.favoriteIcon")
292
+ }
293
+ LeftAccessory={ButtonLeftAccessory}
294
+ >
295
+ <Text
296
+ size="xxs"
297
+ accessibilityLabel={duration.accessibilityLabel}
298
+ weight="medium"
299
+ text={
300
+ isFavorite
301
+ ? translate("demoPodcastListScreen:unfavoriteButton")
302
+ : translate("demoPodcastListScreen:favoriteButton")
303
+ }
304
+ />
305
+ </Button>
306
+ }
307
+ />
308
+ )
309
+ }
310
+
311
+ // #region Styles
312
+ const $listContentContainer: ThemedStyle<ContentStyle> = ({ spacing }) => ({
313
+ paddingHorizontal: spacing.lg,
314
+ paddingTop: spacing.lg + spacing.xl,
315
+ paddingBottom: spacing.lg,
316
+ })
317
+
318
+ const $heading: ThemedStyle<ViewStyle> = ({ spacing }) => ({
319
+ marginBottom: spacing.md,
320
+ })
321
+
322
+ const $item: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
323
+ padding: spacing.md,
324
+ marginTop: spacing.md,
325
+ minHeight: 120,
326
+ backgroundColor: colors.palette.neutral100,
327
+ })
328
+
329
+ const $itemThumbnail: ThemedStyle<ImageStyle> = ({ spacing }) => ({
330
+ marginTop: spacing.sm,
331
+ borderRadius: 50,
332
+ alignSelf: "flex-start",
333
+ })
334
+
335
+ const $toggle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
336
+ marginTop: spacing.md,
337
+ })
338
+
339
+ const $labelStyle: TextStyle = {
340
+ textAlign: "left",
341
+ }
342
+
343
+ const $iconContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
344
+ height: ICON_SIZE,
345
+ width: ICON_SIZE,
346
+ marginEnd: spacing.sm,
347
+ })
348
+
349
+ const $metadata: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
350
+ color: colors.textDim,
351
+ marginTop: spacing.xs,
352
+ })
353
+
354
+ const $metadataText: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
355
+ color: colors.textDim,
356
+ marginEnd: spacing.md,
357
+ marginBottom: spacing.xs,
358
+ })
359
+
360
+ const $favoriteButton: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
361
+ borderRadius: 17,
362
+ marginTop: spacing.md,
363
+ justifyContent: "flex-start",
364
+ backgroundColor: colors.palette.neutral300,
365
+ borderColor: colors.palette.neutral300,
366
+ paddingHorizontal: spacing.md,
367
+ paddingTop: spacing.xxxs,
368
+ paddingBottom: 0,
369
+ minHeight: 32,
370
+ alignSelf: "flex-start",
371
+ })
372
+
373
+ const $unFavoriteButton: ThemedStyle<ViewStyle> = ({ colors }) => ({
374
+ borderColor: colors.palette.primary100,
375
+ backgroundColor: colors.palette.primary100,
376
+ })
377
+
378
+ const $emptyState: ThemedStyle<ViewStyle> = ({ spacing }) => ({
379
+ marginTop: spacing.xxl,
380
+ })
381
+
382
+ const $emptyStateImage: ImageStyle = {
383
+ transform: [{ scaleX: isRTL ? -1 : 1 }],
384
+ }
385
+ // #endregion
386
+
387
+ // @demo remove-file
@@ -0,0 +1,66 @@
1
+ /* eslint-disable react-native/no-inline-styles */
2
+ import { StyleProp, View, ViewStyle } from "react-native"
3
+
4
+ import type { ThemedStyle } from "@/theme/types"
5
+ import { useAppTheme } from "@/theme/context"
6
+
7
+ interface DemoDividerProps {
8
+ type?: "vertical" | "horizontal"
9
+ size?: number
10
+ style?: StyleProp<ViewStyle>
11
+ line?: boolean
12
+ }
13
+
14
+ /**
15
+ * @param {DemoDividerProps} props - The props for the `DemoDivider` component.
16
+ * @returns {JSX.Element} The rendered `DemoDivider` component.
17
+ */
18
+ export function DemoDivider(props: DemoDividerProps) {
19
+ const { type = "horizontal", size = 10, line = false, style: $styleOverride } = props
20
+ const { themed } = useAppTheme()
21
+
22
+ return (
23
+ <View
24
+ style={[
25
+ $divider,
26
+ type === "horizontal" && { height: size },
27
+ type === "vertical" && { width: size },
28
+ $styleOverride,
29
+ ]}
30
+ >
31
+ {line && (
32
+ <View
33
+ style={[
34
+ themed($line),
35
+ type === "horizontal" && {
36
+ width: 150,
37
+ height: 1,
38
+ marginStart: -75,
39
+ marginTop: -1,
40
+ },
41
+ type === "vertical" && {
42
+ height: 50,
43
+ width: 1,
44
+ marginTop: -25,
45
+ marginStart: -1,
46
+ },
47
+ ]}
48
+ />
49
+ )}
50
+ </View>
51
+ )
52
+ }
53
+
54
+ const $divider: ViewStyle = {
55
+ flexGrow: 0,
56
+ flexShrink: 0,
57
+ }
58
+
59
+ const $line: ThemedStyle<ViewStyle> = ({ colors }) => ({
60
+ backgroundColor: colors.border,
61
+ position: "absolute",
62
+ left: "50%",
63
+ top: "50%",
64
+ })
65
+
66
+ // @demo remove-file