~renatofilho/messaging-app/fix-1489330

« back to all changes in this revision

Viewing changes to src/qml/Messages.qml

  • Committer: Renato Araujo Oliveira Filho
  • Date: 2016-01-14 21:41:45 UTC
  • mfrom: (458.1.26 messaging-app)
  • Revision ID: renato.filho@canonical.com-20160114214145-60n2tvfljoydequ8
Trunk merged.

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
 
35
35
    // this property can be overriden by the user using the account switcher,
36
36
    // in the suru divider
37
 
    property QtObject account: mainView.account
38
 
 
 
37
    property string accountId: ""
 
38
    property QtObject account: getCurrentAccount()
 
39
    property bool phoneAccount: isPhoneAccount()
39
40
    property variant participants: []
40
41
    property variant participantIds: []
41
42
    property bool groupChat: participants.length > 1
55
56
    property var pendingEventsToMarkAsRead: []
56
57
    property bool reloadFilters: false
57
58
    // to be used by tests as variant does not work with autopilot
 
59
    property bool userTyping: false
 
60
    property QtObject chatEntry: !account ? null : chatManager.chatEntryForParticipants(account.accountId, participants, true)
58
61
    property string firstParticipantId: participantIds.length > 0 ? participantIds[0] : ""
59
62
    property variant firstParticipant: participants.length > 0 ? participants[0] : null
60
63
    property var threads: []
 
64
    property var accountsModel: getAccountsModel()
 
65
    function getAccountsModel() {
 
66
        var accounts = []
 
67
        // on new chat dialogs display all possible accounts
 
68
        if (accountId == "" && participants.length === 0) {
 
69
            for (var i in telepathyHelper.activeAccounts) {
 
70
                accounts.push(telepathyHelper.activeAccounts[i])
 
71
            }
 
72
            // suru divider must be empty if there is only one sim card
 
73
            if (accounts.length == 1 && accounts[0].type == AccountEntry.PhoneAccount) {
 
74
                return []
 
75
            }
 
76
            return accounts
 
77
        }
 
78
 
 
79
        var tmpAccount = telepathyHelper.accountForId(messages.accountId)
 
80
        // on generic accounts we don't give the option to switch to another account
 
81
        if (tmpAccount && tmpAccount.type == AccountEntry.GenericAccount) {
 
82
            return [tmpAccount]
 
83
        }
 
84
 
 
85
        // if we get here, this is a regular sms conversation. just
 
86
        // add the available phone accounts next
 
87
        for (var i in telepathyHelper.activeAccounts) {
 
88
            var account = telepathyHelper.activeAccounts[i]
 
89
            if (account.type == AccountEntry.PhoneAccount) {
 
90
                accounts.push(account)
 
91
            }
 
92
        }
 
93
 
 
94
        return accounts
 
95
    }
 
96
 
 
97
    function getSectionsModel() {
 
98
        var accountNames = []
 
99
        // suru divider must be empty if there is only one sim card
 
100
        if (messages.accountsModel.length == 1 &&
 
101
                messages.accountsModel[0].type == AccountEntry.PhoneAccount) {
 
102
            return []
 
103
        }
 
104
 
 
105
        for (var i in messages.accountsModel) {
 
106
            accountNames.push(messages.accountsModel[i].displayName)
 
107
        }
 
108
        return accountNames.length > 0 ? accountNames : []
 
109
    }
 
110
 
 
111
    function getSelectedIndex() {
 
112
        if (accountId == "" && participants.length === 0) {
 
113
            // if this is a new message, just pre select the the 
 
114
            // default phone account for messages if available
 
115
            if (multiplePhoneAccounts && telepathyHelper.defaultMessagingAccount) {
 
116
                for (var i in messages.accountsModel) {
 
117
                    if (telepathyHelper.defaultMessagingAccount == messages.accountsModel[i]) {
 
118
                        return i
 
119
                    }
 
120
                }
 
121
            }
 
122
            // otherwise pre-select the first available phone account if any
 
123
            for (var i in messages.accountsModel) {
 
124
                if (messages.accountsModel[i].type == AccountEntry.PhoneAccount) {
 
125
                    return i
 
126
                }
 
127
            }
 
128
            // otherwise select none
 
129
            return -1
 
130
        }
 
131
 
 
132
        // if we get here, just pre-select the account that is set in messages.account
 
133
        return accountIndex(messages.account)
 
134
    }
 
135
 
 
136
    function accountIndex(account) {
 
137
        var index = -1;
 
138
        for (var i in messages.accountsModel) {
 
139
            if (messages.accountsModel[i] == account) {
 
140
                index = i;
 
141
                break;
 
142
            }
 
143
        }
 
144
        return index;
 
145
    }
 
146
 
 
147
    function getCurrentAccount() {
 
148
        if (messages.accountId !== "") {
 
149
            var tmpAccount = telepathyHelper.accountForId(messages.accountId)
 
150
            // if the selected account is a phone account, check if there is a default
 
151
            // phone account for messages
 
152
            if (tmpAccount && tmpAccount.type == AccountEntry.PhoneAccount) {
 
153
                if (telepathyHelper.defaultMessagingAccount) {
 
154
                    for (var i in messages.accountsModel) {
 
155
                        if (messages.accountsModel[i] == telepathyHelper.defaultMessagingAccount) {
 
156
                            return telepathyHelper.defaultMessagingAccount
 
157
                        }
 
158
                    }
 
159
                }
 
160
                for (var i in messages.accountsModel) {
 
161
                    if (messages.accountsModel[i].type == AccountEntry.PhoneAccount) {
 
162
                        return messages.accountsModel[i]
 
163
                    }
 
164
                }
 
165
            }
 
166
            for (var i in messages.accountsModel) {
 
167
                if (tmpAccount.accountId == messages.accountId) {
 
168
                    return tmpAccount
 
169
                }
 
170
            }
 
171
            return null
 
172
        } else {
 
173
            return mainView.account
 
174
        }
 
175
    }
 
176
 
 
177
    function isPhoneAccount() {
 
178
        var tmpAccount = telepathyHelper.accountForId(accountId)
 
179
        return (!tmpAccount || tmpAccount.type == AccountEntry.PhoneAccount || tmpAccount.type == AccountEntry.MultimediaAccount)
 
180
    }
 
181
 
 
182
    Connections {
 
183
        target: telepathyHelper
 
184
        onSetupReady: {
 
185
            // force reevaluation
 
186
            messages.account = Qt.binding(getCurrentAccount)
 
187
            messages.phoneAccount = Qt.binding(isPhoneAccount)
 
188
            head.sections.model = Qt.binding(getSectionsModel)
 
189
            head.sections.selectedIndex = Qt.binding(getSelectedIndex)
 
190
        }
 
191
    }
 
192
 
 
193
 
 
194
    Connections {
 
195
        target: chatManager
 
196
        onChatEntryCreated: {
 
197
            // TODO: track using chatId and not participants
 
198
            if (accountId == account.accountId && 
 
199
                firstParticipant && participants[0] == firstParticipant.identifier) {
 
200
                messages.chatEntry = chatEntry
 
201
            }
 
202
        }
 
203
        onChatsChanged: {
 
204
            for (var i in chatManager.chats) {
 
205
                var chat = chatManager.chats[i]
 
206
                // TODO: track using chatId and not participants
 
207
                if (chat.account.accountId == account.accountId &&
 
208
                    firstParticipant && chat.participants[0] == firstParticipant.identifier) {
 
209
                    messages.chatEntry = chat
 
210
                    return
 
211
                }
 
212
            }
 
213
            messages.chatEntry = null
 
214
        }
 
215
    }
 
216
 
 
217
    Timer {
 
218
        id: typingTimer
 
219
        interval: 6000
 
220
        onTriggered: {
 
221
            messages.userTyping = false;
 
222
        }
 
223
    }
 
224
 
 
225
    Repeater {
 
226
        model: messages.chatEntry ? messages.chatEntry.chatStates : null
 
227
        Item {
 
228
            function processChatState() {
 
229
                if (modelData.state == ChatEntry.ChannelChatStateComposing) {
 
230
                    messages.userTyping = true
 
231
                    typingTimer.start()
 
232
                } else {
 
233
                    messages.userTyping = false
 
234
                }
 
235
            }
 
236
            Component.onCompleted: processChatState()
 
237
            Connections {
 
238
                target: modelData
 
239
                onStateChanged: processChatState()
 
240
            }
 
241
        }
 
242
    }
 
243
 
 
244
    MessagesHeader {
 
245
        id: header
 
246
        width: parent ? parent.width - units.gu(2) : undefined
 
247
        height: units.gu(5)
 
248
        title: messages.title
 
249
        subtitle: {
 
250
            if (userTyping) {
 
251
                return i18n.tr("Typing..")
 
252
            }
 
253
            switch (presenceRequest.type) {
 
254
            case PresenceRequest.PresenceTypeAvailable:
 
255
                return i18n.tr("Online")
 
256
            case PresenceRequest.PresenceTypeOffline:
 
257
                return i18n.tr("Offline")
 
258
            case PresenceRequest.PresenceTypeAway:
 
259
                return i18n.tr("Away")
 
260
            case PresenceRequest.PresenceTypeBusy:
 
261
                return i18n.tr("Busy")
 
262
            default:
 
263
                return ""
 
264
            }
 
265
        }
 
266
        visible: true
 
267
    }
 
268
 
 
269
    head {
 
270
        id: head
 
271
        sections.model: getSectionsModel()
 
272
        sections.selectedIndex: getSelectedIndex()
 
273
    }
 
274
 
61
275
 
62
276
    function addAttachmentsToModel(transfer) {
63
277
        for (var i in transfer.items) {
111
325
        multiRecipient.forceActiveFocus()
112
326
    }
113
327
 
114
 
    function sendMessage(text, participantIds, attachments) {
 
328
    function sendMessage(text, participantIds, attachments, properties) {
 
329
        if (typeof(properties) === 'undefined') {
 
330
            properties = {}
 
331
        }
 
332
 
115
333
        // check if at least one account is selected
116
334
        if (!messages.account) {
117
335
            Qt.inputMethod.hide()
154
372
        for (var i=0; i < eventModel.count; i++) {
155
373
            var event = eventModel.get(i)
156
374
            if (event.senderId == "self" && event.accountId != messages.account.accountId) {
 
375
                var tmpAccount = telepathyHelper.accountForId(event.accountId)
 
376
                if (!tmpAccount || (tmpAccount.type == AccountEntry.MultimediaAccount && messages.account.type == AccountEntry.PhoneAccount)) {
 
377
                    // we don't add the information event if the last outgoing message 
 
378
                    // was a fallback to a multimedia service
 
379
                    break;
 
380
                }
157
381
                // if the last outgoing message used a different accountId, add an
158
382
                // information event and quit the loop
159
383
                eventModel.writeTextInformationEvent(messages.account.accountId,
207
431
            }
208
432
            eventModel.writeEvents([event]);
209
433
        } else {
210
 
            var isMMS = attachments.length > 0
211
 
            var isMmsGroupChat = participantIds.length > 1 && telepathyHelper.mmsGroupChat
 
434
            var isMmsGroupChat = participants.length > 1 && telepathyHelper.mmsGroupChat && messages.account.type == AccountEntry.PhoneAccount
212
435
            // mms group chat only works if we know our own phone number
213
436
            var isSelfContactKnown = account.selfContactId != ""
214
 
            // FIXME: maybe move this to telepathy-ofono itself and treat as just sendMessage on the app?
215
 
            if (isMMS || (isMmsGroupChat && isSelfContactKnown)) {
216
 
                chatManager.sendMMS(participantIds, text, attachments, messages.account.accountId)
217
 
            } else {
218
 
                chatManager.sendMessage(participantIds, text, messages.account.accountId)
 
437
            if (isMmsGroupChat && !isSelfContactKnown) {
 
438
                // TODO: inform the user to enter the phone number of the selected sim card manually
 
439
                // and use it in the telepathy-ofono account as selfContactId. 
 
440
                return
219
441
            }
 
442
            chatManager.sendMessage(messages.account.accountId, participantIds, text, attachments, properties)
220
443
        }
221
444
 
222
445
        // FIXME: soon it won't be just about SIM cards, so the dialogs need updating
223
 
        if (multipleAccounts && !telepathyHelper.defaultMessagingAccount && !settings.messagesDontAsk) {
 
446
        if (multiplePhoneAccounts && !telepathyHelper.defaultMessagingAccount && !settings.messagesDontAsk && account.type == AccountEntry.PhoneAccount) {
224
447
            Qt.inputMethod.hide()
225
448
            PopupUtils.open(Qt.createComponent("Dialogs/SetDefaultSIMCardDialog.qml").createObject(messages))
226
449
        } else {
232
455
        return true
233
456
    }
234
457
 
 
458
    PresenceRequest {
 
459
        id: presenceRequest
 
460
        accountId: {
 
461
            // if this is a regular sms chat, try requesting the presence on
 
462
            // a multimedia account
 
463
            if (!account) {
 
464
                return ""
 
465
            }
 
466
            if (account.type == AccountEntry.PhoneAccount) {
 
467
                for (var i in telepathyHelper.accounts) {
 
468
                    var tmpAccount = telepathyHelper.accounts[i]
 
469
                    if (tmpAccount.type == AccountEntry.MultimediaAccount) {
 
470
                        return tmpAccount.accountId
 
471
                    }
 
472
                }
 
473
                return ""
 
474
            }
 
475
            return account.accountId
 
476
        }
 
477
        // we just request presence on 1-1 chats
 
478
        identifier: participants.length == 1 ? participants[0].identifier : ""
 
479
    }
 
480
 
235
481
    // this is necessary to automatically update the view when the
236
482
    // default account changes in system settings
237
483
    Connections {
238
484
        target: mainView
239
 
        onAccountChanged: messages.account = mainView.account
 
485
        onAccountChanged: {
 
486
            if (!messages.phoneAccount) {
 
487
                return
 
488
            }
 
489
            messages.account = mainView.account
 
490
        }
240
491
    }
241
492
 
242
493
    ActivityIndicator {
281
532
                                           contactWatcher.isInteractive) ||
282
533
                                          contactWatcher.alias === "") ? contactWatcher.identifier : contactWatcher.alias
283
534
    title: {
284
 
        if (selectionMode) {
 
535
        if (selectionMode || participants.length == 0) {
285
536
            return " "
286
537
        }
287
538
 
300
551
    }
301
552
 
302
553
    Component.onCompleted: {
 
554
        if (messages.accountId !== "") {
 
555
            var account = telepathyHelper.accountForId(messages.accountId)
 
556
            if (account && account.type == AccountEntry.MultimediaAccount) {
 
557
                // fallback the first available phone account 
 
558
                if (telepathyHelper.phoneAccounts.length > 0) {
 
559
                    messages.accountId = telepathyHelper.phoneAccounts[0].accountId
 
560
                }
 
561
            }
 
562
        }
303
563
        addAttachmentsToModel(sharedAttachmentsTransfer)
304
564
    }
305
565
 
324
584
            }
325
585
            return Qt.createQmlObject(componentUnion.arg(componentFilters), eventModel)
326
586
        }
327
 
 
328
 
        for (var i in accounts) {
329
 
            var account = accounts[i];
 
587
 
 
588
        var filterAccounts = []
 
589
 
 
590
        if (messages.accountsModel.length == 1 && messages.accountsModel[0].type == AccountEntry.GenericAccount) {
 
591
            filterAccounts = [messages.accountsModel[0]]
 
592
        } else {
 
593
            for (var i in telepathyHelper.accounts) {
 
594
                var account = telepathyHelper.accounts[i]
 
595
                if (account.type === AccountEntry.PhoneAccount || account.type === AccountEntry.MultimediaAccount) {
 
596
                    filterAccounts.push(account)
 
597
                }
 
598
            }
 
599
       }
 
600
 
 
601
       for (var i in filterAccounts) {
 
602
            var account = filterAccounts[i];
330
603
            var filterValue = eventModel.threadIdForParticipants(account.accountId,
331
604
                                                                 HistoryThreadModel.EventTypeText,
332
605
                                                                 participants,
333
 
                                                                 account.type === AccountEntry.PhoneAccount ? HistoryThreadModel.MatchPhoneNumber
 
606
                                                                 account.type === AccountEntry.PhoneAccount || account.type === AccountEntry.MultimediaAccount ? HistoryThreadModel.MatchPhoneNumber
334
607
                                                                                                            : HistoryThreadModel.MatchCaseSensitive);
335
608
            if (filterValue === "") {
336
609
                continue
441
714
            id: noNetworkDialog
442
715
            objectName: "noNetworkDialog"
443
716
            title: i18n.tr("No network")
444
 
            text: multipleAccounts ? i18n.tr("There is currently no network on %1").arg(messages.account.displayName) : i18n.tr("There is currently no network.")
 
717
            text: multiplePhoneAccounts ? i18n.tr("There is currently no network on %1").arg(messages.account.displayName) : i18n.tr("There is currently no network.")
445
718
            Button {
446
719
                objectName: "closeNoNetworkDialog"
447
720
                text: i18n.tr("Close")
454
727
        }
455
728
    }
456
729
 
457
 
    head.sections.model: {
458
 
        // does not show dual sim switch if there is only one sim
459
 
        if (!multipleAccounts) {
460
 
            return undefined
461
 
        }
462
 
 
463
 
        var accountNames = []
464
 
        for(var i=0; i < telepathyHelper.activeAccounts.length; i++) {
465
 
            accountNames.push(telepathyHelper.activeAccounts[i].displayName)
466
 
        }
467
 
        return accountNames
468
 
    }
469
 
    head.sections.selectedIndex: {
470
 
        if (!messages.account) {
471
 
            return -1
472
 
        }
473
 
        for (var i in telepathyHelper.activeAccounts) {
474
 
            if (telepathyHelper.activeAccounts[i].accountId === messages.account.accountId) {
475
 
                return i
476
 
            }
477
 
        }
478
 
        return -1
479
 
    }
480
730
    Connections {
481
731
        target: messages.head.sections
482
 
        onSelectedIndexChanged: messages.account = telepathyHelper.activeAccounts[head.sections.selectedIndex]
 
732
        onSelectedIndexChanged: {
 
733
            messages.account = messages.accountsModel[head.sections.selectedIndex]
 
734
        }
483
735
    }
484
736
 
485
737
    Loader {
553
805
        addressableFields: messages.account ? messages.account.addressableVCardFields : ["tel"] // just to have a fallback there
554
806
    }
555
807
 
 
808
    onAccountsModelChanged: {
 
809
        reloadFilters = !reloadFilters
 
810
    }
 
811
 
556
812
    Action {
557
813
        id: backButton
558
814
        objectName: "backButton"
601
857
            name: "groupChat"
602
858
            head: messages.head
603
859
            when: groupChat
 
860
            contents: header
604
861
            backAction: backButton
605
862
 
606
863
            actions: [
616
873
            head: messages.head
617
874
            when: participants.length == 1 && contactWatcher.isUnknown
618
875
            backAction: backButton
 
876
            contents: header
619
877
 
620
878
            actions: [
621
879
                Action {
675
933
            head: messages.head
676
934
            when: participants.length == 1 && !contactWatcher.isUnknown
677
935
            backAction: backButton
 
936
            contents: header
678
937
            actions: [
679
938
                Action {
680
939
                    objectName: "contactCallKnownAction"
681
 
                    visible: participants.length == 1
 
940
                    visible: participants.length == 1 && messages.phoneAccount
682
941
                    iconName: "call-start"
683
942
                    text: i18n.tr("Call")
684
943
                    onTriggered: {
689
948
                },
690
949
                Action {
691
950
                    objectName: "contactProfileAction"
692
 
                    visible: !contactWatcher.isUnknown && participants.length == 1
 
951
                    visible: !contactWatcher.isUnknown && participants.length == 1 && messages.phoneAccount
693
952
                    iconSource: "image://theme/contact"
694
953
                    text: i18n.tr("Contact")
695
954
                    onTriggered: {
776
1035
 
777
1036
    Item {
778
1037
        id: bottomPanel
 
1038
        property int defaultHeight: textEntry.height + units.gu(2)
779
1039
        anchors.bottom: isSearching ? parent.bottom : keyboard.top
780
1040
        anchors.left: parent.left
781
1041
        anchors.right: parent.right
782
 
        height: selectionMode || (participants.length > 0 && !contactWatcher.interactive) ? 0 : textEntry.height + units.gu(2)
 
1042
        height: {
 
1043
            if (selectionMode || (participants.length > 0 && !contactWatcher.interactive)) {
 
1044
                return 0
 
1045
            } else {
 
1046
                if (messages.height - keyboard.height - screenTop.y > defaultHeight) {
 
1047
                    return defaultHeight
 
1048
                } else {
 
1049
                    return messages.height - keyboard.height - screenTop.y
 
1050
                }
 
1051
            }
 
1052
        }
783
1053
        visible: !selectionMode && !isSearching
784
1054
        clip: true
785
1055
        MouseArea {
891
1161
                    Item {
892
1162
                        id: attachment
893
1163
 
 
1164
                        readonly property int contactsCount:vcardParser.contacts ? vcardParser.contacts.length : 0
894
1165
                        property int index
895
1166
                        property string filePath
896
 
                        property var vcardInfo: application.contactNameFromVCard(attachment.filePath)
 
1167
                        property alias vcard: vcardParser
 
1168
                        property string contactDisplayName: {
 
1169
                            if (contactsCount > 0)  {
 
1170
                                var contact = vcard.contacts[0]
 
1171
                                if (contact.displayLabel.label && (contact.displayLabel.label != "")) {
 
1172
                                    return contact.displayLabel.label
 
1173
                                } else if (contact.name) {
 
1174
                                    var contacFullName  = contact.name.firstName
 
1175
                                    if (contact.name.midleName) {
 
1176
                                        contacFullName += " " + contact.name.midleName
 
1177
                                    }
 
1178
                                    if (contact.name.lastName) {
 
1179
                                        contacFullName += " " + contact.name.lastName
 
1180
                                    }
 
1181
                                    return contacFullName
 
1182
                                }
 
1183
                                return i18n.tr("Unknown contact")
 
1184
                            }
 
1185
                            return ""
 
1186
                        }
 
1187
                        property string title: {
 
1188
                            var result = attachment.contactDisplayName
 
1189
                            if (attachment.contactsCount > 1) {
 
1190
                                return result + " (+%1)".arg(attachment.contactsCount-1)
 
1191
                            } else {
 
1192
                                return result
 
1193
                            }
 
1194
                        }
897
1195
 
898
1196
                        height: units.gu(6)
899
1197
                        width: textEntry.width
906
1204
                                bottom: parent.bottom
907
1205
                                left: parent.left
908
1206
                            }
909
 
                            fallbackAvatarUrl: "image://theme/contact"
910
 
                            fallbackDisplayName: label.name
 
1207
                            contactElement: attachment.contactsCount === 1 ? attachment.vcard.contacts[0] : null
 
1208
                            fallbackAvatarUrl: attachment.contactsCount === 1 ? "image://theme/contact" : "image://theme/contact-group"
 
1209
                            fallbackDisplayName: attachment.contactsCount === 1 ? attachment.contactDisplayName : ""
911
1210
                            width: height
912
1211
                        }
913
1212
                        Label {
914
1213
                            id: label
915
1214
 
916
 
                            property string name: attachment.vcardInfo["name"] !== "" ?
917
 
                                                      attachment.vcardInfo["name"] :
918
 
                                                      i18n.tr("Unknown contact")
919
 
 
920
1215
                            anchors {
921
1216
                                left: avatar.right
922
1217
                                leftMargin: units.gu(1)
927
1222
                            }
928
1223
 
929
1224
                            verticalAlignment: Text.AlignVCenter
930
 
                            text: {
931
 
                                if (attachment.vcardInfo["count"] > 1) {
932
 
                                    return label.name + " (+%1)".arg(attachment.vcardInfo["count"]-1)
933
 
                                } else {
934
 
                                    return label.name
935
 
                                }
936
 
                            }
 
1225
                            text: attachment.title
937
1226
                            elide: Text.ElideMiddle
938
1227
                            color: UbuntuColors.lightAubergine
939
1228
                        }
946
1235
                                PopupUtils.open(attachmentPopover, parent)
947
1236
                            }
948
1237
                        }
 
1238
                        VCardParser {
 
1239
                            id: vcardParser
 
1240
 
 
1241
                            vCardUrl: attachment ? Qt.resolvedUrl(attachment.filePath) : ""
 
1242
                        }
949
1243
                    }
950
1244
                }
951
1245
 
1084
1378
                    // refresh the recipient list
1085
1379
                    multiRecipient.focus = false
1086
1380
 
 
1381
                    if (messages.account && messages.accountId == "") {
 
1382
                        messages.accountId = messages.account.accountId
 
1383
                        messages.head.sections.selectedIndex = Qt.binding(getSelectedIndex)
 
1384
                    }
 
1385
 
1087
1386
                    var newAttachments = []
1088
1387
                    for (var i = 0; i < attachments.count; i++) {
1089
1388
                        var attachment = []