diff --git a/app/settings.tsx b/app/settings.tsx
index 9057531..2951769 100644
--- a/app/settings.tsx
+++ b/app/settings.tsx
@@ -6,6 +6,7 @@ import { ModeToggle } from "@/components/ui/mode-toggle";
import { Separator } from "@/components/ui/separator";
import { Text } from "@/components/ui/text";
import { View } from "@/components/ui/view";
+import { ContributorsList } from "@/components/ContributorsList";
import {
type ExportFormat,
exportAllChats,
@@ -811,7 +812,21 @@ export default function Settings() {
Open Source Credits
-
+ {/* Contributors Section */}
+
+
+ CONTRIBUTORS
+
+
+
{/* Footer */}
diff --git a/assets/contributors.json b/assets/contributors.json
new file mode 100644
index 0000000..4de5e4d
--- /dev/null
+++ b/assets/contributors.json
@@ -0,0 +1,34 @@
+[
+ {
+ "id": 30320791,
+ "login": "mrspence",
+ "avatar_url": "https://avatars.githubusercontent.com/u/30320791?v=4",
+ "html_url": "https://github.com/mrspence"
+ },
+ {
+ "id": 89926355,
+ "login": "will-lamerton",
+ "avatar_url": "https://avatars.githubusercontent.com/u/89926355?v=4",
+ "html_url": "https://github.com/will-lamerton"
+ },
+ {
+ "id": 49699333,
+ "login": "dependabot[bot]",
+ "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4",
+ "html_url": "https://github.com/apps/dependabot"
+ },
+ {
+ "id": "manual-1",
+ "login": "marketing-guru",
+ "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4",
+ "html_url": "https://github.com/marketing-guru",
+ "type": "Marketing"
+ },
+ {
+ "id": "ronak-1",
+ "login": "RONAK-AI647",
+ "avatar_url": "https://avatars.githubusercontent.com/RONAK-AI647",
+ "html_url": "https://github.com/RONAK-AI647",
+ "type": "Developer"
+ }
+]
\ No newline at end of file
diff --git a/components/ContributorsList.tsx b/components/ContributorsList.tsx
new file mode 100644
index 0000000..ff7dbb8
--- /dev/null
+++ b/components/ContributorsList.tsx
@@ -0,0 +1,117 @@
+import type React from "react";
+import {
+ FlatList,
+ Image,
+ Linking,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from "react-native";
+
+import contributorsData from "../assets/contributors.json";
+
+interface ContributorsListProps {
+ variant: "onboarding" | "settings";
+}
+
+export const ContributorsList: React.FC = ({ variant }) => {
+ const openProfile = (url: string) => {
+ if (url) {
+ // biome-ignore lint/suspicious/noConsole: Needed for openURL catch logging
+ Linking.openURL(url).catch((err) => console.error("Couldn't load page", err));
+ }
+ };
+
+ if (variant === "onboarding") {
+ return (
+ item.login}
+ numColumns={4}
+ contentContainerStyle={styles.gridContainer}
+ renderItem={({ item }) => (
+ openProfile(item.html_url)}
+ >
+
+
+ )}
+ />
+ );
+ }
+
+ return (
+ item.login}
+ contentContainerStyle={styles.listContainer}
+ renderItem={({ item }) => (
+ openProfile(item.html_url)}
+ >
+
+ @{item.login}
+ {item.contributions && (
+
+ {item.contributions}
+
+ )}
+
+ )}
+ />
+ );
+};
+
+const styles = StyleSheet.create({
+ gridContainer: {
+ padding: 16,
+ marginTop: 40,
+ alignItems: "center",
+ },
+ avatarWrapper: {
+ margin: 8,
+ },
+ gridAvatar: {
+ width: 60,
+ height: 60,
+ borderRadius: 30,
+ backgroundColor: "#333",
+ },
+ listContainer: {
+ paddingVertical: 8,
+ },
+ rowContainer: {
+ flexDirection: "row",
+ alignItems: "center",
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ borderBottomWidth: 0.5,
+ borderBottomColor: "#333",
+ },
+ listAvatar: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ backgroundColor: "#333",
+ marginRight: 16,
+ },
+ username: {
+ flex: 1,
+ fontSize: 16,
+ fontWeight: "500",
+ color: "#fff",
+ },
+ badge: {
+ backgroundColor: "#222",
+ paddingHorizontal: 8,
+ paddingVertical: 3,
+ borderRadius: 10,
+ },
+ badgeText: {
+ fontSize: 12,
+ color: "#888",
+ },
+});
\ No newline at end of file
diff --git a/components/flows/onboarding-steps.tsx b/components/flows/onboarding-steps.tsx
index dd23461..9a23630 100644
--- a/components/flows/onboarding-steps.tsx
+++ b/components/flows/onboarding-steps.tsx
@@ -4,6 +4,7 @@ import { Colors } from "@/theme/colors";
import { Bird, ShieldCheck } from "lucide-react-native";
import { Logo } from "../logo";
import { LocalAuthStepContent } from "./local-auth-step";
+import { ContributorsList } from "@/components/ContributorsList";
export const onboardingSteps: OnboardingStep[] = [
{
@@ -45,4 +46,14 @@ export const onboardingSteps: OnboardingStep[] = [
),
customContent: ,
},
+ {
+ id: "4",
+ title: "Meet the Contributors",
+ description:
+ "Whisper is built by a community of privacy advocates and open-source enthusiasts.",
+ icon: null,
+ customContent: ,
+ },
+
+
];
diff --git a/manual-contributors.json b/manual-contributors.json
new file mode 100644
index 0000000..ca343b2
--- /dev/null
+++ b/manual-contributors.json
@@ -0,0 +1,17 @@
+[
+ {
+ "id": "manual-1",
+ "login": "marketing-guru",
+ "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4",
+ "html_url": "https://github.com/marketing-guru",
+ "type": "Marketing"
+ },
+ {
+ "id": "ronak-1",
+ "login": "RONAK-AI647",
+ "avatar_url": "https://avatars.githubusercontent.com/RONAK-AI647",
+ "html_url": "https://github.com/RONAK-AI647",
+ "type": "Developer"
+ }
+
+]
\ No newline at end of file
diff --git a/package.json b/package.json
index a076791..a0bbb6b 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
+ "generate-contributors": "node scripts/generate-contributors.js",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
diff --git a/scripts/generate-contributors.js b/scripts/generate-contributors.js
new file mode 100644
index 0000000..0c943b7
--- /dev/null
+++ b/scripts/generate-contributors.js
@@ -0,0 +1,66 @@
+// scripts/generate-contributors.js
+const fs = require('node:fs');
+const path = require('node:path');
+
+const GITHUB_API_URL = 'https://api.github.com/repos/Whisper-AI-App/app/contributors';
+const MANUAL_FILE_PATH = path.join(__dirname, '../manual-contributors.json');
+const OUTPUT_FILE_PATH = path.join(__dirname, '../assets/contributors.json');
+
+async function generateContributors() {
+ try {
+ console.log('Fetching GitHub contributors...');
+
+ // 1. Fetch code contributors (Requires Node 18+ for native fetch)
+ const response = await fetch(GITHUB_API_URL, {
+ headers: {
+ 'User-Agent': 'Whisper-AI-App-Build-Script'
+ }
+ });
+
+ if (!response.ok) throw new Error(`GitHub API responded with ${response.status}`);
+ const codeContributors = await response.json();
+
+ // 2. Read manual contributors
+ let manualContributors = [];
+ if (fs.existsSync(MANUAL_FILE_PATH)) {
+ const manualData = fs.readFileSync(MANUAL_FILE_PATH, 'utf-8');
+ manualContributors = JSON.parse(manualData);
+ }
+
+ // 3. GitHub contributors first (by commit count), manual appended after
+ const uniqueMap = new Map();
+
+ codeContributors.forEach(contributor => {
+ uniqueMap.set(contributor.login, {
+ id: contributor.id,
+ login: contributor.login,
+ avatar_url: contributor.avatar_url,
+ html_url: contributor.html_url,
+ });
+ });
+
+ manualContributors.forEach(contributor => {
+ if (!uniqueMap.has(contributor.login)) {
+ uniqueMap.set(contributor.login, {
+ id: contributor.id,
+ login: contributor.login,
+ avatar_url: contributor.avatar_url,
+ html_url: contributor.html_url,
+ type: contributor.type,
+ });
+ }
+ });
+
+ const finalContributorsList = Array.from(uniqueMap.values());
+
+ // 4. Save to assets folder
+ fs.writeFileSync(OUTPUT_FILE_PATH, JSON.stringify(finalContributorsList, null, 2));
+ console.log(`Successfully generated ${finalContributorsList.length} contributors to assets/contributors.json`);
+
+ } catch (error) {
+ console.error('Failed to generate contributors:', error.message);
+ process.exit(1);
+ }
+}
+
+generateContributors();
\ No newline at end of file