import { Component , OnDestroy , OnInit , ElementRef } from '@angular/core' ; import { NavParams , PopoverController , ModalController } from 'ionic-angular' ; import { MeteorObservable } from 'meteor-rxjs' ; import { _ } from 'meteor/underscore' ; import * as Moment from 'moment' ; import { Observable , Subscription , Subscriber } from 'rxjs' ; import { Messages } from '../../../../imports/collections' ; import { Chat , Message , MessageType , Location } from '../../../../imports/models' ; import { PictureService } from '../../services/picture' ; import { MessagesAttachmentsComponent } from './messages-attachments' ; import { MessagesOptionsComponent } from './messages-options' ; import { ShowPictureComponent } from './show-picture' ; import template from './messages.html' ; @ Component ( { template } ) export class MessagesPage implements OnInit , OnDestroy { selectedChat : Chat ; title : string ; picture : string ; messagesDayGroups ; message : string = '' ; autoScroller : MutationObserver ; scrollOffset = 0 ; senderId : string ; loadingMessages : boolean ; messagesComputation : Subscription ; messagesBatchCounter : number = 0 ; constructor ( navParams , private el , private popoverCtrl , private pictureService , private modalCtrl ) { this . selectedChat = < Chat > navParams . get ( 'chat' ) ; this . title = this . selectedChat . title ; this . picture = this . selectedChat . picture ; this . senderId = Meteor . userId ( ) ; } private get messagesPageContent ( ) : Element { return this . el . nativeElement . querySelector ( '.messages-page-content' ) ; } private get messagesList ( ) : Element { return this . messagesPageContent . querySelector ( '.messages' ) ; } private get scroller ( ) : Element { return this . messagesList . querySelector ( '.scroll-content' ) ; } ngOnInit ( ) { this . autoScroller = this . autoScroll ( ) ; this . subscribeMessages ( ) ; MeteorObservable . call ( 'countMessages' ) . subscribe ( ( messagesCount ) => { Observable . fromEvent ( this . scroller , 'scroll' ) . takeUntil ( this . autoRemoveScrollListener ( messagesCount ) ) . filter ( ( ) => ! this . scroller . scrollTop ) . filter ( ( ) => ! this . loadingMessages ) . forEach ( ( ) => this . subscribeMessages ( ) ) ; } ) ; } ngOnDestroy ( ) { this . autoScroller . disconnect ( ) ; } subscribeMessages ( ) { this . loadingMessages = true ; this . scrollOffset = this . scroller . scrollHeight ; MeteorObservable . subscribe ( 'messages' , this . selectedChat . _id , ++ this . messagesBatchCounter ) . subscribe ( ( ) => { if ( ! this . messagesComputation ) { this . messagesComputation = this . autorunMessages ( ) ; } this . loadingMessages = false ; } ) ; } autorunMessages ( ) { return MeteorObservable . autorun ( ) . subscribe ( ( ) => { this . messagesDayGroups = this . findMessagesDayGroups ( ) ; } ) ; } autoRemoveScrollListener < T > ( messagesCount ) { return Observable . create ( ( observer ) => { Messages . find ( ) . subscribe ( { next : ( messages ) => { if ( messagesCount !== messages . length ) { return ; } observer . next ( ) ; observer . complete ( ) ; } , error : ( e ) => { observer . error ( e ) ; } } ) ; } ) ; } showOptions ( ) { const popover = this . popoverCtrl . create ( MessagesOptionsComponent , { chat : this . selectedChat } , { cssClass : 'options-popover messages-options-popover' } ) ; popover . present ( ) ; } findMessagesDayGroups ( ) { return Messages . find ( { chatId : this . selectedChat . _id } , { sort : { createdAt : 1 } } ) . map ( ( messages ) => { const format = 'D MMMM Y' ; messages . forEach ( ( message ) => { message . ownership = this . senderId == message . senderId ? 'mine' : 'other' ; return message ; } ) ; const groupedMessages = _ . groupBy ( messages , ( message ) => { return Moment ( message . createdAt ) . format ( format ) ; } ) ; return Object . keys ( groupedMessages ) . map ( ( timestamp ) => { return { timestamp : timestamp , messages : groupedMessages [ timestamp ] , today : Moment ( ) . format ( format ) === timestamp } ; } ) ; } ) ; } autoScroll ( ) { const autoScroller = new MutationObserver ( this . scrollDown . bind ( this ) ) ; autoScroller . observe ( this . messagesList , { childList : true , subtree : true } ) ; return autoScroller ; } scrollDown ( ) { if ( this . loadingMessages ) { return ; } this . scroller . scrollTop = this . scroller . scrollHeight - this . scrollOffset ; this . scrollOffset = 0 ; } onInputKeypress ( { keyCode } ) { if ( keyCode === 13 ) { this . sendTextMessage ( ) ; } } sendTextMessage ( ) { if ( ! this . message ) { return ; } MeteorObservable . call ( 'addMessage' , MessageType . TEXT , this . selectedChat . _id , this . message ) . zone ( ) . subscribe ( ( ) => { this . message = '' ; } ) ; } sendLocationMessage ( location ) { MeteorObservable . call ( 'addMessage' , MessageType . LOCATION , this . selectedChat . _id , `${ location . lat },${ location . lng },${ location . zoom }` ) . zone ( ) . subscribe ( ( ) => { this . message = '' ; } ) ; } showAttachments ( ) { const popover = this . popoverCtrl . create ( MessagesAttachmentsComponent , { chat : this . selectedChat } , { cssClass : 'attachments-popover' } ) ; popover . onDidDismiss ( ( params ) => { if ( params ) { if ( params . messageType === MessageType . LOCATION ) { const location = params . selectedLocation ; this . sendLocationMessage ( location ) ; } else if ( params . messageType === MessageType . PICTURE ) { const blob = params . selectedPicture ; this . sendPictureMessage ( blob ) ; } } } ) ; popover . present ( ) ; } sendPictureMessage ( blob ) { this . pictureService . upload ( blob ) . then ( ( picture ) => { MeteorObservable . call ( 'addMessage' , MessageType . PICTURE , this . selectedChat . _id , picture . url ) . zone ( ) . subscribe ( ) ; } ) ; } getLocation ( locationString ) { const splitted = locationString . split ( ',' ) . map ( Number ) ; return < Location > { lat : splitted [ 0 ] , lng : splitted [ 1 ] , zoom : Math . min ( splitted [ 2 ] || 0 , 19 ) } ; } showPicture ( { target } ) { const modal = this . modalCtrl . create ( ShowPictureComponent , { pictureSrc : ( < HTMLImageElement > target ) . src } ) ; modal . present ( ) ; } }