@@ -7,42 +7,77 @@ import { ChatView, ChatViewProps } from './chatView';
77import { Message } from '../../types/message' ;
88import { updateReadTimestamp } from '../../shared/storage' ;
99import { fetchYouTubeVideoDetails , parseVideoIdFromUrl } from '../../shared/utils/youtube' ;
10+ import { User } from '../../types/user' ;
11+ import { Chat , ChatType } from '../../types/chat' ;
12+ import { ViewType } from '../../services/stateService' ;
1013
1114export class ChatController {
12- private view : ChatView ;
15+ private view : ChatView | null = null ;
1316 private messages : Message [ ] = [ ] ;
1417 private oldestMessageDoc : QueryDocumentSnapshot | null = null ;
1518 private isLoadingOlder = false ;
1619 private listeners : Unsubscribe [ ] = [ ] ;
17- private readonly chatId : string ;
20+ private readonly chat : Chat ;
21+ private partner : User | null = null ;
1822
1923 constructor ( private container : HTMLElement ) {
2024 const context = stateService . activeChatContext ;
2125 if ( ! context ) {
2226 throw new Error ( "ChatController initialized without an active chat context." ) ;
2327 }
24- this . chatId = context . chatId ;
25- updateReadTimestamp ( this . chatId ) ;
28+ this . chat = context . chat ;
29+ updateReadTimestamp ( this . chat . id ) ;
30+
31+ this . initializeController ( ) ;
32+ }
33+
34+ private async initializeController ( ) : Promise < void > {
35+ if ( this . chat . type !== ChatType . GROUP ) {
36+ const partnerUid = this . chat . participants . find ( p => p !== authService . currentUser ! . uid ) ;
37+ if ( partnerUid ) {
38+ this . partner = await chatService . getUserProfile ( partnerUid ) ;
39+ }
40+ }
2641
2742 const props : ChatViewProps = {
28- partner : context . partner ,
43+ chat : this . chat ,
44+ partner : this . partner ,
2945 back : ( ) => stateService . closeChat ( ) ,
3046 sendMessage : this . sendMessage . bind ( this ) ,
3147 shareVideo : this . shareVideo . bind ( this ) ,
3248 loadOlderMessages : this . loadOlderMessages . bind ( this ) ,
33- ignoreUser : this . ignoreUser . bind ( this ) ,
3449 getVideoId : ( ) => parseVideoIdFromUrl ( window . location . href ) ,
50+ ignoreUser : this . chat . type !== ChatType . GROUP && this . partner ? this . ignoreUser . bind ( this , this . partner . uid ) : undefined ,
51+ leaveGroup : this . chat . type === ChatType . GROUP ? this . leaveGroup . bind ( this ) : undefined ,
52+ addMember : this . chat . type === ChatType . GROUP ? this . addMember . bind ( this ) : undefined ,
53+ editGroupInfo : this . chat . type === ChatType . GROUP ? this . editGroupInfo . bind ( this ) : undefined ,
54+ deleteGroup : this . chat . type === ChatType . GROUP ? this . deleteGroup . bind ( this ) : undefined ,
3555 } ;
3656
3757 this . view = new ChatView ( this . container , props ) ;
38- this . fetchInitialMessages ( ) ;
58+ await this . fetchInitialMessages ( ) ;
59+ }
60+
61+ private editGroupInfo ( ) : void {
62+ if ( this . chat . type !== ChatType . GROUP ) {
63+ throw new Error ( "Edit group info is only available for group chats." ) ;
64+ }
65+ stateService . setView ( ViewType . EDIT_GROUP_INFO ) ;
66+ }
67+
68+ private addMember ( ) : void {
69+ if ( this . chat . type !== ChatType . GROUP ) {
70+ throw new Error ( "Add member is only available for group chats." ) ;
71+ }
72+ stateService . setView ( ViewType . ADD_MEMBER ) ;
3973 }
4074
4175 private async fetchInitialMessages ( ) : Promise < void > {
42- const { messages, oldestDoc } = await chatService . fetchInitialMessages ( this . chatId ) ;
76+ if ( ! this . view ) return ;
77+ const { messages, oldestDoc } = await chatService . fetchInitialMessages ( this . chat . id ) ;
4378 this . messages = messages ;
4479 this . oldestMessageDoc = oldestDoc ;
45- this . view . renderMessages ( this . messages , 'bottom' ) ;
80+ this . view . renderMessages ( this . messages , this . chat . type , 'bottom' ) ;
4681 this . view . scrollToBottom ( ) ;
4782 this . listenForNewMessages ( ) ;
4883 }
@@ -52,15 +87,16 @@ export class ChatController {
5287 const lastTimestamp = ( latestMessage && latestMessage . timestamp instanceof Timestamp ) ? latestMessage . timestamp : null ;
5388
5489 const newMessagesListener = chatService . listenToNewMessages (
55- this . chatId ,
90+ this . chat . id ,
5691 lastTimestamp ,
5792 ( newMsgs ) => {
93+ if ( ! this . view ) return ;
5894 const trulyNewMsgs = newMsgs . filter ( msg => msg . from !== authService . currentUser ?. uid ) ;
59-
95+
6096 if ( trulyNewMsgs . length > 0 ) {
6197 this . messages . push ( ...trulyNewMsgs ) ;
62- this . view . renderMessages ( trulyNewMsgs , 'bottom' , true ) ;
63- updateReadTimestamp ( this . chatId ) ;
98+ this . view . renderMessages ( trulyNewMsgs , this . chat . type , 'bottom' , true ) ;
99+ updateReadTimestamp ( this . chat . id ) ;
64100 }
65101 }
66102 ) ;
@@ -69,10 +105,11 @@ export class ChatController {
69105
70106 private async sendMessage ( text : string ) : Promise < void > {
71107 this . addOptimisticMessage ( { text } ) ;
72- await chatService . addMessage ( this . chatId , { text } ) ;
108+ await chatService . addMessage ( this . chat . id , { text } ) ;
73109 }
74110
75111 private async shareVideo ( includeTimestamp : boolean ) : Promise < void > {
112+ if ( ! this . view ) return ;
76113 const videoId = parseVideoIdFromUrl ( window . location . href ) ;
77114 if ( ! videoId ) return ;
78115
@@ -81,9 +118,9 @@ export class ChatController {
81118 const player = document . getElementById ( "movie_player" ) as any ;
82119 const timestamp = includeTimestamp && player ? parseInt ( player . getCurrentTime ( ) . toFixed ( 0 ) ) : undefined ;
83120 const videoData = await fetchYouTubeVideoDetails ( videoId , timestamp ) ;
84-
121+
85122 this . addOptimisticMessage ( { video : videoData } ) ;
86- await chatService . addMessage ( this . chatId , { video : videoData } ) ;
123+ await chatService . addMessage ( this . chat . id , { video : videoData } ) ;
87124 } catch ( error ) {
88125 console . error ( "Failed to share video:" , error ) ;
89126 alert ( "Could not share video." ) ;
@@ -93,6 +130,7 @@ export class ChatController {
93130 }
94131
95132 private addOptimisticMessage ( data : { text ?: string ; video ?: any } ) : void {
133+ if ( ! this . view ) return ;
96134 const myUid = authService . currentUser ! . uid ;
97135 const optimisticMessage : Message = {
98136 id : `optimistic_${ Date . now ( ) } ` ,
@@ -101,23 +139,23 @@ export class ChatController {
101139 ...data ,
102140 } ;
103141 this . messages . push ( optimisticMessage ) ;
104- this . view . renderMessages ( [ optimisticMessage ] , 'bottom' , true ) ;
142+ this . view . renderMessages ( [ optimisticMessage ] , this . chat . type , 'bottom' , true ) ;
105143 }
106-
144+
107145 private async loadOlderMessages ( ) : Promise < void > {
108- if ( this . isLoadingOlder || ! this . oldestMessageDoc ) return ;
146+ if ( ! this . view || this . isLoadingOlder || ! this . oldestMessageDoc ) return ;
109147 this . isLoadingOlder = true ;
110-
111- const { messages : olderMessages , oldestDoc : newOldestDoc } = await chatService . fetchOlderMessages ( this . chatId , this . oldestMessageDoc ) ;
112-
148+
149+ const { messages : olderMessages , oldestDoc : newOldestDoc } = await chatService . fetchOlderMessages ( this . chat . id , this . oldestMessageDoc ) ;
150+
113151 if ( olderMessages . length > 0 ) {
114152 this . messages = [ ...olderMessages , ...this . messages ] ;
115153 this . oldestMessageDoc = newOldestDoc ;
116- this . view . renderMessages ( olderMessages , 'top' ) ;
154+ this . view . renderMessages ( olderMessages , this . chat . type , 'top' ) ;
117155 } else {
118156 this . oldestMessageDoc = null ;
119157 }
120-
158+
121159 this . isLoadingOlder = false ;
122160 }
123161
@@ -126,8 +164,38 @@ export class ChatController {
126164 stateService . closeChat ( ) ;
127165 }
128166
167+ private async leaveGroup ( ) : Promise < void > {
168+ if ( confirm ( 'Are you sure you want to leave this group?' ) ) {
169+ try {
170+ await chatService . leaveChat ( this . chat . id ) ;
171+ stateService . setView ( ViewType . DIALOGS ) ;
172+ } catch ( error ) {
173+ console . error ( "Failed to leave group:" , error ) ;
174+ alert ( "Could not leave the group. Please try again." ) ;
175+ }
176+ }
177+ }
178+
179+ private async deleteGroup ( ) : Promise < void > {
180+ const userInput = prompt ( `This action cannot be undone. All messages will be permanently deleted. To confirm, please type the group name "${ this . chat . name } ".` ) ;
181+
182+ if ( userInput === this . chat . name ) {
183+ try {
184+ await chatService . deleteGroup ( this . chat . id ) ;
185+ stateService . setView ( ViewType . DIALOGS ) ;
186+ } catch ( error ) {
187+ console . error ( "Failed to delete group:" , error ) ;
188+ alert ( "Could not delete the group. Please try again." ) ;
189+ }
190+ } else if ( userInput !== null ) {
191+ alert ( 'Confirmation text did not match. Deletion canceled.' ) ;
192+ }
193+ }
194+
129195 public destroy ( ) : void {
130- this . view . destroy ( ) ;
196+ this . view ?. destroy ( ) ;
197+ this . view = null ;
131198 this . listeners . forEach ( unsub => unsub ( ) ) ;
199+ this . listeners = [ ] ;
132200 }
133201}
0 commit comments