- Add tab-based navigation (Chat, Calendar) using Expo-Router - Create auth screens (login, register) as skeletons - Add dynamic routes for event detail and note editing - Implement service layer (ApiClient, AuthService, EventService, ChatService) - Add Zustand stores (AuthStore, EventsStore) for state management - Create EventCard and EventConfirmDialog components - Update CLAUDE.md with new frontend architecture documentation - Add Zustand and FlashList to technology stack
350 lines
7.1 KiB
TypeScript
350 lines
7.1 KiB
TypeScript
import { View, Text, TextInput } from "react-native";
|
|
import currentTheme from "../../Themes";
|
|
import { useState } from "react";
|
|
import Header from "../../components/Header";
|
|
import BaseBackground from "../../components/BaseBackground";
|
|
import { FlashList } from "@shopify/flash-list";
|
|
|
|
// TODO: better shadows for everything
|
|
// (maybe with extra library because of differences between android and ios)
|
|
// TODO: max width for messages
|
|
// TODO: create new messages
|
|
|
|
type BubbleSide = "left" | "right";
|
|
type ChatMessageProps = {
|
|
side: BubbleSide;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
|
|
type MessageData = {
|
|
id: string;
|
|
side: BubbleSide;
|
|
width: number;
|
|
height: number;
|
|
};
|
|
|
|
// NOTE: only for testing
|
|
const getRandomInt = (min: number, max: number) => {
|
|
min = Math.ceil(min);
|
|
max = Math.floor(max);
|
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
};
|
|
|
|
const randomWidth = () => getRandomInt(100, 400);
|
|
const randomHeight = () => getRandomInt(50, 100);
|
|
|
|
const messages: MessageData[] = [
|
|
// {{{
|
|
{
|
|
id: "1",
|
|
side: "left",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "2",
|
|
side: "right",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "3",
|
|
side: "left",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "4",
|
|
side: "right",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "5",
|
|
side: "left",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "6",
|
|
side: "right",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "7",
|
|
side: "left",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "8",
|
|
side: "right",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "9",
|
|
side: "left",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
{
|
|
id: "10",
|
|
side: "right",
|
|
width: randomWidth(),
|
|
height: randomHeight(),
|
|
},
|
|
// {
|
|
// id: "11",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "12",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "13",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "14",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "15",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "16",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "17",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "18",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "19",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "20",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "21",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "22",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "23",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "24",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "25",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "26",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "27",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "28",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "29",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "30",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "31",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "32",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "33",
|
|
// side: "left",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
// {
|
|
// id: "34",
|
|
// side: "right",
|
|
// width: randomWidth(),
|
|
// height: randomHeight(),
|
|
// },
|
|
//, width: randomWidth, height: getRandomInt(50, 500) }}}
|
|
];
|
|
|
|
const Chat = () => {
|
|
return (
|
|
<BaseBackground>
|
|
<ChatHeader />
|
|
<FlashList
|
|
data={messages}
|
|
renderItem={({ item }) => (
|
|
<ChatMessage
|
|
side={item.side}
|
|
width={item.width}
|
|
height={item.height}
|
|
/>
|
|
)}
|
|
maintainVisibleContentPosition={{
|
|
autoscrollToBottomThreshold: 0.2,
|
|
startRenderingFromBottom: true,
|
|
}}
|
|
keyExtractor={(item) => item.id}
|
|
// extraData={selectedId} might need this later for re-rendering
|
|
/>
|
|
<ChatInput />
|
|
</BaseBackground>
|
|
);
|
|
};
|
|
|
|
const ChatHeader = () => {
|
|
return (
|
|
<Header className="flex flex-row items-center">
|
|
<View
|
|
className="ml-3 w-12 h-12 rounded-3xl border border-solid"
|
|
style={{
|
|
backgroundColor: currentTheme.placeholderBg,
|
|
borderColor: currentTheme.primeFg,
|
|
}}
|
|
></View>
|
|
<Text className="text-lg pl-3">CalChat</Text>
|
|
<View
|
|
className="h-2 bg-black"
|
|
style={{
|
|
shadowColor: "#000",
|
|
shadowOffset: {
|
|
width: 0,
|
|
height: 5,
|
|
},
|
|
shadowOpacity: 0.34,
|
|
shadowRadius: 6.27,
|
|
|
|
elevation: 10,
|
|
}}
|
|
/>
|
|
</Header>
|
|
);
|
|
};
|
|
|
|
const ChatInput = () => {
|
|
const [text, onChangeText] = useState("Nachricht");
|
|
|
|
return (
|
|
<View className="flex flex-row w-full h-8 my-2">
|
|
<TextInput
|
|
className="w-4/5 h-full border border-solid rounded-2xl mx-2 px-2"
|
|
style={{
|
|
backgroundColor: currentTheme.messageBorderBg,
|
|
}}
|
|
onChangeText={onChangeText}
|
|
value={text}
|
|
/>
|
|
<View
|
|
className="w-8 h-full rounded-2xl"
|
|
style={{
|
|
backgroundColor: currentTheme.placeholderBg,
|
|
}}
|
|
></View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const ChatMessage = (props: ChatMessageProps) => {
|
|
const borderColor =
|
|
props.side === "left" ? currentTheme.chatBot : currentTheme.primeFg;
|
|
const selfSide =
|
|
props.side === "left"
|
|
? "self-start ml-2 rounded-bl-sm"
|
|
: "self-end mr-2 rounded-br-sm";
|
|
|
|
return (
|
|
<View
|
|
className={
|
|
`bg-white border-2 border-solid rounded-xl my-2 ` + `${selfSide}`
|
|
}
|
|
style={{
|
|
width: props.width,
|
|
height: props.height,
|
|
borderColor: borderColor,
|
|
|
|
elevation: 8,
|
|
}}
|
|
>
|
|
<Text className="p-1">Lorem Ipsum Dolor sit amet</Text>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default Chat;
|