lucy-cli 2.0.0-beta.4 → 2.0.0-beta.6

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 (162) hide show
  1. package/dist/commands/checks.d.ts +2 -2
  2. package/dist/commands/exec.d.ts +1 -1
  3. package/dist/commands/git.d.ts +2 -2
  4. package/dist/commands/home.d.ts +2 -2
  5. package/dist/commands/home.js +25 -2
  6. package/dist/commands/home.js.map +1 -1
  7. package/dist/index.js +0 -0
  8. package/dist/init/blocks.d.ts +1 -1
  9. package/dist/init/cargo.d.ts +1 -1
  10. package/dist/init/expo.d.ts +1 -1
  11. package/dist/init/gitModules.d.ts +1 -1
  12. package/dist/init/index.d.ts +1 -2
  13. package/dist/init/monorepo.d.ts +1 -1
  14. package/dist/init/prepareVelo.d.ts +1 -1
  15. package/dist/init/tauri.d.ts +1 -1
  16. package/dist/init/templates.d.ts +1 -1
  17. package/dist/init/velo.d.ts +1 -1
  18. package/dist/tasks/index.d.ts +1 -1
  19. package/dist/wix-sdk/index.d.ts +1 -1
  20. package/dist/wix-sdk/init.d.ts +1 -1
  21. package/dist/wix-sdk/run.d.ts +1 -1
  22. package/dist/wix-sync/index.d.ts +1 -1
  23. package/dist/wix-sync/init.d.ts +1 -1
  24. package/files/templates/expo verse[D]/files/.nvmrc +1 -0
  25. package/files/templates/expo verse[D]/files/.prettierignore +23 -0
  26. package/files/templates/expo verse[D]/files/.prettierrc.js +16 -0
  27. package/files/templates/expo verse[D]/files/.stylelintrc.json +8 -0
  28. package/files/templates/expo verse[D]/files/.vscode/extensions.json +10 -0
  29. package/files/templates/expo verse[D]/files/.vscode/launch.json +54 -0
  30. package/files/templates/expo verse[D]/files/.vscode/settings.json +30 -0
  31. package/files/templates/expo verse[D]/files/.vscode/tasks.json +0 -0
  32. package/files/templates/expo verse[D]/files/.yarnrc +1 -0
  33. package/files/templates/expo verse[D]/files/.yarnrc.yml +8 -0
  34. package/files/templates/expo verse[D]/files/App.tsx +63 -0
  35. package/files/templates/expo verse[D]/files/DEBUGGING.md +126 -0
  36. package/files/templates/expo verse[D]/files/README.md +45 -0
  37. package/files/templates/expo verse[D]/files/android/app/build.gradle +177 -0
  38. package/files/templates/expo verse[D]/files/android/app/debug.keystore +0 -0
  39. package/files/templates/expo verse[D]/files/android/app/proguard-rules.pro +14 -0
  40. package/files/templates/expo verse[D]/files/android/app/src/debug/AndroidManifest.xml +7 -0
  41. package/files/templates/expo verse[D]/files/android/app/src/main/AndroidManifest.xml +33 -0
  42. package/files/templates/expo verse[D]/files/android/app/src/main/java/so/sunnysideup/daily_verse/MainActivity.kt +65 -0
  43. package/files/templates/expo verse[D]/files/android/app/src/main/java/so/sunnysideup/daily_verse/MainApplication.kt +57 -0
  44. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable/ic_launcher_background.xml +6 -0
  45. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  46. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png +0 -0
  47. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png +0 -0
  48. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-night-hdpi/splashscreen_logo.png +0 -0
  49. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-night-mdpi/splashscreen_logo.png +0 -0
  50. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-night-xhdpi/splashscreen_logo.png +0 -0
  51. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-night-xxhdpi/splashscreen_logo.png +0 -0
  52. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-night-xxxhdpi/splashscreen_logo.png +0 -0
  53. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png +0 -0
  54. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png +0 -0
  55. package/files/templates/expo verse[D]/files/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png +0 -0
  56. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
  57. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
  58. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  59. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp +0 -0
  60. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.webp +0 -0
  61. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  62. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  63. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp +0 -0
  64. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.webp +0 -0
  65. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  66. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  67. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp +0 -0
  68. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.webp +0 -0
  69. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  70. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  71. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp +0 -0
  72. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.webp +0 -0
  73. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  74. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  75. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp +0 -0
  76. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp +0 -0
  77. package/files/templates/expo verse[D]/files/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  78. package/files/templates/expo verse[D]/files/android/app/src/main/res/values/colors.xml +6 -0
  79. package/files/templates/expo verse[D]/files/android/app/src/main/res/values/strings.xml +7 -0
  80. package/files/templates/expo verse[D]/files/android/app/src/main/res/values/styles.xml +12 -0
  81. package/files/templates/expo verse[D]/files/android/app/src/main/res/values-night/colors.xml +3 -0
  82. package/files/templates/expo verse[D]/files/android/build.gradle +37 -0
  83. package/files/templates/expo verse[D]/files/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  84. package/files/templates/expo verse[D]/files/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  85. package/files/templates/expo verse[D]/files/android/gradle.properties +59 -0
  86. package/files/templates/expo verse[D]/files/android/gradlew +251 -0
  87. package/files/templates/expo verse[D]/files/android/gradlew.bat +94 -0
  88. package/files/templates/expo verse[D]/files/android/settings.gradle +39 -0
  89. package/files/templates/expo verse[D]/files/app.config.ts +77 -0
  90. package/files/templates/expo verse[D]/files/assets/data/de.csv +949 -0
  91. package/files/templates/expo verse[D]/files/assets/data/en.csv +949 -0
  92. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-Black.ttf +0 -0
  93. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-BlackItalic.ttf +0 -0
  94. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-Bold.ttf +0 -0
  95. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-BoldItalic.ttf +0 -0
  96. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-ExtraBold.ttf +0 -0
  97. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-ExtraBoldItalic.ttf +0 -0
  98. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-Italic.ttf +0 -0
  99. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-Medium.ttf +0 -0
  100. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-MediumItalic.ttf +0 -0
  101. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-Regular.ttf +0 -0
  102. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-SemiBold.ttf +0 -0
  103. package/files/templates/expo verse[D]/files/assets/fonts/PlayfairDisplay-SemiBoldItalic.ttf +0 -0
  104. package/files/templates/expo verse[D]/files/assets/fonts/SpaceMono-Regular.ttf +0 -0
  105. package/files/templates/expo verse[D]/files/assets/images/andorid/background.png +0 -0
  106. package/files/templates/expo verse[D]/files/assets/images/android-dark.png +0 -0
  107. package/files/templates/expo verse[D]/files/assets/images/android-light.png +0 -0
  108. package/files/templates/expo verse[D]/files/assets/images/background.png +0 -0
  109. package/files/templates/expo verse[D]/files/assets/images/ios-dark.png +0 -0
  110. package/files/templates/expo verse[D]/files/assets/images/ios-light.png +0 -0
  111. package/files/templates/expo verse[D]/files/assets/images/ios-tinted.png +0 -0
  112. package/files/templates/expo verse[D]/files/assets/images/splash-icon-dark.png +0 -0
  113. package/files/templates/expo verse[D]/files/assets/images/splash-icon-light.png +0 -0
  114. package/files/templates/expo verse[D]/files/babel.config.js +10 -0
  115. package/files/templates/expo verse[D]/files/components/.gitkeep +0 -0
  116. package/files/templates/expo verse[D]/files/components/MainScreen.tsx +299 -0
  117. package/files/templates/expo verse[D]/files/components/Share.tsx +132 -0
  118. package/files/templates/expo verse[D]/files/components/ui/.gitkeep +0 -0
  119. package/files/templates/expo verse[D]/files/components/ui/dev.tsx +48 -0
  120. package/files/templates/expo verse[D]/files/constants/config.ts +30 -0
  121. package/files/templates/expo verse[D]/files/constants/theme.ts +18 -0
  122. package/files/templates/expo verse[D]/files/data/de.csv +219 -0
  123. package/files/templates/expo verse[D]/files/data/en.csv +219 -0
  124. package/files/templates/expo verse[D]/files/data/version.json +4 -0
  125. package/files/templates/expo verse[D]/files/eas.json +47 -0
  126. package/files/templates/expo verse[D]/files/eslint.config.mjs +185 -0
  127. package/files/templates/expo verse[D]/files/global.css +47 -0
  128. package/files/templates/expo verse[D]/files/hooks/useColorScheme.ts +17 -0
  129. package/files/templates/expo verse[D]/files/index.ts +28 -0
  130. package/files/templates/expo verse[D]/files/lib/content.ts +31 -0
  131. package/files/templates/expo verse[D]/files/lib/data.ts +180 -0
  132. package/files/templates/expo verse[D]/files/lib/helper.ts +54 -0
  133. package/files/templates/expo verse[D]/files/lib/index.ts +21 -0
  134. package/files/templates/expo verse[D]/files/lib/state.ts +128 -0
  135. package/files/templates/expo verse[D]/files/lib/storage.ts +17 -0
  136. package/files/templates/expo verse[D]/files/lib/taskManager/index.ts +38 -0
  137. package/files/templates/expo verse[D]/files/lib/taskManager/loadData.ts +14 -0
  138. package/files/templates/expo verse[D]/files/lib/utils/index.ts +11 -0
  139. package/files/templates/expo verse[D]/files/lib/utils/polyfills.ts +29 -0
  140. package/files/templates/expo verse[D]/files/lib/utils/screenshot.ts +77 -0
  141. package/files/templates/expo verse[D]/files/lucy.json +8 -0
  142. package/files/templates/expo verse[D]/files/metro.config.js +81 -0
  143. package/files/templates/expo verse[D]/files/models/index.ts +28 -0
  144. package/files/templates/expo verse[D]/files/nativewind-env.d.ts +1 -0
  145. package/files/templates/expo verse[D]/files/package.json +102 -0
  146. package/files/templates/expo verse[D]/files/pnpm-workspace.yaml +3 -0
  147. package/files/templates/expo verse[D]/files/scripts/convert-verses.ts +309 -0
  148. package/files/templates/expo verse[D]/files/scripts/move-artifacts.ts +83 -0
  149. package/files/templates/expo verse[D]/files/scripts/reset-project.ts +116 -0
  150. package/files/templates/expo verse[D]/files/tailwind.config.js +63 -0
  151. package/files/templates/expo verse[D]/files/tsconfig.json +45 -0
  152. package/files/templates/expo verse[D]/files/types/index.ts +4 -0
  153. package/files/templates/expo verse[D]/files/types/reset.d.ts +1 -0
  154. package/files/templates/expo verse[D]/lucy.json +86 -0
  155. package/files/templates/expo[D]/files/app.config.ts +69 -0
  156. package/files/templates/expo[D]/files/assets/fonts/SpaceMono-Regular.ttf +0 -0
  157. package/files/templates/expo[D]/files/scripts/reset-project.ts +0 -0
  158. package/old/index.ts +0 -0
  159. package/package.json +11 -12
  160. package/pnpm-workspace.yaml +6 -0
  161. package/src/commands/home.ts +36 -2
  162. package/src/index.ts +0 -0
@@ -0,0 +1,185 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+ // https://docs.expo.dev/guides/using-eslint/
3
+ import eslint from '@eslint/js';
4
+ // @ts-ignore
5
+ import jsdoc from 'eslint-plugin-jsdoc';
6
+ import namedImportSpacing from 'eslint-plugin-named-import-spacing';
7
+ import simpleImportSort from 'eslint-plugin-simple-import-sort';
8
+ import globals from 'globals';
9
+ import { createRequire } from 'module';
10
+ import tseslint from 'typescript-eslint';
11
+
12
+ const require = createRequire(import.meta.url);
13
+ const expoConfig = require('eslint-config-expo/flat.js');
14
+
15
+ export default tseslint.config(
16
+ eslint.configs.recommended,
17
+ // eslint-disable-next-line import/no-named-as-default-member
18
+ tseslint.configs.recommendedTypeChecked,
19
+ jsdoc.configs['flat/recommended-typescript'],
20
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
21
+ expoConfig,
22
+ {
23
+ ignores: ['dist/*'],
24
+ },
25
+ {
26
+ plugins: {
27
+ 'simple-import-sort': simpleImportSort,
28
+ 'named-import-spacing': namedImportSpacing,
29
+ },
30
+ settings: {
31
+ 'import/resolver': {
32
+ typescript: {
33
+ project: './tsconfig.json',
34
+ }
35
+ }
36
+ },
37
+ languageOptions: {
38
+ // eslint-disable-next-line import/no-named-as-default-member
39
+ parser: tseslint.parser,
40
+ parserOptions: {
41
+ projectService: true,
42
+ },
43
+ ecmaVersion: 2020,
44
+ sourceType: 'module',
45
+ globals: {
46
+ $w: 'readonly',
47
+ ...globals.browser,
48
+ ...globals.node,
49
+ // ...globals.es6,
50
+ },
51
+ },
52
+ rules: {
53
+ 'jsdoc/require-description': 'warn',
54
+ 'no-console': ['error'],
55
+ 'no-restricted-imports': [
56
+ 'error',
57
+ {
58
+ 'patterns': ['*/**/backend/*', '*/**/public/*']
59
+ }
60
+ ],
61
+ 'no-restricted-syntax': [
62
+ 'warn',
63
+ {
64
+ selector: 'StaticBlock',
65
+ message: 'Static blocks are not allowed in classes.',
66
+ },
67
+ {
68
+ selector: "MemberExpression[object.name='globalThis'][property.name='console']",
69
+ message: 'Using globalThis.console is not allowed.'
70
+ },
71
+ {
72
+ selector: "CallExpression[callee.property.name='runPromise'][callee.object.name='runtime']",
73
+ message: 'Usage of runtime.runPromise() is discouraged.',
74
+ },
75
+ {
76
+ selector: "CallExpression[callee.property.name='runFork'][callee.object.name='runtime']",
77
+ message: 'Usage of runtime.runFork() is discouraged.',
78
+ }
79
+ ],
80
+ '@typescript-eslint/no-unsafe-argument': 'error',
81
+ '@typescript-eslint/no-unsafe-assignment': 'off',
82
+ '@typescript-eslint/no-unsafe-call': 'error',
83
+ '@typescript-eslint/no-unsafe-member-access': 'off',
84
+ '@typescript-eslint/no-unsafe-return': 'error',
85
+ quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }],
86
+ curly: ['error', 'multi-line'],
87
+ 'simple-import-sort/imports': 'error',
88
+ 'simple-import-sort/exports': 'error',
89
+ indent: ['error', 'tab'],
90
+ 'no-tabs': 0,
91
+ 'semi-style': ['error', 'last'],
92
+ semi: [2, 'always'],
93
+ 'object-curly-spacing': ['error', 'always'],
94
+ 'space-in-parens': ['error', 'never'],
95
+ 'newline-before-return': 'error',
96
+ 'space-before-blocks': ['error', { functions: 'always', keywords: 'always', classes: 'always' }],
97
+ 'comma-spacing': ['error', { before: false, after: true }],
98
+ 'no-multi-spaces': 'error',
99
+ 'import/newline-after-import': ['error', { count: 1 }],
100
+ 'named-import-spacing/named-import-spacing': 2,
101
+ '@typescript-eslint/no-unused-vars': 'warn',
102
+ 'import/no-unresolved': [0],
103
+ 'no-forbidden-relative-imports': [0],
104
+ '@typescript-eslint/triple-slash-reference': 'off',
105
+ '@typescript-eslint/member-ordering': [
106
+ 'error',
107
+ {
108
+ classes: [
109
+ 'constructor',
110
+ 'private-instance-field',
111
+ 'protected-instance-field',
112
+ 'public-instance-field',
113
+ 'public-instance-method',
114
+ 'private-instance-method',
115
+ ],
116
+ },
117
+ ],
118
+ '@typescript-eslint/naming-convention': [
119
+ 'error',
120
+ {
121
+ selector: ['variable'],
122
+ format: ['camelCase', 'UPPER_CASE'],
123
+ leadingUnderscore: 'allow',
124
+ },
125
+ {
126
+ selector: ['function'],
127
+ format: ['camelCase', 'PascalCase'],
128
+ leadingUnderscore: 'allow',
129
+ },
130
+ {
131
+ selector: ['objectLiteralMethod',],
132
+ format: ['camelCase', 'PascalCase'],
133
+ leadingUnderscore: 'allow',
134
+ },
135
+ {
136
+ selector: ['import',],
137
+ format: ['camelCase', 'PascalCase'],
138
+ leadingUnderscore: 'allow',
139
+ },
140
+ {
141
+ selector: ['objectLiteralProperty'],
142
+ format: null,
143
+ leadingUnderscore: 'allow',
144
+ },
145
+ {
146
+ selector: 'memberLike',
147
+ modifiers: ['private'],
148
+ format: ['camelCase'],
149
+ leadingUnderscore: 'require',
150
+ },
151
+ {
152
+ selector: 'memberLike',
153
+ modifiers: ['protected'],
154
+ format: ['camelCase'],
155
+ leadingUnderscore: 'require',
156
+ },
157
+ {
158
+ selector: 'memberLike',
159
+ modifiers: ['public'],
160
+ format: ['camelCase'],
161
+ leadingUnderscore: 'forbid',
162
+ },
163
+ {
164
+ selector: ['parameterProperty', 'parameter'],
165
+ format: ['camelCase'],
166
+ leadingUnderscore: 'forbid',
167
+ },
168
+ {
169
+ selector: 'default',
170
+ format: ['UPPER_CASE'],
171
+ leadingUnderscore: 'forbid',
172
+ trailingUnderscore: 'forbid',
173
+ custom: {
174
+ regex: '^[A-Z_]+$',
175
+ match: true,
176
+ },
177
+ },
178
+ {
179
+ selector: 'typeLike',
180
+ format: ['PascalCase'],
181
+ },
182
+ ],
183
+ },
184
+ },
185
+ );
@@ -0,0 +1,47 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+ @layer base {
5
+ :root {
6
+ --background: 0 0% 100%;
7
+ --foreground: 240 10% 3.9%;
8
+ --card: 0 0% 100%;
9
+ --card-foreground: 240 10% 3.9%;
10
+ --popover: 0 0% 100%;
11
+ --popover-foreground: 240 10% 3.9%;
12
+ --primary: 240 5.9% 10%;
13
+ --primary-foreground: 0 0% 98%;
14
+ --secondary: 240 4.8% 95.9%;
15
+ --secondary-foreground: 240 5.9% 10%;
16
+ --muted: 240 4.8% 95.9%;
17
+ --muted-foreground: 240 3.8% 46.1%;
18
+ --accent: 240 4.8% 95.9%;
19
+ --accent-foreground: 240 5.9% 10%;
20
+ --destructive: 0 84.2% 60.2%;
21
+ --destructive-foreground: 0 0% 98%;
22
+ --border: 240 5.9% 90%;
23
+ --input: 240 5.9% 90%;
24
+ --ring: 240 5.9% 10%;
25
+ }
26
+ .dark:root {
27
+ --background: 240 10% 3.9%;
28
+ --foreground: 0 0% 98%;
29
+ --card: 240 10% 3.9%;
30
+ --card-foreground: 0 0% 98%;
31
+ --popover: 240 10% 3.9%;
32
+ --popover-foreground: 0 0% 98%;
33
+ --primary: 0 0% 98%;
34
+ --primary-foreground: 240 5.9% 10%;
35
+ --secondary: 240 3.7% 15.9%;
36
+ --secondary-foreground: 0 0% 98%;
37
+ --muted: 240 3.7% 15.9%;
38
+ --muted-foreground: 240 5% 64.9%;
39
+ --accent: 240 3.7% 15.9%;
40
+ --accent-foreground: 0 0% 98%;
41
+ --destructive: 0 72% 51%;
42
+ --destructive-foreground: 0 0% 98%;
43
+ --border: 240 3.7% 15.9%;
44
+ --input: 240 3.7% 15.9%;
45
+ --ring: 240 4.9% 83.9%;
46
+ }
47
+ }
@@ -0,0 +1,17 @@
1
+ import { useColorScheme as useNativewindColorScheme } from 'nativewind';
2
+
3
+ /**
4
+ * Custom hook to get the current color scheme and provide methods to change it.
5
+ * This hook uses the nativewind's useColorScheme to access the color scheme.
6
+ * @returns An object containing the current color scheme, a method to set the color scheme,
7
+ */
8
+ export function useColorScheme() {
9
+ const { colorScheme, setColorScheme, toggleColorScheme } = useNativewindColorScheme();
10
+
11
+ return {
12
+ colorScheme: colorScheme ?? 'dark',
13
+ isDarkColorScheme: colorScheme === 'dark',
14
+ setColorScheme,
15
+ toggleColorScheme,
16
+ };
17
+ }
@@ -0,0 +1,28 @@
1
+ import '@/global.css';
2
+ import '@/lib/utils/polyfills';
3
+
4
+ import { registerRootComponent } from 'expo';
5
+ import * as BackgroundTask from 'expo-background-task';
6
+ import * as TaskManager from 'expo-task-manager';
7
+
8
+ import { appLoaded, initializeBackgroundTask } from '@/lib/taskManager';
9
+
10
+ import App from './App';
11
+ import { BACKGROUND_TASK_IDENTIFIER } from './lib';
12
+
13
+ // TaskManager.unregisterTaskAsync(BACKGROUND_TASK_IDENTIFIER);
14
+ if (__DEV__) {
15
+ void TaskManager.getRegisteredTasksAsync().then((tasks) => {
16
+ // eslint-disable-next-line no-console
17
+ console.log('Registered Tasks: \n', tasks);
18
+ }).catch((error) => {
19
+ // eslint-disable-next-line no-console
20
+ console.error(error);
21
+ });
22
+ }
23
+
24
+ void initializeBackgroundTask(appLoaded);
25
+ // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
26
+ // It also ensures that whether you load the app in Expo Go or in a native build,
27
+ // the environment is set up appropriately
28
+ registerRootComponent(App);
@@ -0,0 +1,31 @@
1
+ import { Data, Effect } from 'effect';
2
+
3
+ import { FALLBACK_QUOTE } from '../constants/config';
4
+ import { getQuote, loadData } from './data';
5
+ import { getTimeData, renewState } from './helper';
6
+ import { setState } from './state';
7
+
8
+ export class ContentNotFoundError extends Data.TaggedError('ContentNotFoundError')<{reason: string}> {}
9
+
10
+ export const getContent = () => {
11
+ return Effect.gen(function* () {
12
+ const { quoteTimestamp } = yield* getTimeData;
13
+ const { state } = yield* loadData;
14
+
15
+ if(state === null) return yield* Effect.succeed(FALLBACK_QUOTE);
16
+
17
+ if(yield* renewState(state)) {
18
+ const { quote, randomIndex } = yield* getQuote();
19
+ yield* setState({ ...state, index: randomIndex, date: quoteTimestamp }).pipe(Effect.catchAll((error) => Effect.logError('Failed to set state', error)));
20
+
21
+ return quote;
22
+ }
23
+ const { quote } = yield* getQuote(state.index);
24
+
25
+ return quote;
26
+ }).pipe(Effect.catchTags({
27
+ GetDataError: () => Effect.succeed(FALLBACK_QUOTE),
28
+ ParseError: () => Effect.succeed(FALLBACK_QUOTE),
29
+ }));
30
+ };
31
+
@@ -0,0 +1,180 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+ import { Data, Effect, Schema } from 'effect';
3
+ import { getLocales } from 'expo-localization';
4
+ import Papa from 'papaparse';
5
+
6
+ import { Quote, quoteSchema } from '@/models';
7
+
8
+ import { BASE_URL, CACHE_CONTROL, STORAGE_KEY, USER_AGENT } from '../constants/config';
9
+ import { checkUpdata, getLanguage, getTimeData, hasLanguageChanged, renewState } from './helper';
10
+ import { getState, getVersionInfo, setState } from './state';
11
+
12
+ export class GetRemoteDataError extends Data.TaggedError('GetRemoteDataError')<{reason: string, error: unknown}> {}
13
+ export class GetQuoteError extends Data.TaggedError('GetDataError')<{reason: string, error: unknown}> {}
14
+ export class SaveQuoteError extends Data.TaggedError('SaveQuoteError')<{reason: string, error: unknown}> {}
15
+
16
+ export const getRemoteData = (lang: string) => Effect.gen(function* () {
17
+ const response = yield* Effect.tryPromise({
18
+ try: () => fetch(`${BASE_URL.toString()}/${lang}.csv`, {
19
+ headers: {
20
+ 'Cache-Control': CACHE_CONTROL,
21
+ 'user-agent': USER_AGENT,
22
+ },
23
+ }),
24
+ catch: (error) => {
25
+ return new GetRemoteDataError({
26
+ reason: 'Failed to fetch CSV data from remote',
27
+ error,
28
+ });
29
+ },
30
+ });
31
+
32
+ if (!response.ok) {
33
+ return yield* Effect.fail(new GetRemoteDataError({
34
+ reason: `Failed to fetch CSV: ${response.status} ${response.statusText}`,
35
+ error: new Error(`Failed to fetch CSV: ${response.status} ${response.statusText}`),
36
+ }));
37
+ }
38
+
39
+ const csvText = yield* Effect.tryPromise({
40
+ try: () => response.text(),
41
+ catch: (error) => {
42
+ return new GetRemoteDataError({
43
+ reason: 'Failed to read CSV text from response',
44
+ error,
45
+ });
46
+ },
47
+ });
48
+ // Parse the CSV text
49
+ const { data: quotes, errors } = Papa.parse<Quote>(csvText, {
50
+ header: true,
51
+ skipEmptyLines: true
52
+ });
53
+
54
+ if (errors.length > 0) {
55
+ return yield* Effect.fail(new GetRemoteDataError({
56
+ reason: `CSV parsing errors: ${errors.map(e => e.message).join(', ')}`,
57
+ error: new Error(`CSV parsing errors: ${errors.map(e => e.message).join(', ')}`),
58
+ }));
59
+ }
60
+
61
+ if (quotes.length === 0) {
62
+ return yield* Effect.fail(new GetRemoteDataError({
63
+ reason: 'No quotes found in CSV data',
64
+ error: new Error('No quotes found in CSV data'),
65
+ }));
66
+ }
67
+
68
+ // Return the parsed quotes
69
+ return quotes;
70
+ });
71
+
72
+ export const getQuote = (index?: number) => Effect.gen(function* () {
73
+ const quotesRaw = yield* Effect.tryPromise({
74
+ try: () => AsyncStorage.getItem(STORAGE_KEY),
75
+ catch: (error) => {
76
+ return new GetQuoteError({ reason: 'Failed to read quote to storage', error });
77
+ }
78
+ });
79
+ if(quotesRaw === null) {return (
80
+ yield *
81
+ Effect.fail(new GetQuoteError({ reason: 'No quotes found in storage', error: new Error('No quotes found in storage') }))
82
+ );}
83
+
84
+ const quotesJSON = yield* Schema.decodeUnknown(Schema.parseJson())(quotesRaw).pipe(Effect.catchAll((error) => {
85
+ // eslint-disable-next-line no-console
86
+ console.error('Error decoding quote from storage:', error);
87
+
88
+ return Effect.succeed(null);
89
+ }));
90
+ const data = yield* Schema.decodeUnknown(Schema.Array(quoteSchema))(quotesJSON);
91
+ const randomIndex = index ?? Math.floor(Math.random() * data.length);
92
+
93
+ return { quote: data[randomIndex], randomIndex };
94
+ });
95
+
96
+ export const saveQuotes = (quote: Quote[]) => Effect.gen(function* () {
97
+ yield* Effect.tryPromise({
98
+ try: () => AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(quote)),
99
+ catch: (error) => {
100
+ return new SaveQuoteError({ reason: 'Failed to save quote to storage', error });
101
+ }
102
+ });
103
+ });
104
+
105
+ export const getQuotes = () => Effect.gen(function* () {
106
+ const quotesRaw = yield* Effect.tryPromise({
107
+ try: () => AsyncStorage.getItem(STORAGE_KEY),
108
+ catch: (error) => {
109
+ return new GetQuoteError({ reason: 'Failed to read quote to storage', error });
110
+ }
111
+ });
112
+ if(quotesRaw === null) {return (
113
+ yield *
114
+ Effect.fail(new GetQuoteError({ reason: 'No quotes found in storage', error: new Error('No quotes found in storage') }))
115
+ );}
116
+
117
+ const quotesJSON = yield* Schema.decodeUnknown(Schema.parseJson())(quotesRaw).pipe(Effect.catchAll((error) => {
118
+ // eslint-disable-next-line no-console
119
+ console.error('Error decoding quote from storage:', error);
120
+
121
+ return Effect.succeed(null);
122
+ }));
123
+ const data = yield* Schema.decodeUnknown(Schema.Array(quoteSchema))(quotesJSON);
124
+
125
+ return data;
126
+ });
127
+
128
+ export const loadData = Effect.gen(function* () {
129
+ const state = yield* getState.pipe(Effect.catchAll((error) => Effect.gen(function* () {
130
+ yield* Effect.logError('Failed to get state', error);
131
+
132
+ return null;
133
+ })));
134
+
135
+ const { quoteTimestamp, currentTime } = yield* getTimeData;
136
+
137
+ if(state === null) {
138
+ const version = yield* getVersionInfo;
139
+
140
+ const data = yield* getRemoteData(getLanguage(version));
141
+
142
+ const state = {
143
+ index: Math.floor(Math.random() * data.length),
144
+ date: quoteTimestamp,
145
+ language: getLanguage(version),
146
+ version: version.version,
147
+ lastUpdateCheck: currentTime,
148
+ deviceLanguage: getLocales()[0].languageTag.split('-')[0],
149
+ };
150
+
151
+ yield* saveQuotes(data);
152
+ yield* setState({ ...state, date: quoteTimestamp }).pipe(Effect.catchAll((error) => Effect.logError('Failed to set state', error)));
153
+
154
+ return { state };
155
+ }
156
+
157
+ if(yield* checkUpdata(state) || hasLanguageChanged(state)) {
158
+ const version = yield* getVersionInfo;
159
+
160
+ const data = yield* getRemoteData(getLanguage(version));
161
+ yield* saveQuotes(data);
162
+ yield* setState({ ...state, date: quoteTimestamp, version: version.version, language: getLanguage(version) }).pipe(Effect.catchAll((error) => Effect.logError('Failed to set state', error)));
163
+ }
164
+
165
+ if((hasLanguageChanged(state)) && (yield* renewState(state))) {
166
+ const version = yield* getVersionInfo;
167
+
168
+ const data = yield* getRemoteData(getLanguage(version));
169
+ yield* saveQuotes(data);
170
+ yield* setState({ ...state, date: quoteTimestamp, version: version.version, language: getLanguage(version) }).pipe(Effect.catchAll((error) => Effect.logError('Failed to set state', error)));
171
+ }
172
+
173
+ return { state };
174
+ }).pipe(Effect.catchAll((error) => {
175
+ return Effect.gen(function* () {
176
+ yield* Effect.logError('Failed to load data', error);
177
+
178
+ return { state: null };
179
+ });
180
+ }));
@@ -0,0 +1,54 @@
1
+ import { DateTime, Effect } from 'effect';
2
+ import { getLocales } from 'expo-localization';
3
+
4
+ import { DataState, Version } from '@/models';
5
+
6
+ import { FALLBACK_LOCALE } from '../constants/config';
7
+
8
+
9
+ export const getTimeData = Effect.gen(function* () {
10
+ const currentTime = yield* DateTime.now;
11
+ const quoteTimestamp = DateTime.setParts(currentTime, { hours: 4, minutes: 30, seconds: 0 });
12
+
13
+ return { quoteTimestamp, currentTime };
14
+ });
15
+ /**
16
+ * Get the language to use for the quote.
17
+ * @param version The version of the app.
18
+ * @returns The language to use for the quote.
19
+ */
20
+ export function getLanguage(version: Version) {
21
+ const locale = getLocales()[0].languageTag.split('-')[0];
22
+ if(version.languages.includes(locale)) return locale.toLowerCase();
23
+
24
+ return FALLBACK_LOCALE.toLowerCase();
25
+ };
26
+
27
+ /**
28
+ * Check if the language has changed.
29
+ * @param state The state of the app.
30
+ * @returns Whether the language has changed.
31
+ */
32
+ export function hasLanguageChanged(state: DataState) {
33
+ const locale = getLocales()[0].languageTag.split('-')[0];
34
+
35
+ return state.deviceLanguage.toLowerCase() !== locale.toLowerCase();
36
+ }
37
+ export const renewState = (state: DataState) => Effect.gen(function* () {
38
+ // if(__DEV__) return true;
39
+
40
+ const { currentTime } = yield* getTimeData;
41
+ const elapsed = DateTime.distance(state.date, currentTime);
42
+ if (elapsed >= 86400000) return true;
43
+
44
+ return false;
45
+ });
46
+ export const checkUpdata = (state: DataState) => Effect.gen(function* () {
47
+ // if(__DEV__) return true;
48
+
49
+ const { currentTime } = yield* getTimeData;
50
+ const elapsed = DateTime.distance(state.lastUpdateCheck, currentTime);
51
+ if (elapsed >= 86400000) return true;
52
+
53
+ return false;
54
+ });
@@ -0,0 +1,21 @@
1
+ import { Effect } from 'effect';
2
+
3
+ import { STATE_KEY, STORAGE_KEY } from '../constants/config';
4
+ import { getQuotes } from './data';
5
+ import { getState, removeState } from './state';
6
+ import { clearStorage } from './storage';
7
+
8
+
9
+ export const clearData = Effect.gen(function* () {
10
+ yield* Effect.log('Clearing data');
11
+ yield* removeState;
12
+ yield* clearStorage(STORAGE_KEY);
13
+ yield* clearStorage(STATE_KEY);
14
+ });
15
+
16
+ export const showData = Effect.gen(function* () {
17
+ yield* Effect.log('Showing data');
18
+ const state = yield* getState;
19
+ const data = yield* getQuotes();
20
+ yield* Effect.log({ data, state });
21
+ });
@@ -0,0 +1,128 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+ import { Data, Effect, Schema } from 'effect';
3
+ import { fetch } from 'expo/fetch';
4
+
5
+ import { DataState, dataStateSchema, versionSchema } from '@/models';
6
+
7
+ import { BASE_URL, CACHE_CONTROL, STATE_KEY, USER_AGENT } from '../constants/config';
8
+ import { clearStorage } from './storage';
9
+
10
+ export class StateError extends Data.TaggedError('StateError')<{reason: string, error: unknown}> {}
11
+ export class GetVersionError extends Data.TaggedError('GetVersionError')<{reason: string, error: unknown}> {}
12
+
13
+ export const getState = Effect.gen(function* () {
14
+ const rawState =yield* Effect.tryPromise({
15
+ try: () => AsyncStorage.getItem(STATE_KEY),
16
+ catch: (error) => {
17
+ return new StateError({ reason: 'Failed to save state to storage', error });
18
+ }
19
+ });
20
+ if(rawState === null) return null;
21
+
22
+ const stateJSON = yield* Schema.decodeUnknown(Schema.parseJson())(rawState).pipe(Effect.catchAll((error) => {
23
+ // eslint-disable-next-line no-console
24
+ console.error('Error decoding quote from storage:', error);
25
+
26
+ return Effect.gen(function* () {
27
+ yield* clearStorage(STATE_KEY).pipe(Effect.catchAll((error) => {
28
+ // eslint-disable-next-line no-console
29
+ console.error('Error clearing storage:', error);
30
+
31
+ return Effect.succeed(null);
32
+ }));
33
+
34
+ return yield* Effect.succeed(null);
35
+ });
36
+ }));
37
+
38
+ const data = yield* Schema.decodeUnknown(dataStateSchema)(stateJSON).pipe(
39
+ Effect.catchAll((error) => {
40
+ // eslint-disable-next-line no-console
41
+ console.error('Failed to decode state from storage:', error);
42
+
43
+ return Effect.gen(function* () {
44
+ yield* clearStorage(STATE_KEY).pipe(Effect.catchAll((error) => {
45
+ // eslint-disable-next-line no-console
46
+ console.error('Error clearing storage:', error);
47
+
48
+ return Effect.succeed(null);
49
+ }));
50
+
51
+ return yield* Effect.succeed(null);
52
+ });
53
+ }
54
+ )
55
+ );
56
+
57
+ return data;
58
+ });
59
+
60
+ export const setState = (state: DataState) => {
61
+ return Effect.gen(function* () {
62
+ yield* Effect.tryPromise({
63
+ try: () => AsyncStorage.setItem(STATE_KEY, JSON.stringify(state)),
64
+ catch: (error) => {
65
+ // eslint-disable-next-line no-console
66
+ console.error('Error saving state to storage:', error);
67
+
68
+ return new StateError({ reason: 'Failed to save state to storage', error });
69
+ }
70
+ });
71
+ });
72
+ };
73
+
74
+ export const removeState = Effect.gen(function* () {
75
+ yield* Effect.tryPromise({
76
+ try: () => AsyncStorage.removeItem(STATE_KEY),
77
+ catch: (error) => {
78
+ return new StateError({ reason: 'Failed to remove state to storage', error });
79
+ }
80
+ });
81
+ });
82
+
83
+
84
+ export const getVersionInfo = Effect.gen(function* () {
85
+ const response = yield* Effect.tryPromise({
86
+ try: () => fetch(BASE_URL.toString() + '/' + 'version.json', {
87
+ headers: {
88
+ 'Cache-Control': CACHE_CONTROL,
89
+ 'user-agent': USER_AGENT,
90
+ },
91
+ }),
92
+ catch: (error) => {
93
+ return new StateError({
94
+ reason: 'Failed to fetch state from remote',
95
+ error,
96
+ });
97
+ },
98
+ });
99
+
100
+ if (!response.ok) {
101
+ return yield* Effect.fail(new StateError({
102
+ reason: `Failed to fetch version: ${response.status} ${response.statusText}`,
103
+ error: new Error(`Failed to fetch version: ${response.status} ${response.statusText}`),
104
+ }));
105
+ }
106
+ const jsonVersion = yield* Effect.tryPromise({
107
+ try: () => response.json(),
108
+ catch: (error) => {
109
+ return new GetVersionError({
110
+ reason: 'Failed to decode remote state from JSON',
111
+ error,
112
+ });
113
+ },
114
+ });
115
+
116
+ const version = yield* Schema.decodeUnknown(versionSchema)(jsonVersion, { onExcessProperty: 'ignore' }).pipe(
117
+ Effect.catchAll((error) => {
118
+ return Effect.fail(
119
+ new GetVersionError({
120
+ reason: 'Failed to decode state from remote state',
121
+ error,
122
+ }),
123
+ );
124
+ }),
125
+ );
126
+
127
+ return version;
128
+ });