Skip to content

Commit

Permalink
Dark mode
Browse files Browse the repository at this point in the history
vedmant committed Mar 6, 2024
1 parent d3135d1 commit fbca0ac
Showing 12 changed files with 100 additions and 57 deletions.
6 changes: 3 additions & 3 deletions App.js
Original file line number Diff line number Diff line change
@@ -4,11 +4,11 @@ import AppNavigator from '@/navigation/AppNavigator'
import 'react-native-gesture-handler'
import { SafeAreaProvider } from 'react-native-safe-area-context'

export default function App() {
export default function App () {
return (
<SafeAreaProvider>
<SafeAreaProvider className="flex-1 dark:bg-gray-900">
<AppNavigator />
<StatusBar style="auto" />
<StatusBar style="light" />
</SafeAreaProvider>
)
}
7 changes: 5 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
@@ -6,11 +6,14 @@
"version": "1.2.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
"backgroundColor": "#ffffff",
"dark": {
"backgroundColor": "#6b7280"
}
},
"assetBundlePatterns": [
"**/*"
12 changes: 7 additions & 5 deletions src/components/InputGroup.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React, { cloneElement, useState } from 'react'
import { Text, TextInput, View } from 'react-native'
import { useColorScheme } from 'nativewind'

export default function ({ label, icon, error, className, ...props }) {
const [focused, setFocused] = useState(false)
const border = error ? 'border-red-600' : (focused ? 'border-indigo-600' : 'border-gray-300')
const border = error ? 'border-red-600' : (focused ? 'border-indigo-600' : 'border-gray-300 dark:border-gray-800')
const { colorScheme } = useColorScheme()

return (
<View className={`mb-4 ${className}`}>
{label && (<Text className="mb-2">{label}</Text>)}
<View className={`border px-2 flex flex-row items-center justify-start rounded-lg bg-white ${border}`}>
{icon && cloneElement(icon, { weight: 'bold', size: 18, style: { marginRight: 5 } })}
<TextInput className="w-full leading-none py-2.5" onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} {...props} />
{label && (<Text className="mb-2 dark:text-white">{label}</Text>)}
<View className={`border px-3 flex flex-row items-center justify-start rounded-lg bg-white dark:bg-gray-800 ${border}`}>
{icon && cloneElement(icon, { weight: 'bold', size: 18, color: (colorScheme === 'dark' ? '#fff' : '#000'), style: { marginRight: 5 } })}
<TextInput className="w-full leading-none py-2.5 dark:text-white" onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} {...props} />
</View>
{error && <Text className="text-red-600 text-sm mt-1">{error}</Text>}
</View>
6 changes: 3 additions & 3 deletions src/components/Panel.js
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@ import { Text, View } from 'react-native'

export default function ({ header, children, ...props }) {
return (
<View className="border-gray-300 border rounded-lg bg-white" {...props}>
<View className="border-gray-300 border rounded-lg bg-white dark:bg-gray-700 dark:border-gray-800" {...props}>
{header && (
<View className="px-3 py-2 border-b border-gray-300">
<Text className="font-medium">{header}</Text>
<View className="px-3 py-2 border-b border-gray-300 dark:border-gray-800">
<Text className="font-medium dark:text-white">{header}</Text>
</View>
)}
<View className="p-3">{children}</View>
15 changes: 12 additions & 3 deletions src/navigation/AppNavigator.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { createStackNavigator } from '@react-navigation/stack'
import { NavigationContainer } from '@react-navigation/native'
import AuthScreen from '../screens/Auth/AuthScreen'
import AuthScreen from '@/screens/Auth/AuthScreen'
import MainTabNavigator from './MainTabNavigator'
import AuthLoadingScreen from '@/screens/Auth/AuthLoadingScreen'
import { useAuthStore } from '@/stores/auth'
import { DarkTheme, DefaultTheme } from '@react-navigation/native';
import { useColorScheme } from 'nativewind'
import merge from 'lodash/merge'
import colors from 'tailwindcss/colors'

const Stack = createStackNavigator()

const LightTheme = merge({}, DefaultTheme)
LightTheme.colors.card = colors.indigo[600]

export default function ApplicationNavigator () {
const user = useAuthStore(s => s.me)
const { colorScheme } = useColorScheme()

return (
<NavigationContainer>
<NavigationContainer theme={{ ...(colorScheme === 'dark' ? DarkTheme : LightTheme) }}>
<Stack.Navigator screenOptions={{ headerShown: false }} initialRouteName={user ? 'Main' : 'AuthLoadingScreen'}>
{user ? (
<>
@@ -24,6 +32,7 @@ export default function ApplicationNavigator () {
</>
)}
</Stack.Navigator>
</NavigationContainer>)
</NavigationContainer>
)
}

41 changes: 32 additions & 9 deletions src/navigation/MainTabNavigator.js
Original file line number Diff line number Diff line change
@@ -7,22 +7,47 @@ import EntriesScreen from '@/screens/Entries/EntriesScreen'
import ProfileScreen from '@/screens/Profile/ProfileScreen'
import { SquaresFour, ListDashes, User } from 'phosphor-react-native'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { useColorScheme } from 'nativewind'
import colors from 'tailwindcss/colors'

const tabsLight = {
tabBarActiveTintColor: colors.white,
tabBarInactiveTintColor: colors.indigo[200],
tabBarActiveBackgroundColor: colors.indigo[500],
tabBarInactiveBackgroundColor: colors.indigo[600],
headerTintColor: colors.white,
headerStyle: { backgroundColor: colors.indigo[600] },
}

const tabsDark = {
tabBarActiveTintColor: colors.indigo[600],
tabBarInactiveTintColor: colors.white,
tabBarActiveBackgroundColor: colors.gray[800],
tabBarInactiveBackgroundColor: colors.gray[900],
headerTintColor: colors.white,
headerStyle: { backgroundColor: colors.gray[900] },
}

const Stack = createStackNavigator()

function EntriesStack () {
return (<Stack.Navigator screenOptions={{ headerShown: true }}>
<Stack.Screen name="ListEntries" component={EntriesScreen} options={{title: 'Runs'}} />
<Stack.Screen name="EditEntry" component={EditEntryScreen} options={{title: 'Edit Run'}} />
<Stack.Screen name="AddEntry" component={AddEntryScreen} options={{title: 'Add Run'}} />
const { colorScheme } = useColorScheme()

return (<Stack.Navigator screenOptions={{ headerShown: true, ...(colorScheme === 'dark' ? tabsDark : tabsLight) }}>
<Stack.Screen name="ListEntries" component={EntriesScreen} options={{ title: 'Runs' }} />
<Stack.Screen name="EditEntry" component={EditEntryScreen} options={{ title: 'Edit Run' }} />
<Stack.Screen name="AddEntry" component={AddEntryScreen} options={{ title: 'Add Run' }} />
</Stack.Navigator>)
}

const Tab = createBottomTabNavigator()

function MyTabs () {
export default function () {
const { colorScheme } = useColorScheme()
const tabBarStyles = { tabBarStyle: { height: 83 }, tabBarItemStyle: { paddingBottom: 7, paddingTop: 5 }}

return (
<Tab.Navigator>
<Tab.Navigator screenOptions={{...tabBarStyles, ...(colorScheme === 'dark' ? tabsDark : tabsLight) }}>
<Tab.Screen name="Dashboard" component={DashboardScreen} options={{
tabBarLabel: 'Dashboard',
tabBarIcon: ({ color }) => (
@@ -41,9 +66,7 @@ function MyTabs () {
tabBarIcon: ({ color }) => (
<User name="home" color={color} size={26} />
),
}} />
}} />
</Tab.Navigator>
)
}

export default MyTabs
4 changes: 2 additions & 2 deletions src/screens/Auth/AuthScreen.js
Original file line number Diff line number Diff line change
@@ -13,8 +13,8 @@ const renderScene = SceneMap({
const renderTabBar = props => (
<TabBar
{...props}
// indicatorStyle={{ backgroundColor: 'white' }}
className="bg-indigo-600 text-white"
indicatorStyle={{ backgroundColor: 'white' }}
className="bg-indigo-600 dark:bg-gray-800 text-white"
/>
)

6 changes: 3 additions & 3 deletions src/screens/Auth/LoginTab.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
import { View } from 'react-native'
import { ScrollView } from 'react-native'
import Toast from 'react-native-root-toast'
import Panel from '@/components/Panel'
import { Lock, User } from 'phosphor-react-native'
@@ -38,7 +38,7 @@ export default function () {
}

return (
<View className="p-2">
<ScrollView className="p-2" enableOnAndroid>
<Panel>
<InputGroup
label="Email"
@@ -65,6 +65,6 @@ export default function () {
className="mt-4"
/>
</Panel>
</View>
</ScrollView>
)
}
35 changes: 19 additions & 16 deletions src/screens/Dashboard/DashboardScreen.js
Original file line number Diff line number Diff line change
@@ -2,14 +2,17 @@ import React, { useState } from 'react'
import { Dimensions, RefreshControl, StyleSheet, Text, View, ScrollView, KeyboardAvoidingView, Platform } from 'react-native'
import { LineChart } from 'react-native-chart-kit'
import Panel from '@/components/Panel'
import EntryForm from '../Entries/EntryForm'
import EntryForm from '@/screens/Entries/EntryForm'
import { useGeneralStore } from '@/stores/general'
import { useColorScheme } from 'nativewind'
import colors from 'tailwindcss/colors'

export default function () {
const [loading, setLoading] = useState(false)
const [booted, setBooted] = useState(false)
const dashboard = useGeneralStore(s => s.dashboard)
const loadDashboard = useGeneralStore(s => s.loadDashboard)
const isDark = useColorScheme().colorScheme === 'dark'

const dispatchLoadDashboard = async () => {
setLoading(true)
@@ -30,40 +33,40 @@ export default function () {
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
<ScrollView
contentContainerStyle={{padding: 10}}
className="gap-y-2"
className="gap-y-2 bg-gray-100 dark:bg-gray-800"
refreshControl={
<RefreshControl onRefresh={onRefresh} refreshing={loading} />
<RefreshControl onRefresh={onRefresh} refreshing={loading} tintColor={isDark ? colors.gray[200] : colors.gray[700]} />
}>
<Panel header="This week">
<Text>
<Text className="dark:text-white">
<Text>Records: </Text>
<Text className="font-bold">{dashboard.weekly_count}</Text>
</Text>
<Text>
<Text className="dark:text-white">
<Text>Average speed: </Text>
<Text className="font-bold">
{Math.round((dashboard.weekly_avg_speed || 0) * 10) / 10} km/h
</Text>
</Text>
<Text>
<Text className="dark:text-white">
<Text>Average pace: </Text>
<Text className="font-bold">
{Math.round((dashboard.weekly_avg_pace || 0) * 10) / 10} min/km
</Text>
</Text>
</Panel>
<Panel header="Best results">
<Text>
<Text className="dark:text-white">
<Text>Best speed: </Text>
<Text className="font-bold">
{Math.round(dashboard.max_speed * 10) / 10} km/h
</Text>
</Text>
<Text>
<Text className="dark:text-white">
<Text>Longest distance: </Text>
<Text className="font-bold">{dashboard.max_distance || 0} km</Text>
</Text>
<Text>
<Text className="dark:text-white">
<Text>Longest run: </Text>
<Text className="font-bold">{dashboard.max_time || 0}</Text>
</Text>
@@ -90,12 +93,12 @@ export default function () {
yAxisSuffix={' km'}
paddingLeft={0}
chartConfig={{
backgroundColor: '#fff',
backgroundGradientFrom: '#fff',
backgroundGradientTo: '#fff',
backgroundColor: isDark ? colors.gray[700] : '#fff',
backgroundGradientFrom: isDark ? colors.gray[700] : '#fff',
backgroundGradientTo: isDark ? colors.gray[700] : '#fff',
decimalPlaces: 0, // optional, defaults to 2dp
color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
color: (opacity = 1) => isDark ? `rgba(255, 255, 255, ${opacity})` : `rgba(0, 0, 0, ${opacity})`,
labelColor: (opacity = 1) => isDark ? `rgba(255, 255, 255, ${opacity})` : `rgba(0, 0, 0, ${opacity})`,
propsForDots: {
r: '6',
strokeWidth: '2',
@@ -117,7 +120,7 @@ export default function () {
{ backgroundColor: 'rgb(220, 57, 18)' },
]}
/>
<Text> - Speed</Text>
<Text className="dark:text-white"> - Speed</Text>
</View>
<View style={[styles.legendRow, { paddingBottom: 15 }]}>
<View
@@ -126,7 +129,7 @@ export default function () {
{ backgroundColor: 'rgb(51, 102, 204)' },
]}
/>
<Text> - Distance</Text>
<Text className="dark:text-white"> - Distance</Text>
</View>
</View>
</View>
21 changes: 12 additions & 9 deletions src/screens/Entries/EntriesScreen.js
Original file line number Diff line number Diff line change
@@ -7,12 +7,15 @@ import { Pencil, Plus, Trash } from 'phosphor-react-native'
import { useEntriesStore } from '@/stores/entries'
import { useNavigation } from '@react-navigation/native'
import Button from '@/components/Button'
import colors from 'tailwindcss/colors'
import { useColorScheme } from 'nativewind'

export default function () {
const navigation = useNavigation()
const entries = useEntriesStore(s => s.entries)
const [loading, setLoading] = useState(false)
const [loadingMore, setLoadingMore] = useState(false)
const isDark = useColorScheme().colorScheme === 'dark'

async function loadEntries () {
setLoading(true)
@@ -50,17 +53,17 @@ export default function () {
<Panel className="mb-2">
<View className="flex flex-row">
<View className="flex-1">
<Text>
<Text className="dark:text-white">
Date: {dayjs(item.date).format('MM/DD/YY')}
</Text>
<Text>Distance: {item.distance} km</Text>
<Text>Time: {item.time}</Text>
<Text className="dark:text-white">Distance: {item.distance} km</Text>
<Text className="dark:text-white">Time: {item.time}</Text>
</View>
<View style={{ flex: 1 }}>
<Text>
<Text className="dark:text-white">
Avg. Speed: {Math.round(item.speed * 10) / 10}
</Text>
<Text>
<Text className="dark:text-white">
Avg. Pace: {Math.round(item.pace * 10) / 10}
</Text>
</View>
@@ -86,29 +89,29 @@ export default function () {

return (
<View>
<ActivityIndicator animating size="large" />
<ActivityIndicator animating size="large" color={isDark ? colors.gray[200] : colors.gray[700]} />
</View>
)
}

return (
<View className="flex-1">
<View className="flex-1 bg-gray-100 dark:bg-gray-800">
<FlatList
className="flex-1"
data={entries.data}
contentContainerStyle={{padding: 10}}
renderItem={({ item }) => renderItem(item)}
keyExtractor={item => item.id + ''}
refreshControl={
<RefreshControl onRefresh={loadEntries} refreshing={loading} />
<RefreshControl onRefresh={loadEntries} refreshing={loading} tintColor={isDark ? colors.gray[200] : colors.gray[700]} />
}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
ListEmptyComponent={
loading ? null : (
<View className="justify-center flex-1 flex-row">
<Text>The list is empty</Text>
<Text className="dark:text-white">The list is empty</Text>
</View>
)
}
2 changes: 1 addition & 1 deletion src/screens/Profile/ProfileScreen.js
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import Button from '@/components/Button'

export default function () {
return (
<ScrollView className="p-2">
<ScrollView className="p-2 bg-gray-100 dark:bg-gray-800">
<Button
label="Logout"
className="mb-4"
2 changes: 1 addition & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
content: ['./App.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},

0 comments on commit fbca0ac

Please sign in to comment.