Polished up some UI and added dark/light mode

This commit is contained in:
Christopher Sanden
2026-03-06 17:20:41 +01:00
parent 45ab15ff40
commit 3e81e46b1a
25 changed files with 472 additions and 309 deletions

View File

@@ -1,26 +1,35 @@
import { DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-reanimated';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { useAuthContext } from '@/hooks/use-auth-context'
import AuthProvider from '@/providers/auth-provider'
import { NotesProvider } from "@/src/notes/NotesContext"
import { useColorScheme } from '@/hooks/use-color-scheme';
import { useAuthContext } from '@/hooks/use-auth-context';
import AuthProvider from '@/providers/auth-provider';
import { AppThemeProvider, useAppTheme } from '@/src/theme/AppThemeProvider'
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { Stack } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
import 'react-native-reanimated'
import { SafeAreaProvider } from 'react-native-safe-area-context'
// Separate RootNavigator so we can access the AuthContext
function RootNavigator() {
const { isLoggedIn, isLoading } = useAuthContext()
const { palette } = useAppTheme()
if (isLoading) {
return null
}
return (
<Stack>
<Stack
screenOptions={{
contentStyle: { backgroundColor: palette.background },
headerStyle: { backgroundColor: palette.surface },
headerShadowVisible: false,
headerTintColor: palette.text,
headerTitleStyle: { color: palette.text },
}}
>
<Stack.Protected guard={isLoggedIn}>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="index" options={{ headerShown: false,title: "Notes", headerBackTitle: "Notes" }} />
<Stack.Screen name="newNote" options={{ headerShown: true, title: 'New Note' }} />
<Stack.Screen name="detail" options={{ headerShown: true, title: 'Note' }} />
</Stack.Protected>
@@ -28,27 +37,57 @@ function RootNavigator() {
<Stack.Screen name="login" options={{ headerShown: false }} />
<Stack.Screen name="signup" options={{ headerShown: false }} />
</Stack.Protected>
<Stack.Screen name="+not-found" />
</Stack>
)
}
export default function RootLayout() {
const colorScheme = useColorScheme()
/*TODO
Fix ThemeProvider to work with dark theme
*/
function ThemedRootLayout() {
const { colorScheme, palette } = useAppTheme()
const navigationTheme = colorScheme === "dark"
? {
...DarkTheme,
colors: {
...DarkTheme.colors,
background: palette.background,
card: palette.surface,
text: palette.text,
border: palette.border,
primary: palette.accent,
},
}
: {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: palette.background,
card: palette.surface,
text: palette.text,
border: palette.border,
primary: palette.accent,
},
}
return (
<SafeAreaProvider>
<ThemeProvider value={colorScheme === 'dark' ? DefaultTheme : DefaultTheme}>
<ThemeProvider value={navigationTheme}>
<AuthProvider>
<NotesProvider>
<RootNavigator />
</NotesProvider>
</AuthProvider>
<StatusBar style="auto" />
<StatusBar
style={colorScheme === "dark" ? "light" : "dark"}
backgroundColor={palette.statusBar}
/>
</ThemeProvider>
</SafeAreaProvider>
);
)
}
export default function RootLayout() {
return (
<AppThemeProvider>
<ThemedRootLayout />
</AppThemeProvider>
)
}

View File

@@ -1,66 +1,72 @@
import { useEffect, useState } from "react";
import { Alert, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
import { router, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react"
import { Alert, KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View } from "react-native"
import { router, useLocalSearchParams } from "expo-router"
import { useHeaderHeight } from "@react-navigation/elements"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { useAppTheme } from "@/src/theme/AppThemeProvider"
import { useAuthContext } from "@/hooks/use-auth-context";
import { useNotes } from "@/src/notes/NotesContext";
import { useAuthContext } from "@/hooks/use-auth-context"
import { useNotes } from "@/src/notes/NotesContext"
export default function DetailScreen()
{
const { id } = useLocalSearchParams<
{
id?: string;
}>();
const { claims } = useAuthContext();
const { deleteNote, errorMessage, notes, updateNote } = useNotes();
const note = notes.find((entry) => entry.id === id);
const canEdit = note?.createdBy === claims?.sub;
const [title, setTitle] = useState(note?.title ?? "");
const [content, setContent] = useState(note?.content ?? "");
const [isSaving, setIsSaving] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [localErrorMessage, setLocalErrorMessage] = useState<string | null>(null);
const [statusMessage, setStatusMessage] = useState<string | null>(null);
id?: string
}>()
const { claims } = useAuthContext()
const { deleteNote, errorMessage, notes, updateNote } = useNotes()
const note = notes.find((entry) => entry.id === id)
const canEdit = note?.createdBy === claims?.sub
const [title, setTitle] = useState(note?.title ?? "")
const [content, setContent] = useState(note?.content ?? "")
const [isSaving, setIsSaving] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
const [localErrorMessage, setLocalErrorMessage] = useState<string | null>(null)
const [statusMessage, setStatusMessage] = useState<string | null>(null)
const insets = useSafeAreaInsets()
const headerHeight = useHeaderHeight()
const { colorScheme, palette } = useAppTheme()
const formatTimestamp = (value: string) => {
const parsed = new Date(value);
const parsed = new Date(value)
if (Number.isNaN(parsed.getTime())) {
return "Unknown";
return "Unknown"
}
return parsed.toLocaleString();
};
return parsed.toLocaleString()
}
useEffect(() => {
setTitle(note?.title ?? "");
setContent(note?.content ?? "");
}, [note?.content, note?.title]);
setTitle(note?.title ?? "")
setContent(note?.content ?? "")
}, [note?.content, note?.title])
const onSave = async () => {
if (!id) {
setLocalErrorMessage("This note could not be found.");
return;
setLocalErrorMessage("This note could not be found.")
return
}
if (!title.trim() || !content.trim()) {
setLocalErrorMessage("Title and content are required.");
return;
setLocalErrorMessage("Title and content are required.")
return
}
setIsSaving(true);
setLocalErrorMessage(null);
setStatusMessage(null);
setIsSaving(true)
setLocalErrorMessage(null)
setStatusMessage(null)
const wasSaved = await updateNote(id, title, content);
const wasSaved = await updateNote(id, title, content)
setIsSaving(false);
setIsSaving(false)
if (wasSaved) {
setStatusMessage("Note updated.");
setStatusMessage("Note updated.")
}
};
}
const confirmDelete = () => {
// Require explicit confirmation before deleting the note.
@@ -76,91 +82,106 @@ export default function DetailScreen()
text: "Delete",
style: "destructive",
onPress: () => {
void onDelete();
void onDelete()
},
},
]
);
};
)
}
const onDelete = async () => {
if (!id) {
setLocalErrorMessage("This note could not be found.");
return;
setLocalErrorMessage("This note could not be found.")
return
}
setIsDeleting(true);
setLocalErrorMessage(null);
setStatusMessage(null);
setIsDeleting(true)
setLocalErrorMessage(null)
setStatusMessage(null)
const wasDeleted = await deleteNote(id);
const wasDeleted = await deleteNote(id)
setIsDeleting(false);
setIsDeleting(false)
if (wasDeleted) {
router.replace("/");
router.replace("/")
}
};
}
if (!note) {
return (
<View style={styles.container}>
<Text style={styles.title}>Note not found</Text>
<Text style={styles.content}>The note may have been deleted.</Text>
<View style={[styles.container, { backgroundColor: palette.background, padding: 16 }]}>
<Text style={[styles.title, { color: palette.text }]}>Note not found</Text>
<Text style={[styles.content, { color: palette.mutedText }]}>The note may have been deleted.</Text>
</View>
);
)
}
return(
<View style={styles.container}>
<TextInput
editable={canEdit}
onChangeText={setTitle}
style={styles.titleInput}
value={title}
/>
<Text style={styles.signature}>Created by {note.creatorLabel}</Text>
<Text style={styles.signature}>
Last changed {formatTimestamp(note.lastChangedAt)}
</Text>
<TextInput
editable={canEdit}
multiline
onChangeText={setContent}
style={styles.contentInput}
textAlignVertical="top"
value={content}
/>
{!canEdit ? (
<Text style={styles.readOnlyText}>
Only the creator of this note can update or delete it.
</Text>
) : null}
{localErrorMessage ? <Text style={styles.errorText}>{localErrorMessage}</Text> : null}
{!localErrorMessage && errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null}
{statusMessage ? <Text style={styles.successText}>{statusMessage}</Text> : null}
{canEdit ? (
<View style={styles.actions}>
<Pressable disabled={isSaving} onPress={onSave} style={styles.primaryButton}>
<Text style={styles.primaryButtonText}>
{isSaving ? "Saving..." : "Save changes"}
return(
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={headerHeight}
style={styles.keyboardAvoider}
>
<View style={[styles.container, { backgroundColor: palette.background }]}>
<ScrollView
contentContainerStyle={[styles.formContent, { paddingBottom: insets.bottom + 112 }]}
keyboardShouldPersistTaps="handled"
>
<TextInput
editable={canEdit}
onChangeText={setTitle}
style={[styles.titleInput, { color: palette.text, borderColor: palette.border, backgroundColor: palette.input }]}
value={title}
placeholderTextColor={palette.mutedText}
/>
<Text style={[styles.signature, { color: palette.mutedText }]}>Created by {note.creatorLabel}</Text>
<Text style={[styles.signature, { color: palette.mutedText }]}>
Last changed {formatTimestamp(note.lastChangedAt)}
</Text>
<TextInput
editable={canEdit}
multiline
onChangeText={setContent}
style={[styles.contentInput, { color: palette.text, borderColor: palette.border, backgroundColor: palette.input }]}
textAlignVertical="top"
value={content}
placeholderTextColor={palette.mutedText}
/>
{!canEdit ? (
<Text style={[styles.readOnlyText, { color: palette.mutedText }]}>
Only the creator of this note can update or delete it.
</Text>
</Pressable>
<Pressable disabled={isDeleting} onPress={confirmDelete} style={styles.deleteButton}>
<Text style={styles.deleteButtonText}>
{isDeleting ? "Deleting..." : "Delete note"}
</Text>
</Pressable>
</View>
) : null}
</View>
);
) : null}
{localErrorMessage ? <Text style={styles.errorText}>{localErrorMessage}</Text> : null}
{!localErrorMessage && errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null}
{statusMessage ? <Text style={styles.successText}>{statusMessage}</Text> : null}
</ScrollView>
{canEdit ? (
<View style={[styles.actions, { bottom: insets.bottom + 16 }]}>
<Pressable disabled={isSaving} onPress={onSave} style={[styles.primaryButton, { backgroundColor: palette.accent }]}>
<Text style={[styles.primaryButtonText, { color: colorScheme === "dark" ? "#000" : "#fff" }]}>
{isSaving ? "Saving..." : "Save changes"}
</Text>
</Pressable>
<Pressable disabled={isDeleting} onPress={confirmDelete} style={[styles.deleteButton, { backgroundColor: palette.destructive }]}>
<Text style={styles.deleteButtonText}>
{isDeleting ? "Deleting..." : "Delete note"}
</Text>
</Pressable>
</View>
) : null}
</View>
</KeyboardAvoidingView>
)
}
const styles = StyleSheet.create(
{
container: { flex: 1, padding: 16, gap: 12 },
keyboardAvoider: { flex: 1 },
container: { flex: 1 },
formContent: { padding: 16, gap: 12 },
title: { fontSize: 22, fontWeight:"700" },
content: { fontSize: 16 },
titleInput: {
@@ -175,7 +196,6 @@ const styles = StyleSheet.create(
color: "#666",
},
contentInput: {
flex: 1,
minHeight: 200,
borderWidth: 1,
borderRadius: 8,
@@ -192,28 +212,41 @@ const styles = StyleSheet.create(
color: "#666",
},
actions: {
position: "absolute",
left: 16,
right: 16,
flexDirection: "row",
gap: 12,
},
primaryButton: {
flex: 1,
borderRadius: 12,
paddingVertical: 14,
alignItems: "center",
backgroundColor: "#111",
shadowColor: "#000",
shadowOpacity: 0.18,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 16,
elevation: 8,
},
primaryButtonText: {
color: "#fff",
fontSize: 16,
fontWeight: "700",
},
deleteButton: {
flex: 1,
borderRadius: 12,
paddingVertical: 14,
alignItems: "center",
backgroundColor: "#b71c1c",
shadowColor: "#000",
shadowOpacity: 0.18,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 16,
elevation: 8,
},
deleteButtonText: {
color: "#fff",
fontSize: 16,
fontWeight: "700",
},
});
})

View File

@@ -1,20 +1,23 @@
import { useMemo, useState } from "react";
import { FlatList, Pressable, StyleSheet, Text, View } from "react-native";
import { router } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useAuthContext } from "@/hooks/use-auth-context";
import { useNotes } from "@/src/notes/NotesContext";
import { useMemo, useState } from "react"
import { Appearance, FlatList, Pressable, StyleSheet, Text, View } from "react-native"
import { router } from "expo-router"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { useAuthContext } from "@/hooks/use-auth-context"
import { useNotes } from "@/src/notes/NotesContext"
import SignOutButton from '@/components/social-auth-buttons/sign-out-button'
import { useAppTheme } from "@/src/theme/AppThemeProvider"
type TabKey = "my-notes" | "work-notes"
export default function HomeScreen()
{
const { claims } = useAuthContext();
const { errorMessage, isLoading, notes } = useNotes();
const [activeTab, setActiveTab] = useState<TabKey>("my-notes");
const insets = useSafeAreaInsets();
const userId = claims?.sub;
const { claims } = useAuthContext()
const { errorMessage, isLoading, notes } = useNotes()
const [activeTab, setActiveTab] = useState<TabKey>("my-notes")
const insets = useSafeAreaInsets()
const { colorScheme, palette } = useAppTheme()
const userId = claims?.sub
const filteredNotes = useMemo(
() =>
@@ -22,7 +25,7 @@ export default function HomeScreen()
activeTab === "my-notes" ? note.createdBy === userId : note.createdBy !== userId
),
[activeTab, notes, userId]
);
)
const emptyText =
activeTab === "my-notes"
@@ -39,10 +42,10 @@ export default function HomeScreen()
return parsed.toLocaleString()
}
return (
<View style={styles.container}>
<View style={[styles.topBar, { paddingTop: insets.top + 8 }]}>
<Text style={styles.screenTitle}>FastNotes</Text>
return (
<View style={[styles.container, { backgroundColor: palette.background }]}>
<View style={[styles.topBar, { paddingTop: insets.top + 8, backgroundColor: palette.surface, borderBottomColor: palette.border }]}>
<Text style={[styles.screenTitle, { color: palette.text }]}>FastNotes</Text>
<SignOutButton />
</View>
@@ -51,12 +54,14 @@ return (
onPress={() => setActiveTab("my-notes")}
style={[
styles.tabButton,
{ backgroundColor: palette.elevated, borderColor: palette.border },
activeTab === "my-notes" ? styles.tabButtonActive : null,
]}
>
<Text
style={[
styles.tabButtonText,
{ color: palette.text },
activeTab === "my-notes" ? styles.tabButtonTextActive : null,
]}
>
@@ -67,12 +72,14 @@ return (
onPress={() => setActiveTab("work-notes")}
style={[
styles.tabButton,
{ backgroundColor: palette.elevated, borderColor: palette.border },
activeTab === "work-notes" ? styles.tabButtonActive : null,
]}
>
<Text
style={[
styles.tabButtonText,
{ color: palette.text },
activeTab === "work-notes" ? styles.tabButtonTextActive : null,
]}
>
@@ -94,7 +101,7 @@ return (
}
renderItem={({ item }) => (
<Pressable
style={styles.noteItem}
style={[styles.noteItem, { backgroundColor: palette.surface, borderColor: palette.border }]}
onPress={() =>
router.push({
pathname: "/detail",
@@ -102,10 +109,10 @@ return (
})
}
>
<Text style={styles.noteTitle}>{item.title}</Text>
<Text numberOfLines={2} style={styles.notePreview}>{item.content}</Text>
<Text style={styles.noteMeta}>Created by {item.creatorLabel}</Text>
<Text style={styles.noteMeta}>
<Text style={[styles.noteTitle, { color: palette.text }]}>{item.title}</Text>
<Text numberOfLines={2} style={[styles.notePreview, { color: palette.mutedText }]}>{item.content}</Text>
<Text style={[styles.noteMeta, { color: palette.mutedText }]}>Created by {item.creatorLabel}</Text>
<Text style={[styles.noteMeta, { color: palette.mutedText }]}>
Last changed {formatTimestamp(item.lastChangedAt)}
</Text>
</Pressable>
@@ -114,14 +121,14 @@ return (
{activeTab === "my-notes" ? (
<Pressable
style={[styles.fab, { bottom: insets.bottom + 24, right: insets.right + 40 }]}
style={[styles.fab, { bottom: insets.bottom + 24, right: insets.right + 40, backgroundColor: palette.accent }]}
onPress={() => router.push("/newNote")}
>
<Text style={styles.fabText}>+</Text>
<Text style={[styles.fabText, { color: colorScheme === "dark" ? "#000" : "#fff" }]}>+</Text>
</Pressable>
) : null}
</View>
);
)
}
const styles = StyleSheet.create({
@@ -132,6 +139,7 @@ const styles = StyleSheet.create({
alignItems: "center",
paddingHorizontal: 16,
paddingBottom: 8,
borderBottomWidth: 1,
},
screenTitle: {
fontSize: 24,
@@ -142,21 +150,21 @@ const styles = StyleSheet.create({
gap: 8,
paddingHorizontal: 16,
paddingBottom: 8,
paddingTop: 12
},
tabButton: {
flex: 1,
borderWidth: 1,
borderRadius: 999,
paddingVertical: 10,
alignItems: "center",
backgroundColor: "#e6e6e6",
},
tabButtonActive: {
backgroundColor: "#111",
backgroundColor: Appearance.getColorScheme() === "light" ? "#000000":"#8a8888",
},
tabButtonText: {
fontSize: 14,
fontWeight: "600",
color: "#111",
},
tabButtonTextActive: {
color: "#fff",
@@ -164,8 +172,8 @@ const styles = StyleSheet.create({
list: { padding: 16, gap: 12, paddingTop: 8 },
noteItem: { padding: 16, borderWidth: 1, borderRadius:12, gap: 8 },
noteTitle: { fontSize: 16, fontWeight: "600" },
notePreview: { fontSize: 14, color: "#444" },
noteMeta: { fontSize: 12, color: "#666" },
notePreview: { fontSize: 14 },
noteMeta: { fontSize: 12 },
emptyText: {
textAlign: "center",
paddingVertical: 32,
@@ -181,7 +189,11 @@ const styles = StyleSheet.create({
position: "absolute",
width: 56, height: 56, borderRadius: 28,
alignItems: "center", justifyContent: "center",
backgroundColor: "grey"
shadowColor: "#000",
shadowOpacity: 0.18,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 16,
elevation: 8,
},
fabText: { fontSize: 28, lineHeight: 28, fontWeight: "700"}
});
})

View File

@@ -1,13 +1,15 @@
import { useState } from 'react'
import { Link, Stack } from 'expo-router'
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'
import { Appearance, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'
import { supabase } from '@/libs/supabase'
import { useAppTheme } from '@/src/theme/AppThemeProvider'
export default function LoginScreen() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [isSubmitting, setIsSubmitting] = useState(false)
const { colorScheme, palette } = useAppTheme()
const onLogin = async () => {
if (!email.trim() || !password) {
@@ -33,30 +35,30 @@ export default function LoginScreen() {
return (
<>
<Stack.Screen options={{ title: 'Login' }} />
<View style={styles.container}>
<Text style={styles.title}>Login</Text>
<View style={[styles.container, { backgroundColor: palette.background }]}>
<Text style={[styles.title, { color: palette.text }]}>Login</Text>
<Text style={styles.label}>E-mail</Text>
<Text style={[styles.label, { color: palette.text }]}>E-mail</Text>
<TextInput
autoCapitalize="none"
autoComplete="email"
keyboardType="email-address"
onChangeText={setEmail}
placeholder="name@email.com"
placeholderTextColor="#666"
style={styles.input}
placeholderTextColor={palette.mutedText}
style={[styles.input, { color: palette.text, backgroundColor: palette.input, borderColor: palette.border }]}
value={email}
/>
<Text style={styles.label}>Password</Text>
<Text style={[styles.label, { color: palette.text }]}>Password</Text>
<TextInput
autoCapitalize="none"
autoComplete="password"
onChangeText={setPassword}
placeholder="Password"
placeholderTextColor="#666"
placeholderTextColor={palette.mutedText}
secureTextEntry
style={styles.input}
style={[styles.input, { color: palette.text, backgroundColor: palette.input, borderColor: palette.border }]}
value={password}
/>
@@ -79,7 +81,7 @@ export default function LoginScreen() {
</Pressable>
<Link href="/signup" style={styles.link}>
<Text style={styles.linkText}>Create a new account</Text>
<Text style={[styles.linkText, { color: colorScheme === "dark" ? "#8ab4ff" : "#0b57d0" }]}>Create a new account</Text>
</Link>
</View>
</>
@@ -92,26 +94,20 @@ const styles = StyleSheet.create({
justifyContent: 'center',
padding: 24,
gap: 12,
backgroundColor: '#f7f7f7',
},
title: {
fontSize: 28,
fontWeight: '700',
color: '#111',
},
label: {
fontSize: 14,
fontWeight: '600',
color: '#111',
},
input: {
borderWidth: 1,
borderColor: '#cfcfcf',
borderRadius: 8,
padding: 12,
fontSize: 16,
color: '#111',
backgroundColor: '#fff',
},
errorText: {
color: '#c62828',
@@ -121,7 +117,7 @@ const styles = StyleSheet.create({
borderRadius: 12,
paddingVertical: 14,
alignItems: 'center',
backgroundColor: '#111',
backgroundColor: Appearance.getColorScheme() === "light" ? '#000000':'#696969',
marginTop: 8,
},
loginButtonPressed: {

View File

@@ -2,12 +2,13 @@ import
{
StyleSheet, Text, View, KeyboardAvoidingView,
Platform, Pressable, TextInput, ScrollView
} from "react-native";
import { router, } from "expo-router";
import { useNotes } from "@/src/notes/NotesContext";
import { useState, useRef } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useHeaderHeight } from "@react-navigation/elements";
} from "react-native"
import { router, } from "expo-router"
import { useNotes } from "@/src/notes/NotesContext"
import { useState, useRef } from "react"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { useHeaderHeight } from "@react-navigation/elements"
import { useAppTheme } from "@/src/theme/AppThemeProvider"
@@ -20,6 +21,7 @@ export default function NewNoteScreen()
const [localErrorMessage, setLocalErrorMessage] = useState<string | null>(null)
const insets = useSafeAreaInsets()
const headerHeight = useHeaderHeight()
const { colorScheme, palette } = useAppTheme()
const [contentHeight, setContentHeight] = useState(160)
const scrollRef = useRef<ScrollView>(null)
@@ -43,22 +45,24 @@ export default function NewNoteScreen()
}
return (
<KeyboardAvoidingView style={{ flex: 1}} behavior={Platform.OS === "ios" ? "padding" : undefined}
<KeyboardAvoidingView
style={styles.keyboardAvoider}
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={headerHeight}>
<View style={styles.container}>
<ScrollView ref={scrollRef} contentContainerStyle={{ padding: 16, paddingBottom: 120, gap: 10}}
<View style={[styles.container, { backgroundColor: palette.background }]}>
<ScrollView ref={scrollRef} contentContainerStyle={[styles.formContent, { paddingBottom: insets.bottom + 112 }]}
keyboardShouldPersistTaps="handled">
<Text style={styles.label}>Title</Text>
<TextInput value={title} onChangeText={setTitle}
placeholder="Give it a title..." style={styles.input}
placeholder="Give it a title..." style={[styles.titleInput, { color: palette.text, borderColor: palette.border, backgroundColor: palette.input }]}
placeholderTextColor={palette.mutedText}
returnKeyType="next"/>
<Text style={styles.label}>Content</Text>
<TextInput value={content} onChangeText={setContent} placeholder="Write your note..."
style={[styles.input, { height: Math.max(160, contentHeight) }]} multiline
style={[styles.contentInput, { minHeight: Math.max(200, contentHeight), color: palette.text, borderColor: palette.border, backgroundColor: palette.input }]} multiline
placeholderTextColor={palette.mutedText}
textAlignVertical="top"
onContentSizeChange={(e) => {setContentHeight(e.nativeEvent.contentSize.height);
scrollRef.current?.scrollToEnd({ animated: true });
onContentSizeChange={(e) => {setContentHeight(e.nativeEvent.contentSize.height)
scrollRef.current?.scrollToEnd({ animated: true })
}}/>
{localErrorMessage ? (
@@ -70,59 +74,60 @@ export default function NewNoteScreen()
) : null}
</ScrollView>
<View>
<View style={[styles.actions, { bottom: insets.bottom + 16 }]}>
<Pressable disabled={isSaving} onPress={onSave}
style={[styles.saveFloating, { bottom: insets.bottom + 16 }]}>
<Text style={styles.saveFloatingText}>
{isSaving ? "Saving..." : "Save"}
style={[styles.saveButton, { backgroundColor: palette.accent }]}>
<Text style={[styles.saveFloatingText, { color: colorScheme === "dark" ? "#000" : "#fff" }]}>
{isSaving ? "Saving..." : "Save note"}
</Text>
</Pressable>
</View>
</View>
</KeyboardAvoidingView>
);
)
}
const styles = StyleSheet.create(
{
container: { flex: 1, gap: 10 },
label: { fontSize: 14, fontWeight: "600" },
input:
keyboardAvoider: { flex: 1 },
container: { flex: 1 },
formContent: { padding: 16, gap: 12 },
titleInput:
{
borderWidth: 1, borderRadius: 7,
padding: 12, fontSize: 16
borderWidth: 1,
borderRadius: 8,
padding: 12,
fontSize: 22,
fontWeight: "700",
},
saveBtn:
contentInput:
{
borderRadius: 12,
paddingVertical: 14, alignItems: "center",
backgroundColor: "grey"
borderWidth: 1,
borderRadius: 8,
padding: 12,
fontSize: 16,
},
saveText: { color: "white", fontSize: 16, fontWeight: "700"},
saveBar:
{
position: "absolute",
left: 0,
right: 0,
bottom: 0,
paddingTop: 12,
paddingHorizontal: 16,
backgroundColor: "white",
borderTopWidth: 1
},
saveFloating:
{
actions: {
position: "absolute",
left: 16,
right: 16,
flexDirection: "row",
gap: 12,
},
saveButton:
{
flex: 1,
borderRadius: 12,
paddingVertical: 14,
alignItems: "center",
backgroundColor: "#111"
shadowColor: "#000",
shadowOpacity: 0.18,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 16,
elevation: 8,
},
saveFloatingText:
{
color: "white",
fontSize: 16,
fontWeight: "700"
},
@@ -130,4 +135,4 @@ const styles = StyleSheet.create(
color: "#c62828"
},
}
);
)

View File

@@ -1,7 +1,8 @@
import { supabase } from "@/libs/supabase";
import { Link, Stack } from "expo-router";
import { useState } from "react";
import { supabase } from "@/libs/supabase"
import { Link, Stack } from "expo-router"
import { useState } from "react"
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'
import { useAppTheme } from "@/src/theme/AppThemeProvider"
export default function SignupScreen(){
const [email, setEmail] = useState('')
@@ -9,6 +10,7 @@ export default function SignupScreen(){
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [successMessage, setSuccessMessage] = useState<string | null>(null)
const [isSubmitting, setIsSubmitting] = useState(false)
const { colorScheme, palette } = useAppTheme()
const onSignup = async () => {
if(!email.trim() || !password){
@@ -56,30 +58,30 @@ export default function SignupScreen(){
return (
<>
<Stack.Screen options={{title: "Signup"}}/>
<View style={styles.container}>
<Text style={styles.title}>Sign up</Text>
<View style={[styles.container, { backgroundColor: palette.background }]}>
<Text style={[styles.title, { color: palette.text }]}>Sign up</Text>
<Text style={styles.label}>E-mail</Text>
<Text style={[styles.label, { color: palette.text }]}>E-mail</Text>
<TextInput
autoCapitalize="none"
autoComplete="email"
keyboardType="email-address"
onChangeText={setEmail}
placeholder="name@email.com"
placeholderTextColor="#666"
style={styles.input}
placeholderTextColor={palette.mutedText}
style={[styles.input, { color: palette.text, backgroundColor: palette.input, borderColor: palette.border }]}
value={email}
/>
<Text style={styles.label} >Password</Text>
<Text style={[styles.label, { color: palette.text }]} >Password</Text>
<TextInput
autoCapitalize="none"
autoComplete="password"
onChangeText={setPassword}
placeholder="Password"
placeholderTextColor="#666"
placeholderTextColor={palette.mutedText}
secureTextEntry
style={styles.input}
style={[styles.input, { color: palette.text, backgroundColor: palette.input, borderColor: palette.border }]}
value={password}
/>
@@ -106,7 +108,7 @@ export default function SignupScreen(){
</Pressable>
<Link href="/login" style={styles.link}>
<Text style={styles.linkText}>Already have an account? Log in</Text>
<Text style={[styles.linkText, { color: colorScheme === "dark" ? "#8ab4ff" : "#0b57d0" }]}>Already have an account? Log in</Text>
</Link>
</View>
@@ -121,26 +123,20 @@ const styles = StyleSheet.create({
justifyContent: 'center',
padding: 24,
gap: 12,
backgroundColor: '#f7f7f7',
},
title: {
fontSize: 28,
fontWeight: '700',
color: '#111',
},
label: {
fontSize: 14,
fontWeight: '600',
color: '#111',
},
input: {
borderWidth: 1,
borderColor: '#cfcfcf',
borderRadius: 8,
padding: 12,
fontSize: 16,
color: '#111',
backgroundColor: '#fff',
},
errorText: {
color: '#c62828',