Skip to content

Commit

Permalink
feat: implemented chat drawer
Browse files Browse the repository at this point in the history
  • Loading branch information
Vali-98 committed Sep 22, 2024
1 parent e842faf commit 9dba26f
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 65 deletions.
9 changes: 4 additions & 5 deletions app/components/ChatMenu/ChatMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,18 @@ const ChatMenu = () => {
}}
/>

{!chat ? (
<CharacterList showHeader={!showDrawer} />
) : (
{!chat && <CharacterList showHeader={!showDrawer} />}
{chat && (
<View style={styles.container}>
<ChatWindow />

<View style={styles.inputContainer}>
<OptionsMenu menuRef={menuRef} />
<OptionsMenu menuRef={menuRef} showChats={setShowChats} />
<ChatInput />
</View>
</View>
)}
<ChatsDrawer booleans={[showChats, setShowChats]} />
{showChats && <ChatsDrawer booleans={[showChats, setShowChats]} />}
<SettingsDrawer booleans={[showDrawer, setShowDrawer]} />
</SafeAreaView>
</GestureDetector>
Expand Down
211 changes: 156 additions & 55 deletions app/components/ChatMenu/ChatsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Style } from '@globals'
import { SetStateAction, useEffect } from 'react'
import { Ionicons, AntDesign } from '@expo/vector-icons'
import { Characters, Chats, Style } from '@globals'
import { useLiveQuery } from 'drizzle-orm/expo-sqlite'
import { SetStateAction } from 'react'
import {
Text,
GestureResponderEvent,
TouchableOpacity,
StyleSheet,
View,
BackHandler,
FlatList,
Alert,
} from 'react-native'
import Animated, {
Easing,
Expand All @@ -20,33 +23,126 @@ type ChatsDrawerProps = {
booleans: [boolean, (b: boolean | SetStateAction<boolean>) => void]
}

type ListItem = {
id: number
character_id: number
create_date: Date
name: string
last_modified: null | number
entryCount: number
}

const ChatsDrawer: React.FC<ChatsDrawerProps> = ({ booleans: [showModal, setShowModal] }) => {
const { charId } = Characters.useCharacterCard((state) => ({ charId: state.id }))

const { data } = useLiveQuery(Chats.db.query.chatListQuery(charId ?? 0))

const { deleteChat, loadChat, currentChatId } = Chats.useChat((state) => ({
deleteChat: state.delete,
loadChat: state.load,
currentChatId: state.data?.id,
}))

const handleOverlayClick = (e: GestureResponderEvent) => {
if (e.target === e.currentTarget) setShowModal(false)
}

if (showModal)
const handleLoadChat = async (chatId: number) => {
await loadChat(chatId)
setShowModal(false)
}

const handleCreateChat = async () => {
if (charId)
Chats.db.mutate.createChat(charId).then((chatId) => {
if (chatId) handleLoadChat(chatId)
})
}

const handleDeleteChat = (item: ListItem) => {
Alert.alert(
`Delete Chat`,
`Are you sure you want to delete this chat file: '${item.name}'?`,
[
{
text: 'Cancel',
onPress: () => {},
style: 'cancel',
},
{
text: 'Confirm',
onPress: async () => {
await deleteChat(item.id)
if (charId && currentChatId === item.id) {
const returnedChatId = await Chats.db.query.chatNewest(charId)
const chatId = returnedChatId
? returnedChatId
: await Chats.db.mutate.createChat(charId)
chatId && (await loadChat(chatId))
}
},
style: 'destructive',
},
]
)
}

const renderChat = (item: ListItem, index: number) => {
const date = new Date(item.last_modified ?? 0)
return (
<View style={styles.absolute}>
<Animated.View
entering={FadeIn.duration(200)}
exiting={FadeOut.duration(300)}
style={styles.absolute}>
<TouchableOpacity
activeOpacity={1}
onPress={handleOverlayClick}
style={styles.backdrop}
/>
</Animated.View>

<Animated.View
style={styles.drawer}
entering={SlideInRight.duration(200).easing(Easing.out(Easing.quad))}
exiting={SlideOutRight.duration(300).easing(Easing.out(Easing.quad))}>
<Text>Chat History</Text>
</Animated.View>
<View style={item.id === currentChatId ? styles.chatItemActive : styles.chatItem}>
<TouchableOpacity
style={{ flex: 1, paddingHorizontal: 2, paddingVertical: 8 }}
onPress={() => handleLoadChat(item.id)}>
<Text style={styles.title}>{item.name}</Text>
<View style={{ flexDirection: 'row', alignItems: 'center', marginTop: 16 }}>
<Ionicons
name="chatbox"
size={20}
color={Style.getColor('primary-text2')}
/>
<Text style={styles.smallTextChat}>{item.entryCount}</Text>
<Text style={styles.smallText}>{date.toLocaleDateString()}</Text>
<Text style={styles.smallText}>{date.toLocaleTimeString()}</Text>
</View>
</TouchableOpacity>
<TouchableOpacity style={styles.editButton} onPress={() => handleDeleteChat(item)}>
<AntDesign color={Style.getColor('primary-text2')} name="edit" size={26} />
</TouchableOpacity>
</View>
)
}

return (
<View style={styles.absolute}>
<Animated.View
entering={FadeIn.duration(200)}
exiting={FadeOut.duration(300)}
style={styles.absolute}>
<TouchableOpacity
activeOpacity={1}
onPress={handleOverlayClick}
style={styles.backdrop}
/>
</Animated.View>

<Animated.View
style={styles.drawer}
entering={SlideInRight.duration(200).easing(Easing.out(Easing.quad))}
exiting={SlideOutRight.duration(300).easing(Easing.out(Easing.quad))}>
<Text style={styles.drawerTitle}>Chats</Text>
<FlatList
style={styles.listContainer}
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item, index }) => renderChat(item, index)}
/>
<TouchableOpacity onPress={handleCreateChat} style={styles.newButton}>
<Text style={{ color: Style.getColor('primary-surface2') }}>New Chat</Text>
</TouchableOpacity>
</Animated.View>
</View>
)
}

export default ChatsDrawer
Expand Down Expand Up @@ -75,55 +171,60 @@ const styles = StyleSheet.create({
elevation: 20,
position: 'absolute',
height: '100%',
padding: 32,
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 32,
},

userContainer: {
flexDirection: 'row',
paddingBottom: 24,
paddingTop: 40,
padding: 16,
drawerTitle: {
color: Style.getColor('primary-text2'),
fontSize: 18,
paddingLeft: 16,
},

buttonContainer: {
flexDirection: 'row',
marginLeft: 12,
title: {
color: Style.getColor('primary-text1'),
fontSize: 16,
},

button: {
borderColor: Style.getColor('primary-surface3'),
borderWidth: 2,
marginRight: 10,
borderRadius: 4,
padding: 8,
listContainer: {
flex: 1,
marginTop: 16,
marginBottom: 8,
borderRadius: 8,
},

userName: {
fontSize: 20,
marginTop: 4,
chatItem: {
paddingHorizontal: 8,
flexDirection: 'row',
flex: 1,
marginBottom: 8,
marginLeft: 12,
color: Style.getColor('primary-text1'),
borderRadius: 8,
borderWidth: 2,
borderColor: Style.getColor('primary-surface1'),
},

userImage: {
width: 80,
height: 80,
borderRadius: 20,
borderColor: Style.getColor('primary-brand'),
chatItemActive: {
paddingHorizontal: 8,
flexDirection: 'row',
flex: 1,
marginBottom: 8,
borderRadius: 8,
borderWidth: 2,
borderColor: Style.getColor('primary-brand'),
},
smallText: { color: Style.getColor('primary-text2'), marginLeft: 12 },
smallTextChat: { color: Style.getColor('primary-text2'), marginLeft: 4 },

largeButtonText: {
fontSize: 18,
paddingVertical: 12,
paddingLeft: 15,
color: Style.getColor('primary-text1'),
editButton: {
paddingHorizontal: 8,
justifyContent: 'center',
},

largeButton: {
paddingLeft: 15,
flexDirection: 'row',
newButton: {
backgroundColor: Style.getColor('primary-brand'),
alignItems: 'center',
paddingVertical: 8,
borderRadius: 12,
},
})
5 changes: 3 additions & 2 deletions app/components/ChatMenu/OptionsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ type MenuData = {

type OptionsMenuProps = {
menuRef: React.MutableRefObject<Menu | null>
showChats: (b: boolean) => void
}

const OptionsMenu: React.FC<OptionsMenuProps> = ({ menuRef }) => {
const OptionsMenu: React.FC<OptionsMenuProps> = ({ menuRef, showChats }) => {
const router = useRouter()
const { unloadCharacter } = Characters.useCharacterCard((state) => ({
unloadCharacter: state.unloadCard,
Expand All @@ -49,7 +50,7 @@ const OptionsMenu: React.FC<OptionsMenuProps> = ({ menuRef }) => {
},
{
callback: () => {
router.push('/ChatSelector')
showChats(true)
},
text: 'Chat History',
button: 'paperclip',
Expand Down
33 changes: 30 additions & 3 deletions app/constants/Chat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { db as database } from '@db'
import { chatEntries, chatSwipes, chats } from 'db/schema'
import { eq } from 'drizzle-orm'
import { count, desc, eq, getTableColumns } from 'drizzle-orm'
import { create } from 'zustand'

import { API } from './API'
Expand Down Expand Up @@ -37,8 +37,9 @@ export type ChatEntry = {

export type ChatData = {
id: number
createDate: Date
create_date: Date
character_id: number
name: string
messages: ChatEntry[] | undefined
}

Expand Down Expand Up @@ -317,13 +318,39 @@ export namespace Chats {
return chatIds?.[chatIds?.length - 1]?.id
}

export const chatList = async (charId: number) => {
export const chatListOld = async (charId: number) => {
const chatIds = await database.query.chats.findMany({
where: eq(chats.character_id, charId),
})
return chatIds
}

export const chatList = async (charId: number) => {
const result = await database
.select({
...getTableColumns(chats),
entryCount: count(chatEntries.id),
})
.from(chats)
.leftJoin(chatEntries, eq(chats.id, chatEntries.chat_id))
.groupBy(chats.id)
.where(eq(chats.character_id, charId))
return result
}

export const chatListQuery = (charId: number) => {
return database
.select({
...getTableColumns(chats),
entryCount: count(chatEntries.id),
})
.from(chats)
.leftJoin(chatEntries, eq(chats.id, chatEntries.chat_id))
.groupBy(chats.id)
.where(eq(chats.character_id, charId))
.orderBy(desc(chats.last_modified))
}

export const chatExists = async (chatId: number) => {
return await database.query.chats.findFirst({ where: eq(chats.id, chatId) })
}
Expand Down

0 comments on commit 9dba26f

Please sign in to comment.