@highbeek/create-rnstarterkit 1.0.1-beta.0 → 1.0.1-beta.1
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.
- package/dist/src/generators/appGenerator.js +113 -51
- package/dist/templates/cli-base/App.tsx +161 -23
- package/dist/templates/cli-base/assets/images/icon.png +0 -0
- package/dist/templates/cli-base/assets/images/partial-react-logo.png +0 -0
- package/dist/templates/cli-base/assets/images/react-logo.png +0 -0
- package/dist/templates/expo-base/app/index.tsx +164 -5
- package/dist/templates/optional/auth-context/components/layout/ScreenLayout.tsx +46 -0
- package/dist/templates/optional/auth-context/navigation/ProtectedStack.tsx +4 -3
- package/dist/templates/optional/auth-context/screens/HomeScreen.tsx +3 -3
- package/dist/templates/optional/auth-context/screens/LoginScreen.tsx +3 -3
- package/dist/templates/optional/auth-context/screens/ProfileScreen.tsx +3 -3
- package/dist/templates/optional/auth-context/screens/RegisterScreen.tsx +3 -3
- package/dist/templates/optional/auth-context/screens/SettingsScreen.tsx +3 -3
- package/dist/templates/optional/auth-redux/components/layout/ScreenLayout.tsx +46 -0
- package/dist/templates/optional/auth-redux/screens/HomeScreen.tsx +3 -3
- package/dist/templates/optional/auth-redux/screens/LoginScreen.tsx +3 -3
- package/dist/templates/optional/auth-redux/screens/ProfileScreen.tsx +7 -11
- package/dist/templates/optional/auth-redux/screens/RegisterScreen.tsx +3 -3
- package/dist/templates/optional/auth-redux/screens/SettingsScreen.tsx +6 -10
- package/dist/templates/optional/auth-zustand/components/layout/ScreenLayout.tsx +46 -0
- package/dist/templates/optional/auth-zustand/navigation/ProtectedStack.tsx +4 -3
- package/dist/templates/optional/auth-zustand/screens/HomeScreen.tsx +3 -3
- package/dist/templates/optional/auth-zustand/screens/LoginScreen.tsx +3 -3
- package/dist/templates/optional/auth-zustand/screens/ProfileScreen.tsx +3 -3
- package/dist/templates/optional/auth-zustand/screens/RegisterScreen.tsx +3 -3
- package/dist/templates/optional/auth-zustand/screens/SettingsScreen.tsx +3 -3
- package/package.json +1 -1
- package/dist/templates/expo-base/components/ui/collapsible.tsx +0 -45
- package/dist/templates/expo-base/components/ui/icon-symbol.ios.tsx +0 -32
- package/dist/templates/expo-base/components/ui/icon-symbol.tsx +0 -41
|
@@ -18,6 +18,9 @@ async function generateApp(options) {
|
|
|
18
18
|
filter: (src) => shouldCopyPath(src, templatePath),
|
|
19
19
|
});
|
|
20
20
|
await replaceProjectName(targetPath, projectName);
|
|
21
|
+
if (platform === "React Native CLI") {
|
|
22
|
+
await configureCliNativeProjectNames(targetPath, projectName);
|
|
23
|
+
}
|
|
21
24
|
await createStandardStructure(targetPath, platform);
|
|
22
25
|
if (auth) {
|
|
23
26
|
const authFolder = state === "Redux Toolkit"
|
|
@@ -102,6 +105,59 @@ async function replaceProjectName(dir, projectName) {
|
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
107
|
}
|
|
108
|
+
function toNativeAppName(projectName) {
|
|
109
|
+
const cleaned = projectName
|
|
110
|
+
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
111
|
+
.trim()
|
|
112
|
+
.split(/\s+/)
|
|
113
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
114
|
+
.join("");
|
|
115
|
+
const fallback = cleaned || "App";
|
|
116
|
+
return /^\d/.test(fallback) ? `App${fallback}` : fallback;
|
|
117
|
+
}
|
|
118
|
+
function toAndroidPackageSegment(projectName) {
|
|
119
|
+
const cleaned = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
120
|
+
const fallback = cleaned || "app";
|
|
121
|
+
return /^\d/.test(fallback) ? `app${fallback}` : fallback;
|
|
122
|
+
}
|
|
123
|
+
async function replaceInTextFiles(dir, replacements) {
|
|
124
|
+
const files = await fs_extra_1.default.readdir(dir);
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
const filePath = path_1.default.join(dir, file);
|
|
127
|
+
const stat = await fs_extra_1.default.stat(filePath);
|
|
128
|
+
if (stat.isDirectory()) {
|
|
129
|
+
await replaceInTextFiles(filePath, replacements);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!isTextFile(filePath))
|
|
133
|
+
continue;
|
|
134
|
+
let content = await fs_extra_1.default.readFile(filePath, "utf8");
|
|
135
|
+
for (const [pattern, replacement] of replacements) {
|
|
136
|
+
content = content.replace(pattern, replacement);
|
|
137
|
+
}
|
|
138
|
+
await fs_extra_1.default.writeFile(filePath, content, "utf8");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function renameIfExists(fromPath, toPath) {
|
|
142
|
+
if (!(await fs_extra_1.default.pathExists(fromPath)))
|
|
143
|
+
return;
|
|
144
|
+
await fs_extra_1.default.move(fromPath, toPath, { overwrite: true });
|
|
145
|
+
}
|
|
146
|
+
async function configureCliNativeProjectNames(targetPath, projectName) {
|
|
147
|
+
const nativeAppName = toNativeAppName(projectName);
|
|
148
|
+
const androidPackageSegment = toAndroidPackageSegment(projectName);
|
|
149
|
+
await replaceInTextFiles(targetPath, [
|
|
150
|
+
[/BaseApp/g, nativeAppName],
|
|
151
|
+
[/com\.baseapp/g, `com.${androidPackageSegment}`],
|
|
152
|
+
]);
|
|
153
|
+
const iosPath = path_1.default.join(targetPath, "ios");
|
|
154
|
+
const androidJavaRoot = path_1.default.join(targetPath, "android", "app", "src", "main", "java");
|
|
155
|
+
await renameIfExists(path_1.default.join(iosPath, "BaseApp"), path_1.default.join(iosPath, nativeAppName));
|
|
156
|
+
await renameIfExists(path_1.default.join(iosPath, "BaseApp.xcodeproj"), path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`));
|
|
157
|
+
await renameIfExists(path_1.default.join(iosPath, "BaseApp.xcworkspace"), path_1.default.join(iosPath, `${nativeAppName}.xcworkspace`));
|
|
158
|
+
await renameIfExists(path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`, "xcshareddata", "xcschemes", "BaseApp.xcscheme"), path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`, "xcshareddata", "xcschemes", `${nativeAppName}.xcscheme`));
|
|
159
|
+
await renameIfExists(path_1.default.join(androidJavaRoot, "com", "baseapp"), path_1.default.join(androidJavaRoot, "com", androidPackageSegment));
|
|
160
|
+
}
|
|
105
161
|
async function installDependencies(targetPath) {
|
|
106
162
|
console.log("📦 Installing dependencies...");
|
|
107
163
|
try {
|
|
@@ -496,8 +552,9 @@ export default function RootLayout() {
|
|
|
496
552
|
`,
|
|
497
553
|
indexRoute: `import React from "react";
|
|
498
554
|
import { useEffect } from "react";
|
|
499
|
-
import { ActivityIndicator
|
|
555
|
+
import { ActivityIndicator } from "react-native";
|
|
500
556
|
import { Redirect } from "expo-router";
|
|
557
|
+
import ScreenLayout from "../components/layout/ScreenLayout";
|
|
501
558
|
import { useAuthStore } from "../store/authStore";
|
|
502
559
|
|
|
503
560
|
export default function Index() {
|
|
@@ -511,9 +568,9 @@ export default function Index() {
|
|
|
511
568
|
|
|
512
569
|
if (!isHydrated) {
|
|
513
570
|
return (
|
|
514
|
-
<
|
|
571
|
+
<ScreenLayout centered padded={false}>
|
|
515
572
|
<ActivityIndicator />
|
|
516
|
-
</
|
|
573
|
+
</ScreenLayout>
|
|
517
574
|
);
|
|
518
575
|
}
|
|
519
576
|
|
|
@@ -521,8 +578,9 @@ export default function Index() {
|
|
|
521
578
|
}
|
|
522
579
|
`,
|
|
523
580
|
authLayout: `import React, { useEffect } from "react";
|
|
524
|
-
import { ActivityIndicator
|
|
581
|
+
import { ActivityIndicator } from "react-native";
|
|
525
582
|
import { Redirect, Stack } from "expo-router";
|
|
583
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
526
584
|
import { useAuthStore } from "../../store/authStore";
|
|
527
585
|
|
|
528
586
|
export default function AuthLayout() {
|
|
@@ -536,9 +594,9 @@ export default function AuthLayout() {
|
|
|
536
594
|
|
|
537
595
|
if (!isHydrated) {
|
|
538
596
|
return (
|
|
539
|
-
<
|
|
597
|
+
<ScreenLayout centered padded={false}>
|
|
540
598
|
<ActivityIndicator />
|
|
541
|
-
</
|
|
599
|
+
</ScreenLayout>
|
|
542
600
|
);
|
|
543
601
|
}
|
|
544
602
|
|
|
@@ -553,8 +611,9 @@ export default function AuthLayout() {
|
|
|
553
611
|
}
|
|
554
612
|
`,
|
|
555
613
|
tabsLayout: `import React, { useEffect } from "react";
|
|
556
|
-
import { ActivityIndicator
|
|
614
|
+
import { ActivityIndicator } from "react-native";
|
|
557
615
|
import { Redirect, Tabs } from "expo-router";
|
|
616
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
558
617
|
import { useAuthStore } from "../../store/authStore";
|
|
559
618
|
|
|
560
619
|
export default function TabsLayout() {
|
|
@@ -568,9 +627,9 @@ export default function TabsLayout() {
|
|
|
568
627
|
|
|
569
628
|
if (!isHydrated) {
|
|
570
629
|
return (
|
|
571
|
-
<
|
|
630
|
+
<ScreenLayout centered padded={false}>
|
|
572
631
|
<ActivityIndicator />
|
|
573
|
-
</
|
|
632
|
+
</ScreenLayout>
|
|
574
633
|
);
|
|
575
634
|
}
|
|
576
635
|
|
|
@@ -604,8 +663,9 @@ export default function RootLayout() {
|
|
|
604
663
|
}
|
|
605
664
|
`,
|
|
606
665
|
indexRoute: `import React, { useContext } from "react";
|
|
607
|
-
import { ActivityIndicator
|
|
666
|
+
import { ActivityIndicator } from "react-native";
|
|
608
667
|
import { Redirect } from "expo-router";
|
|
668
|
+
import ScreenLayout from "../components/layout/ScreenLayout";
|
|
609
669
|
import { AuthContext } from "../context/AuthContext";
|
|
610
670
|
|
|
611
671
|
export default function Index() {
|
|
@@ -613,9 +673,9 @@ export default function Index() {
|
|
|
613
673
|
|
|
614
674
|
if (!isHydrated) {
|
|
615
675
|
return (
|
|
616
|
-
<
|
|
676
|
+
<ScreenLayout centered padded={false}>
|
|
617
677
|
<ActivityIndicator />
|
|
618
|
-
</
|
|
678
|
+
</ScreenLayout>
|
|
619
679
|
);
|
|
620
680
|
}
|
|
621
681
|
|
|
@@ -623,8 +683,9 @@ export default function Index() {
|
|
|
623
683
|
}
|
|
624
684
|
`,
|
|
625
685
|
authLayout: `import React, { useContext } from "react";
|
|
626
|
-
import { ActivityIndicator
|
|
686
|
+
import { ActivityIndicator } from "react-native";
|
|
627
687
|
import { Redirect, Stack } from "expo-router";
|
|
688
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
628
689
|
import { AuthContext } from "../../context/AuthContext";
|
|
629
690
|
|
|
630
691
|
export default function AuthLayout() {
|
|
@@ -632,9 +693,9 @@ export default function AuthLayout() {
|
|
|
632
693
|
|
|
633
694
|
if (!isHydrated) {
|
|
634
695
|
return (
|
|
635
|
-
<
|
|
696
|
+
<ScreenLayout centered padded={false}>
|
|
636
697
|
<ActivityIndicator />
|
|
637
|
-
</
|
|
698
|
+
</ScreenLayout>
|
|
638
699
|
);
|
|
639
700
|
}
|
|
640
701
|
|
|
@@ -649,8 +710,9 @@ export default function AuthLayout() {
|
|
|
649
710
|
}
|
|
650
711
|
`,
|
|
651
712
|
tabsLayout: `import React, { useContext } from "react";
|
|
652
|
-
import { ActivityIndicator
|
|
713
|
+
import { ActivityIndicator } from "react-native";
|
|
653
714
|
import { Redirect, Tabs } from "expo-router";
|
|
715
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
654
716
|
import { AuthContext } from "../../context/AuthContext";
|
|
655
717
|
|
|
656
718
|
export default function TabsLayout() {
|
|
@@ -658,9 +720,9 @@ export default function TabsLayout() {
|
|
|
658
720
|
|
|
659
721
|
if (!isHydrated) {
|
|
660
722
|
return (
|
|
661
|
-
<
|
|
723
|
+
<ScreenLayout centered padded={false}>
|
|
662
724
|
<ActivityIndicator />
|
|
663
|
-
</
|
|
725
|
+
</ScreenLayout>
|
|
664
726
|
);
|
|
665
727
|
}
|
|
666
728
|
|
|
@@ -680,7 +742,7 @@ export default function TabsLayout() {
|
|
|
680
742
|
function getExpoAuthRouteFiles(state) {
|
|
681
743
|
const registerRedux = `import React, { useState } from "react";
|
|
682
744
|
import { Button, Text, TextInput } from "react-native";
|
|
683
|
-
import
|
|
745
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
684
746
|
import { useDispatch } from "react-redux";
|
|
685
747
|
import { registerApi } from "../../api";
|
|
686
748
|
import { login } from "../../store/authSlice";
|
|
@@ -702,7 +764,7 @@ export default function RegisterScreen() {
|
|
|
702
764
|
};
|
|
703
765
|
|
|
704
766
|
return (
|
|
705
|
-
<
|
|
767
|
+
<ScreenLayout>
|
|
706
768
|
<TextInput
|
|
707
769
|
placeholder="Email"
|
|
708
770
|
value={email}
|
|
@@ -739,13 +801,13 @@ export default function RegisterScreen() {
|
|
|
739
801
|
/>
|
|
740
802
|
{!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
|
|
741
803
|
<Button title="Register" onPress={() => void handleRegister()} />
|
|
742
|
-
</
|
|
804
|
+
</ScreenLayout>
|
|
743
805
|
);
|
|
744
806
|
}
|
|
745
807
|
`;
|
|
746
808
|
const registerZustand = `import React, { useState } from "react";
|
|
747
809
|
import { Button, Text, TextInput } from "react-native";
|
|
748
|
-
import
|
|
810
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
749
811
|
import { registerApi } from "../../api";
|
|
750
812
|
import { useAuthStore } from "../../store/authStore";
|
|
751
813
|
|
|
@@ -766,7 +828,7 @@ export default function RegisterScreen() {
|
|
|
766
828
|
};
|
|
767
829
|
|
|
768
830
|
return (
|
|
769
|
-
<
|
|
831
|
+
<ScreenLayout>
|
|
770
832
|
<TextInput
|
|
771
833
|
placeholder="Email"
|
|
772
834
|
value={email}
|
|
@@ -803,13 +865,13 @@ export default function RegisterScreen() {
|
|
|
803
865
|
/>
|
|
804
866
|
{!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
|
|
805
867
|
<Button title="Register" onPress={() => void handleRegister()} />
|
|
806
|
-
</
|
|
868
|
+
</ScreenLayout>
|
|
807
869
|
);
|
|
808
870
|
}
|
|
809
871
|
`;
|
|
810
872
|
const registerContext = `import React, { useContext, useState } from "react";
|
|
811
873
|
import { Button, Text, TextInput } from "react-native";
|
|
812
|
-
import
|
|
874
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
813
875
|
import { registerApi } from "../../api";
|
|
814
876
|
import { AuthContext } from "../../context/AuthContext";
|
|
815
877
|
|
|
@@ -830,7 +892,7 @@ export default function RegisterScreen() {
|
|
|
830
892
|
};
|
|
831
893
|
|
|
832
894
|
return (
|
|
833
|
-
<
|
|
895
|
+
<ScreenLayout>
|
|
834
896
|
<TextInput
|
|
835
897
|
placeholder="Email"
|
|
836
898
|
value={email}
|
|
@@ -867,31 +929,31 @@ export default function RegisterScreen() {
|
|
|
867
929
|
/>
|
|
868
930
|
{!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
|
|
869
931
|
<Button title="Register" onPress={() => void handleRegister()} />
|
|
870
|
-
</
|
|
932
|
+
</ScreenLayout>
|
|
871
933
|
);
|
|
872
934
|
}
|
|
873
935
|
`;
|
|
874
936
|
const profile = `import React from "react";
|
|
875
937
|
import { Text } from "react-native";
|
|
876
|
-
import
|
|
938
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
877
939
|
|
|
878
940
|
export default function ProfileScreen() {
|
|
879
941
|
return (
|
|
880
|
-
<
|
|
942
|
+
<ScreenLayout>
|
|
881
943
|
<Text>ProfileScreen</Text>
|
|
882
|
-
</
|
|
944
|
+
</ScreenLayout>
|
|
883
945
|
);
|
|
884
946
|
}
|
|
885
947
|
`;
|
|
886
948
|
const settings = `import React from "react";
|
|
887
949
|
import { Text } from "react-native";
|
|
888
|
-
import
|
|
950
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
889
951
|
|
|
890
952
|
export default function SettingsScreen() {
|
|
891
953
|
return (
|
|
892
|
-
<
|
|
954
|
+
<ScreenLayout>
|
|
893
955
|
<Text>SettingsScreen</Text>
|
|
894
|
-
</
|
|
956
|
+
</ScreenLayout>
|
|
895
957
|
);
|
|
896
958
|
}
|
|
897
959
|
`;
|
|
@@ -899,7 +961,7 @@ export default function SettingsScreen() {
|
|
|
899
961
|
return {
|
|
900
962
|
login: `import React, { useState } from "react";
|
|
901
963
|
import { Button, Text, TextInput } from "react-native";
|
|
902
|
-
import
|
|
964
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
903
965
|
import { useDispatch } from "react-redux";
|
|
904
966
|
import { loginApi } from "../../api";
|
|
905
967
|
import { login } from "../../store/authSlice";
|
|
@@ -921,7 +983,7 @@ export default function LoginScreen() {
|
|
|
921
983
|
};
|
|
922
984
|
|
|
923
985
|
return (
|
|
924
|
-
<
|
|
986
|
+
<ScreenLayout>
|
|
925
987
|
<TextInput
|
|
926
988
|
placeholder="Email"
|
|
927
989
|
value={email}
|
|
@@ -958,14 +1020,14 @@ export default function LoginScreen() {
|
|
|
958
1020
|
/>
|
|
959
1021
|
{!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
|
|
960
1022
|
<Button title="Login" onPress={() => void handleLogin()} />
|
|
961
|
-
</
|
|
1023
|
+
</ScreenLayout>
|
|
962
1024
|
);
|
|
963
1025
|
}
|
|
964
1026
|
`,
|
|
965
1027
|
register: registerRedux,
|
|
966
1028
|
home: `import React from "react";
|
|
967
1029
|
import { Button, Text } from "react-native";
|
|
968
|
-
import
|
|
1030
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
969
1031
|
import { useDispatch } from "react-redux";
|
|
970
1032
|
import { logout } from "../../store/authSlice";
|
|
971
1033
|
|
|
@@ -973,10 +1035,10 @@ export default function HomeScreen() {
|
|
|
973
1035
|
const dispatch = useDispatch();
|
|
974
1036
|
|
|
975
1037
|
return (
|
|
976
|
-
<
|
|
1038
|
+
<ScreenLayout>
|
|
977
1039
|
<Text>Welcome Home!</Text>
|
|
978
1040
|
<Button title="Logout" onPress={() => dispatch(logout())} />
|
|
979
|
-
</
|
|
1041
|
+
</ScreenLayout>
|
|
980
1042
|
);
|
|
981
1043
|
}
|
|
982
1044
|
`,
|
|
@@ -988,7 +1050,7 @@ export default function HomeScreen() {
|
|
|
988
1050
|
return {
|
|
989
1051
|
login: `import React, { useState } from "react";
|
|
990
1052
|
import { Button, Text, TextInput } from "react-native";
|
|
991
|
-
import
|
|
1053
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
992
1054
|
import { loginApi } from "../../api";
|
|
993
1055
|
import { useAuthStore } from "../../store/authStore";
|
|
994
1056
|
|
|
@@ -1009,7 +1071,7 @@ export default function LoginScreen() {
|
|
|
1009
1071
|
};
|
|
1010
1072
|
|
|
1011
1073
|
return (
|
|
1012
|
-
<
|
|
1074
|
+
<ScreenLayout>
|
|
1013
1075
|
<TextInput
|
|
1014
1076
|
placeholder="Email"
|
|
1015
1077
|
value={email}
|
|
@@ -1046,24 +1108,24 @@ export default function LoginScreen() {
|
|
|
1046
1108
|
/>
|
|
1047
1109
|
{!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
|
|
1048
1110
|
<Button title="Login" onPress={() => void handleLogin()} />
|
|
1049
|
-
</
|
|
1111
|
+
</ScreenLayout>
|
|
1050
1112
|
);
|
|
1051
1113
|
}
|
|
1052
1114
|
`,
|
|
1053
1115
|
register: registerZustand,
|
|
1054
1116
|
home: `import React from "react";
|
|
1055
1117
|
import { Button, Text } from "react-native";
|
|
1056
|
-
import
|
|
1118
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
1057
1119
|
import { useAuthStore } from "../../store/authStore";
|
|
1058
1120
|
|
|
1059
1121
|
export default function HomeScreen() {
|
|
1060
1122
|
const logout = useAuthStore((state) => state.logout);
|
|
1061
1123
|
|
|
1062
1124
|
return (
|
|
1063
|
-
<
|
|
1125
|
+
<ScreenLayout>
|
|
1064
1126
|
<Text>Welcome Home!</Text>
|
|
1065
1127
|
<Button title="Logout" onPress={() => void logout()} />
|
|
1066
|
-
</
|
|
1128
|
+
</ScreenLayout>
|
|
1067
1129
|
);
|
|
1068
1130
|
}
|
|
1069
1131
|
`,
|
|
@@ -1074,7 +1136,7 @@ export default function HomeScreen() {
|
|
|
1074
1136
|
return {
|
|
1075
1137
|
login: `import React, { useContext, useState } from "react";
|
|
1076
1138
|
import { Button, Text, TextInput } from "react-native";
|
|
1077
|
-
import
|
|
1139
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
1078
1140
|
import { loginApi } from "../../api";
|
|
1079
1141
|
import { AuthContext } from "../../context/AuthContext";
|
|
1080
1142
|
|
|
@@ -1095,7 +1157,7 @@ export default function LoginScreen() {
|
|
|
1095
1157
|
};
|
|
1096
1158
|
|
|
1097
1159
|
return (
|
|
1098
|
-
<
|
|
1160
|
+
<ScreenLayout>
|
|
1099
1161
|
<TextInput
|
|
1100
1162
|
placeholder="Email"
|
|
1101
1163
|
value={email}
|
|
@@ -1132,24 +1194,24 @@ export default function LoginScreen() {
|
|
|
1132
1194
|
/>
|
|
1133
1195
|
{!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
|
|
1134
1196
|
<Button title="Login" onPress={() => void handleLogin()} />
|
|
1135
|
-
</
|
|
1197
|
+
</ScreenLayout>
|
|
1136
1198
|
);
|
|
1137
1199
|
}
|
|
1138
1200
|
`,
|
|
1139
1201
|
register: registerContext,
|
|
1140
1202
|
home: `import React, { useContext } from "react";
|
|
1141
1203
|
import { Button, Text } from "react-native";
|
|
1142
|
-
import
|
|
1204
|
+
import ScreenLayout from "../../components/layout/ScreenLayout";
|
|
1143
1205
|
import { AuthContext } from "../../context/AuthContext";
|
|
1144
1206
|
|
|
1145
1207
|
export default function HomeScreen() {
|
|
1146
1208
|
const { logout } = useContext(AuthContext);
|
|
1147
1209
|
|
|
1148
1210
|
return (
|
|
1149
|
-
<
|
|
1211
|
+
<ScreenLayout>
|
|
1150
1212
|
<Text>Welcome Home!</Text>
|
|
1151
1213
|
<Button title="Logout" onPress={() => void logout()} />
|
|
1152
|
-
</
|
|
1214
|
+
</ScreenLayout>
|
|
1153
1215
|
);
|
|
1154
1216
|
}
|
|
1155
1217
|
`,
|
|
@@ -1,44 +1,182 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import React, { useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
FlatList,
|
|
4
|
+
Image,
|
|
5
|
+
ImageSourcePropType,
|
|
6
|
+
Pressable,
|
|
7
|
+
StatusBar,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
Text,
|
|
10
|
+
useWindowDimensions,
|
|
11
|
+
View,
|
|
12
|
+
ViewToken,
|
|
13
|
+
} from "react-native";
|
|
10
14
|
import {
|
|
11
15
|
SafeAreaProvider,
|
|
12
|
-
|
|
13
|
-
} from
|
|
16
|
+
SafeAreaView,
|
|
17
|
+
} from "react-native-safe-area-context";
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
type Slide = {
|
|
20
|
+
id: string;
|
|
21
|
+
title: string;
|
|
22
|
+
description: string;
|
|
23
|
+
image: ImageSourcePropType;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const slides: Slide[] = [
|
|
27
|
+
{
|
|
28
|
+
id: "1",
|
|
29
|
+
title: "Welcome to RN Starter Kit",
|
|
30
|
+
description: "Start with a clean React Native CLI project structure.",
|
|
31
|
+
image: require("./assets/images/react-logo.png"),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "2",
|
|
35
|
+
title: "Ready for Scale",
|
|
36
|
+
description: "Build faster with sensible architecture you can extend.",
|
|
37
|
+
image: require("./assets/images/partial-react-logo.png"),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "3",
|
|
41
|
+
title: "Make It Yours",
|
|
42
|
+
description: "Use this onboarding as a placeholder for your product design.",
|
|
43
|
+
image: require("./assets/images/icon.png"),
|
|
44
|
+
},
|
|
45
|
+
];
|
|
17
46
|
|
|
47
|
+
function App() {
|
|
18
48
|
return (
|
|
19
49
|
<SafeAreaProvider>
|
|
20
|
-
<StatusBar barStyle=
|
|
21
|
-
<
|
|
50
|
+
<StatusBar barStyle="dark-content" />
|
|
51
|
+
<WelcomeScreen />
|
|
22
52
|
</SafeAreaProvider>
|
|
23
53
|
);
|
|
24
54
|
}
|
|
25
55
|
|
|
26
|
-
function
|
|
27
|
-
const
|
|
56
|
+
function WelcomeScreen() {
|
|
57
|
+
const { width } = useWindowDimensions();
|
|
58
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
59
|
+
const listRef = useRef<FlatList<Slide>>(null);
|
|
60
|
+
|
|
61
|
+
const onViewableItemsChanged = useRef(
|
|
62
|
+
({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
|
|
63
|
+
if (viewableItems[0]?.index != null) {
|
|
64
|
+
setActiveIndex(viewableItems[0].index);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
).current;
|
|
68
|
+
|
|
69
|
+
const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 }).current;
|
|
70
|
+
|
|
71
|
+
const goNext = () => {
|
|
72
|
+
const nextIndex = activeIndex + 1;
|
|
73
|
+
if (nextIndex < slides.length) {
|
|
74
|
+
listRef.current?.scrollToIndex({ index: nextIndex, animated: true });
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const isLastSlide = activeIndex === slides.length - 1;
|
|
28
79
|
|
|
29
80
|
return (
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
81
|
+
<SafeAreaView style={styles.safeArea}>
|
|
82
|
+
<FlatList
|
|
83
|
+
ref={listRef}
|
|
84
|
+
data={slides}
|
|
85
|
+
horizontal
|
|
86
|
+
pagingEnabled
|
|
87
|
+
bounces={false}
|
|
88
|
+
keyExtractor={(item) => item.id}
|
|
89
|
+
showsHorizontalScrollIndicator={false}
|
|
90
|
+
renderItem={({ item }) => (
|
|
91
|
+
<View style={[styles.slide, { width }]}>
|
|
92
|
+
<Image source={item.image} style={styles.image} resizeMode="contain" />
|
|
93
|
+
<Text style={styles.title}>{item.title}</Text>
|
|
94
|
+
<Text style={styles.description}>{item.description}</Text>
|
|
95
|
+
</View>
|
|
96
|
+
)}
|
|
97
|
+
onViewableItemsChanged={onViewableItemsChanged}
|
|
98
|
+
viewabilityConfig={viewabilityConfig}
|
|
34
99
|
/>
|
|
35
|
-
|
|
100
|
+
|
|
101
|
+
<View style={styles.footer}>
|
|
102
|
+
<View style={styles.dotsRow}>
|
|
103
|
+
{slides.map((slide, index) => (
|
|
104
|
+
<View
|
|
105
|
+
key={slide.id}
|
|
106
|
+
style={[styles.dot, index === activeIndex && styles.dotActive]}
|
|
107
|
+
/>
|
|
108
|
+
))}
|
|
109
|
+
</View>
|
|
110
|
+
|
|
111
|
+
<Pressable style={styles.button} onPress={goNext}>
|
|
112
|
+
<Text style={styles.buttonLabel}>{isLastSlide ? "Get Started" : "Next"}</Text>
|
|
113
|
+
</Pressable>
|
|
114
|
+
</View>
|
|
115
|
+
</SafeAreaView>
|
|
36
116
|
);
|
|
37
117
|
}
|
|
38
118
|
|
|
39
119
|
const styles = StyleSheet.create({
|
|
40
|
-
|
|
120
|
+
safeArea: {
|
|
121
|
+
flex: 1,
|
|
122
|
+
backgroundColor: "#F7F7F9",
|
|
123
|
+
},
|
|
124
|
+
slide: {
|
|
41
125
|
flex: 1,
|
|
126
|
+
paddingHorizontal: 24,
|
|
127
|
+
justifyContent: "center",
|
|
128
|
+
alignItems: "center",
|
|
129
|
+
},
|
|
130
|
+
image: {
|
|
131
|
+
width: 220,
|
|
132
|
+
height: 220,
|
|
133
|
+
marginBottom: 28,
|
|
134
|
+
},
|
|
135
|
+
title: {
|
|
136
|
+
fontSize: 28,
|
|
137
|
+
fontWeight: "700",
|
|
138
|
+
color: "#111827",
|
|
139
|
+
textAlign: "center",
|
|
140
|
+
marginBottom: 12,
|
|
141
|
+
},
|
|
142
|
+
description: {
|
|
143
|
+
fontSize: 16,
|
|
144
|
+
lineHeight: 24,
|
|
145
|
+
color: "#4B5563",
|
|
146
|
+
textAlign: "center",
|
|
147
|
+
maxWidth: 320,
|
|
148
|
+
},
|
|
149
|
+
footer: {
|
|
150
|
+
paddingHorizontal: 24,
|
|
151
|
+
paddingBottom: 28,
|
|
152
|
+
gap: 20,
|
|
153
|
+
},
|
|
154
|
+
dotsRow: {
|
|
155
|
+
flexDirection: "row",
|
|
156
|
+
justifyContent: "center",
|
|
157
|
+
gap: 8,
|
|
158
|
+
},
|
|
159
|
+
dot: {
|
|
160
|
+
width: 8,
|
|
161
|
+
height: 8,
|
|
162
|
+
borderRadius: 999,
|
|
163
|
+
backgroundColor: "#D1D5DB",
|
|
164
|
+
},
|
|
165
|
+
dotActive: {
|
|
166
|
+
width: 24,
|
|
167
|
+
backgroundColor: "#111827",
|
|
168
|
+
},
|
|
169
|
+
button: {
|
|
170
|
+
height: 52,
|
|
171
|
+
borderRadius: 12,
|
|
172
|
+
backgroundColor: "#111827",
|
|
173
|
+
alignItems: "center",
|
|
174
|
+
justifyContent: "center",
|
|
175
|
+
},
|
|
176
|
+
buttonLabel: {
|
|
177
|
+
color: "#FFFFFF",
|
|
178
|
+
fontSize: 16,
|
|
179
|
+
fontWeight: "600",
|
|
42
180
|
},
|
|
43
181
|
});
|
|
44
182
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|