Retrying & deleting failed messages

- Display an exclamation mark (on a red background). In case of a multi-line message
- When a message with an error is selected, show a bottom bar with the 4 following actions: Retry - Delete - Edit - Copy
- If users press on Delete, a confirmation dialog is displayed
- When error messages occur, a general error message appears above the composer. Selecting Delete will delete all error messages. Pressing on Retry will attempt to resend error messages
- If users press on Delete, a confirmation dialog is displayed
- In room lists, decorate rooms with errored messages with the error icon. Rooms with errors should be sorted first
This commit is contained in:
Gil Eluard 2021-03-02 21:56:50 +01:00
parent 26bd006711
commit 9a41f0d4b8
47 changed files with 625 additions and 201 deletions

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "error_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "error_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "error_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "room_activities_retry.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "room_activities_retry@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "room_activities_retry@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

View file

@ -209,8 +209,8 @@
"encrypted_room_message_placeholder" = "Изпрати шифровано съобщение…";
"room_message_short_placeholder" = "Изпрати съобщение…";
"room_offline_notification" = "Връзката със сървъра е изгубена.";
"room_unsent_messages_notification" = "Съобщенията не са изпратени. %@ или %@?";
"room_unsent_messages_unknown_devices_notification" = "Съобщението не е изпратено поради наличието на непознати сесии. %@ или %@?";
"room_unsent_messages_notification" = "Съобщенията не са изпратени.";
"room_unsent_messages_unknown_devices_notification" = "Съобщението не е изпратено поради наличието на непознати сесии.";
"room_ongoing_conference_call" = "Текущ групов разговор. Присъедини се с %@ или %@.";
"room_ongoing_conference_call_with_close" = "Текущ групов разговор. Присъедини се с %@ или %@. %@ го.";
"room_ongoing_conference_call_close" = "Затвори";

View file

@ -204,8 +204,8 @@
"encrypted_room_message_placeholder" = "Envia un missatge encriptat…";
"room_message_short_placeholder" = "Envia un missatge…";
"room_offline_notification" = "S'ha perdut la connexió amb el servidor.";
"room_unsent_messages_notification" = "Missatges no enviats. %@ o %@ ara?";
"room_unsent_messages_unknown_devices_notification" = "El missatge no s'ha enviat perquè hi ha presents dispositius desconeguts. %@ o %@ ara?";
"room_unsent_messages_notification" = "Missatges no enviats.";
"room_unsent_messages_unknown_devices_notification" = "El missatge no s'ha enviat perquè hi ha presents dispositius desconeguts.";
"room_ongoing_conference_call" = "Conferència en curs. Unir-te com a %@ o %@.";
"room_ongoing_conference_call_with_close" = "Conferència en curs. Unir-te com a %@ o %@. %@.";
"room_ongoing_conference_call_close" = "Tancar";

View file

@ -237,8 +237,8 @@
"room_message_short_placeholder" = "Anfon neges…";
"room_message_reply_to_short_placeholder" = "Anfon ateb…";
"room_offline_notification" = "Collwyd cysylltedd â'r gweinydd.";
"room_unsent_messages_notification" = "Negeseuon heb eu hanfon. %@ neu %@ rwan?";
"room_unsent_messages_unknown_devices_notification" = "Ni anfonwyd neges oherwydd bod sesiynau anhysbys yn bresennol. %@ neu %@ rwan?";
"room_unsent_messages_notification" = "Negeseuon heb eu hanfon.";
"room_unsent_messages_unknown_devices_notification" = "Ni anfonwyd neges oherwydd bod sesiynau anhysbys yn bresennol.";
"room_ongoing_conference_call" = "Galwad gynhadledd ar y gweill. Ymunwch fel %@ neu %@.";
"room_ongoing_conference_call_with_close" = "Galwad gynhadledd ar y gweill. Ymunwch fel %@ neu %@. %@ o.";
"room_ongoing_conference_call_close" = "Cau";

View file

@ -157,8 +157,8 @@
"encrypted_room_message_placeholder" = "Verschlüsselte Nachricht…";
"room_message_short_placeholder" = "Sende eine Nachricht…";
"room_offline_notification" = "Verbindung zum Server wurde unterbrochen.";
"room_unsent_messages_notification" = "Nachrichten wurden nicht gesendet. Jetzt %@ oder %@?";
"room_unsent_messages_unknown_devices_notification" = "Nachrichten wurden nicht gesendet, da unbekannte Sitzungen vorhanden waren. Jetzt %@ oder %@?";
"room_unsent_messages_notification" = "Nachrichten wurden nicht gesendet.";
"room_unsent_messages_unknown_devices_notification" = "Nachrichten wurden nicht gesendet, da unbekannte Sitzungen vorhanden waren.";
"room_prompt_resend" = "alle erneut senden";
"room_prompt_cancel" = "alles abbrechen";
"room_resend_unsent_messages" = "Ungesendete Nachrichten erneut senden";

View file

@ -328,8 +328,10 @@ Tap the + to start adding people.";
"room_message_short_placeholder" = "Send a message…";
"room_message_reply_to_short_placeholder" = "Send a reply…";
"room_offline_notification" = "Connectivity to the server has been lost.";
"room_unsent_messages_notification" = "Messages not sent. %@ or %@ now?";
"room_unsent_messages_unknown_devices_notification" = "Message not sent due to unknown sessions being present. %@ or %@ now?";
"room_unsent_messages_notification" = "Messages failed to send.";
"room_unsent_messages_unknown_devices_notification" = "Message failed to send due to unknown sessions being present.";
"room_unsent_messages_cancel_title" = "Delete unsent messages";
"room_unsent_messages_cancel_message" = "Are you sure you want to delete all unsent messages in this room?";
"room_ongoing_conference_call" = "Ongoing conference call. Join as %@ or %@.";
"room_ongoing_conference_call_with_close" = "Ongoing conference call. Join as %@ or %@. %@ it.";
"room_ongoing_conference_call_close" = "Close";

View file

@ -216,8 +216,8 @@
"room_message_short_placeholder" = "Enviar un mensaje…";
"room_message_reply_to_short_placeholder" = "Enviar una respuesta…";
"room_offline_notification" = "Se perdió la conexión con el servidor.";
"room_unsent_messages_notification" = "Los mensajes no se enviaron. ¿%@ o %@ ahora?";
"room_unsent_messages_unknown_devices_notification" = "No se envió el mensaje debido a dispositivos desconocidos presentes. ¿%@ o %@ ahora?";
"room_unsent_messages_notification" = "Los mensajes no se enviaron.";
"room_unsent_messages_unknown_devices_notification" = "No se envió el mensaje debido a dispositivos desconocidos presentes.";
"room_recents_server_notice_section" = "ALERTAS DE SISTEMA";
"room_ongoing_conference_call" = "Llamada de conferencia en curso. Unirse con %@ o %@.";
"room_ongoing_conference_call_with_close" = "Llamada de conferencia en curso. Unirse con %@ o %@. %@ la.";

View file

@ -242,8 +242,8 @@
"room_message_short_placeholder" = "Saada sõnum…";
"room_message_reply_to_short_placeholder" = "Saada vastus…";
"room_offline_notification" = "Ühendus sinu serveriga on katkenud.";
"room_unsent_messages_notification" = "Sõnumid pole saadetud. %@ või %@ nüüd?";
"room_unsent_messages_unknown_devices_notification" = "Kuna leidub tundmatuid sessioone, siis sõnumid pole saadetud. %@ või %@ nüüd?";
"room_unsent_messages_notification" = "Sõnumid pole saadetud.";
"room_unsent_messages_unknown_devices_notification" = "Kuna leidub tundmatuid sessioone, siis sõnumid pole saadetud.";
"room_ongoing_conference_call" = "Konverentsikõne on käsil. Liitu kas %@ või %@.";
"room_ongoing_conference_call_with_close" = "Konverentsikõne on käsil. Liitu kas %@ või %@. %@ seda.";
"room_ongoing_conference_call_close" = "Sulge";

View file

@ -286,8 +286,8 @@
"room_two_users_are_typing" = "%@ eta %@ idazten ari dira…";
"room_many_users_are_typing" = "%@, %@, eta beste batzuk idazten ari dira…";
"room_message_short_placeholder" = "Bidali mezu bat…";
"room_unsent_messages_notification" = "Bidali gabeko mezuak daude. %@ edo %@ orain?";
"room_unsent_messages_unknown_devices_notification" = "Mezua ez da bidali saio ezezagunak daudelako. %@ edo %@ orain?";
"room_unsent_messages_notification" = "Bidali gabeko mezuak daude.";
"room_unsent_messages_unknown_devices_notification" = "Mezua ez da bidali saio ezezagunak daudelako.";
"room_ongoing_conference_call" = "Konferentzia deia abioan. Elkartu %@ edo %@ erabiliz.";
"room_event_action_more" = "Gehiago";
"room_event_action_save" = "Gorde";

View file

@ -195,8 +195,8 @@
"encrypted_room_message_placeholder" = "Envoyer un message chiffré…";
"room_message_short_placeholder" = "Envoyer un message…";
"room_offline_notification" = "La connexion au serveur a été perdue.";
"room_unsent_messages_notification" = "Messages non envoyés. %@ ou %@ maintenant ?";
"room_unsent_messages_unknown_devices_notification" = "Message non envoyé car des sessions inconnues sont présentes. %@ ou %@ maintenant ?";
"room_unsent_messages_notification" = "Messages non envoyés.";
"room_unsent_messages_unknown_devices_notification" = "Message non envoyé car des sessions inconnues sont présentes.";
"room_ongoing_conference_call" = "Téléconférence en cours. Rejoindre en %@ ou %@.";
"room_prompt_resend" = "Tout renvoyer";
"room_prompt_cancel" = "tout annuler";

View file

@ -210,8 +210,8 @@
"room_message_short_placeholder" = "Üzenet küldése…";
"room_message_reply_to_short_placeholder" = "Válasz küldése…";
"room_offline_notification" = "A szerverrel megszakadt a kapcsolat.";
"room_unsent_messages_notification" = "Üzenet nincs elküldve. Most %@ vagy %@?";
"room_unsent_messages_unknown_devices_notification" = "Az üzenet nincs elküldve, mert ismeretlen munkamenet van jelen. Most %@ vagy %@?";
"room_unsent_messages_notification" = "Üzenet nincs elküldve.";
"room_unsent_messages_unknown_devices_notification" = "Az üzenet nincs elküldve, mert ismeretlen munkamenet van jelen.";
"room_ongoing_conference_call" = "Konferencia hívás van folyamatban. Csatlakozol mint %@ vagy %@.";
"room_ongoing_conference_call_with_close" = "Konferencia hívás van folyamatban. Csatlakozol mint %@ vagy %@. %@.";
"room_ongoing_conference_call_close" = "Bezár";

View file

@ -401,8 +401,8 @@
"room_participants_remove_prompt_msg" = "Ertu viss um að þú viljir fjarlægja %@ úr þessu spjalli?";
"room_participants_invite_prompt_msg" = "Ertu viss um að þú viljir bjóða %@ á þetta spjall?";
"room_participants_invite_malformed_id" = "Rangt formað auðkenni. Ætti að vera tölvupóstfang eða Matrix-auðkenni á borð við'@sérheiti:lén'";
"room_unsent_messages_notification" = "Skilaboð ekki send. %@ eða %@ núna?";
"room_unsent_messages_unknown_devices_notification" = "Skilaboð ekki send vegna þess að vart var við óþekkt tæki. %@ eða %@ núna?";
"room_unsent_messages_notification" = "Skilaboð ekki send.";
"room_unsent_messages_unknown_devices_notification" = "Skilaboð ekki send vegna þess að vart var við óþekkt tæki.";
"room_ongoing_conference_call" = "Símafundur í gangi. Taka þátt með %@ eða %@.";
"room_ongoing_conference_call_with_close" = "Símafundur í gangi. Taka þátt með %@ eða %@. %@ því.";
"room_conference_call_no_power" = "Þú þarft aðgangsheimildir til að sýsla með símafundi á þessari spjallrás";

View file

@ -216,8 +216,8 @@
"room_message_short_placeholder" = "Invia un messaggio…";
"room_message_reply_to_short_placeholder" = "Invia una risposta…";
"room_offline_notification" = "La connessione al server è stata persa.";
"room_unsent_messages_notification" = "Messaggi non inviati. %1$s o %2$s ora?";
"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa della presenza di sessioni sconosciute. %1$s o %2$s ora?";
"room_unsent_messages_notification" = "Messaggi non inviati.";
"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa della presenza di sessioni sconosciute.";
"room_ongoing_conference_call" = "Avvio conferenza. Unisciti come %@ o %@.";
"room_ongoing_conference_call_with_close" = "Avvio conferenza. Unisciti come %@ o %@. %@.";
"room_ongoing_conference_call_close" = "Chiudi";

View file

@ -200,8 +200,8 @@
"encrypted_room_message_placeholder" = "暗号文を送信…";
"room_message_short_placeholder" = "ここに送信文を入力…";
"room_offline_notification" = "サーバとの接続が失われました.";
"room_unsent_messages_notification" = "文章が送信できませんでした。現在 %@ または %@ ?";
"room_unsent_messages_unknown_devices_notification" = "未知のセッションが存在するために文章が送信されませんでした。現在%@ or %@ ";
"room_unsent_messages_notification" = "文章が送信できませんでした.";
"room_unsent_messages_unknown_devices_notification" = "未知のセッションが存在するために文章が送信されませんでした.";
"room_ongoing_conference_call" = "会議通話実施中。 %@ または %@で参加してください。";
"room_ongoing_conference_call_with_close" = "会議通話実施中。%@または%@で参加してください。 %@。";
"room_ongoing_conference_call_close" = "閉じる";

View file

@ -204,8 +204,8 @@
"encrypted_room_message_placeholder" = "Stuur een versleuteld bericht…";
"room_message_short_placeholder" = "Stuur een bericht…";
"room_offline_notification" = "De verbinding met de server is verbroken.";
"room_unsent_messages_notification" = "Berichten niet verstuurd. Nu %@ of %@?";
"room_unsent_messages_unknown_devices_notification" = "Bericht is niet verstuurd doordat er onbekende apparaten aanwezig zijn. Nu %@ of %@?";
"room_unsent_messages_notification" = "Berichten niet verstuurd.";
"room_unsent_messages_unknown_devices_notification" = "Bericht is niet verstuurd doordat er onbekende apparaten aanwezig zijn.";
"room_ongoing_conference_call" = "Er is een vergadergesprek gaande. Neem deel met %@ of %@.";
"room_prompt_resend" = "alles opnieuw versturen";
"room_prompt_cancel" = "alles annuleren";

View file

@ -392,7 +392,7 @@
"contacts_address_book_no_contact" = "Brak lokalnych kontaktów";
"auth_msisdn_validation_error" = "Nie można zweryfikować numeru telefonu.";
"room_participants_invite_malformed_id" = "Uszkodzony ID. Powinien być adres e-mail lub Matrix ID podobny do '@localpart:domain'";
"room_unsent_messages_notification" = "Wiadomość nie została wysłana. Czy %@ lub %@ teraz?";
"room_unsent_messages_notification" = "Wiadomość nie została wysłana.";
"room_ongoing_conference_call_with_close" = "Przychodzące połączenie grupowe. Dołącz z %@ lub z %@. %@ to.";
"directory_search_results_title" = "Przeglądaj wyniki katalogów";
"room_event_action_kick_prompt_reason" = "Powód wyrzucenia użytkownika";

View file

@ -185,8 +185,8 @@
"encrypted_room_message_placeholder" = "Отправить зашифрованное сообщение…";
"room_message_short_placeholder" = "Отправить сообщение…";
"room_offline_notification" = "Связь с сервером потеряна.";
"room_unsent_messages_notification" = "Сообщения не отправлены. %@ или %@ сейчас?";
"room_unsent_messages_unknown_devices_notification" = "Сообщение не отправлено из-за присутствия неизвестных сеансов. %@ или %@ сейчас?";
"room_unsent_messages_notification" = "Сообщения не отправлены.";
"room_unsent_messages_unknown_devices_notification" = "Сообщение не отправлено из-за присутствия неизвестных сеансов.";
"room_ongoing_conference_call" = "Текущий групповой вызов. Войти как %@ или %@.";
"room_prompt_resend" = "Отправить все";
"room_prompt_cancel" = "отменить все";

View file

@ -173,7 +173,7 @@
"encrypted_room_message_placeholder" = "Dërgoni një mesazhi të fshehtëzuar…";
"room_message_short_placeholder" = "Dërgoni një mesazh…";
"room_offline_notification" = "Humbi lidhja me shërbyesin.";
"room_unsent_messages_notification" = "Mesazhet su dërguan. %@ apo %@ tani?";
"room_unsent_messages_notification" = "Mesazhet su dërguan.";
"room_ongoing_conference_call_close" = "Mbylle";
"room_prompt_resend" = "Ridërgoji krejt";
"room_prompt_cancel" = "anuloji krejt";

View file

@ -200,8 +200,8 @@
"encrypted_room_message_placeholder" = "Gửi tin nhắn đã mã hoá…";
"room_message_short_placeholder" = "Gửi tin nhắn…";
"room_offline_notification" = "Kết nối tới máy chủ thất bại.";
"room_unsent_messages_notification" = "Các tin nhắn chưa được gửi. %@ hoặc %@ ngay bây giờ?";
"room_unsent_messages_unknown_devices_notification" = "Các tin nhắn chưa được gửi tới các thiết bị không xác định hiện hành. %@ hoặc %@ ngay bây giờ?";
"room_unsent_messages_notification" = "Các tin nhắn chưa được gửi.";
"room_unsent_messages_unknown_devices_notification" = "Các tin nhắn chưa được gửi tới các thiết bị không xác định hiện hành.";
"room_ongoing_conference_call" = "Cuộc gọi hội nghị đang diễn ra. Tham gia như %@ hoặc %@.";
"room_ongoing_conference_call_with_close" = "Cuộc gọi hội nghị đang diễn ra. Tham gia như %@ hoặc %@. %@ nó.";
"room_ongoing_conference_call_close" = "Đóng";

View file

@ -189,8 +189,8 @@
"encrypted_room_message_placeholder" = "发送加密消息…";
"room_message_short_placeholder" = "发送消息…";
"room_offline_notification" = "到服务器的连接已经丢失。";
"room_unsent_messages_notification" = "消息没有发送。现在 %@ 或 %@ ";
"room_unsent_messages_unknown_devices_notification" = "由于未知会话在线所以消息没有发送。现在 %@ 或 %@";
"room_unsent_messages_notification" = "消息没有发送。";
"room_unsent_messages_unknown_devices_notification" = "由于未知会话在线所以消息没有发送。";
"room_ongoing_conference_call" = "收到会议通话。以 %@ 或 %@ 加入。";
"room_prompt_resend" = "全部重新发送";
"room_prompt_cancel" = "全部取消";

View file

@ -266,8 +266,8 @@
"room_two_users_are_typing" = "%@ 和 %@ 正在輸入…";
"room_many_users_are_typing" = "%@、%@ 和 %@ 正在輸入…";
"room_message_short_placeholder" = "傳送訊息…";
"room_unsent_messages_notification" = "訊息未被傳送。現在 %@ 或 %@ 嗎?";
"room_unsent_messages_unknown_devices_notification" = "由於存在未知的工作階段導致訊息未被傳送。現在 %@ 或 %@ 嗎?";
"room_unsent_messages_notification" = "訊息未被傳送。";
"room_unsent_messages_unknown_devices_notification" = "由於存在未知的工作階段導致訊息未被傳送。";
"room_conference_call_no_power" = "您需要管理此聊天室群組通話的權限";
"room_prompt_resend" = "全部重新傳送";
"room_prompt_cancel" = "全部取消";

View file

@ -19,6 +19,13 @@
#import "UserEncryptionTrustLevel.h"
typedef NS_ENUM(NSUInteger, MXRoomSentStatus)
{
MXRoomSentStatusOk,
MXRoomSentStatusSentFailed,
MXRoomSentStatusSentFailedDueToUnknownDevices
};
/**
Define a `MXRoom` category at Riot level.
*/
@ -44,6 +51,9 @@
*/
@property (nonatomic) id notificationCenterDidUpdateObserver;
/// Check if all messages have been sent.
@property (nonatomic, readonly) MXRoomSentStatus sentStatus;
/**
Update the room tag.

View file

@ -656,4 +656,30 @@
return objc_getAssociatedObject(self, @selector(notificationCenterDidUpdateObserver));
}
#pragma mark - Unread messages
- (MXRoomSentStatus)sentStatus
{
MXRoomSentStatus status = MXRoomSentStatusOk;
NSArray<MXEvent*> *outgoingMsgs = self.outgoingMessages;
for (MXEvent *event in outgoingMsgs)
{
if (event.sentState == MXEventSentStateFailed)
{
status = MXRoomSentStatusSentFailed;
// Check if the error is due to unknown devices
if ([event.sentError.domain isEqualToString:MXEncryptingErrorDomain]
&& event.sentError.code == MXEncryptingErrorUnknownDeviceCode)
{
status = MXRoomSentStatusSentFailedDueToUnknownDevices;
break;
}
}
}
return status;
}
@end

View file

@ -50,6 +50,7 @@ internal enum Asset {
internal static let chevron = ImageAsset(name: "chevron")
internal static let closeButton = ImageAsset(name: "close_button")
internal static let disclosureIcon = ImageAsset(name: "disclosure_icon")
internal static let errorIcon = ImageAsset(name: "error_icon")
internal static let faceidIcon = ImageAsset(name: "faceid_icon")
internal static let group = ImageAsset(name: "group")
internal static let monitor = ImageAsset(name: "monitor")
@ -98,8 +99,8 @@ internal enum Asset {
internal static let peopleEmptyScreenArtworkDark = ImageAsset(name: "people_empty_screen_artwork_dark")
internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action")
internal static let error = ImageAsset(name: "error")
internal static let roomActivitiesRetry = ImageAsset(name: "room_activities_retry")
internal static let scrolldown = ImageAsset(name: "scrolldown")
internal static let sendingMessageTick = ImageAsset(name: "sending_message_tick")
internal static let sentMessageTick = ImageAsset(name: "sent_message_tick")
internal static let typing = ImageAsset(name: "typing")
internal static let roomContextMenuCopy = ImageAsset(name: "room_context_menu_copy")

View file

@ -3334,13 +3334,21 @@ internal enum VectorL10n {
internal static func roomTwoUsersAreTyping(_ p1: String, _ p2: String) -> String {
return VectorL10n.tr("Vector", "room_two_users_are_typing", p1, p2)
}
/// Messages not sent. %@ or %@ now?
internal static func roomUnsentMessagesNotification(_ p1: String, _ p2: String) -> String {
return VectorL10n.tr("Vector", "room_unsent_messages_notification", p1, p2)
/// Are you sure you want to delete all unsent messages in this room?
internal static var roomUnsentMessagesCancelMessage: String {
return VectorL10n.tr("Vector", "room_unsent_messages_cancel_message")
}
/// Message not sent due to unknown sessions being present. %@ or %@ now?
internal static func roomUnsentMessagesUnknownDevicesNotification(_ p1: String, _ p2: String) -> String {
return VectorL10n.tr("Vector", "room_unsent_messages_unknown_devices_notification", p1, p2)
/// Delete unsent messages
internal static var roomUnsentMessagesCancelTitle: String {
return VectorL10n.tr("Vector", "room_unsent_messages_cancel_title")
}
/// Messages failed to send.
internal static var roomUnsentMessagesNotification: String {
return VectorL10n.tr("Vector", "room_unsent_messages_notification")
}
/// Message failed to send due to unknown sessions being present.
internal static var roomUnsentMessagesUnknownDevicesNotification: String {
return VectorL10n.tr("Vector", "room_unsent_messages_unknown_devices_notification")
}
/// End-to-end encryption is in beta and may not be reliable.\n\nYou should not yet trust it to secure data.\n\nDevices will not yet be able to decrypt history from before they joined the room.\n\nEncrypted messages will not be visible on clients that do not yet implement encryption.
internal static var roomWarningAboutEncryption: String {

View file

@ -0,0 +1,31 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
IB_DESIGNABLE
@interface BadgeLabel : UILabel
@property IBInspectable (nonatomic, weak) UIColor *badgeColor;
@property IBInspectable (nonatomic) CGFloat borderWidth;
@property IBInspectable (nonatomic, weak) UIColor *borderColor;
@property IBInspectable (nonatomic) CGSize padding;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,96 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "BadgeLabel.h"
@implementation BadgeLabel
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self setupView];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setupView];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.layer.cornerRadius = self.bounds.size.height / 2;
}
- (CGSize)intrinsicContentSize
{
CGSize intrinsicSize = [super intrinsicContentSize];
intrinsicSize.height = MAX(intrinsicSize.height + self.padding.height, intrinsicSize.height) + self.borderWidth / 2;
intrinsicSize.width = MAX(intrinsicSize.width + self.padding.width, intrinsicSize.height);
return intrinsicSize;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGRect backgroundRect = CGRectInset(self.bounds, self.borderWidth / 2, self.borderWidth / 2);
CGFloat cornerRadius = backgroundRect.size.height / 2;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:backgroundRect cornerRadius:cornerRadius];
CGContextAddPath(context, [path CGPath]);
CGContextSetLineWidth(context, self.borderWidth);
CGContextSetStrokeColorWithColor(context, [self.borderColor CGColor]);
CGContextSetFillColorWithColor(context, [self.badgeColor CGColor]);
if (self.borderWidth > 0)
{
CGContextDrawPath(context, kCGPathFillStroke);
}
else
{
CGContextDrawPath(context, kCGPathFill);
}
CGContextRestoreGState(context);
[super drawRect:rect];
}
- (void)prepareForInterfaceBuilder
{
[super prepareForInterfaceBuilder];
[self setupView];
}
- (void)setupView
{
self.badgeColor = UIColor.redColor;
self.borderWidth = 0;
self.borderColor = UIColor.whiteColor;
self.padding = CGSizeMake(10, 2);
self.textAlignment = NSTextAlignmentCenter;
self.textColor = UIColor.whiteColor;
}
@end

View file

@ -0,0 +1,111 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
@IBDesignable
@objcMembers
class BadgeLabel: UILabel {
// MARK: - Public properties
@IBInspectable var badgeColor: UIColor = .red {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var borderWidth: CGFloat = 0 {
didSet {
invalidateIntrinsicContentSize()
}
}
@IBInspectable var borderColor: UIColor = .white {
didSet {
invalidateIntrinsicContentSize()
}
}
@IBInspectable var padding: CGSize = CGSize(width: 5, height: 2) {
didSet {
invalidateIntrinsicContentSize()
}
}
// MARK: - Lifecycle
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.bounds.size.height / 2
}
override var intrinsicContentSize: CGSize {
var intrinsicSize = super.intrinsicContentSize
intrinsicSize.height = max(intrinsicSize.height + padding.height, intrinsicSize.height) + borderWidth / 2
intrinsicSize.width = max(intrinsicSize.width + padding.width, intrinsicSize.height)
return intrinsicSize
}
override func draw(_ rect: CGRect) {
if let context = UIGraphicsGetCurrentContext() {
context.saveGState()
let rect = self.bounds.insetBy(dx: borderWidth / 2, dy: borderWidth / 2)
let cornerRadius = rect.height / 2
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
context.addPath(path.cgPath)
context.setLineWidth(borderWidth)
context.setStrokeColor(borderColor.cgColor)
context.setFillColor(badgeColor.cgColor)
if borderWidth > 0 {
context.drawPath(using: .fillStroke)
} else {
context.drawPath(using: .fill)
}
context.restoreGState()
}
super.draw(rect)
}
// MARK: - Interface Builder
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
// MARK: - Private methods
private func setupView() {
self.textAlignment = .center
}
}

View file

@ -1294,6 +1294,18 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
// Sort each rooms collection by considering first the rooms with some missed notifs, the rooms with unread, then the others.
comparator = ^NSComparisonResult(id<MXKRecentCellDataStoring> recentCellData1, id<MXKRecentCellDataStoring> recentCellData2) {
if (recentCellData1.roomSummary.room.sentStatus != MXRoomSentStatusOk
&& recentCellData2.roomSummary.room.sentStatus == MXRoomSentStatusOk)
{
return NSOrderedAscending;
}
if (recentCellData2.roomSummary.room.sentStatus != MXRoomSentStatusOk
&& recentCellData1.roomSummary.room.sentStatus == MXRoomSentStatusOk)
{
return NSOrderedDescending;
}
if (recentCellData1.highlightCount)
{
if (recentCellData2.highlightCount)
@ -1351,6 +1363,18 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
// Sort each rooms collection by considering first the rooms with some unread messages then the others.
comparator = ^NSComparisonResult(id<MXKRecentCellDataStoring> recentCellData1, id<MXKRecentCellDataStoring> recentCellData2) {
if (recentCellData1.roomSummary.room.sentStatus != MXRoomSentStatusOk
&& recentCellData2.roomSummary.room.sentStatus == MXRoomSentStatusOk)
{
return NSOrderedAscending;
}
if (recentCellData2.roomSummary.room.sentStatus != MXRoomSentStatusOk
&& recentCellData1.roomSummary.room.sentStatus == MXRoomSentStatusOk)
{
return NSOrderedDescending;
}
if (recentCellData1.hasUnread)
{
if (recentCellData2.hasUnread)
@ -1389,6 +1413,24 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
return [session compareRoomsByTag:kMXRoomTagFavourite room1:recentCellData1.roomSummary.room room2:recentCellData2.roomSummary.room];
}];
} else if (conversationCellDataArray.count > 0 && (_recentsDataSourceMode == RecentsDataSourceModeRooms || _recentsDataSourceMode == RecentsDataSourceModePeople))
{
[conversationCellDataArray sortUsingComparator:^NSComparisonResult(id<MXKRecentCellDataStoring> recentCellData1, id<MXKRecentCellDataStoring> recentCellData2) {
if (recentCellData1.roomSummary.room.sentStatus != MXRoomSentStatusOk
&& recentCellData2.roomSummary.room.sentStatus == MXRoomSentStatusOk)
{
return NSOrderedAscending;
}
if (recentCellData2.roomSummary.room.sentStatus != MXRoomSentStatusOk
&& recentCellData1.roomSummary.room.sentStatus == MXRoomSentStatusOk)
{
return NSOrderedDescending;
}
return NSOrderedAscending;
}];
}
}

View file

@ -15,6 +15,7 @@
*/
#import <MatrixKit/MatrixKit.h>
#import "BadgeLabel.h"
/**
`RecentTableViewCell` instances display a room in the context of the recents list.
@ -30,4 +31,7 @@
@property (weak, nonatomic) IBOutlet UIView *missedNotifAndUnreadBadgeBgView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *missedNotifAndUnreadBadgeBgViewWidthConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *lastEventDecriptionLabelTrailingConstraint;
@property (weak, nonatomic) IBOutlet UIImageView *unsentImageView;
@end

View file

@ -20,6 +20,7 @@
#import "AvatarGenerator.h"
#import "MXEvent.h"
#import "MXRoom+Riot.h"
#import "ThemeService.h"
#import "Riot-Swift.h"
@ -102,6 +103,9 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
self.lastEventDescription.text = roomCellData.lastEventTextMessage;
}
self.unsentImageView.hidden = roomCellData.roomSummary.room.sentStatus == MXRoomSentStatusOk;
self.lastEventDecriptionLabelTrailingConstraint.constant = self.unsentImageView.hidden ? 10 : 30;
// Notify unreads and bing
if (roomCellData.hasUnread)
{

View file

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -15,7 +13,7 @@
<rect key="frame" x="0.0" y="0.0" width="600" height="74"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="L2L-l5-wPx" id="aXz-IR-jj5">
<rect key="frame" x="0.0" y="0.0" width="600" height="73.5"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="74"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e7r-zL-9bw" userLabel="Bing indicator">
@ -55,7 +53,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LastEventDescription" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dQt-mN-T6b">
<rect key="frame" x="69" y="39" width="521" height="20"/>
<rect key="frame" x="69" y="39" width="501" height="20"/>
<accessibility key="accessibilityConfiguration" identifier="LastEventDescription"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="e6p-DU-3ny"/>
@ -101,8 +99,16 @@
<constraint firstAttribute="width" constant="16" id="i9u-Bs-pBy"/>
</constraints>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="error_icon" translatesAutoresizingMaskIntoConstraints="NO" id="F6K-PV-15f">
<rect key="frame" x="574" y="41" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="dkD-bq-4ND"/>
<constraint firstAttribute="height" constant="16" id="lNd-8B-gRr"/>
</constraints>
</imageView>
</subviews>
<constraints>
<constraint firstItem="F6K-PV-15f" firstAttribute="trailing" secondItem="360-Go-RcG" secondAttribute="trailing" id="3qB-Na-Eqs"/>
<constraint firstItem="8CY-Ku-0qh" firstAttribute="centerX" secondItem="RX5-eD-c3c" secondAttribute="centerX" id="FVh-1h-VkB"/>
<constraint firstItem="360-Go-RcG" firstAttribute="leading" secondItem="OeZ-wN-eil" secondAttribute="trailing" constant="8" id="KuE-8m-e9A"/>
<constraint firstItem="e7r-zL-9bw" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" id="PUW-if-ewh"/>
@ -114,10 +120,11 @@
<constraint firstAttribute="trailing" secondItem="360-Go-RcG" secondAttribute="trailing" constant="10" id="YqC-WC-Wqe"/>
<constraint firstItem="NAZ-zd-MHS" firstAttribute="centerY" secondItem="dQt-mN-T6b" secondAttribute="centerY" constant="2" id="c6L-cn-sJL"/>
<constraint firstItem="OeZ-wN-eil" firstAttribute="centerY" secondItem="360-Go-RcG" secondAttribute="centerY" id="fYc-th-Nay"/>
<constraint firstItem="F6K-PV-15f" firstAttribute="centerY" secondItem="dQt-mN-T6b" secondAttribute="centerY" id="iMe-h4-M1a"/>
<constraint firstItem="dQt-mN-T6b" firstAttribute="top" secondItem="Lg1-xQ-AGn" secondAttribute="bottom" constant="4" id="iaT-57-GOs"/>
<constraint firstItem="RX5-eD-c3c" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="15" id="mga-fG-I0L"/>
<constraint firstItem="NAZ-zd-MHS" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="44" id="ofi-HP-uke"/>
<constraint firstAttribute="trailing" secondItem="dQt-mN-T6b" secondAttribute="trailing" constant="10" id="t2m-pb-5zd"/>
<constraint firstAttribute="trailing" secondItem="dQt-mN-T6b" secondAttribute="trailing" constant="30" id="t2m-pb-5zd"/>
<constraint firstItem="Lg1-xQ-AGn" firstAttribute="top" secondItem="aXz-IR-jj5" secondAttribute="top" constant="14" id="tY3-6V-A3B"/>
<constraint firstItem="RX5-eD-c3c" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="13" id="tgy-cX-Wxm"/>
<constraint firstItem="8CY-Ku-0qh" firstAttribute="centerY" secondItem="RX5-eD-c3c" secondAttribute="centerY" id="ukJ-yz-Kit"/>
@ -129,6 +136,7 @@
<outlet property="directRoomBorderView" destination="8CY-Ku-0qh" id="vKk-ZO-TQq"/>
<outlet property="encryptedRoomIcon" destination="NAZ-zd-MHS" id="ZsH-Zr-Q4K"/>
<outlet property="lastEventDate" destination="360-Go-RcG" id="Y0L-Dj-ZVn"/>
<outlet property="lastEventDecriptionLabelTrailingConstraint" destination="t2m-pb-5zd" id="cxp-7Z-bEg"/>
<outlet property="lastEventDescription" destination="dQt-mN-T6b" id="MSz-h1-cAL"/>
<outlet property="missedNotifAndUnreadBadgeBgView" destination="OeZ-wN-eil" id="tVo-8Z-hc2"/>
<outlet property="missedNotifAndUnreadBadgeBgViewWidthConstraint" destination="86T-0d-rAI" id="KLF-Du-rHT"/>
@ -136,10 +144,13 @@
<outlet property="missedNotifAndUnreadIndicator" destination="e7r-zL-9bw" id="jWE-0y-BMv"/>
<outlet property="roomAvatar" destination="RX5-eD-c3c" id="dIC-8p-inL"/>
<outlet property="roomTitle" destination="Lg1-xQ-AGn" id="q7Q-TM-5C8"/>
<outlet property="unsentImageView" destination="F6K-PV-15f" id="EnH-mb-j6s"/>
</connections>
<point key="canvasLocation" x="-131.19999999999999" y="81.859070464767626"/>
</tableViewCell>
</objects>
<resources>
<image name="e2e_verified" width="10" height="12"/>
<image name="error_icon" width="16" height="16"/>
</resources>
</document>

View file

@ -15,6 +15,7 @@
*/
#import <MatrixKit/MatrixKit.h>
#import "BadgeLabel.h"
/**
'RoomCollectionViewCell' class is used to display a room in a collection view.
@ -38,9 +39,7 @@
@property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar;
@property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon;
@property (weak, nonatomic) IBOutlet UILabel *missedNotifAndUnreadBadgeLabel;
@property (weak, nonatomic) IBOutlet UIView *missedNotifAndUnreadBadgeBgView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *missedNotifAndUnreadBadgeBgViewWidthConstraint;
@property (weak, nonatomic) IBOutlet BadgeLabel *badgeLabel;
@property (nonatomic, readonly) NSString *roomId;

View file

@ -22,6 +22,7 @@
#import "Riot-Swift.h"
#import "MXRoomSummary+Riot.h"
#import "MXRoom+Riot.h"
#import "MXTools.h"
@ -42,10 +43,6 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
[_roomAvatar.layer setCornerRadius:_roomAvatar.frame.size.width / 2];
_roomAvatar.clipsToBounds = YES;
// Initialize unread count badge
[_missedNotifAndUnreadBadgeBgView.layer setCornerRadius:10];
_missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = 0;
// Disable the user interaction on the room avatar.
self.roomAvatar.userInteractionEnabled = NO;
@ -69,7 +66,6 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
self.roomTitle.textColor = ThemeService.shared.theme.textPrimaryColor;
self.roomTitle1.textColor = ThemeService.shared.theme.textPrimaryColor;
self.roomTitle2.textColor = ThemeService.shared.theme.textPrimaryColor;
self.missedNotifAndUnreadBadgeLabel.textColor = ThemeService.shared.theme.baseTextPrimaryColor;
// Prepare direct room border
CGColorRef directRoomBorderColor = CGColorCreateCopyWithAlpha(ThemeService.shared.theme.tintColor.CGColor, kDirectRoomBorderColorAlpha);
@ -94,8 +90,7 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
- (void)render:(MXKCellData *)cellData
{
// Hide by default missed notifications and unread widgets
self.missedNotifAndUnreadBadgeBgView.hidden = YES;
self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = 0;
self.badgeLabel.hidden = YES;
roomCellData = (id<MXKRecentCellDataStoring>)cellData;
if (roomCellData)
@ -125,28 +120,21 @@ static const CGFloat kDirectRoomBorderWidth = 3.0;
{
if (0 < roomCellData.notificationCount)
{
self.missedNotifAndUnreadBadgeBgView.hidden = NO;
self.missedNotifAndUnreadBadgeBgView.backgroundColor = roomCellData.highlightCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor;
self.missedNotifAndUnreadBadgeLabel.text = roomCellData.notificationCountStringValue;
[self.missedNotifAndUnreadBadgeLabel sizeToFit];
self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = self.missedNotifAndUnreadBadgeLabel.frame.size.width + 18;
self.badgeLabel.hidden = NO;
self.badgeLabel.badgeColor = roomCellData.highlightCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor;
self.badgeLabel.text = roomCellData.notificationCountStringValue;
}
// Use bold font for the room title
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold];
}
else if (roomCellData.roomSummary.room.summary.membership == MXMembershipInvite)
else if (roomCellData.roomSummary.room.summary.membership == MXMembershipInvite
|| roomCellData.roomSummary.room.sentStatus != MXRoomSentStatusOk)
{
self.missedNotifAndUnreadBadgeBgView.hidden = NO;
self.missedNotifAndUnreadBadgeBgView.backgroundColor = ThemeService.shared.theme.noticeColor;
self.missedNotifAndUnreadBadgeLabel.text = @"!";
[self.missedNotifAndUnreadBadgeLabel sizeToFit];
self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = self.missedNotifAndUnreadBadgeLabel.frame.size.width + 18;
self.badgeLabel.hidden = NO;
self.badgeLabel.badgeColor = ThemeService.shared.theme.noticeColor;
self.badgeLabel.text = @"!";
// Use bold font for the room title
self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold];
}

View file

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -34,25 +32,12 @@
<constraint firstAttribute="width" secondItem="xws-BR-H47" secondAttribute="height" multiplier="1:1" id="bEP-hL-5lg"/>
</constraints>
</view>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Q6g-b0-3sZ">
<rect key="frame" x="80" y="5" width="0.0" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZUZ-tv-dVV">
<rect key="frame" x="-4.5" y="1.5" width="9" height="17"/>
<accessibility key="accessibilityConfiguration" identifier="MissedNotifAndUnreadBadge"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.0" green="0.72547743060000003" blue="0.0041503219759999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="D9K-B0-riB"/>
<constraint firstItem="ZUZ-tv-dVV" firstAttribute="centerY" secondItem="Q6g-b0-3sZ" secondAttribute="centerY" id="XGQ-9J-nIX"/>
<constraint firstAttribute="width" id="jeX-st-swl"/>
<constraint firstItem="ZUZ-tv-dVV" firstAttribute="centerX" secondItem="Q6g-b0-3sZ" secondAttribute="centerX" id="km0-Wj-nJ0"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="!" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="scs-Ov-Tjv" customClass="BadgeLabel">
<rect key="frame" x="56" y="8" width="16.5" height="16.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="encryption_normal" translatesAutoresizingMaskIntoConstraints="NO" id="5Yd-df-HbB">
<rect key="frame" x="51" y="52" width="21" height="20"/>
<constraints>
@ -83,7 +68,7 @@
</label>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jta-3V-4wL">
<rect key="frame" x="30" y="105" width="20" height="10"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="eU4-Qq-wum"/>
<constraint firstAttribute="width" constant="20" id="sH5-UK-Q7A"/>
@ -96,9 +81,9 @@
<constraint firstItem="oxX-IL-dG4" firstAttribute="top" secondItem="T1Q-RS-8o6" secondAttribute="bottom" constant="4" id="2DB-H2-E2v"/>
<constraint firstAttribute="bottom" secondItem="jta-3V-4wL" secondAttribute="bottom" id="3rt-Ig-1rG"/>
<constraint firstItem="oxX-IL-dG4" firstAttribute="leading" secondItem="eCk-zY-LXq" secondAttribute="leading" constant="4" id="6gu-JD-Gb1"/>
<constraint firstItem="scs-Ov-Tjv" firstAttribute="centerY" secondItem="xws-BR-H47" secondAttribute="top" constant="6" id="CRF-wK-Lcp"/>
<constraint firstItem="X8H-1U-wc3" firstAttribute="centerX" secondItem="oxX-IL-dG4" secondAttribute="centerX" id="K9T-eO-WNb"/>
<constraint firstItem="xws-BR-H47" firstAttribute="centerX" secondItem="T1Q-RS-8o6" secondAttribute="centerX" id="Lo3-Ov-Bw8"/>
<constraint firstAttribute="trailing" secondItem="Q6g-b0-3sZ" secondAttribute="trailing" id="Mf1-H6-oH4"/>
<constraint firstItem="Jkz-Zp-aaG" firstAttribute="centerX" secondItem="oxX-IL-dG4" secondAttribute="centerX" id="OQy-tF-e3Z"/>
<constraint firstItem="jta-3V-4wL" firstAttribute="centerX" secondItem="eCk-zY-LXq" secondAttribute="centerX" id="R87-mq-SlO"/>
<constraint firstItem="5Yd-df-HbB" firstAttribute="bottom" secondItem="T1Q-RS-8o6" secondAttribute="bottom" constant="2" id="RQU-MS-Qfr"/>
@ -107,29 +92,37 @@
<constraint firstItem="T1Q-RS-8o6" firstAttribute="top" secondItem="eCk-zY-LXq" secondAttribute="top" constant="10" id="cc7-bg-15Z"/>
<constraint firstItem="Jkz-Zp-aaG" firstAttribute="leading" secondItem="eCk-zY-LXq" secondAttribute="leading" constant="7" id="fPh-Lb-Bv9"/>
<constraint firstItem="xws-BR-H47" firstAttribute="centerY" secondItem="T1Q-RS-8o6" secondAttribute="centerY" id="faX-hg-WfP"/>
<constraint firstItem="scs-Ov-Tjv" firstAttribute="centerX" secondItem="xws-BR-H47" secondAttribute="trailing" priority="750" constant="-6" id="fgA-Sf-Y4E"/>
<constraint firstAttribute="trailing" secondItem="oxX-IL-dG4" secondAttribute="trailing" constant="4" id="hDl-X9-M4n"/>
<constraint firstItem="T1Q-RS-8o6" firstAttribute="centerX" secondItem="eCk-zY-LXq" secondAttribute="centerX" id="hmB-fl-oN2"/>
<constraint firstItem="Q6g-b0-3sZ" firstAttribute="top" secondItem="eCk-zY-LXq" secondAttribute="top" constant="5" id="jST-Ic-lsn"/>
<constraint firstAttribute="trailing" secondItem="X8H-1U-wc3" secondAttribute="trailing" constant="7" id="o5i-7H-n0G"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="scs-Ov-Tjv" secondAttribute="trailing" id="qGg-o0-u1H"/>
<constraint firstAttribute="trailing" secondItem="Jkz-Zp-aaG" secondAttribute="trailing" constant="7" id="uQe-FG-3lb"/>
<constraint firstItem="X8H-1U-wc3" firstAttribute="top" secondItem="oxX-IL-dG4" secondAttribute="top" id="uTm-3W-QoM"/>
<constraint firstItem="5Yd-df-HbB" firstAttribute="trailing" secondItem="T1Q-RS-8o6" secondAttribute="trailing" constant="2" id="wqP-uJ-EaY"/>
</constraints>
<connections>
<outlet property="badgeLabel" destination="scs-Ov-Tjv" id="KPV-Qb-cnT"/>
<outlet property="directRoomBorderView" destination="xws-BR-H47" id="34A-hu-DXq"/>
<outlet property="editionArrowView" destination="jta-3V-4wL" id="XLj-Cx-3bn"/>
<outlet property="encryptedRoomIcon" destination="5Yd-df-HbB" id="5pc-Vi-wMZ"/>
<outlet property="missedNotifAndUnreadBadgeBgView" destination="Q6g-b0-3sZ" id="jNG-dA-dfP"/>
<outlet property="missedNotifAndUnreadBadgeBgViewWidthConstraint" destination="jeX-st-swl" id="4sa-wn-bgT"/>
<outlet property="missedNotifAndUnreadBadgeLabel" destination="ZUZ-tv-dVV" id="6IV-rz-s4I"/>
<outlet property="roomAvatar" destination="T1Q-RS-8o6" id="4sR-Wm-jwz"/>
<outlet property="roomTitle" destination="oxX-IL-dG4" id="vff-Fw-AOf"/>
<outlet property="roomTitle1" destination="X8H-1U-wc3" id="nnS-Hp-1qV"/>
<outlet property="roomTitle2" destination="Jkz-Zp-aaG" id="xO1-Av-NID"/>
</connections>
<point key="canvasLocation" x="22" y="81"/>
</collectionViewCell>
</objects>
<designables>
<designable name="scs-Ov-Tjv">
<size key="intrinsicContentSize" width="16.5" height="16.5"/>
</designable>
</designables>
<resources>
<image name="encryption_normal" width="16" height="16"/>
<systemColor name="groupTableViewBackgroundColor">
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View file

@ -4618,32 +4618,14 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo
-(BOOL)checkUnsentMessages
{
BOOL hasUnsent = NO;
BOOL hasUnsentDueToUnknownDevices = NO;
MXRoomSentStatus sentStatus = MXRoomSentStatusOk;
if ([self.activitiesView isKindOfClass:RoomActivitiesView.class])
{
NSArray<MXEvent*> *outgoingMsgs = self.roomDataSource.room.outgoingMessages;
for (MXEvent *event in outgoingMsgs)
sentStatus = self.roomDataSource.room.sentStatus;
if (sentStatus != MXRoomSentStatusOk)
{
if (event.sentState == MXEventSentStateFailed)
{
hasUnsent = YES;
// Check if the error is due to unknown devices
if ([event.sentError.domain isEqualToString:MXEncryptingErrorDomain]
&& event.sentError.code == MXEncryptingErrorUnknownDeviceCode)
{
hasUnsentDueToUnknownDevices = YES;
break;
}
}
}
if (hasUnsent)
{
NSString *notification = hasUnsentDueToUnknownDevices ?
NSString *notification = sentStatus == MXRoomSentStatusSentFailedDueToUnknownDevices ?
NSLocalizedStringFromTable(@"room_unsent_messages_unknown_devices_notification", @"Vector", nil) :
NSLocalizedStringFromTable(@"room_unsent_messages_notification", @"Vector", nil);
@ -4713,7 +4695,7 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo
}
}
return hasUnsent;
return sentStatus != MXRoomSentStatusOk;
}
- (void)eventDidChangeSentState:(NSNotification *)notif
@ -4851,19 +4833,32 @@ NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNo
- (void)cancelAllUnsentMessages
{
// Remove unsent event ids
for (NSUInteger index = 0; index < self.roomDataSource.room.outgoingMessages.count;)
{
MXEvent *event = self.roomDataSource.room.outgoingMessages[index];
if (event.sentState == MXEventSentStateFailed)
currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_unsent_messages_cancel_title", @"Vector", nil) message:NSLocalizedStringFromTable(@"room_unsent_messages_cancel_message", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert];
MXWeakify(self);
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
MXStrongifyAndReturnIfNil(self);
self->currentAlert = nil;
}]];
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"delete"] style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {
MXStrongifyAndReturnIfNil(self);
// Remove unsent event ids
for (NSUInteger index = 0; index < self.roomDataSource.room.outgoingMessages.count;)
{
[self.roomDataSource removeEventWithEventId:event.eventId];
MXEvent *event = self.roomDataSource.room.outgoingMessages[index];
if (event.sentState == MXEventSentStateFailed)
{
[self.roomDataSource removeEventWithEventId:event.eventId];
}
else
{
index ++;
}
}
else
{
index ++;
}
}
}]];
[self presentViewController:currentAlert animated:YES completion:nil];
}
# pragma mark - Encryption Information view

View file

@ -30,6 +30,11 @@
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainHeightConstraint;
@property (weak, nonatomic) IBOutlet UIView *unsentMessagesContentView;
@property (weak, nonatomic) IBOutlet UIButton *resendButton;
@property (weak, nonatomic) IBOutlet UIButton *deleteButton;
@property (weak, nonatomic) IBOutlet UILabel *unsentMessageLabel;
/**
Notify that some messages are not sent.
Replace the current notification if any.

View file

@ -118,64 +118,50 @@
{
self.messageLabel.textColor = ThemeService.shared.theme.textSecondaryColor;
}
[self.resendButton.layer setCornerRadius:5];
self.resendButton.clipsToBounds = YES;
[self.resendButton setTitle:NSLocalizedStringFromTable(@"room_event_action_resend", @"Vector", nil) forState:UIControlStateNormal];
self.resendButton.backgroundColor = ThemeService.shared.theme.tintColor;
UIImage *image = [[UIImage imageNamed:@"room_context_menu_delete"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[self.deleteButton setImage:image forState:UIControlStateNormal];
self.deleteButton.tintColor = ThemeService.shared.theme.warningColor;
self.unsentMessageLabel.textColor = ThemeService.shared.theme.textPrimaryColor;
}
#pragma mark -
- (IBAction)onCancelSendingPressed:(id)sender
{
void (^onCancelLinkPressed)(void) = objc_getAssociatedObject(self.deleteButton, "onCancelLinkPressed");
if (onCancelLinkPressed)
{
onCancelLinkPressed ();
}
}
- (IBAction)onResendMessagesPressed:(id)sender
{
void (^onResendLinkPressed)(void) = objc_getAssociatedObject(self.resendButton, "onResendLinkPressed");
if (onResendLinkPressed)
{
onResendLinkPressed();
}
}
- (void)displayUnsentMessagesNotification:(NSString*)notification withResendLink:(void (^)(void))onResendLinkPressed andCancelLink:(void (^)(void))onCancelLinkPressed andIconTapGesture:(void (^)(void))onIconTapGesture
{
[self reset];
if (onResendLinkPressed && onCancelLinkPressed)
{
NSString *resendLink = NSLocalizedStringFromTable(@"room_prompt_resend", @"Vector", nil);
NSString *cancelLink = NSLocalizedStringFromTable(@"room_prompt_cancel", @"Vector", nil);
NSString *notif = [NSString stringWithFormat:notification, resendLink, cancelLink];
NSMutableAttributedString *tappableNotif = [[NSMutableAttributedString alloc] initWithString:notif];
self.unsentMessagesContentView.hidden = NO;
self.unsentMessageLabel.text = notification;
objc_setAssociatedObject(self.messageTextView, "onResendLinkPressed", [onResendLinkPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self.messageTextView, "onCancelLinkPressed", [onCancelLinkPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSRange range = [notif rangeOfString:resendLink];
[tappableNotif addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
[tappableNotif addAttribute:NSLinkAttributeName value:@"onResendLink" range:range];
range = [notif rangeOfString:cancelLink];
[tappableNotif addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
[tappableNotif addAttribute:NSLinkAttributeName value:@"onCancelLink" range:range];
NSRange wholeString = NSMakeRange(0, tappableNotif.length);
[tappableNotif addAttribute:NSForegroundColorAttributeName value:ThemeService.shared.theme.warningColor range:wholeString];
[tappableNotif addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:15] range:wholeString];
self.messageTextView.attributedText = tappableNotif;
self.messageTextView.tintColor = ThemeService.shared.theme.warningColor;
self.messageTextView.hidden = NO;
self.messageTextView.backgroundColor = [UIColor clearColor];
}
else
{
self.messageLabel.text = notification;
self.messageLabel.textColor = ThemeService.shared.theme.warningColor;
self.messageLabel.hidden = NO;
}
self.iconImageView.image = [UIImage imageNamed:@"error"];
self.iconImageView.tintColor = ThemeService.shared.theme.tintColor;
self.iconImageView.hidden = NO;
if (onIconTapGesture)
{
objc_setAssociatedObject(self.iconImageView, "onIconTapGesture", [onIconTapGesture copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Listen to icon tap
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onIconTap:)];
[tapGesture setNumberOfTouchesRequired:1];
[tapGesture setNumberOfTapsRequired:1];
[tapGesture setDelegate:self];
[self.iconImageView addGestureRecognizer:tapGesture];
self.iconImageView.userInteractionEnabled = YES;
objc_setAssociatedObject(self.resendButton, "onResendLinkPressed", [onResendLinkPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self.deleteButton, "onCancelLinkPressed", [onCancelLinkPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[self checkHeight:YES];
@ -517,6 +503,7 @@
- (void)reset
{
self.separatorView.hidden = NO;
self.unsentMessagesContentView.hidden = YES;
self.backgroundColor = UIColor.clearColor;

View file

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -56,16 +55,71 @@
<outlet property="delegate" destination="iN0-l3-epB" id="ZAk-sC-QpQ"/>
</connections>
</textView>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="l7B-JK-ZKf">
<rect key="frame" x="0.0" y="1" width="600" height="52"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="error_icon" translatesAutoresizingMaskIntoConstraints="NO" id="f50-cK-AVb">
<rect key="frame" x="13" y="18" width="16" height="16"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Messages failed to send" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZD8-X6-qAB" userLabel="Message Label">
<rect key="frame" x="41" y="17" width="166" height="18"/>
<accessibility key="accessibilityConfiguration" identifier="RoomActivitiesViewMessageLabel"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.66666666669999997" green="0.66666666669999997" blue="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="UwY-F8-8nJ">
<rect key="frame" x="467" y="14" width="24" height="24"/>
<state key="normal" image="room_context_menu_delete"/>
<connections>
<action selector="onCancelSendingPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="nCu-h1-Hek"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d0g-ev-lDj">
<rect key="frame" x="503" y="12" width="85" height="28"/>
<color key="backgroundColor" red="0.028153735480000001" green="0.82494870580000002" blue="0.051896891280000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="LeftButton"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="LhJ-4D-OZI"/>
<constraint firstAttribute="width" constant="85" id="n0H-1z-udV"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="6" maxY="0.0"/>
<state key="normal" title="Retry" image="room_activities_retry">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onResendMessagesPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="Aj1-oT-c0C"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="d0g-ev-lDj" firstAttribute="leading" secondItem="UwY-F8-8nJ" secondAttribute="trailing" constant="12" id="IZZ-du-a29"/>
<constraint firstItem="d0g-ev-lDj" firstAttribute="centerY" secondItem="l7B-JK-ZKf" secondAttribute="centerY" id="Uos-j3-ARf"/>
<constraint firstItem="ZD8-X6-qAB" firstAttribute="centerY" secondItem="l7B-JK-ZKf" secondAttribute="centerY" id="YCs-5T-5kQ"/>
<constraint firstItem="UwY-F8-8nJ" firstAttribute="centerY" secondItem="l7B-JK-ZKf" secondAttribute="centerY" id="YRY-fg-2qs"/>
<constraint firstItem="f50-cK-AVb" firstAttribute="leading" secondItem="l7B-JK-ZKf" secondAttribute="leading" constant="13" id="da5-bp-l7B"/>
<constraint firstAttribute="trailing" secondItem="d0g-ev-lDj" secondAttribute="trailing" constant="12" id="foj-YD-tjx"/>
<constraint firstItem="UwY-F8-8nJ" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="ZD8-X6-qAB" secondAttribute="trailing" constant="12" id="mPP-0E-MHa"/>
<constraint firstItem="ZD8-X6-qAB" firstAttribute="leading" secondItem="f50-cK-AVb" secondAttribute="trailing" constant="12" id="tWy-Tu-kM5"/>
<constraint firstItem="f50-cK-AVb" firstAttribute="centerY" secondItem="l7B-JK-ZKf" secondAttribute="centerY" id="zea-t6-SnQ"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="1Mq-77-vvo" firstAttribute="leading" secondItem="qhJ-5H-64e" secondAttribute="leading" constant="13" id="2S8-TD-jHO"/>
<constraint firstItem="7bS-1u-GUX" firstAttribute="leading" secondItem="d6j-oN-9uA" secondAttribute="trailing" constant="13" id="6Js-Lq-pKz"/>
<constraint firstAttribute="trailing" secondItem="l7B-JK-ZKf" secondAttribute="trailing" id="6ub-bL-RIw"/>
<constraint firstItem="7bS-1u-GUX" firstAttribute="centerY" secondItem="d6j-oN-9uA" secondAttribute="centerY" id="BIB-uG-AY7"/>
<constraint firstAttribute="bottom" secondItem="l7B-JK-ZKf" secondAttribute="bottom" id="G9G-RU-ftj"/>
<constraint firstAttribute="trailing" secondItem="1Mq-77-vvo" secondAttribute="trailing" constant="10" id="YBp-az-maq"/>
<constraint firstItem="d6j-oN-9uA" firstAttribute="leading" secondItem="qhJ-5H-64e" secondAttribute="leading" constant="13" id="YYC-Q1-IoC"/>
<constraint firstItem="6ND-Cq-ABj" firstAttribute="leading" secondItem="d6j-oN-9uA" secondAttribute="trailing" constant="13" id="ZdK-4Z-Yfz"/>
<constraint firstItem="6ND-Cq-ABj" firstAttribute="top" secondItem="qhJ-5H-64e" secondAttribute="top" constant="10" id="j7I-pC-7fV"/>
<constraint firstItem="l7B-JK-ZKf" firstAttribute="leading" secondItem="qhJ-5H-64e" secondAttribute="leading" id="k98-cf-ItU"/>
<constraint firstAttribute="bottom" secondItem="6ND-Cq-ABj" secondAttribute="bottom" constant="10" id="m0j-16-mJw"/>
<constraint firstItem="l7B-JK-ZKf" firstAttribute="top" secondItem="1Mq-77-vvo" secondAttribute="bottom" id="qLJ-nv-QD0"/>
<constraint firstItem="d6j-oN-9uA" firstAttribute="centerY" secondItem="qhJ-5H-64e" secondAttribute="centerY" id="rEU-px-J59"/>
<constraint firstItem="1Mq-77-vvo" firstAttribute="top" secondItem="qhJ-5H-64e" secondAttribute="top" id="stG-o2-SK4"/>
<constraint firstAttribute="trailing" secondItem="7bS-1u-GUX" secondAttribute="trailing" constant="30" id="tCW-EI-6Yt"/>
@ -87,15 +141,26 @@
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="deleteButton" destination="UwY-F8-8nJ" id="aV5-96-x5E"/>
<outlet property="iconImageView" destination="d6j-oN-9uA" id="Qhw-fw-uGc"/>
<outlet property="mainHeightConstraint" destination="xPK-Yw-hQ9" id="dJ5-pI-KyT"/>
<outlet property="messageLabel" destination="7bS-1u-GUX" id="5c1-hT-y49"/>
<outlet property="messageTextView" destination="6ND-Cq-ABj" id="xWk-ga-9xS"/>
<outlet property="resendButton" destination="d0g-ev-lDj" id="iQ1-LZ-cZh"/>
<outlet property="separatorView" destination="1Mq-77-vvo" id="8fc-AO-8hF"/>
<outlet property="unsentMessageLabel" destination="ZD8-X6-qAB" id="0FN-Nu-lfo"/>
<outlet property="unsentMessagesContentView" destination="l7B-JK-ZKf" id="6aW-n4-Bc5"/>
</connections>
<point key="canvasLocation" x="-390.39999999999998" y="80.50974512743629"/>
</view>
</objects>
<resources>
<image name="typing" width="31" height="30"/>
<image name="error_icon" width="16" height="16"/>
<image name="room_activities_retry" width="16" height="16"/>
<image name="room_context_menu_delete" width="24" height="24"/>
<image name="typing" width="30" height="30"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>