diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 217a907..744f73a 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -1,13 +1,48 @@
-import { Stack } from 'expo-router';
+import { Tabs } from 'expo-router';
+import { TouchableOpacity } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { useRouter } from 'expo-router';
+import { useTheme } from '../themeContext';
import React from 'react';
-export default function AppLayout() {
+export default function TabLayout() {
+ const router = useRouter();
+ const { isDarkMode } = useTheme();
+
return (
-
-
-
+ tabBarActiveTintColor: '#6B21A8',
+ tabBarInactiveTintColor: isDarkMode ? '#9CA3AF' : '#6B7280',
+ tabBarStyle: {
+ backgroundColor: isDarkMode ? '#1F2937' : '#FFFFFF',
+ borderTopColor: isDarkMode ? '#374151' : '#E5E7EB',
+ },
+ headerStyle: {
+ backgroundColor: isDarkMode ? '#121212' : '#FFFFFF',
+ },
+ headerTintColor: isDarkMode ? '#F3F4F6' : '#1F2937',
+ headerRight: () => (
+ router.push('/settings')}
+ style={{ marginRight: 15 }}
+ >
+
+
+ ),
+ }}
+ >
+ ,
+ }}
+ />
+
);
}
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 2508ba5..fff77d0 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -12,6 +12,8 @@ import * as SecureStore from 'expo-secure-store';
import Constants from 'expo-constants';
import { useUser } from "../userContext";
import { AppState } from "react-native";
+import { useTheme } from "../themeContext";
+import ThemeToggle from "../../components/ThemeToggle";
const gov_logo = require('@/assets/images/gov_logo.png');
const billion_readers = require('@/assets/images/billion_readers.png');
@@ -55,6 +57,7 @@ const VideoList = () => {
const router = useRouter();
const db = useSQLiteContext();
const { role, setRole } = useUser();
+ const { isDarkMode } = useTheme();
const [open, setOpen] = useState(false);
const [language, setLanguage] = useState("en");
const [loading, setLoading] = useState(true);
@@ -186,8 +189,7 @@ const VideoList = () => {
};
return (
-
-
+
@@ -200,11 +202,34 @@ const VideoList = () => {
if (levelOpen) setLevelOpen(false);
}}
setValue={handleLanguageChange}
- containerStyle={{ maxWidth: 100, paddingVertical: 0, paddingHorizontal: 0, flex: 1.6 }}
- style={{ height: 40, minHeight: 30 }}
- textStyle={{ fontSize: 11 }}
- arrowIconStyle={{ marginHorizontal: -5 }}
+ style={{
+ borderColor: isDarkMode ? '#374151' : '#E5E7EB',
+ backgroundColor: isDarkMode ? '#1F2937' : '#FFFFFF',
+ width: 112,
+ height: 40,
+ zIndex: 20
+ }}
+ textStyle={{
+ color: isDarkMode ? '#F3F4F6' : '#1F2937',
+ }}
+ dropDownContainerStyle={{
+ borderColor: isDarkMode ? '#374151' : '#E5E7EB',
+ backgroundColor: isDarkMode ? '#1F2937' : '#FFFFFF',
+ }}
+ theme={isDarkMode ? "DARK" : "LIGHT"}
/>
+
+
+
+
+
+
+
+
+ Watch and Learn
+
+
{
if (open) setOpen(false);
}}
setValue={handleLevelChange}
- containerStyle={{ maxWidth: 100, paddingVertical: 0, flex: 2, paddingHorizontal: 0 }}
- style={{ height: 40, minHeight: 30 }}
- textStyle={{ fontSize: 11 }}
- arrowIconStyle={{ marginHorizontal: -5 }}
+ style={{
+ borderColor: isDarkMode ? '#374151' : '#E5E7EB',
+ backgroundColor: isDarkMode ? '#1F2937' : '#FFFFFF',
+ width: 112,
+ height: 40,
+ zIndex: 10
+ }}
+ textStyle={{
+ color: isDarkMode ? '#F3F4F6' : '#1F2937',
+ }}
+ dropDownContainerStyle={{
+ borderColor: isDarkMode ? '#374151' : '#E5E7EB',
+ backgroundColor: isDarkMode ? '#1F2937' : '#FFFFFF',
+ }}
+ theme={isDarkMode ? "DARK" : "LIGHT"}
/>
- router.push(`/login`)} delayLongPress={5000}>
-
-
- {
- loading ? (
- item.toString()}
- renderItem={() => (
-
-
-
-
-
-
-
- )}
- />
- ) : (
+
level === "all" || item.level === level)}
+ data={level === "all" ? videoDetails : videoDetails.filter(item => item.level === level)}
keyExtractor={(item) => item.id}
- renderItem={({ item }) => (
-
- handleVideoPress(item)}
- >
-
-
-
- {/* Video Details along with pdf and translation option */}
-
-
- {videoLanguages[item.id] === "en" ? item.english_title : item.punjabi_title}
-
-
- toggleVideoLanguage(item.id)}
- className="bg-white p-2.5 rounded-full">
-
-
- handlePdfPress(item)}
- className="bg-white p-2.5 rounded-full">
-
-
+ numColumns={2}
+ showsVerticalScrollIndicator={false}
+ columnWrapperStyle={{ justifyContent: "space-between", marginBottom: 20 }}
+ renderItem={({ item }) => {
+ const isEnglish = videoLanguages[item.id] === "en";
+ return (
+
+
+ handleVideoPress(item)}>
+
+
+
+
+
+ {isEnglish ? item.english_title : item.punjabi_title}
+
+
+
+ toggleVideoLanguage(item.id)}
+ className="bg-purple-600 p-2 rounded-md"
+ >
+
+
+
+ handlePdfPress(item)}
+ className="bg-purple-600 p-2 rounded-md"
+ >
+
+
+
-
- )}
+ );
+ }}
/>
- )}
);
};
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 822f672..873df8e 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -11,14 +11,39 @@ import * as SplashScreen from 'expo-splash-screen';
import { SQLiteProvider } from 'expo-sqlite';
import { initializeDatabase } from './database/database';
import { UserProvider } from './userContext';
-import { downloadVideo,clearDownloadedVideos } from "./video/videoDownlaoder";
+import { ThemeProvider, useTheme } from './themeContext';
+import { downloadVideo, clearDownloadedVideos } from "./video/videoDownlaoder";
import { ProgressBar } from 'react-native-paper';
+import React from 'react';
// Prevent auto-hide at the start
-
SplashScreen.preventAutoHideAsync();
+// Main Layout component that applies theme
+const ThemedLayout = () => {
+ const { isDarkMode } = useTheme();
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
export default function RootLayout() {
const VIDEO_LIST = [
{ id: '1_en', url: 'https://storage.googleapis.com/bird-planet-read/Videos/English/A_Cloud_of_Trash_English.mp4' },
@@ -94,20 +119,20 @@ export default function RootLayout() {
preloadAssets();
}, []);
- //download the videos on the first installation in the next installation check if they exists or not
- useEffect(() => {
- (async () => {
- // Download all videos
- // await clearDownloadedVideos(); // only for testing purposes don't use it in production
- let completed = 0;
- for (const video of VIDEO_LIST) {
- await downloadVideo(video.id, video.url);
- completed++;
- setDownloadProgress(completed);
- }
- setVideoAssetsLoaded(true);
- })();
- }, []);
+ //download the videos on the first installation in the next installation check if they exists or not
+ useEffect(() => {
+ (async () => {
+ // Download all videos
+ // await clearDownloadedVideos(); // only for testing purposes don't use it in production
+ let completed = 0;
+ for (const video of VIDEO_LIST) {
+ await downloadVideo(video.id, video.url);
+ completed++;
+ setDownloadProgress(completed);
+ }
+ setVideoAssetsLoaded(true);
+ })();
+ }, []);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
@@ -116,16 +141,16 @@ export default function RootLayout() {
return (
- <>
- { isLoading && (
-
-
-
-
- )}
-
- { !isLoading && !videoAssetsLoaded && (
- //show a popup of number of videos downloading and stuff.... a progress bar modal
+
+ <>
+ { isLoading && (
+
+
+
+
+ )}
+
+ { !isLoading && !videoAssetsLoaded && (
@@ -133,38 +158,29 @@ export default function RootLayout() {
Downloading Videos...
- {/* Show number of videos downloaded out of total */}
{downloadProgress} videos downloaded out of {VIDEO_LIST.length}
- {/* Progress Bar */}
- {/* Optional: Estimated time or animated loading */}
Please wait...
- )}
-
- { !isLoading && videoAssetsLoaded && (
-
-
-
-
-
-
-
-
-
-
-
-
- )}
- >
- )
-
+ )}
+
+ { !isLoading && videoAssetsLoaded && (
+
+
+
+
+
+
+ )}
+ >
+
+ );
}
\ No newline at end of file
diff --git a/app/dashboard/_layout.tsx b/app/dashboard/_layout.tsx
index ad6c8fc..4c27b2e 100644
--- a/app/dashboard/_layout.tsx
+++ b/app/dashboard/_layout.tsx
@@ -1,8 +1,22 @@
import { Stack } from "expo-router";
+import { useTheme } from "../themeContext";
+import React from "react";
export default function Layout() {
+ const { isDarkMode } = useTheme();
+
return (
-
+
);
diff --git a/app/dashboard/index.tsx b/app/dashboard/index.tsx
index 1639aef..ee67d52 100644
--- a/app/dashboard/index.tsx
+++ b/app/dashboard/index.tsx
@@ -24,6 +24,7 @@ import DateTimePicker from '@react-native-community/datetimepicker';
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { Modal } from "react-native";
import { create } from "react-test-renderer";
+import { useTheme } from "../themeContext";
// Define type for analytics data
@@ -46,6 +47,7 @@ type AnalyticsData = {
const AnalyticsDashboard = () => {
const db = useSQLiteContext();
+ const { isDarkMode } = useTheme();
const [analyticsData, setAnalyticsData] = useState([]);
const [userId, setUserId] = useState(null);
@@ -324,10 +326,10 @@ const AnalyticsDashboard = () => {
return (
-
+
Analytics
@@ -339,10 +341,10 @@ const AnalyticsDashboard = () => {
cover={0.8}
/>
-
+
{totalViews}
- Views
+ Views
@@ -353,60 +355,55 @@ const AnalyticsDashboard = () => {
cover={0.8}
/>
-
+
{totalTime}
- Watch Time
+ Watch Time
setEditModalVisible(true)}
->
- Edit Username : {username}
-
-
-
-
- EXPORT
-
+ className={`${isDarkMode ? 'bg-primary-dark' : 'bg-purple-700'} p-2 rounded mt-4`}
+ onPress={() => setEditModalVisible(true)}
+ >
+ Edit Username : {username}
+
-
- {/* SyncToCloud component taking half width */}
-
-
-
-
- {/* Delete button taking half width */}
-
- DELETE USER DATA
+ EXPORT
-
-
+
+ {/* SyncToCloud component taking half width */}
+
+
+
+
+ {/* Delete button taking half width */}
+
+
+ DELETE USER DATA
+
+
+
{/* Level, Language and Date Dropdowns */}
- Filter
-
-
+ Filter
+
{/* Level Dropdown */}
@@ -420,22 +417,26 @@ const AnalyticsDashboard = () => {
placeholder="Select Level"
style={{
borderWidth: 1,
- borderColor: "#d5d5d9",
- backgroundColor: "#ECE6F0",
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
+ backgroundColor: isDarkMode ? "#1F2937" : "#ECE6F0",
borderRadius: 0,
paddingHorizontal: 5,
minHeight: 35,
}}
+ textStyle={{
+ color: isDarkMode ? "#F3F4F6" : "#000000",
+ }}
dropDownContainerStyle={{
- backgroundColor: "#ECE6F0",
- borderColor: "#d5d5d9",
+ backgroundColor: isDarkMode ? "#1F2937" : "#ECE6F0",
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
zIndex: 1000,
borderRadius: 0,
}}
+ theme={isDarkMode ? "DARK" : "LIGHT"}
/>
- {/* Language Dropdown */}
- {
placeholder="Select Lang"
style={{
borderWidth: 1,
- borderColor: "#d5d5d9",
- backgroundColor: "#ECE6F0",
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
+ backgroundColor: isDarkMode ? "#1F2937" : "#ECE6F0",
borderRadius: 0,
paddingHorizontal: 5,
minHeight: 35,
}}
+ textStyle={{
+ color: isDarkMode ? "#F3F4F6" : "#000000",
+ }}
dropDownContainerStyle={{
- backgroundColor: "#ECE6F0",
- borderColor: "#d5d5d9",
+ backgroundColor: isDarkMode ? "#1F2937" : "#ECE6F0",
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
zIndex: 1000,
borderRadius: 0,
}}
+ theme={isDarkMode ? "DARK" : "LIGHT"}
/>
-
- {/* Date Range Filter Section */}
-
- {/* Start Date Picker */}
- setShowStartDatePicker(true)}
- style={{
- borderWidth: 1,
- borderColor: "#d5d5d9",
- backgroundColor: "#ECE6F0",
- padding: 8,
- flexDirection: 'row',
- alignItems: 'center',
- flex: 1,
- }}
- >
-
- {startDate ? formatDate(startDate) : "Start Date"}
-
-
- {/* End Date Picker */}
+ {/* Date Range Filter Section */}
+
+ {/* Start Date Picker */}
+ setShowStartDatePicker(true)}
+ style={{
+ borderWidth: 1,
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
+ backgroundColor: isDarkMode ? "#1F2937" : "#ECE6F0",
+ padding: 8,
+ flexDirection: 'row',
+ alignItems: 'center',
+ flex: 1,
+ }}
+ >
+
+
+ {startDate ? formatDate(startDate) : "Start Date"}
+
+
+
+ {/* End Date Picker */}
+ setShowEndDatePicker(true)}
+ style={{
+ borderWidth: 1,
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
+ backgroundColor: isDarkMode ? "#1F2937" : "#ECE6F0",
+ padding: 8,
+ flexDirection: 'row',
+ alignItems: 'center',
+ flex: 1,
+ }}
+ >
+
+
+ {endDate ? formatDate(endDate) : "End Date"}
+
+
+
+ {/* Reset Button */}
+ {(startDate || endDate) && (
setShowEndDatePicker(true)}
+ onPress={resetDateFilters}
style={{
borderWidth: 1,
- borderColor: "#d5d5d9",
- backgroundColor: "#ECE6F0",
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
+ backgroundColor: isDarkMode ? "#4C1D95" : "#7e22ce",
padding: 8,
- flexDirection: 'row',
alignItems: 'center',
- flex: 1,
+ justifyContent: 'center',
}}
>
-
- {endDate ? formatDate(endDate) : "End Date"}
+
-
- {/* Reset Button */}
- {(startDate || endDate) && (
-
-
-
- )}
-
-
-
- {/* Date Pickers (hidden by default) */}
- {showStartDatePicker && (
-
- )}
-
- {showEndDatePicker && (
-
- )}
-
+ )}
+
+
{searchQuery.length > 0 && (
setSearchQuery("")}>
-
+
)}
-
+
- Videos
+ Videos
-
+
Sort By
{
alignSelf: "center",
marginBottom: 0,
}}
- textStyle={{ fontSize: 12 }}
+ textStyle={{
+ fontSize: 12,
+ color: isDarkMode ? "#F3F4F6" : "#000000",
+ }}
arrowIconStyle={{ marginHorizontal: -5 }}
modalAnimationType="slide"
placeholder={"Select"}
style={{
borderWidth: 1,
- borderColor: "#d5d5d9",
- backgroundColor: "#ECE6F0",
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
+ backgroundColor: isDarkMode ? "#1F2937" : "#ECE6F0",
borderRadius: 0,
paddingHorizontal: 5,
minHeight: 35,
zIndex: 100,
}}
dropDownContainerStyle={{
- backgroundColor: "#ECE6F0",
+ backgroundColor: isDarkMode ? "#1F2937" : "#ECE6F0",
borderWidth: 1,
- borderColor: "#d5d5d9",
+ borderColor: isDarkMode ? "#374151" : "#d5d5d9",
borderRadius: 0,
gap: 10,
}}
+ theme={isDarkMode ? "DARK" : "LIGHT"}
/>
@@ -609,8 +602,8 @@ const AnalyticsDashboard = () => {
data={filteredData}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item }) => (
-
-
+
+
{
{/* Video Details */}
-
+
{item.language === "en"
? item.english_title
: item.punjabi_title}
-
+
Level: {item.level}
-
+
Watch Time: {item.total_time_day} s
-
+
Total Views: {item.total_views_day}
-
+
Watched in:{" "}
{item.language === "en" ? "English" : "Punjabi"}
-
+
Last Watched: {item.date}
@@ -658,58 +651,59 @@ const AnalyticsDashboard = () => {
setEditModalVisible(false)}
->
-
-
- <>
- Edit Username from {username} to:
-
-
- warning:
-
- setEditModalVisible(false)}
- >
- Cancel
-
- {
- if (newUsername.trim()) {
- setUsername(newUsername);
- deleteData();
- editUserName(db, userId!, newUsername)
- .then(() => {
- setEditSuccess(true);
-
- setTimeout(() => {
- setEditModalVisible(false);
- setEditSuccess(false);
- }, 1500);
- });
- setNewUsername("");
- }
- }}
- >
- Save
-
+ animationType="slide"
+ transparent={true}
+ visible={editModalVisible}
+ onRequestClose={() => setEditModalVisible(false)}
+ >
+
+
+ <>
+
+ Edit Username from {username} to:
+
+
+
+ warning:
+
+ setEditModalVisible(false)}
+ >
+ Cancel
+
+ {
+ if (newUsername.trim()) {
+ setUsername(newUsername);
+ deleteData();
+ editUserName(db, userId!, newUsername)
+ .then(() => {
+ setEditSuccess(true);
+
+ setTimeout(() => {
+ setEditModalVisible(false);
+ setEditSuccess(false);
+ }, 1500);
+ });
+ setNewUsername("");
+ }
+ }}
+ >
+ Save
+
+
+ >
- >
-
-
-
-
-
+
+
);
};
diff --git a/app/login/_layout.tsx b/app/login/_layout.tsx
index ad6c8fc..4c65739 100644
--- a/app/login/_layout.tsx
+++ b/app/login/_layout.tsx
@@ -1,8 +1,22 @@
import { Stack } from "expo-router";
+import { useTheme } from "../themeContext";
+import React from "react";
-export default function Layout() {
+export default function Layout() {
+ const { isDarkMode } = useTheme();
+
return (
-
+
);
diff --git a/app/login/index.tsx b/app/login/index.tsx
index 7da00f6..100fb19 100644
--- a/app/login/index.tsx
+++ b/app/login/index.tsx
@@ -5,13 +5,13 @@ import { Image } from 'react-native';
import { useState } from 'react';
import { useRouter } from 'expo-router';
import { Alert } from 'react-native';
+import { useTheme } from '../themeContext';
const index = () => {
const id = process.env.EXPO_PUBLIC_ADMIN_ID
const pass = process.env.EXPO_PUBLIC_ADMIN_PASSWORD
+ const { isDarkMode } = useTheme();
- console.log("Admin ID: ", id);
- console.log("Admin Password: ", pass);
const gov_logo = require('@/assets/images/billion_readers.png');
const [adminId, setAdminId] = useState('');
const [password, setPassword] = useState('');
@@ -26,7 +26,7 @@ const index = () => {
};
return (
-
+
@@ -35,7 +35,8 @@ const index = () => {
placeholder="Admin ID"
value={adminId}
onChangeText={setAdminId}
- className="w-full bg-white p-3 mt-4 rounded-md"
+ className={`w-full p-3 mt-4 rounded-md ${isDarkMode ? 'bg-gray-800 text-text-dark border-gray-700 border' : 'bg-white text-black'}`}
+ placeholderTextColor={isDarkMode ? "#9CA3AF" : "#6B7280"}
/>
{
value={password}
onChangeText={setPassword}
secureTextEntry
- className="w-full bg-white p-3 mt-4 rounded-md"
+ className={`w-full p-3 mt-4 rounded-md ${isDarkMode ? 'bg-gray-800 text-text-dark border-gray-700 border' : 'bg-white text-black'}`}
+ placeholderTextColor={isDarkMode ? "#9CA3AF" : "#6B7280"}
/>
-
+
LOGIN
diff --git a/app/pdf/[id].tsx b/app/pdf/[id].tsx
index 59de0b7..fd43c4e 100644
--- a/app/pdf/[id].tsx
+++ b/app/pdf/[id].tsx
@@ -5,11 +5,13 @@ import * as FileSystem from 'expo-file-system';
import { Asset } from 'expo-asset';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { videoDetails } from '../../assets/details';
+import { useTheme } from '../themeContext';
const PdfViewer = () => {
const [pdfUri, setPdfUri] = useState(null);
const [loading, setLoading] = useState(true);
const router = useRouter();
+ const { isDarkMode } = useTheme();
const { id, language } = useLocalSearchParams<{ id?: string; language?: string }>();
const video = videoDetails.find((v) => v.id === id);
@@ -55,24 +57,24 @@ const PdfViewer = () => {
}, []);
return (
-
+
{/* Header with Back Button and Title */}
-
- {/* router.push("/")} className="p-2">
-
- */}
-
- {title || 'PDF Viewer'}
-
-
-
+
+ router.push("/")} className="p-2">
+
+
+
+ {title || 'PDF Viewer'}
+
+
{loading ? (
-
+
) : pdfUri ? (
+
);
diff --git a/app/settings.tsx b/app/settings.tsx
new file mode 100644
index 0000000..54f7e99
--- /dev/null
+++ b/app/settings.tsx
@@ -0,0 +1,92 @@
+import React from 'react';
+import { View, Text, ScrollView, TouchableOpacity } from 'react-native';
+import { Stack, useRouter } from 'expo-router';
+import { Ionicons } from '@expo/vector-icons';
+import { useTheme } from './themeContext';
+import ThemeToggle from '../components/ThemeToggle';
+
+const SettingsScreen = () => {
+ const router = useRouter();
+ const { isDarkMode } = useTheme();
+
+ return (
+
+ (
+ router.back()}
+ className="ml-4"
+ >
+
+
+ ),
+ }}
+ />
+
+
+
+
+ Appearance
+
+
+ Customize the app's appearance
+
+
+
+
+ Theme
+
+
+
+
+
+
+ You can choose between Light, Dark, or System theme. The System option will follow your device's theme settings.
+
+
+
+
+
+
+ About
+
+
+ Information about this app
+
+
+
+
+ Version
+
+
+ 1.0.0
+
+
+
+
+
+ Made with
+
+
+ ❤️ in India
+
+
+
+
+
+ );
+};
+
+export default SettingsScreen;
\ No newline at end of file
diff --git a/app/themeContext.tsx b/app/themeContext.tsx
new file mode 100644
index 0000000..a4d7eac
--- /dev/null
+++ b/app/themeContext.tsx
@@ -0,0 +1,82 @@
+import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import { useColorScheme } from 'react-native';
+
+type ThemeType = 'light' | 'dark' | 'system';
+
+interface ThemeContextType {
+ theme: ThemeType;
+ isDarkMode: boolean;
+ setTheme: (theme: ThemeType) => void;
+ toggleTheme: () => void;
+}
+
+const ThemeContext = createContext(undefined);
+
+export const ThemeProvider = ({ children }: { children: ReactNode }) => {
+ const systemColorScheme = useColorScheme();
+ const [theme, setThemeState] = useState('system');
+
+ // Computed property to determine if dark mode is active
+ const isDarkMode = theme === 'system'
+ ? systemColorScheme === 'dark'
+ : theme === 'dark';
+
+ // Load saved theme preference on initial render
+ useEffect(() => {
+ const loadTheme = async () => {
+ try {
+ const savedTheme = await AsyncStorage.getItem('theme');
+ if (savedTheme) {
+ setThemeState(savedTheme as ThemeType);
+ }
+ } catch (error) {
+ console.error('Failed to load theme preference:', error);
+ }
+ };
+
+ loadTheme();
+ }, []);
+
+ // Save theme preference whenever it changes
+ useEffect(() => {
+ const saveTheme = async () => {
+ try {
+ await AsyncStorage.setItem('theme', theme);
+ } catch (error) {
+ console.error('Failed to save theme preference:', error);
+ }
+ };
+
+ saveTheme();
+ }, [theme]);
+
+ // Function to update theme
+ const setTheme = (newTheme: ThemeType) => {
+ setThemeState(newTheme);
+ };
+
+ // Convenience function to toggle between light and dark
+ const toggleTheme = () => {
+ if (theme === 'system') {
+ setThemeState(systemColorScheme === 'dark' ? 'light' : 'dark');
+ } else {
+ setThemeState(theme === 'dark' ? 'light' : 'dark');
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+// Custom hook to use the ThemeContext
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
\ No newline at end of file
diff --git a/app/video/[id].tsx b/app/video/[id].tsx
index 6d3fb1a..5a3eab6 100644
--- a/app/video/[id].tsx
+++ b/app/video/[id].tsx
@@ -1,22 +1,21 @@
import { useLocalSearchParams } from "expo-router";
import { useVideoPlayer, VideoView } from "expo-video";
-import { StyleSheet, View, Text, TouchableOpacity, Image,TouchableWithoutFeedback } from "react-native";
+import { StyleSheet, View, Text, TouchableOpacity, Image, TouchableWithoutFeedback } from "react-native";
import { videoDetails } from "@/assets/details";
import { useEffect, useState } from "react";
import * as ScreenOrientation from "expo-screen-orientation";
import { useRouter } from "expo-router";
import { useKeepAwake } from 'expo-keep-awake';
import { useSQLiteContext } from "expo-sqlite";
-import { getVideoAnalyticsByUser,getUsers } from "../database/database";
+import { getVideoAnalyticsByUser, getUsers } from "../database/database";
import { getVideoUri } from "./videoDownlaoder";
import { BackHandler } from "react-native"; // for handling back button press on android
-
-
-//Here back issue is solved but controls by default they are showing......
+import { useTheme } from "../themeContext";
export default function VideoScreen() {
useKeepAwake();
const router = useRouter();
+ const { isDarkMode } = useTheme();
const { id, language } = useLocalSearchParams<{ id?: string; language?: string }>();
const [originalOrientation, setOriginalOrientation] = useState();
const back = require('@/assets/images/back.png');
@@ -45,21 +44,12 @@ export default function VideoScreen() {
fetchVideoUri();
}, []);
+
const handlePress = () => {
setShowControls(true);
setTimeout(() => setShowControls(false), 3000); // Hide controls after 3 seconds
};
- // useEffect(() => {
- // if (video) {
- // setVideoSource(language === "pa" ? video.url_punjabi : video.url_en);
- // }
- // }, [video, language]);
-
-
-
-
-
// Move player up here and modify
const player = useVideoPlayer(
videoSource || '',
@@ -85,29 +75,6 @@ export default function VideoScreen() {
return () => backHandler.remove(); // Cleanup when unmounting
}, [player, originalOrientation, router]); // Add dependencies
-
- // Remove the conditional return here
- // if (!video || !fileUri) {
- // return (
- //
- //
-
- // const player = useVideoPlayer(
- // language === "pa" ? fileUri : fileUri,
- // async (player) => {
- // player.loop = false;
-
- // // Store the original orientation before changing
- // const currentOrientation = await ScreenOrientation.getOrientationAsync();
- // setOriginalOrientation(currentOrientation);
-
- // // Change to landscape mode automatically
- // await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
-
- // await player.play();
- // }
- // );
-
// Restore original orientation when exiting
useEffect(() => {
return () => {
@@ -117,7 +84,6 @@ export default function VideoScreen() {
};
}, [originalOrientation]);
-
const updateVideoAnalytics = async (watchedTime: number) => {
try {
const users = await getUsers(db);
@@ -128,7 +94,6 @@ export default function VideoScreen() {
const today = new Date().toISOString().split("T")[0]; // Get YYYY-MM-DD format
const lastWatchedTimestamp = new Date().toISOString(); // Get full timestamp
-
// Check if the analytics entry exists for this user, video, language, and date
const existingRecords = await db.getAllAsync(
"SELECT * FROM video_analytics WHERE user_id = ? AND video_id = ? AND language = ? AND date = ?",
@@ -160,7 +125,6 @@ export default function VideoScreen() {
}
};
-
const returnBackToHome = async () => {
const watchedTime = Math.floor(player.currentTime);
console.log(`Watched Till: ${watchedTime} seconds`);
@@ -178,13 +142,12 @@ export default function VideoScreen() {
};
return (
-
-
+
-
+
+
);
diff --git a/components/ThemeToggle.tsx b/components/ThemeToggle.tsx
new file mode 100644
index 0000000..0a5ba69
--- /dev/null
+++ b/components/ThemeToggle.tsx
@@ -0,0 +1,118 @@
+import React from 'react';
+import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
+import { Ionicons } from '@expo/vector-icons';
+import { useTheme } from '../app/themeContext';
+
+interface ThemeToggleProps {
+ showLabel?: boolean;
+ size?: number;
+ containerStyle?: object;
+}
+
+const ThemeToggle: React.FC = ({
+ showLabel = true,
+ size = 24,
+ containerStyle = {}
+}) => {
+ const { theme, setTheme, isDarkMode } = useTheme();
+
+ // Function to determine the icon based on current theme
+ const getThemeIcon = () => {
+ switch (theme) {
+ case 'light':
+ return 'sunny';
+ case 'dark':
+ return 'moon';
+ case 'system':
+ return isDarkMode ? 'moon' : 'sunny';
+ default:
+ return 'sunny';
+ }
+ };
+
+ // Cycle through themes: light -> dark -> system -> light
+ const cycleTheme = () => {
+ if (theme === 'light') {
+ setTheme('dark');
+ } else if (theme === 'dark') {
+ setTheme('system');
+ } else {
+ setTheme('light');
+ }
+ };
+
+ // Get the theme label
+ const getThemeLabel = () => {
+ switch (theme) {
+ case 'light':
+ return 'Light';
+ case 'dark':
+ return 'Dark';
+ case 'system':
+ return 'System';
+ default:
+ return 'Light';
+ }
+ };
+
+ return (
+
+
+
+ {showLabel && (
+
+ {getThemeLabel()}
+
+ )}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ alignItems: 'center',
+ },
+ button: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingVertical: 8,
+ paddingHorizontal: 12,
+ borderRadius: 8,
+ gap: 6,
+ },
+ buttonLight: {
+ backgroundColor: '#F9FAFB',
+ borderWidth: 1,
+ borderColor: '#E5E7EB',
+ },
+ buttonDark: {
+ backgroundColor: '#1F2937',
+ borderWidth: 1,
+ borderColor: '#374151',
+ },
+ label: {
+ fontWeight: '500',
+ fontSize: 14,
+ },
+ labelLight: {
+ color: '#1F2937',
+ },
+ labelDark: {
+ color: '#F3F4F6',
+ },
+});
+
+export default ThemeToggle;
\ No newline at end of file
diff --git a/tailwind.config.js b/tailwind.config.js
index 38f7798..b9ed08b 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,10 +1,35 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all of your component files.
- content: ["./app/**/*.{js,jsx,ts,tsx}"],
+ content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
+ darkMode: "class",
presets: [require("nativewind/preset")],
theme: {
- extend: {},
+ extend: {
+ colors: {
+ primary: {
+ DEFAULT: "#6B21A8", // purple-800
+ dark: "#4C1D95", // purple-900
+ light: "#8B5CF6", // purple-500
+ },
+ background: {
+ light: "#FFFFFF",
+ dark: "#121212",
+ },
+ text: {
+ light: "#1F2937", // gray-800
+ dark: "#F3F4F6", // gray-100
+ },
+ surface: {
+ light: "#F9FAFB", // gray-50
+ dark: "#1F2937", // gray-800
+ },
+ border: {
+ light: "#E5E7EB", // gray-200
+ dark: "#374151", // gray-700
+ },
+ },
+ },
},
plugins: [],
}
\ No newline at end of file