Adding notes app in React Native

This commit is contained in:
Christopher Sanden
2026-02-23 20:23:20 +01:00
commit 31e8ece40d
37 changed files with 13904 additions and 0 deletions

31
FastNotes/app/_layout.tsx Normal file
View File

@@ -0,0 +1,31 @@
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';
import { NotesProvider } from "@/src/notes/NotesContext"
import { useColorScheme } from '@/hooks/use-color-scheme';
;
export default function RootLayout() {
const colorScheme = useColorScheme();
/*TODO
Sort ThemeProvider to work with dark theme on iOS
*/
return (
<SafeAreaProvider>
<NotesProvider>
<ThemeProvider value={colorScheme === 'dark' ? DefaultTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="index" options={{ title: 'FastNotes' }} />
<Stack.Screen name="newNote" options={{ title: 'New Note'}}/>
<Stack.Screen name="detail" options={{ title: 'Detail'}}/>
</Stack>
<StatusBar style="auto" />
</ThemeProvider>
</NotesProvider>
</SafeAreaProvider>
);
}

26
FastNotes/app/detail.tsx Normal file
View File

@@ -0,0 +1,26 @@
import { StyleSheet, Text, View } from "react-native";
import { useLocalSearchParams } from "expo-router";
export default function DetailScreen()
{
const { title, content } = useLocalSearchParams<
{
title?: string;
content?: string;
}>();
return(
<View style={styles.container}>
<Text style={styles.title}>{title ?? "(No title)"}</Text>
<Text style={styles.content}>{content ?? ""}</Text>
</View>
);
}
const styles = StyleSheet.create(
{
container: { flex: 1, padding: 16, gap: 12 },
title: { fontSize: 22, fontWeight:"700" },
content: { fontSize: 16 }
});

55
FastNotes/app/index.tsx Normal file
View File

@@ -0,0 +1,55 @@
import { FlatList, Pressable, StyleSheet, Text, View } from "react-native";
import { router } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useNotes } from "@/src/notes/NotesContext";
export default function HomeScreen()
{
const { notes } = useNotes();
const insets = useSafeAreaInsets();
return (
<View style={styles.container}>
<FlatList
data={notes}
keyExtractor={(n) => n.id}
contentContainerStyle={[styles.list, { paddingBottom: 120 }]}
renderItem={({ item }) => (
<Pressable
style={styles.noteItem}
onPress={() =>
router.push({
pathname: "/detail",
params: { title: item.title, content: item.content },
})
}
>
<Text style={styles.noteTitle}>{item.title}</Text>
</Pressable>
)}
/>
<Pressable
style={[styles.fab, { bottom: insets.bottom + 24, right: insets.right + 40 }]}
onPress={() => router.push("/newNote")}
>
<Text style={styles.fabText}>+</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
list: { padding: 16, gap: 12, paddingTop: 20 },
noteItem: { padding: 16, borderWidth: 1, borderRadius:12 },
noteTitle: { fontSize: 16, fontWeight: "600" },
fab:
{
position: "absolute",
width: 56, height: 56, borderRadius: 28,
alignItems: "center", justifyContent: "center",
backgroundColor: "grey"
},
fabText: { fontSize: 28, lineHeight: 28, fontWeight: "700"}
});

106
FastNotes/app/newNote.tsx Normal file
View File

@@ -0,0 +1,106 @@
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";
export default function NewNoteScreen()
{
const { addNote } = useNotes();
const[title, setTitle] = useState("");
const [content, setContent] = useState("");
const insets = useSafeAreaInsets();
const headerHeight = useHeaderHeight();
const [contentHeight, setContentHeight] = useState(160);
const scrollRef = useRef<ScrollView>(null);
const onSave = () =>
{
if(!title.trim() && !content.trim()) return;
addNote(title, content);
router.back();
};
return (
<KeyboardAvoidingView style={{ flex: 1}} behavior={Platform.OS === "ios" ? "padding" : undefined}
keyboardVerticalOffset={headerHeight}>
<View style={styles.container}>
<ScrollView ref={scrollRef} contentContainerStyle={{ padding: 16, paddingBottom: 120, gap: 10}}
keyboardShouldPersistTaps="handled">
<Text style={styles.label}>Title</Text>
<TextInput value={title} onChangeText={setTitle}
placeholder="Give it a title..." style={styles.input}
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
textAlignVertical="top"
onContentSizeChange={(e) => {setContentHeight(e.nativeEvent.contentSize.height);
scrollRef.current?.scrollToEnd({ animated: true });
}}/>
</ScrollView>
<View>
<Pressable onPress={onSave}
style={[styles.saveFloating, { bottom: insets.bottom + 16 }]}>
<Text style={styles.saveFloatingText}>Save</Text>
</Pressable>
</View>
</View>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create(
{
container: { flex: 1, gap: 10 },
label: { fontSize: 14, fontWeight: "600" },
input:
{
borderWidth: 1, borderRadius: 7,
padding: 12, fontSize: 16
},
saveBtn:
{
borderRadius: 12,
paddingVertical: 14, alignItems: "center",
backgroundColor: "grey"
},
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:
{
position: "absolute",
left: 16,
right: 16,
borderRadius: 12,
paddingVertical: 14,
alignItems: "center",
backgroundColor: "#111"
},
saveFloatingText:
{
color: "white",
fontSize: 16,
fontWeight: "700"
}
}
);