1/**
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import HiLog from '../utils/HiLog';
17import common from '../data/commonData';
18import ConversationListModel from '../model/ConversationListModel';
19import ConversationService from './ConversationService';
20import ContactService from './ContactsService';
21import commonService from './CommonService';
22import telephoneUtils from '../utils/TelephoneUtil';
23import LooseObject from '../data/LooseObject'
24
25const TAG = 'ConversationListService';
26
27export default class ConversationListService {
28    private static instance: ConversationListService;
29    private conversationListModel: ConversationListModel = new ConversationListModel();
30
31    private constructor() {
32    }
33
34    public static getInstance(): ConversationListService {
35        if (ConversationListService.instance == null) {
36            ConversationListService.instance = new ConversationListService();
37        }
38        return ConversationListService.instance;
39    }
40
41    public insertSession(valueBucket, callback, context): void {
42        let mmsContext = context ? context : globalThis.mmsContext;
43        if (globalThis.DataWorker != null) {
44            globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.insertSession, {
45                valueBucket: valueBucket,
46                context: mmsContext
47            }, res => {
48                if (callback) {
49                    callback(res);
50                }
51            });
52        } else {
53            this.conversationListModel.insertSession(valueBucket, callback, mmsContext);
54        }
55        globalThis.needToUpdate = true;
56    }
57
58    public deleteSessionByCondition(actionData, callback, context): void {
59        let mmsContext = context ? context : globalThis.mmsContext;
60        if (globalThis.DataWorker != null) {
61            globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.deleteSessionByCondition, {
62                actionData: actionData,
63                context: mmsContext
64            }, res => {
65                if (callback) {
66                    callback(res);
67                }
68            });
69        } else {
70            this.conversationListModel.deleteSessionByCondition(actionData, callback, mmsContext);
71        }
72        globalThis.needToUpdate = true;
73    }
74
75    public updateSessionByCondition(actionData, valueBucket, callback, context): void {
76        let mmsContext = context ? context : globalThis.mmsContext;
77        if (globalThis.DataWorker != null) {
78            globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.updateSessionByCondition, {
79                actionData: actionData,
80                valueBucket: valueBucket,
81                context: mmsContext
82            }, res => {
83                if (callback) {
84                    callback(res);
85                }
86            });
87        } else {
88            this.conversationListModel.updateSessionByCondition(actionData, valueBucket, callback, mmsContext);
89        }
90        globalThis.needToUpdate = true;
91    }
92
93    public querySessionByCondition(actionData, callback, context): void {
94        let mmsContext = context ? context : globalThis.mmsContext;
95        if (globalThis.DataWorker != null) {
96            globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.querySessionByCondition, {
97                actionData: actionData,
98                context: mmsContext
99            }, res => {
100                callback(res);
101            });
102        } else {
103            this.conversationListModel.querySessionByCondition(actionData, callback, mmsContext);
104        }
105    }
106
107    public querySessionSizeByCondition(actionData, callback, context): void {
108        let mmsContext = context ? context : globalThis.mmsContext;
109        if (globalThis.DataWorker != null) {
110            globalThis.DataWorker.sendRequest(common.RUN_IN_WORKER_METHOD.querySessionSizeByCondition, {
111                actionData: actionData,
112                context: mmsContext
113            }, res => {
114                callback(res);
115            });
116        } else {
117            this.conversationListModel.querySessionSizeByCondition(actionData, callback, mmsContext);
118        }
119    }
120
121    public async getSessionListSize(actionData, context): Promise<number> {
122        let condition: LooseObject = {};
123        condition.smsType = actionData.smsType;
124        let result: Promise<number> = new Promise((resolve) => {
125            this.querySessionSizeByCondition(condition, res => {
126                let size: number = 0;
127                if (res.code == common.int.SUCCESS && res.abilityResult != null) {
128                    size = res.abilityResult;
129                }
130                resolve(size);
131            }, context);
132        });
133        return result;
134    }
135
136    public getSessionListResult(actionData, context): Promise<LooseObject> {
137        let result: Promise<LooseObject> = new Promise((resolve) => {
138            this.querySessionByCondition(actionData, res => {
139                let result: LooseObject = {
140                    'messageList': [],
141                    'telephones': []
142                };
143                if (res.code == common.int.SUCCESS && res.abilityResult != null) {
144                    result = this.getConvertSessionListResult(res.abilityResult);
145                }
146                resolve(result);
147            }, context)
148        });
149        return result;
150    }
151
152    public isExistNoticeMessage(context): Promise<boolean> {
153        let result: Promise<boolean> = new Promise((resolve) => {
154            let actionData: LooseObject = {};
155            actionData.smsType = common.sms_type.NOTICE;
156            this.querySessionSizeByCondition(actionData, res => {
157                let isExistNoticeMessage: boolean = false;
158                if (res.code == common.int.SUCCESS && res.abilityResult != null && res.abilityResult > 0) {
159                    isExistNoticeMessage = true;
160                }
161                resolve(isExistNoticeMessage);
162            }, context);
163        });
164        return result;
165    }
166
167    public querySessionList(actionData, callback, context): void {
168        let result: LooseObject = {};
169        let queryPromise: Promise<LooseObject> = this.getSessionListResult(actionData, context);
170        let countPromise: Promise<number> = this.getSessionListSize(actionData, context);
171        let noticePromise: Promise<boolean> = this.isExistNoticeMessage(context);
172        Promise.all([queryPromise, countPromise, noticePromise]).then(res => {
173            result.code = common.int.SUCCESS;
174            result.total = res[1];
175            result.hasInfoMsg = res[2];
176            result.response = [];
177            HiLog.i(TAG, 'querySessionList, sessionList.length: ' + res[0].messageList.length + ', total=' +
178            result.total + ', hasInfoMsg=' + result.hasInfoMsg);
179            if (result.total > 0) {
180                this.dealContactsName(res[0].telephones, actionData, res[0].messageList, res => {
181                    result.response = res;
182                    callback(result);
183                }, context);
184            } else {
185                callback(result);
186            }
187        }).catch(error => {
188            HiLog.e(TAG, 'querySessionList, error: ' + JSON.stringify(error));
189            result.code = common.int.FAILURE;
190            callback(result);
191        });
192    }
193
194    private getConvertSessionListResult(sessionList): LooseObject {
195        let result: LooseObject = {
196            'messageList': [],
197            'telephones': []
198        };
199        if (sessionList == null) {
200            return result;
201        }
202        let messageList: Array<LooseObject> = [];
203        let telephones: Array<LooseObject> = [];
204        for (let session of sessionList) {
205            let item: LooseObject = {};
206            item.name = common.string.EMPTY_STR;
207            item.contactsNum = session.contactsNum;
208            item.content = session.content;
209            item.countOfUnread = session.unreadCount;
210            if (session.smsType == common.sms_type.COMMON) {
211                item.icon = 'icon/user_avatar_full_fill.svg';
212            } else {
213                item.icon = 'icon/entrance_icon01.svg';
214            }
215            item.smsType = session.smsType;
216            item.isCbChecked = false;
217            item.isLock = false;
218            item.sendingFailed = session.sendStatus == common.int.SEND_MESSAGE_FAILED ? true : false;
219            item.telephone = session.telephone;
220            if (item.contactsNum > 1) {
221                let telephoneSplit = item.telephone.split(common.string.COMMA);
222                for (let item of telephoneSplit) {
223                    telephones.push(item);
224                }
225            } else {
226                telephones.push(item.telephone);
227            }
228            item.telephoneFormat = session.telephoneFormat;
229            item.threadId = session.id;
230            item.timeMillisecond = session.time;
231            item.isDraft = session.hasDraft == 1 ? true : false;
232            item.isLock = session.hasLock == 1 ? true : false;
233            item.time = common.string.EMPTY_STR;
234            item.messageCount = session.messageCount;
235            item.hasMms = session.hasMms == 1 ? true : false;
236            item.hasAttachment = session.hasAttachment == 1 ? true : false;
237            messageList.push(item);
238        }
239        result.messageList = messageList;
240        result.telephones = telephones;
241        return result;
242    }
243
244    private dealContactsName(telephones, actionData, sessionLists, callback, context) {
245        if (telephones.length == 0) {
246            HiLog.w(TAG, 'dealContactsName, has no telephones');
247            callback(sessionLists);
248            return;
249        }
250        actionData.telephones = telephones;
251        actionData.hasDelete = '0';
252        ContactService.getInstance().queryContactDataByCondition(actionData, res => {
253            if (res.code == common.int.FAILURE || res.abilityResult.length == 0) {
254                HiLog.w(TAG, 'dealContactsName, has no contacts');
255                callback(sessionLists);
256            } else {
257                callback(this.buildSessionNames(res.abilityResult, sessionLists));
258            }
259        }, context);
260    }
261
262    private buildSessionNames(contacts, sessionLists) {
263        let telephoneMap = new Map();
264        for (let item of contacts) {
265            if (item.displayName == common.string.EMPTY_STR) {
266                telephoneMap.set(item.detailInfo, item.detailInfo);
267            } else {
268                telephoneMap.set(item.detailInfo, item.displayName);
269            }
270        }
271        for (let session of sessionLists) {
272            if (session.contactsNum > 1) {
273                this.dealMultiName(session, telephoneMap);
274            } else if (telephoneMap.has(session.telephone)) {
275                session.name = telephoneMap.get(session.telephone);
276            }
277        }
278        return sessionLists;
279    }
280
281    private dealMultiName(session, telephoneMap): void {
282        let telephones: Array<string> = session.telephone.split(common.string.COMMA);
283        let name: string = common.string.EMPTY_STR;
284        for (let telephone of telephones) {
285            if (telephoneMap.has(telephone)) {
286                name = name + telephoneMap.get(telephone) + common.string.COMMA;
287            } else {
288                name = name + telephone + common.string.COMMA;
289            }
290        }
291        session.name = name.substring(0, name.length - 1);
292    }
293
294    public statisticalData(callBack, context): void {
295        let normalPromise = new Promise<LooseObject>((resolve) => {
296            ConversationService.getInstance().statisticalData(res => {
297                if (res.code == common.int.SUCCESS) {
298                    resolve(res.abilityResult);
299                } else {
300                    HiLog.w(TAG, 'statisticalData, failed');
301                }
302            }, context);
303        });
304        let notifyPromise = new Promise<number>((resolve) => {
305            ConversationService.getInstance().statisticsUnreadNotify(res => {
306                resolve(res);
307            }, context);
308        });
309        let result: LooseObject = {};
310        Promise.all([normalPromise, notifyPromise]).then(res => {
311            let normalResult: LooseObject = res[0];
312            let notifyResult: number = res[1];
313            result.code = common.int.SUCCESS;
314            let response: LooseObject = {};
315            response.totalListCount = normalResult.totalListCount;
316            response.unreadCount = normalResult.totalListCount - notifyResult;
317            response.unreadTotalOfInfo = notifyResult;
318            result.response = response;
319            callBack(result);
320        }).catch(error => {
321            HiLog.e(TAG, 'statisticalData, failed: ' + JSON.stringify(error));
322            result.code = common.int.FAILURE;
323            callBack(result);
324        });
325    }
326
327    public deleteMessageById(actionData, callback, context): void {
328        // Deletes data from the session list.
329        this.deleteSessionByCondition(actionData, callback, context);
330        // Deletes data from the information list.
331        ConversationService.getInstance().deleteSmsMmsInfoByCondition(actionData, null, null);
332    }
333
334    public markAllToRead(actionData) {
335        actionData.unreadCount_greaterThan = 0;
336        let sessionValueBucket: LooseObject = {
337            'unread_count': 0
338        };
339        this.updateSessionByCondition(actionData, sessionValueBucket, null, null);
340
341        actionData.hasRead = common.is_read.UN_READ;
342        let smsMmsInfoValueBucket: LooseObject = {
343            'is_read': common.is_read.READ
344        };
345        ConversationService.getInstance().updateSmsMmsInfoByCondition(actionData, smsMmsInfoValueBucket, null, null);
346    }
347
348    /**
349     * Adding a session draft list
350     *
351     * @param valueBucket New Data
352     * @callback callback
353     */
354    public insertSessionDraft(actionData, callback, context): void {
355        let param: LooseObject = this.dealSendResults(actionData);
356        // Check whether a session list has been created
357        this.querySessionByTelephone(param.telephone, res => {
358            if (res.code == common.int.SUCCESS && res.response.id <= 0) {
359                // If you modify the recipient in draft state and save the modification again, you need to delete the
360                // unnecessary session draft before modification.
361                if (actionData.threadId != 0 && actionData.isDraft) {
362                    this.deleteSessionByCondition(actionData, null, null);
363                }
364                this.dealInsertSession(param, actionData, callback, context);
365            } else {
366                this.deleteDraftDataOrUpdate(actionData, res.response, param, callback, context);
367            }
368            globalThis.needToUpdate = true;
369        }, context);
370    }
371
372    public dealInsertSession(param, actionData, callback, context): void {
373        let valueBucket: LooseObject = {
374            'telephone': param.telephone,
375            'content': param.content,
376            'contacts_num': param.contactsNum,
377            'sms_type': param.smsType,
378            'unread_count': 0,
379            'sending_status': common.int.SEND_MESSAGE_SENDING,
380            'has_draft': common.has_draft.HAVE,
381            'time': param.timestamp,
382            'has_mms': param.hasMms,
383            'has_attachment': param.hasAttachment,
384        }
385        this.insertSession(valueBucket, sessionResult => {
386            // Invoke the SMS database to insert SMS messages.
387            let sessionId: number = sessionResult.abilityResult;
388            ConversationService.getInstance().dealInsertMessageDetail(param, actionData, sessionId, res => {
389                callback();
390            }, context);
391        }, context);
392    }
393
394    public deleteDraftDataOrUpdate(actionData, response, param, callback, context): void {
395        if (actionData.groupId > 0) {
396            ConversationService.getInstance().deleteSmsMmsInfoByCondition(actionData, null, null);
397        }
398        if (actionData.content != common.string.EMPTY_STR || actionData.mmsSource.length > 0) {
399            // Save New Draft
400            this.updateDraftData(response, param, actionData, callback, context);
401        } else {
402            if (callback) {
403                callback();
404            }
405        }
406    }
407
408    public updateDraftData(response, param, actionData, callback, context): void {
409        let valueBucket: LooseObject = {
410            'content': param.content,
411            'has_draft': common.has_draft.HAVE,
412            'time': new Date().getTime(),
413            'has_attachment': param.hasAttachment,
414            'has_mms': param.hasMms,
415        }
416        if (response == undefined || response == null) {
417            return
418        }
419        let sessionId: number = response.id;
420        let condition: LooseObject = {};
421        condition.threadId = sessionId;
422        this.updateSessionByCondition(condition, valueBucket, null, null);
423        ConversationService.getInstance().dealInsertMessageDetail(param, actionData, sessionId, res => {
424            if (callback) {
425                callback();
426            }
427        }, context);
428    }
429
430    public dealSendResults(actionData): LooseObject {
431        let contactsNum: number = 1;
432        let telephone: string = common.string.EMPTY_STR;
433        if (actionData.isNewMsg) {
434            let selectContacts = actionData.selectContacts;
435            if (selectContacts.length > 1) {
436                for (let contact of selectContacts) {
437                    telephone = telephone + contact.telephone + common.string.COMMA;
438                }
439                // If it fails, then the session list turns out to be a failure.
440                telephone = telephone.substring(0, telephone.length - 1);
441                contactsNum = selectContacts.length;
442            } else if (selectContacts.length == 1) {
443                telephone = selectContacts[0]?.telephone;
444            }
445            let receiveContactValue = actionData.receiveContactValue;
446            if (receiveContactValue != common.string.EMPTY_STR) {
447                telephone = actionData.receiveContactValue;
448            }
449        } else {
450            telephone = actionData.telephone;
451        }
452        let smsType: number = common.sms_type.COMMON;
453        if (contactsNum == 1 && telephoneUtils.judgeIsInfoMsg(telephone)) {
454            smsType = common.sms_type.NOTICE;
455        }
456        let sendResult: LooseObject = {
457            telephone: telephone,
458            content: actionData.content,
459            sendStatus: common.int.SEND_DRAFT
460        }
461        actionData.sendResults = [sendResult];
462        let timestamp = new Date().getTime();
463        let result: LooseObject = {};
464        result.contactsNum = contactsNum;
465        result.telephone = telephoneUtils.dealTelephoneSort(telephone);
466        result.content = actionData.content;
467        if (actionData.isMms) {
468            result.content = commonService.getMmsContent(actionData.mmsSource);
469        }
470        result.sendStatus = common.int.SEND_MESSAGE_SENDING;
471        result.smsType = smsType;
472        result.timestamp = timestamp;
473        result.hasMms = actionData.isMms ? 1 : 0;
474        result.hasAttachment = actionData.hasAttachment ? 1 : 0;
475
476        return result;
477    }
478
479    public querySessionByTelephone(telephone, callback, context): void {
480        let result: LooseObject = {};
481        if (telephone == null) {
482            HiLog.w(TAG, 'querySessionByTelephone, telephone is null!');
483            result.code = common.int.FAILURE;
484            callback(result);
485        } else {
486            HiLog.i(TAG, 'querySessionByTelephone, telephone != null');
487            let actionData: LooseObject = {};
488            actionData.telephone = telephone;
489            this.querySessionByCondition(actionData, res => {
490                if (res.code == common.int.FAILURE) {
491                    callback(res);
492                } else {
493                    result.code = common.int.SUCCESS;
494                    result.response = res.abilityResult[0];
495                    callback(result);
496                }
497            }, context);
498        }
499    }
500
501    public dealMessageLockContent(actionData, callback, context): void {
502        let threadIds = actionData.threadIds;
503        let length: number = threadIds.length;
504        let count: number = 0;
505        for (let id of threadIds) {
506            actionData.threadId = id;
507            if (!actionData.isMessageDetail) {
508                actionData.hasLock = 1;
509            }
510            ConversationService.getInstance().queryMessageDetail(actionData, res => {
511                if (res.code == common.int.SUCCESS && res.response.length > 0) {
512                    count++;
513                    actionData.mmsList = res.response;
514                    this.updateLastItemContent(actionData, null, null);
515                }
516                if (count == length) {
517                    callback(common.int.SUCCESS);
518                }
519            }, context);
520        }
521    }
522
523    public updateLastItemContent(actionData, callback, context): void {
524        let valueBucket: LooseObject = {
525            'content': common.string.EMPTY_STR,
526            'sending_status': common.int.SEND_MESSAGE_FAILED,
527            'has_mms': common.has_mms.NO,
528            'has_attachment': common.has_attachment.NO,
529            'message_count': 0,
530            'unread_count': 0
531        };
532        let length: number = actionData.mmsList.length;
533        if (length > 0) {
534            let item: LooseObject = actionData.mmsList[length - 1];
535            valueBucket.content = item.isMsm ? commonService.getMmsContent(item.mms) : item.content;
536            valueBucket.sending_status = item.sendStatus;
537            valueBucket.has_mms = item.isMsm ? common.has_mms.HAVE : common.has_mms.NO;
538            valueBucket.has_attachment = item.isMsm ? common.has_attachment.HAVE : common.has_attachment.NO;
539            valueBucket.message_count = length;
540        }
541        let condition: LooseObject = {
542            threadId: actionData.threadId
543        };
544        this.updateSessionByCondition(condition, valueBucket, callback, context);
545    }
546}