Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ app-example
# generated native folders
/ios
/android

# IDE specific files
.vscode/
.idea/
*.swp
1 change: 0 additions & 1 deletion .vscode/extensions.json

This file was deleted.

12 changes: 0 additions & 12 deletions .vscode/settings.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import {
import { ReactNode } from "react";
import { Provider } from "react-redux";

import browserReducer from "../../features/browser/browserSlice";
import { BrowserState, BrowserTab } from "../../features/browser/types";
import scriptsReducer from "../../features/scripts/scriptsSlice";
import browserReducer from "@/src/features/browser/browserSlice";
import { BrowserState, BrowserTab } from "@/src/features/browser/types";
import scriptsReducer from "@/src/features/scripts/scriptsSlice";
import {
ScriptExecution,
ScriptsState,
UserScript,
} from "../../features/scripts/types";
import settingsReducer from "../../features/settings/settingsSlice";
import { SettingsState } from "../../features/settings/types";
import { RootState } from "../../store";
} from "@/src/features/scripts/types";
import settingsReducer from "@/src/features/settings/settingsSlice";
import { SettingsState } from "@/src/features/settings/types";
import { RootState } from "@/src/store";

// Default states for each slice
export const defaultBrowserState: BrowserState = {
Expand Down
27 changes: 21 additions & 6 deletions src/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ThemeProvider } from "@/src/theme/ThemeContext";
import { useTheme } from "@/src/theme/useTheme";
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import React from "react";
Expand All @@ -17,16 +19,29 @@ const LoadingComponent = () => (
</View>
);

const ThemedStatusBar = () => {
const theme = useTheme();
return <StatusBar style={theme.statusBarStyle} />;
};

const AppContent = () => {
return (
<SafeAreaProvider>
<ThemedStatusBar />
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
</Stack>
</SafeAreaProvider>
);
};

export default function RootLayout() {
return (
<Provider store={store}>
<PersistGate loading={<LoadingComponent />} persistor={persistor}>
<SafeAreaProvider>
<StatusBar style="auto" />
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
</Stack>
</SafeAreaProvider>
<ThemeProvider>
<AppContent />
</ThemeProvider>
</PersistGate>
</Provider>
);
Expand Down
48 changes: 46 additions & 2 deletions src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,59 @@
import { Text, View } from "react-native";
import { useSettings } from "@/src/features/settings/hooks/useSettings";
import { useTheme } from "@/src/theme/useTheme";
import { Pressable, Text, View } from "react-native";

export default function Index() {
const theme = useTheme();
const settings = useSettings();

return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: theme.colors.background,
}}
>
<Text>Scriptian Browser</Text>
<Text
style={{
...theme.typography.h4,
color: theme.colors.warning,
}}
>
The current theme color is {theme.name}.{"\n"}
The current theme mode is {settings.theme}.
</Text>
<Pressable onPress={() => settings.switchTheme("system")}>
<Text
style={{
...theme.typography.button,
color: theme.colors.textPrimary,
}}
>
System Theme
</Text>
</Pressable>
<Pressable onPress={() => settings.switchTheme("light")}>
<Text
style={{
...theme.typography.button,
color: theme.colors.textPrimary,
}}
>
Light Theme
</Text>
</Pressable>
<Pressable onPress={() => settings.switchTheme("dark")}>
<Text
style={{
...theme.typography.button,
color: theme.colors.textPrimary,
}}
>
Dark Theme
</Text>
</Pressable>
</View>
);
}
80 changes: 80 additions & 0 deletions src/constants/Icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* A collection of icon name constants used throughout the application UI.
*
* @remarks
* - All icon names are marked as `const` to ensure type safety and prevent accidental modification.
* - The keys are grouped by their usage context for clarity.
*
* @example
* ```typescript
* import { Icons } from './constants/Icons';
*
* // Use an icon for a back button
* const backIcon = Icons.back; // "chevron-back"
* ```
*/
export const Icons = {
// Bottom toolbar navigation
back: "chevron-back" as const,
forward: "chevron-forward" as const,
share: "share-outline" as const,
bookmarks: "book-outline" as const,
tabs: "copy-outline" as const,

// Address bar icons
refresh: "refresh" as const,
stop: "close" as const,
secure: "lock-closed" as const,
insecure: "warning" as const,
search: "search" as const,

// Tab management
closeTab: "close" as const,
addTab: "add" as const,
tabOverview: "albums-outline" as const,

// Menu and settings
menu: "ellipsis-horizontal" as const,
settings: "settings-outline" as const,
more: "ellipsis-vertical" as const,

// Browser actions
reload: "refresh" as const,
home: "home-outline" as const,
downloads: "download-outline" as const,
history: "time-outline" as const,

// Script management
scripts: "code-outline" as const,
scriptActive: "code" as const,
scriptDisabled: "code-slash-outline" as const,

// Reader and accessibility
reader: "reader-outline" as const,
textSize: "text" as const,

// Bookmarks and favorites
bookmark: "bookmark-outline" as const,
bookmarkFilled: "bookmark" as const,
star: "star-outline" as const,
starFilled: "star" as const,

// General UI
check: "checkmark" as const,
error: "alert-circle" as const,
info: "information-circle" as const,
edit: "create-outline" as const,
delete: "trash-outline" as const,
copy: "copy-outline" as const,
} as const;

export type Icon = (typeof Icons)[keyof typeof Icons];

export const IconSizes = {
small: 16,
medium: 20,
large: 24,
xlarge: 28,
toolbar: 24,
tab: 20,
} as const;
4 changes: 2 additions & 2 deletions src/features/browser/hooks/__tests__/useBrowser-test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { act } from "@testing-library/react-native";
import {
createMockTab,
defaultBrowserState,
renderHookWithStore,
} from "../../../../__tests__/utils/testUtils";
} from "@/__tests__/utils/testUtils";
import { act } from "@testing-library/react-native";
import { useBrowser } from "../useBrowser";

describe("useBrowser Hook", () => {
Expand Down
4 changes: 2 additions & 2 deletions src/features/scripts/hooks/__tests__/useScripts-test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { act } from "@testing-library/react-native";
import {
createMockExecution,
createMockScript,
defaultScriptsState,
defaultSettingsState,
renderHookWithStore,
} from "../../../../__tests__/utils/testUtils";
} from "@/__tests__/utils/testUtils";
import { act } from "@testing-library/react-native";
import { useScripts } from "../useScripts";

describe("useScripts Hook", () => {
Expand Down
16 changes: 7 additions & 9 deletions src/features/settings/hooks/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
toggleScriptsEnabled,
updateSettings,
} from "../settingsSlice";
import { SettingsState } from "../types";
import { SettingsState, ThemeMode } from "../types";

export const useSettings = () => {
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -36,8 +36,12 @@ export const useSettings = () => {
dispatch(toggleAutoUpdateScripts());
};

// Theme settings
const switchTheme = (theme: "light" | "dark" | "system") => {
/**
* Switches the application's theme to the specified mode.
*
* @param theme - The desired theme mode to apply (e.g., 'system', 'light', 'dark').
*/
const switchTheme = (theme: ThemeMode) => {
dispatch(setTheme(theme));
};

Expand All @@ -56,19 +60,13 @@ export const useSettings = () => {
};

// Computed values
const isDarkMode = settings.theme === "dark";
const isLightMode = settings.theme === "light";
const isSystemMode = settings.theme === "system";
const executionTimeInSeconds = settings.maxExecutionTime / 1000;

return {
// Current settings state
...settings,

// Computed values
isDarkMode,
isLightMode,
isSystemMode,
executionTimeInSeconds,

// Actions
Expand Down
4 changes: 2 additions & 2 deletions src/features/settings/settingsSlice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { SettingsState } from "./types";
import { SettingsState, ThemeMode } from "./types";

const initialState: SettingsState = {
scriptsEnabled: true,
Expand Down Expand Up @@ -29,7 +29,7 @@ const settingsSlice = createSlice({
state.logExecutions = !state.logExecutions;
},

setTheme: (state, action: PayloadAction<"light" | "dark" | "system">) => {
setTheme: (state, action: PayloadAction<ThemeMode>) => {
state.theme = action.payload;
},

Expand Down
4 changes: 3 additions & 1 deletion src/features/settings/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export type ThemeMode = "light" | "dark" | "system";

export interface SettingsState {
scriptsEnabled: boolean;
maxExecutionTime: number;
logExecutions: boolean;
theme: "light" | "dark" | "system";
theme: ThemeMode;
autoUpdateScripts: boolean;
}
35 changes: 35 additions & 0 deletions src/theme/ThemeContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useAppSelector } from "@/src/store";
import { createContext, useMemo } from "react";
import { useColorScheme } from "react-native";
import { themes } from "./themes";
import { Theme } from "./types/theme";

export const ThemeContext = createContext<Theme>(themes.default);

function getTheme(theme: string): Theme {
if (theme in themes) {
return themes[theme];
}
return themes.default;
}

export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const themeMode = useAppSelector((state) => state.settings.theme);
const colorScheme = useColorScheme();

const themeName: string = useMemo(() => {
if (themeMode === "system") {
return colorScheme === "light" ? "light" : "dark";
} else {
return themeMode;
}
}, [themeMode, colorScheme]);

const value = useMemo(() => getTheme(themeName), [themeName]);

return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
};
Loading