From f9390654e7e8444ce98db6ec9c9d0dd037b70369 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 12 Jan 2023 14:54:38 +1100 Subject: [PATCH 01/44] add info action in context menu --- .../Context Menu/ContextMenuVC+Action.swift | 10 ++++++++++ Session/Conversations/ConversationVC+Interaction.swift | 4 ++++ Session/Meta/Translations/de.lproj/Localizable.strings | 1 + Session/Meta/Translations/en.lproj/Localizable.strings | 1 + Session/Meta/Translations/es.lproj/Localizable.strings | 1 + Session/Meta/Translations/fa.lproj/Localizable.strings | 1 + Session/Meta/Translations/fi.lproj/Localizable.strings | 1 + Session/Meta/Translations/fr.lproj/Localizable.strings | 1 + Session/Meta/Translations/hi.lproj/Localizable.strings | 1 + Session/Meta/Translations/hr.lproj/Localizable.strings | 1 + .../Meta/Translations/id-ID.lproj/Localizable.strings | 1 + Session/Meta/Translations/it.lproj/Localizable.strings | 1 + Session/Meta/Translations/ja.lproj/Localizable.strings | 1 + Session/Meta/Translations/nl.lproj/Localizable.strings | 1 + Session/Meta/Translations/pl.lproj/Localizable.strings | 3 ++- .../Meta/Translations/pt_BR.lproj/Localizable.strings | 1 + Session/Meta/Translations/ru.lproj/Localizable.strings | 1 + Session/Meta/Translations/si.lproj/Localizable.strings | 1 + Session/Meta/Translations/sk.lproj/Localizable.strings | 1 + Session/Meta/Translations/sv.lproj/Localizable.strings | 1 + Session/Meta/Translations/th.lproj/Localizable.strings | 1 + .../Meta/Translations/vi-VN.lproj/Localizable.strings | 1 + .../Translations/zh-Hant.lproj/Localizable.strings | 1 + .../Meta/Translations/zh_CN.lproj/Localizable.strings | 1 + 24 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index b8f19816b..7a42f2aa5 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -34,6 +34,14 @@ extension ContextMenuVC { } // MARK: - Actions + + static func info(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action { + return Action( + icon: UIImage(named: "ic_info"), + title: "context_menu_info".localized(), + accessibilityLabel: "Message info" + ) { delegate?.info(cellViewModel) } + } static func reply(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action { return Action( @@ -185,6 +193,7 @@ extension ContextMenuVC { (canDelete ? Action.delete(cellViewModel, delegate) : nil), (canBan ? Action.ban(cellViewModel, delegate) : nil), (canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate) : nil), + Action.info(cellViewModel, delegate), ] .appending(contentsOf: (shouldShowEmojiActions ? recentEmojis : []).map { Action.react(cellViewModel, $0, delegate) }) .appending(Action.emojiPlusButton(cellViewModel, delegate)) @@ -199,6 +208,7 @@ extension ContextMenuVC { // MARK: - Delegate protocol ContextMenuActionDelegate { + func info(_ cellViewModel: MessageViewModel) func reply(_ cellViewModel: MessageViewModel) func copy(_ cellViewModel: MessageViewModel) func copySessionID(_ cellViewModel: MessageViewModel) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 7758c398e..66de47bff 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1556,6 +1556,10 @@ extension ConversationVC: } // MARK: - ContextMenuActionDelegate + + func info(_ cellViewModel: MessageViewModel) { + + } func reply(_ cellViewModel: MessageViewModel) { let maybeQuoteDraft: QuotedReplyModel? = QuotedReplyModel.quotedReplyForSending( diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 8d583d798..f13af38e0 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Nur für mich löschen"; "delete_message_for_everyone" = "Für jeden löschen"; "delete_message_for_me_and_recipient" = "Für mich und %@ löschen"; +"context_menu_info" = "Info"; "context_menu_reply" = "Antworten"; "context_menu_save" = "Speichern"; "context_menu_ban_user" = "Nutzer sperren"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 3829aceab..50a3f2de2 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Delete just for me"; "delete_message_for_everyone" = "Delete for everyone"; "delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Reply"; "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 31d0bc578..174cdd3d7 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Eliminar solo para mí"; "delete_message_for_everyone" = "Eliminar para todos"; "delete_message_for_me_and_recipient" = "Eliminar para mí y para %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Responder"; "context_menu_save" = "Guardar"; "context_menu_ban_user" = "Banear Usuario"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 75cc6f737..47e852e1a 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "حذف برای من"; "delete_message_for_everyone" = "حذف برای همه"; "delete_message_for_me_and_recipient" = "حذف برای من و %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "پاسخ"; "context_menu_save" = "ذخیره"; "context_menu_ban_user" = "مسدود کردن کاربر"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index 92e2e6a11..e83aa02d1 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Poista vain minun nähtäväksi"; "delete_message_for_everyone" = "Poista kaikkien näkyviltä"; "delete_message_for_me_and_recipient" = "Poista minulta ja vastaanottajalta"; +"context_menu_info" = "Info"; "context_menu_reply" = "Vastaa"; "context_menu_save" = "Tallenna"; "context_menu_ban_user" = "Estä Käyttäjä"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 17c7b0dff..b9679b991 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Supprimer pour moi uniquement"; "delete_message_for_everyone" = "Supprimer pour tout le monde"; "delete_message_for_me_and_recipient" = "Supprimer pour moi et %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Répondre"; "context_menu_save" = "Enregistrer"; "context_menu_ban_user" = "Bannir l'utilisateur"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 8bab51375..4a2ce852b 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Delete just for me"; "delete_message_for_everyone" = "Delete for everyone"; "delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Reply"; "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 8af92e999..7fb31da3b 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Izbriši samo za mene"; "delete_message_for_everyone" = "Izbriši za sve"; "delete_message_for_me_and_recipient" = "Izbriši za mene i %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Odgovori"; "context_menu_save" = "Spremi"; "context_menu_ban_user" = "Zabrani korisnik"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index 5ff981ef7..ac4308d19 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Delete just for me"; "delete_message_for_everyone" = "Delete for everyone"; "delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Reply"; "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index f6725544a..dd11268b3 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Elimina solo per me"; "delete_message_for_everyone" = "Elimina per tutti"; "delete_message_for_me_and_recipient" = "Elimina per me e %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Rispondi"; "context_menu_save" = "Salva"; "context_menu_ban_user" = "Banna utente"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 6319ef7fd..418ccdc3e 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "自分の端末から削除"; "delete_message_for_everyone" = "全員の端末から削除"; "delete_message_for_me_and_recipient" = "自分と %@ の端末から削除する"; +"context_menu_info" = "Info"; "context_menu_reply" = "返信"; "context_menu_save" = "保存"; "context_menu_ban_user" = "ユーザーをBAN"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 284441372..6f0370319 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Verwijder alleen voor mij"; "delete_message_for_everyone" = "Verwijder voor iedereen"; "delete_message_for_me_and_recipient" = "Verwijderen voor mij en %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Antwoord"; "context_menu_save" = "Opslaan"; "context_menu_ban_user" = "Gebruiker verbannen"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index ab8b48d0b..1d6bb8278 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -374,11 +374,12 @@ "delete_message_for_me" = "Usuń tylko dla mnie"; "delete_message_for_everyone" = "Usuń dla wszystkich"; "delete_message_for_me_and_recipient" = "Usuń dla mnie i %@"; -"context_menu_ban_user_error_alert_message" = "Unable to ban user"; +"context_menu_info" = "Info"; "context_menu_reply" = "Odpowiedz"; "context_menu_save" = "Zapisz"; "context_menu_ban_user" = "Zbanuj użytkownika"; "context_menu_ban_and_delete_all" = "Zbanuj i usuń wszystko"; +"context_menu_ban_user_error_alert_message" = "Unable to ban user"; "accessibility_expanding_attachments_button" = "Dodaj załączniki"; "accessibility_gif_button" = "Gif"; "accessibility_document_button" = "Dokument"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index cd772f650..2dc69754f 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Apagar para mim"; "delete_message_for_everyone" = "Apagar para todos"; "delete_message_for_me_and_recipient" = "Apagar para mim e para %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Responder"; "context_menu_save" = "Salvar"; "context_menu_ban_user" = "Banir Usuário"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 632e50aa4..a01d30520 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Удалить только для меня"; "delete_message_for_everyone" = "Удалить для всех"; "delete_message_for_me_and_recipient" = "Удалить для меня и %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Ответить"; "context_menu_save" = "Сохранить"; "context_menu_ban_user" = "Заблокировать пользователя"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 753687545..f971d0c37 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Delete just for me"; "delete_message_for_everyone" = "Delete for everyone"; "delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "පිළිතුරු"; "context_menu_save" = "සුරකින්න"; "context_menu_ban_user" = "Ban User"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index 1388f0536..fcd6897ba 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Vymazať len u mňa"; "delete_message_for_everyone" = "Vymazať u všetkých"; "delete_message_for_me_and_recipient" = "Vymazať pre mňa a %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Odpovedať"; "context_menu_save" = "Uložiť"; "context_menu_ban_user" = "Zablokovanie používateľa"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index 2af704369..faa1d814f 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Delete just for me"; "delete_message_for_everyone" = "Delete for everyone"; "delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Reply"; "context_menu_save" = "Spara"; "context_menu_ban_user" = "Ban User"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 24910f380..4fda388e2 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Delete just for me"; "delete_message_for_everyone" = "Delete for everyone"; "delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Reply"; "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 6a27510bc..522786f15 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "Delete just for me"; "delete_message_for_everyone" = "Delete for everyone"; "delete_message_for_me_and_recipient" = "Delete for me and %@"; +"context_menu_info" = "Info"; "context_menu_reply" = "Reply"; "context_menu_save" = "Save"; "context_menu_ban_user" = "Ban User"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index d408fc648..d8fd4deb6 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "只為我自己刪除"; "delete_message_for_everyone" = "從所有人的裝置上刪除"; "delete_message_for_me_and_recipient" = "為我和 %@ 刪除"; +"context_menu_info" = "Info"; "context_menu_reply" = "回覆"; "context_menu_save" = "儲存"; "context_menu_ban_user" = "封鎖用戶"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index f391b1784..501a4027e 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -374,6 +374,7 @@ "delete_message_for_me" = "仅为我删除"; "delete_message_for_everyone" = "为所有人删除"; "delete_message_for_me_and_recipient" = "为我和 %@ 删除"; +"context_menu_info" = "Info"; "context_menu_reply" = "回复"; "context_menu_save" = "保存"; "context_menu_ban_user" = "封禁用户"; From 7515e81b4ba2adeffdcc5405558b6af8746f6df7 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 13 Jan 2023 16:54:48 +1100 Subject: [PATCH 02/44] WIP: add message info view to contextMenuVC --- Session.xcodeproj/project.pbxproj | 4 + .../ContextMenuVC+MessageInfoView.swift | 87 +++++++++++++++++++ .../Context Menu/ContextMenuVC.swift | 15 ++++ 3 files changed, 106 insertions(+) create mode 100644 Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 16253cb68..709944a25 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -109,6 +109,7 @@ 7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */; }; 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; + 7B3A392C2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; @@ -1175,6 +1176,7 @@ 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = ""; }; 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; + 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+MessageInfoView.swift"; sourceTree = ""; }; 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -2712,6 +2714,7 @@ C328254825CA60E60062D0A7 /* ContextMenuVC+Action.swift */, C328255125CA64470062D0A7 /* ContextMenuVC+ActionView.swift */, 7BFA8AE22831D0D4001876F3 /* ContextMenuVC+EmojiReactsView.swift */, + 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */, ); path = "Context Menu"; sourceTree = ""; @@ -5614,6 +5617,7 @@ 34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */, FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, + 7B3A392C2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift in Sources */, 7B8C44C528B49DDA00FBE25F /* NewConversationVC.swift in Sources */, 7B1B52E028580D51006069F2 /* EmojiSkinTonePicker.swift in Sources */, B849789625D4A2F500D0D0B3 /* LinkPreviewView.swift in Sources */, diff --git a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift new file mode 100644 index 000000000..04e1977c9 --- /dev/null +++ b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift @@ -0,0 +1,87 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionUIKit +import SessionUtilitiesKit + +extension ContextMenuVC { + final class MessageInfoView: UIView { + private static let cornerRadius: CGFloat = 8 + + private let cellViewModel: MessageViewModel + + // MARK: - UI + + private lazy var messageSentDateLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var messageReceivedDateLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var profilePictureView: ProfilePictureView = { + let result: ProfilePictureView = ProfilePictureView() + result.set(.height, to: Values.verySmallProfilePictureSize) + result.size = Values.verySmallProfilePictureSize + + return result + }() + + private lazy var displayNameLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.verySmallFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var sessionIDLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.verySmallFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + // MARK: - Lifecycle + + init(cellViewModel: MessageViewModel) { + self.cellViewModel = cellViewModel + + super.init(frame: CGRect.zero) + self.accessibilityLabel = "Message info" + setUpViewHierarchy() + } + + override init(frame: CGRect) { + preconditionFailure("Use init(cellViewModel:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(cellViewModel:) instead.") + } + + private func setUpViewHierarchy() { + let backgroundView: UIView = UIView() + backgroundView.clipsToBounds = true + backgroundView.themeBackgroundColor = .contextMenu_background + backgroundView.layer.cornerRadius = Self.cornerRadius + addSubview(backgroundView) + backgroundView.pin(to: self) + + let stackView: UIStackView = UIStackView() + stackView.axis = .vertical + backgroundView.addSubview(stackView) + stackView.pin(to: backgroundView) + } + } +} diff --git a/Session/Conversations/Context Menu/ContextMenuVC.swift b/Session/Conversations/Context Menu/ContextMenuVC.swift index d68851588..f7d46d695 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC.swift @@ -59,6 +59,17 @@ final class ContextMenuVC: UIViewController { return result }() + private lazy var messageInfoView: MessageInfoView = { + let result: MessageInfoView = MessageInfoView(cellViewModel: self.cellViewModel) + result.themeShadowColor = .black + result.layer.shadowOffset = CGSize.zero + result.layer.shadowOpacity = 0.4 + result.layer.shadowRadius = 4 + result.alpha = 0 + + return result + }() + private lazy var timestampLabel: UILabel = { let result: UILabel = UILabel() result.font = .systemFont(ofSize: Values.verySmallFontSize) @@ -171,6 +182,10 @@ final class ContextMenuVC: UIViewController { menuStackView.pin(to: menuBackgroundView) view.addSubview(menuView) + // MessageInfo + + view.addSubview(messageInfoView) + // Timestamp view.addSubview(timestampLabel) timestampLabel.center(.vertical, in: snapshot) From 7f5e03fad90840e72a305ba79fdda30f2966456c Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 16 Jan 2023 15:40:21 +1100 Subject: [PATCH 03/44] update translation --- .../Context Menu/ContextMenuVC+MessageInfoView.swift | 2 ++ Session/Meta/Translations/de.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/en.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/es.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/fa.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/fi.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/fr.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/hi.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/hr.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/id-ID.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/it.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/ja.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/nl.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/pl.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/pt_BR.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/ru.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/si.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/sk.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/sv.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/th.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/vi-VN.lproj/Localizable.strings | 8 ++++++++ .../Meta/Translations/zh-Hant.lproj/Localizable.strings | 8 ++++++++ Session/Meta/Translations/zh_CN.lproj/Localizable.strings | 8 ++++++++ 23 files changed, 178 insertions(+) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift index 04e1977c9..3f7d0d1a0 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift @@ -82,6 +82,8 @@ extension ContextMenuVC { stackView.axis = .vertical backgroundView.addSubview(stackView) stackView.pin(to: backgroundView) + + } } } diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index f13af38e0..4d45d42c9 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 50a3f2de2..5c31790a2 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 174cdd3d7..cf05e72f3 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index 47e852e1a..db61b0152 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "خوانده شد"; "MESSAGE_STATE_SENT" = "ارسال شد"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index e83aa02d1..29f19f871 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index b9679b991..362e61aba 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 4a2ce852b..70ba70783 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index 7fb31da3b..99a61c7de 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index ac4308d19..cdcfc7ea0 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index dd11268b3..fcd04c155 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 418ccdc3e..e1acb80d4 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 6f0370319..b62ac0b2a 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index 1d6bb8278..93c3b63ca 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 2dc69754f..29ae2995c 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index a01d30520..085e86d85 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index f971d0c37..1433d2787 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index fcd6897ba..d8dbd1fb5 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index faa1d814f..c46a59a80 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index 4fda388e2..3060680cf 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 522786f15..326425af6 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index d8fd4deb6..451a5d9f0 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 501a4027e..c7dc76d39 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -597,3 +597,11 @@ "MESSAGE_STATE_READ" = "Read"; "MESSAGE_STATE_SENT" = "Sent"; "MESSAGE_REQUEST_PENDING_APPROVAL_INFO" = "You will be able to send voice messages and attachments once the recipient has approved this message request"; +"MESSAGE_INFO_SENT" = "Sent"; +"MESSAGE_INFO_RECEIVED" = "Received"; +"MESSAGE_INFO_FROM" = "From"; +"ATTACHMENT_INFO_FILE_ID" = "File ID"; +"ATTACHMENT_INFO_FILE_TYPE" = "File Type"; +"ATTACHMENT_INFO_FILE_SIZE" = "File Size"; +"ATTACHMENT_INFO_RESOLUTION" = "Resolution"; +"ATTACHMENT_INFO_DURATION" = "Duration"; From d97730ab637dc5a493c6cec69c9809cbaa26649a Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 16 Jan 2023 17:17:29 +1100 Subject: [PATCH 04/44] WIP: add receivedAtTimestampMs to MessageViewModel --- .../ContextMenuVC+MessageInfoView.swift | 30 ++++++++++++++++++- .../Context Menu/ContextMenuVC.swift | 7 ++++- Session/Utilities/Date+Utilities.swift | 8 +++++ .../Shared Models/MessageViewModel.swift | 11 ++++++- 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift index 3f7d0d1a0..24a23eb84 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift @@ -38,7 +38,7 @@ extension ContextMenuVC { private lazy var displayNameLabel: UILabel = { let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.verySmallFontSize) + result.font = .boldSystemFont(ofSize: Values.verySmallFontSize) result.themeTextColor = .textPrimary return result @@ -83,7 +83,35 @@ extension ContextMenuVC { backgroundView.addSubview(stackView) stackView.pin(to: backgroundView) + messageSentDateLabel.text = "MESSAGE_INFO_SENT".localized() + ":\n" + cellViewModel.dateForUI.fromattedForMessageInfo + stackView.addArrangedSubview(messageSentDateLabel) + messageReceivedDateLabel.text = "MESSAGE_INFO_RECEIVED".localized() + ":\n" + cellViewModel.receivedDateForUI.fromattedForMessageInfo + stackView.addArrangedSubview(messageReceivedDateLabel) + + let senderTitleLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + result.text = "MESSAGE_INFO_FROM".localized() + ":" + + return result + }() + stackView.addArrangedSubview(senderTitleLabel) + + let displayNameStackView: UIStackView = UIStackView(arrangedSubviews: [ displayNameLabel, sessionIDLabel ]) + displayNameStackView.axis = .vertical + displayNameLabel.text = cellViewModel.authorName + sessionIDLabel.text = cellViewModel.authorId + + let profileStackView: UIStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameStackView ]) + profileStackView.axis = .horizontal + profilePictureView.update( + publicKey: cellViewModel.authorId, + profile: cellViewModel.profile, + threadVariant: cellViewModel.threadVariant + ) + stackView.addArrangedSubview(profileStackView) } } } diff --git a/Session/Conversations/Context Menu/ContextMenuVC.swift b/Session/Conversations/Context Menu/ContextMenuVC.swift index f7d46d695..efc57f11e 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC.swift @@ -66,6 +66,7 @@ final class ContextMenuVC: UIViewController { result.layer.shadowOpacity = 0.4 result.layer.shadowRadius = 4 result.alpha = 0 + result.set(.width, lessThanOrEqualTo: 280) return result }() @@ -183,7 +184,6 @@ final class ContextMenuVC: UIViewController { view.addSubview(menuView) // MessageInfo - view.addSubview(messageInfoView) // Timestamp @@ -230,18 +230,22 @@ final class ContextMenuVC: UIViewController { snapshot.frame = self.frame emojiBar.pin(.bottom, to: .top, of: view, withInset: targetFrame.minY - spacing) menuView.pin(.top, to: .top, of: view, withInset: targetFrame.maxY + spacing) + messageInfoView.pin(.top, to: .top, of: view, withInset: targetFrame.maxY + spacing) switch cellViewModel.variant { case .standardOutgoing: menuView.pin(.right, to: .right, of: view, withInset: -(UIScreen.main.bounds.width - targetFrame.maxX)) + messageInfoView.pin(.right, to: .right, of: view, withInset: -(UIScreen.main.bounds.width - targetFrame.maxX)) emojiBar.pin(.right, to: .right, of: view, withInset: -(UIScreen.main.bounds.width - targetFrame.maxX)) case .standardIncoming, .standardIncomingDeleted: menuView.pin(.left, to: .left, of: view, withInset: targetFrame.minX) + messageInfoView.pin(.left, to: .left, of: view, withInset: targetFrame.minX) emojiBar.pin(.left, to: .left, of: view, withInset: targetFrame.minX) default: // Should generally only be the 'delete' action menuView.pin(.left, to: .left, of: view, withInset: targetFrame.minX) + messageInfoView.pin(.left, to: .left, of: view, withInset: targetFrame.minX) } // Tap gesture @@ -269,6 +273,7 @@ final class ContextMenuVC: UIViewController { UIView.animate(withDuration: 0.2) { [weak self] in self?.emojiBar.alpha = 1 self?.menuView.alpha = 1 + self?.messageInfoView.alpha = 1 self?.timestampLabel.alpha = 1 self?.fallbackTimestampLabel.alpha = 1 } diff --git a/Session/Utilities/Date+Utilities.swift b/Session/Utilities/Date+Utilities.swift index 5336c20b4..ed7aab4f4 100644 --- a/Session/Utilities/Date+Utilities.swift +++ b/Session/Utilities/Date+Utilities.swift @@ -29,6 +29,14 @@ public extension Date { return "DATE_NOW".localized() } + + var fromattedForMessageInfo: String { + let formatter: DateFormatter = DateFormatter() + formatter.locale = Locale.current + formatter.dateFormat = "h:mm a EEE, DD/MM/YYYY" + + return formatter.string(from: self) + } } // MARK: - Formatters diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 81c452039..72b939b70 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -73,6 +73,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, public let id: Int64 public let variant: Interaction.Variant public let timestampMs: Int64 + public let receivedTimestampMs: Int64 public let authorId: String private let authorNameInternal: String? public let body: String? @@ -122,6 +123,9 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, /// This value will be used to populate the Context Menu and date header (if present) public var dateForUI: Date { Date(timeIntervalSince1970: (TimeInterval(self.timestampMs) / 1000)) } + /// This value will be used to populate the Message Info (if present) + public var receivedDateForUI: Date { Date(timeIntervalSince1970: (TimeInterval(self.receivedTimestampMs) / 1000)) } + /// This value specifies whether the body contains only emoji characters public let containsOnlyEmoji: Bool? @@ -161,6 +165,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, id: self.id, variant: self.variant, timestampMs: self.timestampMs, + receivedTimestampMs: self.receivedTimestampMs, authorId: self.authorId, authorNameInternal: self.authorNameInternal, body: self.body, @@ -316,6 +321,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, id: self.id, variant: self.variant, timestampMs: self.timestampMs, + receivedTimestampMs: self.receivedTimestampMs, authorId: self.authorId, authorNameInternal: self.authorNameInternal, body: (!self.variant.isInfoMessage ? @@ -494,6 +500,7 @@ public extension MessageViewModel { init( variant: Interaction.Variant = .standardOutgoing, timestampMs: Int64 = Int64.max, + receivedTimestampMs: Int64 = Int64.max, body: String? = nil, quote: Quote? = nil, cellType: CellType = .typingIndicator, @@ -520,6 +527,7 @@ public extension MessageViewModel { self.id = targetId self.variant = variant self.timestampMs = timestampMs + self.receivedTimestampMs = receivedTimestampMs self.authorId = "" self.authorNameInternal = nil self.body = body @@ -650,7 +658,7 @@ public extension MessageViewModel { let groupMemberProfileIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.profileId.name) let groupMemberRoleColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.role.name) - let numColumnsBeforeLinkedRecords: Int = 20 + let numColumnsBeforeLinkedRecords: Int = 21 let finalGroupSQL: SQL = (groupSQL ?? "") let request: SQLRequest = """ SELECT @@ -668,6 +676,7 @@ public extension MessageViewModel { \(interaction[.id]), \(interaction[.variant]), \(interaction[.timestampMs]), + \(interaction[.receivedAtTimestampMs]), \(interaction[.authorId]), IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.authorNameInternalKey), \(interaction[.body]), From f185ccf506772766c37bec42a79db86c6483113f Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 17 Jan 2023 14:24:21 +1100 Subject: [PATCH 05/44] add info message view in context menu vc --- .../ContextMenuVC+MessageInfoView.swift | 47 +++++++++++++------ .../Context Menu/ContextMenuVC.swift | 2 +- .../Shared Models/MessageViewModel.swift | 12 ++--- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift index 24a23eb84..01cc7bd40 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift @@ -14,31 +14,33 @@ extension ContextMenuVC { private lazy var messageSentDateLabel: UILabel = { let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) + result.font = .systemFont(ofSize: Values.smallFontSize) result.themeTextColor = .textPrimary + result.numberOfLines = 0 return result }() private lazy var messageReceivedDateLabel: UILabel = { let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) + result.font = .systemFont(ofSize: Values.smallFontSize) result.themeTextColor = .textPrimary + result.numberOfLines = 0 return result }() private lazy var profilePictureView: ProfilePictureView = { let result: ProfilePictureView = ProfilePictureView() - result.set(.height, to: Values.verySmallProfilePictureSize) - result.size = Values.verySmallProfilePictureSize + result.set(.height, to: Values.smallProfilePictureSize) + result.size = Values.smallProfilePictureSize return result }() private lazy var displayNameLabel: UILabel = { let result: UILabel = UILabel() - result.font = .boldSystemFont(ofSize: Values.verySmallFontSize) + result.font = .boldSystemFont(ofSize: Values.smallFontSize) result.themeTextColor = .textPrimary return result @@ -48,6 +50,8 @@ extension ContextMenuVC { let result: UILabel = UILabel() result.font = .systemFont(ofSize: Values.verySmallFontSize) result.themeTextColor = .textPrimary + result.numberOfLines = 0 + result.lineBreakMode = .byCharWrapping return result }() @@ -80,8 +84,9 @@ extension ContextMenuVC { let stackView: UIStackView = UIStackView() stackView.axis = .vertical + stackView.spacing = Values.smallSpacing backgroundView.addSubview(stackView) - stackView.pin(to: backgroundView) + stackView.pin(to: backgroundView, withInset: Values.mediumSpacing) messageSentDateLabel.text = "MESSAGE_INFO_SENT".localized() + ":\n" + cellViewModel.dateForUI.fromattedForMessageInfo stackView.addArrangedSubview(messageSentDateLabel) @@ -91,27 +96,39 @@ extension ContextMenuVC { let senderTitleLabel: UILabel = { let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) + result.font = .systemFont(ofSize: Values.smallFontSize) result.themeTextColor = .textPrimary result.text = "MESSAGE_INFO_FROM".localized() + ":" return result }() - stackView.addArrangedSubview(senderTitleLabel) - - let displayNameStackView: UIStackView = UIStackView(arrangedSubviews: [ displayNameLabel, sessionIDLabel ]) - displayNameStackView.axis = .vertical + displayNameLabel.text = cellViewModel.authorName sessionIDLabel.text = cellViewModel.authorId - - let profileStackView: UIStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameStackView ]) - profileStackView.axis = .horizontal profilePictureView.update( publicKey: cellViewModel.authorId, profile: cellViewModel.profile, threadVariant: cellViewModel.threadVariant ) - stackView.addArrangedSubview(profileStackView) + + let profileContainerView: UIView = UIView() + profileContainerView.addSubview(senderTitleLabel) + senderTitleLabel.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top ], to: profileContainerView) + profileContainerView.addSubview(profilePictureView) + profilePictureView.pin(.leading, to: .leading, of: profileContainerView) + profilePictureView.pin(.top, to: .bottom, of: senderTitleLabel, withInset: Values.mediumSpacing) + profilePictureView.pin(.bottom, to: .bottom, of: profileContainerView, withInset: -Values.verySmallSpacing) + + let infoContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ displayNameLabel, sessionIDLabel ]) + infoContainerStackView.axis = .vertical + profileContainerView.addSubview(infoContainerStackView) + infoContainerStackView.pin(.leading, to: .trailing, of: profilePictureView, withInset: Values.mediumSpacing) + infoContainerStackView.pin(.trailing, to: .trailing, of: profileContainerView) + infoContainerStackView.pin(.bottom, to: .bottom, of: profileContainerView) + infoContainerStackView.set(.width, to: 240) + + stackView.addArrangedSubview(profileContainerView) + } } } diff --git a/Session/Conversations/Context Menu/ContextMenuVC.swift b/Session/Conversations/Context Menu/ContextMenuVC.swift index efc57f11e..31e2e940a 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC.swift @@ -66,7 +66,7 @@ final class ContextMenuVC: UIViewController { result.layer.shadowOpacity = 0.4 result.layer.shadowRadius = 4 result.alpha = 0 - result.set(.width, lessThanOrEqualTo: 280) + result.set(.width, to: 320) return result }() diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 72b939b70..9283df818 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -73,7 +73,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, public let id: Int64 public let variant: Interaction.Variant public let timestampMs: Int64 - public let receivedTimestampMs: Int64 + public let receivedAtTimestampMs: Int64 public let authorId: String private let authorNameInternal: String? public let body: String? @@ -124,7 +124,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, public var dateForUI: Date { Date(timeIntervalSince1970: (TimeInterval(self.timestampMs) / 1000)) } /// This value will be used to populate the Message Info (if present) - public var receivedDateForUI: Date { Date(timeIntervalSince1970: (TimeInterval(self.receivedTimestampMs) / 1000)) } + public var receivedDateForUI: Date { Date(timeIntervalSince1970: (TimeInterval(self.receivedAtTimestampMs) / 1000)) } /// This value specifies whether the body contains only emoji characters public let containsOnlyEmoji: Bool? @@ -165,7 +165,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, id: self.id, variant: self.variant, timestampMs: self.timestampMs, - receivedTimestampMs: self.receivedTimestampMs, + receivedAtTimestampMs: self.receivedAtTimestampMs, authorId: self.authorId, authorNameInternal: self.authorNameInternal, body: self.body, @@ -321,7 +321,7 @@ public struct MessageViewModel: FetchableRecordWithRowId, Decodable, Equatable, id: self.id, variant: self.variant, timestampMs: self.timestampMs, - receivedTimestampMs: self.receivedTimestampMs, + receivedAtTimestampMs: self.receivedAtTimestampMs, authorId: self.authorId, authorNameInternal: self.authorNameInternal, body: (!self.variant.isInfoMessage ? @@ -500,7 +500,7 @@ public extension MessageViewModel { init( variant: Interaction.Variant = .standardOutgoing, timestampMs: Int64 = Int64.max, - receivedTimestampMs: Int64 = Int64.max, + receivedAtTimestampMs: Int64 = Int64.max, body: String? = nil, quote: Quote? = nil, cellType: CellType = .typingIndicator, @@ -527,7 +527,7 @@ public extension MessageViewModel { self.id = targetId self.variant = variant self.timestampMs = timestampMs - self.receivedTimestampMs = receivedTimestampMs + self.receivedAtTimestampMs = receivedAtTimestampMs self.authorId = "" self.authorNameInternal = nil self.body = body From 890d822a2b6c9c8acec0654efd76bc00df021664 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 18 Jan 2023 11:31:57 +1100 Subject: [PATCH 06/44] wrap up message info --- .../Context Menu/ContextMenuVC+Action.swift | 4 +++ .../ContextMenuVC+MessageInfoView.swift | 36 ++++++++++++++++--- .../Context Menu/ContextMenuVC.swift | 28 ++++++++++++--- .../ConversationVC+Interaction.swift | 3 +- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index 7a42f2aa5..5b2f2ebad 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -10,6 +10,7 @@ extension ContextMenuVC { let isEmojiAction: Bool let isEmojiPlus: Bool let isDismissAction: Bool + let shouldDismissAfterAction: Bool let accessibilityLabel: String? let work: () -> Void @@ -21,6 +22,7 @@ extension ContextMenuVC { isEmojiAction: Bool = false, isEmojiPlus: Bool = false, isDismissAction: Bool = false, + shouldDismissAfterAction: Bool = true, accessibilityLabel: String? = nil, work: @escaping () -> Void ) { @@ -29,6 +31,7 @@ extension ContextMenuVC { self.isEmojiAction = isEmojiAction self.isEmojiPlus = isEmojiPlus self.isDismissAction = isDismissAction + self.shouldDismissAfterAction = shouldDismissAfterAction self.accessibilityLabel = accessibilityLabel self.work = work } @@ -39,6 +42,7 @@ extension ContextMenuVC { return Action( icon: UIImage(named: "ic_info"), title: "context_menu_info".localized(), + shouldDismissAfterAction: false, accessibilityLabel: "Message info" ) { delegate?.info(cellViewModel) } } diff --git a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift index 01cc7bd40..048629812 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift @@ -9,9 +9,25 @@ extension ContextMenuVC { private static let cornerRadius: CGFloat = 8 private let cellViewModel: MessageViewModel + private let dismissAction: () -> Void // MARK: - UI + private lazy var dismissButton: UIButton = { + let result: UIButton = UIButton(type: .custom) + result.setImage( + UIImage(named: "small_chevron_left")? + .withRenderingMode(.alwaysTemplate), + for: .normal + ) + result.addTarget(self, action: #selector(dismiss), for: UIControl.Event.touchUpInside) + result.themeTintColor = .white + result.set(.width, to: 20) + result.set(.height, to: 20) + + return result + }() + private lazy var messageSentDateLabel: UILabel = { let result: UILabel = UILabel() result.font = .systemFont(ofSize: Values.smallFontSize) @@ -58,8 +74,9 @@ extension ContextMenuVC { // MARK: - Lifecycle - init(cellViewModel: MessageViewModel) { + init(cellViewModel: MessageViewModel, dismissAction: @escaping () -> Void) { self.cellViewModel = cellViewModel + self.dismissAction = dismissAction super.init(frame: CGRect.zero) self.accessibilityLabel = "Message info" @@ -67,20 +84,25 @@ extension ContextMenuVC { } override init(frame: CGRect) { - preconditionFailure("Use init(cellViewModel:) instead.") + preconditionFailure("Use init(cellViewModel:dismiss:) instead.") } required init?(coder: NSCoder) { - preconditionFailure("Use init(cellViewModel:) instead.") + preconditionFailure("Use init(cellViewModel:dismiss:) instead.") } private func setUpViewHierarchy() { + addSubview(dismissButton) + dismissButton.pin(.top, to: .top, of: self, withInset: Values.smallSpacing) + dismissButton.pin(.leading, to: .leading, of: self) + let backgroundView: UIView = UIView() backgroundView.clipsToBounds = true backgroundView.themeBackgroundColor = .contextMenu_background backgroundView.layer.cornerRadius = Self.cornerRadius addSubview(backgroundView) - backgroundView.pin(to: self) + backgroundView.pin([ UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top, UIView.VerticalEdge.bottom ], to: self) + backgroundView.pin(.leading, to: .trailing, of: dismissButton) let stackView: UIStackView = UIStackView() stackView.axis = .vertical @@ -130,5 +152,11 @@ extension ContextMenuVC { stackView.addArrangedSubview(profileContainerView) } + + // MARK: - Interaction + + @objc private func dismiss() { + dismissAction() + } } } diff --git a/Session/Conversations/Context Menu/ContextMenuVC.swift b/Session/Conversations/Context Menu/ContextMenuVC.swift index 31e2e940a..5992fea74 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC.swift @@ -60,13 +60,13 @@ final class ContextMenuVC: UIViewController { }() private lazy var messageInfoView: MessageInfoView = { - let result: MessageInfoView = MessageInfoView(cellViewModel: self.cellViewModel) + let result: MessageInfoView = MessageInfoView(cellViewModel: self.cellViewModel, dismissAction: hideMessageInfo) result.themeShadowColor = .black result.layer.shadowOffset = CGSize.zero result.layer.shadowOpacity = 0.4 result.layer.shadowRadius = 4 result.alpha = 0 - result.set(.width, to: 320) + result.set(.width, to: 340) return result }() @@ -176,7 +176,12 @@ final class ContextMenuVC: UIViewController { let menuStackView = UIStackView( arrangedSubviews: actions .filter { !$0.isEmojiAction && !$0.isEmojiPlus && !$0.isDismissAction } - .map { action -> ActionView in ActionView(for: action, dismiss: snDismiss) } + .map { action -> ActionView in + ActionView( + for: action, + dismiss: action.shouldDismissAfterAction ? snDismiss : {} + ) + } ) menuStackView.axis = .vertical menuBackgroundView.addSubview(menuStackView) @@ -273,7 +278,6 @@ final class ContextMenuVC: UIViewController { UIView.animate(withDuration: 0.2) { [weak self] in self?.emojiBar.alpha = 1 self?.menuView.alpha = 1 - self?.messageInfoView.alpha = 1 self?.timestampLabel.alpha = 1 self?.fallbackTimestampLabel.alpha = 1 } @@ -360,6 +364,22 @@ final class ContextMenuVC: UIViewController { // MARK: - Interaction + func showMessageInfo() { + UIView.animate(withDuration: 0.2) { [weak self] in + self?.emojiBar.alpha = 0 + self?.menuView.alpha = 0 + self?.messageInfoView.alpha = 1 + } + } + + func hideMessageInfo() { + UIView.animate(withDuration: 0.2) { [weak self] in + self?.emojiBar.alpha = 1 + self?.menuView.alpha = 1 + self?.messageInfoView.alpha = 0 + } + } + @objc private func handleTap() { snDismiss() } diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 66de47bff..af4b60c7f 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1558,7 +1558,8 @@ extension ConversationVC: // MARK: - ContextMenuActionDelegate func info(_ cellViewModel: MessageViewModel) { - + guard let contextMenuVC = self.contextMenuVC else { return } + contextMenuVC.showMessageInfo() } func reply(_ cellViewModel: MessageViewModel) { From 107231d5dba89c4d044836e2af788d813883d781 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 18 Jan 2023 16:56:58 +1100 Subject: [PATCH 07/44] WIP: media info screen --- Session.xcodeproj/project.pbxproj | 4 + .../Media Viewing & Editing/MediaInfoVC.swift | 146 ++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 Session/Media Viewing & Editing/MediaInfoVC.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 709944a25..159c8234b 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -110,6 +110,7 @@ 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; 7B3A392C2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */; }; + 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; @@ -1177,6 +1178,7 @@ 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+MessageInfoView.swift"; sourceTree = ""; }; + 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -2983,6 +2985,7 @@ 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */, 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */, 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */, + 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */, ); path = "Media Viewing & Editing"; sourceTree = ""; @@ -5629,6 +5632,7 @@ FD71164828E2CE8700B47552 /* SessionCell+AccessoryView.swift in Sources */, B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */, FD71163A28E2C53700B47552 /* SessionAvatarCell.swift in Sources */, + 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */, 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */, B835247925C38D880089A44F /* MessageCell.swift in Sources */, B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */, diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift new file mode 100644 index 000000000..ab317f95c --- /dev/null +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -0,0 +1,146 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +final class MediaInfoVC: BaseVC { + private static let mediaInfoContainerCornerRadius: CGFloat = 8 + + // MARK: - UI + + private lazy var fullScreenButton: UIButton = { + let result: UIButton = UIButton(type: .custom) + + return result + }() + + private lazy var fileIdLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var fileTypeLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var fileSizeLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var resolutionLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var durationLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + // MARK: - Initialization + + init() { + super.init(nibName: nil, bundle: nil) + } + + override init(nibName: String?, bundle: Bundle?) { + preconditionFailure("Use init() instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init() instead.") + } + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + let mediaInfoContainer: UIView = UIView() + mediaInfoContainer.clipsToBounds = true + mediaInfoContainer.themeBackgroundColor = .contextMenu_background + mediaInfoContainer.layer.cornerRadius = Self.mediaInfoContainerCornerRadius + + // File ID + let fileIdTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_FILE_ID".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + fileIdLabel.text = "" // TODO: + let fileIdContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileIdTitleLabel, fileIdLabel ]) + fileIdContainerStackView.axis = .vertical + + // File Type + let fileTypeTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_FILE_TYPE".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + fileTypeLabel.text = "" // TODO: + let fileTypeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileTypeTitleLabel, fileTypeLabel ]) + fileTypeContainerStackView.axis = .vertical + + // File Size + let fileSizeTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_FILE_SIZE".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + fileSizeLabel.text = "" // TODO: + let fileSizeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileSizeTitleLabel, fileSizeLabel ]) + fileSizeContainerStackView.axis = .vertical + + // Resolution + let resolutionTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_RESOLUTION".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + resolutionLabel.text = "" // TODO: + let resolutionContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ resolutionTitleLabel, resolutionLabel ]) + resolutionContainerStackView.axis = .vertical + + // File Size + let durationTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_DURATION".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + durationLabel.text = "" // TODO: + let durationContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ durationTitleLabel, durationLabel ]) + durationContainerStackView.axis = .vertical + + } +} From 94456edd2ecc038dc086aaa82a61c8eb82ba2fa1 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 19 Jan 2023 10:47:21 +1100 Subject: [PATCH 08/44] wrap up media info container view --- Session.xcodeproj/project.pbxproj | 4 + .../Content Views/DocumentView.swift | 2 +- .../MediaInfoVC+MediaInfoView.swift | 174 ++++++++++++++++++ .../Media Viewing & Editing/MediaInfoVC.swift | 121 +----------- 4 files changed, 188 insertions(+), 113 deletions(-) create mode 100644 Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 159c8234b..236066890 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -109,6 +109,7 @@ 7B1D74AA27BCC16E0030B423 /* NSENotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */; }; 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; + 7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */; }; 7B3A392C2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */; }; 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; @@ -1176,6 +1177,7 @@ 7B1D74A927BCC16E0030B423 /* NSENotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSENotificationPresenter.swift; sourceTree = ""; }; 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = ""; }; 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = ""; }; + 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+MessageInfoView.swift"; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; @@ -2986,6 +2988,7 @@ 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */, 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */, 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */, + 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */, ); path = "Media Viewing & Editing"; sourceTree = ""; @@ -5740,6 +5743,7 @@ FD39352C28F382920084DADA /* VersionFooterView.swift in Sources */, 7B9F71D22852EEE2006DFE7B /* Emoji+SkinTones.swift in Sources */, 7B7CB18E270D066F0079FF93 /* IncomingCallBanner.swift in Sources */, + 7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */, B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */, 7BFD1A8A2745C4F000FB91B9 /* Permissions.swift in Sources */, B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */, diff --git a/Session/Conversations/Message Cells/Content Views/DocumentView.swift b/Session/Conversations/Message Cells/Content Views/DocumentView.swift index cdcfe5fed..88d2dc07c 100644 --- a/Session/Conversations/Message Cells/Content Views/DocumentView.swift +++ b/Session/Conversations/Message Cells/Content Views/DocumentView.swift @@ -46,7 +46,7 @@ final class DocumentView: UIView { // Size label let sizeLabel = UILabel() sizeLabel.font = .systemFont(ofSize: Values.verySmallFontSize) - sizeLabel.text = OWSFormat.formatFileSize(UInt(attachment.byteCount)) + sizeLabel.text = OWSFormat.formatFileSize(attachment.byteCount) sizeLabel.themeTextColor = textColor sizeLabel.lineBreakMode = .byTruncatingTail diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift new file mode 100644 index 000000000..145e781ae --- /dev/null +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift @@ -0,0 +1,174 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionUIKit +import SessionUtilitiesKit + +extension MediaInfoVC { + final class MediaInfoView: UIView { + private static let cornerRadius: CGFloat = 8 + + private let attachment: Attachment + + // MARK: - UI + + private lazy var fileIdLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var fileTypeLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var fileSizeLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var resolutionLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + private lazy var durationLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .systemFont(ofSize: Values.mediumFontSize) + result.themeTextColor = .textPrimary + + return result + }() + + // MARK: - Lifecycle + + init(attachment: Attachment) { + self.attachment = attachment + + super.init(frame: CGRect.zero) + self.accessibilityLabel = "Media info" + setUpViewHierarchy() + } + + override init(frame: CGRect) { + preconditionFailure("Use init(attachment:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(attachment:) instead.") + } + + private func setUpViewHierarchy() { + let backgroundView: UIView = UIView() + backgroundView.clipsToBounds = true + backgroundView.themeBackgroundColor = .contextMenu_background + backgroundView.layer.cornerRadius = Self.cornerRadius + addSubview(backgroundView) + backgroundView.pin(to: self) + + let container: UIView = UIView() + container.set(.width, to: 245) + + // File ID + let fileIdTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_FILE_ID".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + fileIdLabel.text = attachment.serverId + let fileIdContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileIdTitleLabel, fileIdLabel ]) + fileIdContainerStackView.axis = .vertical + container.addSubview(fileIdContainerStackView) + fileIdContainerStackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top ], to: container) + + // File Type + let fileTypeTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_FILE_TYPE".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + fileTypeLabel.text = attachment.contentType + let fileTypeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileTypeTitleLabel, fileTypeLabel ]) + fileTypeContainerStackView.axis = .vertical + container.addSubview(fileTypeContainerStackView) + fileTypeContainerStackView.pin(.leading, to: .leading, of: container) + fileTypeContainerStackView.pin(.top, to: .bottom, of: fileIdContainerStackView, withInset: Values.mediumSpacing) + + // File Size + let fileSizeTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_FILE_SIZE".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + fileSizeLabel.text = OWSFormat.formatFileSize(attachment.byteCount) + let fileSizeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileSizeTitleLabel, fileSizeLabel ]) + fileSizeContainerStackView.axis = .vertical + container.addSubview(fileSizeContainerStackView) + fileSizeContainerStackView.pin(.trailing, to: .trailing, of: container) + fileSizeContainerStackView.pin(.top, to: .bottom, of: fileIdContainerStackView, withInset: Values.mediumSpacing) + fileSizeContainerStackView.set(.width, to: 90) + + // Resolution + let resolutionTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_RESOLUTION".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + resolutionLabel.text = { + guard let width = attachment.width, let height = attachment.height else { return "N/A" } + return "\(width)×\(height)" + }() + let resolutionContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ resolutionTitleLabel, resolutionLabel ]) + resolutionContainerStackView.axis = .vertical + container.addSubview(resolutionContainerStackView) + resolutionContainerStackView.pin(.leading, to: .leading, of: container) + resolutionContainerStackView.pin(.top, to: .bottom, of: fileTypeContainerStackView, withInset: Values.mediumSpacing) + + // File Size + let durationTitleLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = "ATTACHMENT_INFO_DURATION".localized() + ":" + result.themeTextColor = .textPrimary + + return result + }() + durationLabel.text = { + guard let duration = attachment.duration else { return "N/A" } + return "\(duration)" + }() + let durationContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ durationTitleLabel, durationLabel ]) + durationContainerStackView.axis = .vertical + durationContainerStackView.pin(.trailing, to: .trailing, of: container) + durationContainerStackView.pin(.top, to: .bottom, of: fileSizeContainerStackView, withInset: Values.mediumSpacing) + durationContainerStackView.set(.width, to: 90) + + addSubview(container) + container.pin(to: self, withInset: Values.mediumSpacing) + } + } +} diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index ab317f95c..6d7c195ee 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -1,9 +1,12 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit +import SessionUIKit +import SessionUtilitiesKit final class MediaInfoVC: BaseVC { - private static let mediaInfoContainerCornerRadius: CGFloat = 8 + + private let attachments: [Attachment] // MARK: - UI @@ -13,58 +16,21 @@ final class MediaInfoVC: BaseVC { return result }() - private lazy var fileIdLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) - result.themeTextColor = .textPrimary - - return result - }() - private lazy var fileTypeLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) - result.themeTextColor = .textPrimary - - return result - }() - - private lazy var fileSizeLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) - result.themeTextColor = .textPrimary - - return result - }() - - private lazy var resolutionLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) - result.themeTextColor = .textPrimary - - return result - }() - - private lazy var durationLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) - result.themeTextColor = .textPrimary - - return result - }() // MARK: - Initialization - init() { + init(attachments: [Attachment]) { + self.attachments = attachments super.init(nibName: nil, bundle: nil) } override init(nibName: String?, bundle: Bundle?) { - preconditionFailure("Use init() instead.") + preconditionFailure("Use init(attachments:) instead.") } required init?(coder: NSCoder) { - preconditionFailure("Use init() instead.") + preconditionFailure("Use init(attachments:) instead.") } // MARK: - Lifecycle @@ -72,75 +38,6 @@ final class MediaInfoVC: BaseVC { override func viewDidLoad() { super.viewDidLoad() - let mediaInfoContainer: UIView = UIView() - mediaInfoContainer.clipsToBounds = true - mediaInfoContainer.themeBackgroundColor = .contextMenu_background - mediaInfoContainer.layer.cornerRadius = Self.mediaInfoContainerCornerRadius - - // File ID - let fileIdTitleLabel: UILabel = { - let result = UILabel() - result.font = .boldSystemFont(ofSize: Values.mediumFontSize) - result.text = "ATTACHMENT_INFO_FILE_ID".localized() + ":" - result.themeTextColor = .textPrimary - - return result - }() - fileIdLabel.text = "" // TODO: - let fileIdContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileIdTitleLabel, fileIdLabel ]) - fileIdContainerStackView.axis = .vertical - - // File Type - let fileTypeTitleLabel: UILabel = { - let result = UILabel() - result.font = .boldSystemFont(ofSize: Values.mediumFontSize) - result.text = "ATTACHMENT_INFO_FILE_TYPE".localized() + ":" - result.themeTextColor = .textPrimary - - return result - }() - fileTypeLabel.text = "" // TODO: - let fileTypeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileTypeTitleLabel, fileTypeLabel ]) - fileTypeContainerStackView.axis = .vertical - - // File Size - let fileSizeTitleLabel: UILabel = { - let result = UILabel() - result.font = .boldSystemFont(ofSize: Values.mediumFontSize) - result.text = "ATTACHMENT_INFO_FILE_SIZE".localized() + ":" - result.themeTextColor = .textPrimary - - return result - }() - fileSizeLabel.text = "" // TODO: - let fileSizeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileSizeTitleLabel, fileSizeLabel ]) - fileSizeContainerStackView.axis = .vertical - - // Resolution - let resolutionTitleLabel: UILabel = { - let result = UILabel() - result.font = .boldSystemFont(ofSize: Values.mediumFontSize) - result.text = "ATTACHMENT_INFO_RESOLUTION".localized() + ":" - result.themeTextColor = .textPrimary - - return result - }() - resolutionLabel.text = "" // TODO: - let resolutionContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ resolutionTitleLabel, resolutionLabel ]) - resolutionContainerStackView.axis = .vertical - - // File Size - let durationTitleLabel: UILabel = { - let result = UILabel() - result.font = .boldSystemFont(ofSize: Values.mediumFontSize) - result.text = "ATTACHMENT_INFO_DURATION".localized() + ":" - result.themeTextColor = .textPrimary - - return result - }() - durationLabel.text = "" // TODO: - let durationContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ durationTitleLabel, durationLabel ]) - durationContainerStackView.axis = .vertical } } From 68efe731e7e1af35dfb6044b53e13dd730b385c1 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 19 Jan 2023 11:37:49 +1100 Subject: [PATCH 09/44] WIP: Media info vc --- Session/Conversations/ConversationVC+Interaction.swift | 4 ++-- .../Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift | 6 ++++-- Session/Media Viewing & Editing/MediaInfoVC.swift | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index af4b60c7f..23767f5a0 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1558,8 +1558,8 @@ extension ConversationVC: // MARK: - ContextMenuActionDelegate func info(_ cellViewModel: MessageViewModel) { - guard let contextMenuVC = self.contextMenuVC else { return } - contextMenuVC.showMessageInfo() + let mediaInfoVC = MediaInfoVC(attachments: (cellViewModel.attachments ?? [])) + navigationController?.pushViewController(mediaInfoVC, animated: true) } func reply(_ cellViewModel: MessageViewModel) { diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift index 145e781ae..a39c82c1e 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift @@ -163,12 +163,14 @@ extension MediaInfoVC { }() let durationContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ durationTitleLabel, durationLabel ]) durationContainerStackView.axis = .vertical + container.addSubview(durationContainerStackView) durationContainerStackView.pin(.trailing, to: .trailing, of: container) durationContainerStackView.pin(.top, to: .bottom, of: fileSizeContainerStackView, withInset: Values.mediumSpacing) durationContainerStackView.set(.width, to: 90) + container.pin(.bottom, to: .bottom, of: durationContainerStackView) - addSubview(container) - container.pin(to: self, withInset: Values.mediumSpacing) + backgroundView.addSubview(container) + container.pin(to: backgroundView, withInset: Values.largeSpacing) } } } diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 6d7c195ee..cc988dafa 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -38,6 +38,12 @@ final class MediaInfoVC: BaseVC { override func viewDidLoad() { super.viewDidLoad() + self.title = "Message Info" + attachments.forEach { + let mediaInfoView: MediaInfoView = MediaInfoView(attachment: $0) + self.view.addSubview(mediaInfoView) + mediaInfoView.center(in: self.view) + } } } From 0c91f3e25868972edd4c84be8a6855b0cc2d9e3b Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 19 Jan 2023 11:48:15 +1100 Subject: [PATCH 10/44] clean --- Session.xcodeproj/project.pbxproj | 4 - .../Context Menu/ContextMenuVC+Action.swift | 4 - .../ContextMenuVC+MessageInfoView.swift | 162 ------------------ .../Context Menu/ContextMenuVC.swift | 40 +---- 4 files changed, 1 insertion(+), 209 deletions(-) delete mode 100644 Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 236066890..9edcb6015 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -110,7 +110,6 @@ 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; 7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */; }; - 7B3A392C2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */; }; 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; @@ -1179,7 +1178,6 @@ 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = ""; }; 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; - 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+MessageInfoView.swift"; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; @@ -2718,7 +2716,6 @@ C328254825CA60E60062D0A7 /* ContextMenuVC+Action.swift */, C328255125CA64470062D0A7 /* ContextMenuVC+ActionView.swift */, 7BFA8AE22831D0D4001876F3 /* ContextMenuVC+EmojiReactsView.swift */, - 7B3A392B2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift */, ); path = "Context Menu"; sourceTree = ""; @@ -5623,7 +5620,6 @@ 34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */, FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, - 7B3A392C2971100D002FE4AC /* ContextMenuVC+MessageInfoView.swift in Sources */, 7B8C44C528B49DDA00FBE25F /* NewConversationVC.swift in Sources */, 7B1B52E028580D51006069F2 /* EmojiSkinTonePicker.swift in Sources */, B849789625D4A2F500D0D0B3 /* LinkPreviewView.swift in Sources */, diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index 5b2f2ebad..7a42f2aa5 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -10,7 +10,6 @@ extension ContextMenuVC { let isEmojiAction: Bool let isEmojiPlus: Bool let isDismissAction: Bool - let shouldDismissAfterAction: Bool let accessibilityLabel: String? let work: () -> Void @@ -22,7 +21,6 @@ extension ContextMenuVC { isEmojiAction: Bool = false, isEmojiPlus: Bool = false, isDismissAction: Bool = false, - shouldDismissAfterAction: Bool = true, accessibilityLabel: String? = nil, work: @escaping () -> Void ) { @@ -31,7 +29,6 @@ extension ContextMenuVC { self.isEmojiAction = isEmojiAction self.isEmojiPlus = isEmojiPlus self.isDismissAction = isDismissAction - self.shouldDismissAfterAction = shouldDismissAfterAction self.accessibilityLabel = accessibilityLabel self.work = work } @@ -42,7 +39,6 @@ extension ContextMenuVC { return Action( icon: UIImage(named: "ic_info"), title: "context_menu_info".localized(), - shouldDismissAfterAction: false, accessibilityLabel: "Message info" ) { delegate?.info(cellViewModel) } } diff --git a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift b/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift deleted file mode 100644 index 048629812..000000000 --- a/Session/Conversations/Context Menu/ContextMenuVC+MessageInfoView.swift +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. - -import UIKit -import SessionUIKit -import SessionUtilitiesKit - -extension ContextMenuVC { - final class MessageInfoView: UIView { - private static let cornerRadius: CGFloat = 8 - - private let cellViewModel: MessageViewModel - private let dismissAction: () -> Void - - // MARK: - UI - - private lazy var dismissButton: UIButton = { - let result: UIButton = UIButton(type: .custom) - result.setImage( - UIImage(named: "small_chevron_left")? - .withRenderingMode(.alwaysTemplate), - for: .normal - ) - result.addTarget(self, action: #selector(dismiss), for: UIControl.Event.touchUpInside) - result.themeTintColor = .white - result.set(.width, to: 20) - result.set(.height, to: 20) - - return result - }() - - private lazy var messageSentDateLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.smallFontSize) - result.themeTextColor = .textPrimary - result.numberOfLines = 0 - - return result - }() - - private lazy var messageReceivedDateLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.smallFontSize) - result.themeTextColor = .textPrimary - result.numberOfLines = 0 - - return result - }() - - private lazy var profilePictureView: ProfilePictureView = { - let result: ProfilePictureView = ProfilePictureView() - result.set(.height, to: Values.smallProfilePictureSize) - result.size = Values.smallProfilePictureSize - - return result - }() - - private lazy var displayNameLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .boldSystemFont(ofSize: Values.smallFontSize) - result.themeTextColor = .textPrimary - - return result - }() - - private lazy var sessionIDLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.verySmallFontSize) - result.themeTextColor = .textPrimary - result.numberOfLines = 0 - result.lineBreakMode = .byCharWrapping - - return result - }() - - // MARK: - Lifecycle - - init(cellViewModel: MessageViewModel, dismissAction: @escaping () -> Void) { - self.cellViewModel = cellViewModel - self.dismissAction = dismissAction - - super.init(frame: CGRect.zero) - self.accessibilityLabel = "Message info" - setUpViewHierarchy() - } - - override init(frame: CGRect) { - preconditionFailure("Use init(cellViewModel:dismiss:) instead.") - } - - required init?(coder: NSCoder) { - preconditionFailure("Use init(cellViewModel:dismiss:) instead.") - } - - private func setUpViewHierarchy() { - addSubview(dismissButton) - dismissButton.pin(.top, to: .top, of: self, withInset: Values.smallSpacing) - dismissButton.pin(.leading, to: .leading, of: self) - - let backgroundView: UIView = UIView() - backgroundView.clipsToBounds = true - backgroundView.themeBackgroundColor = .contextMenu_background - backgroundView.layer.cornerRadius = Self.cornerRadius - addSubview(backgroundView) - backgroundView.pin([ UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top, UIView.VerticalEdge.bottom ], to: self) - backgroundView.pin(.leading, to: .trailing, of: dismissButton) - - let stackView: UIStackView = UIStackView() - stackView.axis = .vertical - stackView.spacing = Values.smallSpacing - backgroundView.addSubview(stackView) - stackView.pin(to: backgroundView, withInset: Values.mediumSpacing) - - messageSentDateLabel.text = "MESSAGE_INFO_SENT".localized() + ":\n" + cellViewModel.dateForUI.fromattedForMessageInfo - stackView.addArrangedSubview(messageSentDateLabel) - - messageReceivedDateLabel.text = "MESSAGE_INFO_RECEIVED".localized() + ":\n" + cellViewModel.receivedDateForUI.fromattedForMessageInfo - stackView.addArrangedSubview(messageReceivedDateLabel) - - let senderTitleLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.smallFontSize) - result.themeTextColor = .textPrimary - result.text = "MESSAGE_INFO_FROM".localized() + ":" - - return result - }() - - displayNameLabel.text = cellViewModel.authorName - sessionIDLabel.text = cellViewModel.authorId - profilePictureView.update( - publicKey: cellViewModel.authorId, - profile: cellViewModel.profile, - threadVariant: cellViewModel.threadVariant - ) - - let profileContainerView: UIView = UIView() - profileContainerView.addSubview(senderTitleLabel) - senderTitleLabel.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top ], to: profileContainerView) - profileContainerView.addSubview(profilePictureView) - profilePictureView.pin(.leading, to: .leading, of: profileContainerView) - profilePictureView.pin(.top, to: .bottom, of: senderTitleLabel, withInset: Values.mediumSpacing) - profilePictureView.pin(.bottom, to: .bottom, of: profileContainerView, withInset: -Values.verySmallSpacing) - - let infoContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ displayNameLabel, sessionIDLabel ]) - infoContainerStackView.axis = .vertical - profileContainerView.addSubview(infoContainerStackView) - infoContainerStackView.pin(.leading, to: .trailing, of: profilePictureView, withInset: Values.mediumSpacing) - infoContainerStackView.pin(.trailing, to: .trailing, of: profileContainerView) - infoContainerStackView.pin(.bottom, to: .bottom, of: profileContainerView) - infoContainerStackView.set(.width, to: 240) - - stackView.addArrangedSubview(profileContainerView) - - } - - // MARK: - Interaction - - @objc private func dismiss() { - dismissAction() - } - } -} diff --git a/Session/Conversations/Context Menu/ContextMenuVC.swift b/Session/Conversations/Context Menu/ContextMenuVC.swift index 5992fea74..3c9d9aab0 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC.swift @@ -59,18 +59,6 @@ final class ContextMenuVC: UIViewController { return result }() - private lazy var messageInfoView: MessageInfoView = { - let result: MessageInfoView = MessageInfoView(cellViewModel: self.cellViewModel, dismissAction: hideMessageInfo) - result.themeShadowColor = .black - result.layer.shadowOffset = CGSize.zero - result.layer.shadowOpacity = 0.4 - result.layer.shadowRadius = 4 - result.alpha = 0 - result.set(.width, to: 340) - - return result - }() - private lazy var timestampLabel: UILabel = { let result: UILabel = UILabel() result.font = .systemFont(ofSize: Values.verySmallFontSize) @@ -177,10 +165,7 @@ final class ContextMenuVC: UIViewController { arrangedSubviews: actions .filter { !$0.isEmojiAction && !$0.isEmojiPlus && !$0.isDismissAction } .map { action -> ActionView in - ActionView( - for: action, - dismiss: action.shouldDismissAfterAction ? snDismiss : {} - ) + ActionView(for: action, dismiss: snDismiss) } ) menuStackView.axis = .vertical @@ -188,9 +173,6 @@ final class ContextMenuVC: UIViewController { menuStackView.pin(to: menuBackgroundView) view.addSubview(menuView) - // MessageInfo - view.addSubview(messageInfoView) - // Timestamp view.addSubview(timestampLabel) timestampLabel.center(.vertical, in: snapshot) @@ -235,22 +217,18 @@ final class ContextMenuVC: UIViewController { snapshot.frame = self.frame emojiBar.pin(.bottom, to: .top, of: view, withInset: targetFrame.minY - spacing) menuView.pin(.top, to: .top, of: view, withInset: targetFrame.maxY + spacing) - messageInfoView.pin(.top, to: .top, of: view, withInset: targetFrame.maxY + spacing) switch cellViewModel.variant { case .standardOutgoing: menuView.pin(.right, to: .right, of: view, withInset: -(UIScreen.main.bounds.width - targetFrame.maxX)) - messageInfoView.pin(.right, to: .right, of: view, withInset: -(UIScreen.main.bounds.width - targetFrame.maxX)) emojiBar.pin(.right, to: .right, of: view, withInset: -(UIScreen.main.bounds.width - targetFrame.maxX)) case .standardIncoming, .standardIncomingDeleted: menuView.pin(.left, to: .left, of: view, withInset: targetFrame.minX) - messageInfoView.pin(.left, to: .left, of: view, withInset: targetFrame.minX) emojiBar.pin(.left, to: .left, of: view, withInset: targetFrame.minX) default: // Should generally only be the 'delete' action menuView.pin(.left, to: .left, of: view, withInset: targetFrame.minX) - messageInfoView.pin(.left, to: .left, of: view, withInset: targetFrame.minX) } // Tap gesture @@ -364,22 +342,6 @@ final class ContextMenuVC: UIViewController { // MARK: - Interaction - func showMessageInfo() { - UIView.animate(withDuration: 0.2) { [weak self] in - self?.emojiBar.alpha = 0 - self?.menuView.alpha = 0 - self?.messageInfoView.alpha = 1 - } - } - - func hideMessageInfo() { - UIView.animate(withDuration: 0.2) { [weak self] in - self?.emojiBar.alpha = 1 - self?.menuView.alpha = 1 - self?.messageInfoView.alpha = 0 - } - } - @objc private func handleTap() { snDismiss() } From 8a2264e95f9277bba478ef58eef22175648bbf03 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 20 Jan 2023 14:12:14 +1100 Subject: [PATCH 11/44] wip: add media preview view --- Session.xcodeproj/project.pbxproj | 4 ++ .../MediaInfoVC+MediaPreviewView.swift | 44 +++++++++++++++++++ .../Media Viewing & Editing/MediaInfoVC.swift | 10 ----- 3 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 9edcb6015..920ed78ab 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -111,6 +111,7 @@ 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; 7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */; }; 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; + 7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; @@ -1179,6 +1180,7 @@ 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; + 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = ""; }; 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -2985,6 +2987,7 @@ 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */, 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */, 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */, + 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */, 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */, ); path = "Media Viewing & Editing"; @@ -5588,6 +5591,7 @@ 7BAF54D027ACCEEC003D12F8 /* EmptySearchResultCell.swift in Sources */, B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */, FD37E9D928A230F2003AE748 /* TraitObservingWindow.swift in Sources */, + 7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */, B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */, FD848B8F283EF2A8000E298B /* UIScrollView+Utilities.swift in Sources */, B879D449247E1BE300DB3608 /* PathVC.swift in Sources */, diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift new file mode 100644 index 000000000..b507e6aef --- /dev/null +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -0,0 +1,44 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionUIKit +import SessionUtilitiesKit + +extension MediaInfoVC { + final class MediaPreviewView: UIView { + private static let cornerRadius: CGFloat = 8 + + private let attachment: Attachment + + // MARK: - UI + + private lazy var fullScreenButton: UIButton = { + let result: UIButton = UIButton(type: .custom) + + return result + }() + + // MARK: - Lifecycle + + init(attachment: Attachment) { + self.attachment = attachment + + super.init(frame: CGRect.zero) + self.accessibilityLabel = "Media info" + setUpViewHierarchy() + } + + override init(frame: CGRect) { + preconditionFailure("Use init(attachment:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(attachment:) instead.") + } + + private func setUpViewHierarchy() { + + } + + } +} diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index cc988dafa..5ccdc8b30 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -8,16 +8,6 @@ final class MediaInfoVC: BaseVC { private let attachments: [Attachment] - // MARK: - UI - - private lazy var fullScreenButton: UIButton = { - let result: UIButton = UIButton(type: .custom) - - return result - }() - - - // MARK: - Initialization init(attachments: [Attachment]) { From da0fb2602cab2e396fb06acb7a51412e0d1e7a55 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 23 Jan 2023 16:53:23 +1100 Subject: [PATCH 12/44] WIP: media preview view --- .../ConversationVC+Interaction.swift | 5 ++- .../Content Views/MediaAlbumView.swift | 3 +- .../Content Views/MediaView.swift | 5 +-- .../MediaInfoVC+MediaPreviewView.swift | 40 ++++++++++++++++++- .../Media Viewing & Editing/MediaInfoVC.swift | 24 +++++++++-- 5 files changed, 66 insertions(+), 11 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 23767f5a0..28d24e193 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1558,7 +1558,10 @@ extension ConversationVC: // MARK: - ContextMenuActionDelegate func info(_ cellViewModel: MessageViewModel) { - let mediaInfoVC = MediaInfoVC(attachments: (cellViewModel.attachments ?? [])) + let mediaInfoVC = MediaInfoVC( + attachments: (cellViewModel.attachments ?? []), + isOutgoing: (cellViewModel.variant == .standardOutgoing) + ) navigationController?.pushViewController(mediaInfoVC, animated: true) } diff --git a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift index 846e0aa72..9835f9920 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift @@ -28,8 +28,7 @@ public class MediaAlbumView: UIStackView { MediaView( mediaCache: mediaCache, attachment: $0, - isOutgoing: isOutgoing, - maxMessageWidth: maxMessageWidth + isOutgoing: isOutgoing ) } diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index 301818a8b..63a975d35 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -19,7 +19,6 @@ public class MediaView: UIView { private let mediaCache: NSCache public let attachment: Attachment private let isOutgoing: Bool - private let maxMessageWidth: CGFloat private var loadBlock: (() -> Void)? private var unloadBlock: (() -> Void)? @@ -48,13 +47,11 @@ public class MediaView: UIView { public required init( mediaCache: NSCache, attachment: Attachment, - isOutgoing: Bool, - maxMessageWidth: CGFloat + isOutgoing: Bool ) { self.mediaCache = mediaCache self.attachment = attachment self.isOutgoing = isOutgoing - self.maxMessageWidth = maxMessageWidth super.init(frame: .zero) diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift index b507e6aef..917e85f82 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -8,20 +8,48 @@ extension MediaInfoVC { final class MediaPreviewView: UIView { private static let cornerRadius: CGFloat = 8 + private let mediaCache: NSCache private let attachment: Attachment + private let isOutgoing: Bool // MARK: - UI + private lazy var mediaView: MediaView = { + let result: MediaView = MediaView.init( + mediaCache: mediaCache, + attachment: attachment, + isOutgoing: isOutgoing + ) + + return result + }() + private lazy var fullScreenButton: UIButton = { let result: UIButton = UIButton(type: .custom) + result.setImage( + UIImage(systemName: "arrow.up.left.and.arrow.down.right")? + .withRenderingMode(.alwaysTemplate), + for: .normal + ) + result.themeTintColor = .textPrimary + result.backgroundColor = .init(white: 0, alpha: 0.4) + result.layer.cornerRadius = 14 + result.set(.width, to: 28) + result.set(.height, to: 28) return result }() // MARK: - Lifecycle - init(attachment: Attachment) { + init( + mediaCache: NSCache, + attachment: Attachment, + isOutgoing: Bool + ) { + self.mediaCache = mediaCache self.attachment = attachment + self.isOutgoing = isOutgoing super.init(frame: CGRect.zero) self.accessibilityLabel = "Media info" @@ -37,7 +65,17 @@ extension MediaInfoVC { } private func setUpViewHierarchy() { + set(.width, to: 293) + set(.height, to: 293) + addSubview(mediaView) + mediaView.pin(to: self) + + addSubview(fullScreenButton) + fullScreenButton.pin(.trailing, to: .trailing, of: self, withInset: -Values.smallSpacing) + fullScreenButton.pin(.bottom, to: .bottom, of: self, withInset: -Values.smallSpacing) + + mediaView.loadMedia() } } diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 5ccdc8b30..c74c8fd03 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -7,10 +7,19 @@ import SessionUtilitiesKit final class MediaInfoVC: BaseVC { private let attachments: [Attachment] + private let isOutgoing: Bool + + // FIXME: Would be good to create a Swift-based cache and replace this + lazy var mediaCache: NSCache = { + let result = NSCache() + result.countLimit = 40 + return result + }() // MARK: - Initialization - init(attachments: [Attachment]) { + init(attachments: [Attachment], isOutgoing: Bool) { + self.isOutgoing = isOutgoing self.attachments = attachments super.init(nibName: nil, bundle: nil) } @@ -31,9 +40,18 @@ final class MediaInfoVC: BaseVC { self.title = "Message Info" attachments.forEach { + let mediaPreviewView: MediaPreviewView = MediaPreviewView( + mediaCache: mediaCache, + attachment: $0, + isOutgoing: isOutgoing) let mediaInfoView: MediaInfoView = MediaInfoView(attachment: $0) - self.view.addSubview(mediaInfoView) - mediaInfoView.center(in: self.view) + + let stackView: UIStackView = UIStackView(arrangedSubviews: [ mediaPreviewView, mediaInfoView ]) + stackView.axis = .vertical + stackView.spacing = Values.largeSpacing + + self.view.addSubview(stackView) + stackView.center(in: self.view) } } } From 5c629b2ab4d8bc179b2149892576bda47f37d21d Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 23 Jan 2023 17:13:37 +1100 Subject: [PATCH 13/44] clean --- .../Message Cells/Content Views/MediaView.swift | 8 ++++---- .../MediaInfoVC+MediaPreviewView.swift | 9 +-------- Session/Media Viewing & Editing/MediaInfoVC.swift | 8 -------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index 63a975d35..214c22f1d 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -16,7 +16,7 @@ public class MediaView: UIView { // MARK: - - private let mediaCache: NSCache + private let mediaCache: NSCache? public let attachment: Attachment private let isOutgoing: Bool private var loadBlock: (() -> Void)? @@ -45,7 +45,7 @@ public class MediaView: UIView { // MARK: - Initializers public required init( - mediaCache: NSCache, + mediaCache: NSCache? = nil, attachment: Attachment, isOutgoing: Bool ) { @@ -393,7 +393,7 @@ public class MediaView: UIView { applyMediaBlock(media) - self?.mediaCache.setObject(media, forKey: cacheKey as NSString) + self?.mediaCache?.setObject(media, forKey: cacheKey as NSString) self?.loadState.mutate { $0 = .loaded } } @@ -402,7 +402,7 @@ public class MediaView: UIView { return } - if let media: AnyObject = self.mediaCache.object(forKey: cacheKey as NSString) { + if let media: AnyObject = self.mediaCache?.object(forKey: cacheKey as NSString) { Logger.verbose("media cache hit") guard Thread.isMainThread else { diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift index 917e85f82..2b7fee601 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -8,7 +8,6 @@ extension MediaInfoVC { final class MediaPreviewView: UIView { private static let cornerRadius: CGFloat = 8 - private let mediaCache: NSCache private let attachment: Attachment private let isOutgoing: Bool @@ -16,7 +15,6 @@ extension MediaInfoVC { private lazy var mediaView: MediaView = { let result: MediaView = MediaView.init( - mediaCache: mediaCache, attachment: attachment, isOutgoing: isOutgoing ) @@ -42,12 +40,7 @@ extension MediaInfoVC { // MARK: - Lifecycle - init( - mediaCache: NSCache, - attachment: Attachment, - isOutgoing: Bool - ) { - self.mediaCache = mediaCache + init(attachment: Attachment, isOutgoing: Bool) { self.attachment = attachment self.isOutgoing = isOutgoing diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index c74c8fd03..b0bb7a9be 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -9,13 +9,6 @@ final class MediaInfoVC: BaseVC { private let attachments: [Attachment] private let isOutgoing: Bool - // FIXME: Would be good to create a Swift-based cache and replace this - lazy var mediaCache: NSCache = { - let result = NSCache() - result.countLimit = 40 - return result - }() - // MARK: - Initialization init(attachments: [Attachment], isOutgoing: Bool) { @@ -41,7 +34,6 @@ final class MediaInfoVC: BaseVC { attachments.forEach { let mediaPreviewView: MediaPreviewView = MediaPreviewView( - mediaCache: mediaCache, attachment: $0, isOutgoing: isOutgoing) let mediaInfoView: MediaInfoView = MediaInfoView(attachment: $0) From 671720c67bf4f1f55ae040f2f6ae591a94ca2605 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 23 Jan 2023 17:18:17 +1100 Subject: [PATCH 14/44] set up session style message info title --- Session/Media Viewing & Editing/MediaInfoVC.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index b0bb7a9be..17a4666dd 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -30,7 +30,11 @@ final class MediaInfoVC: BaseVC { override func viewDidLoad() { super.viewDidLoad() - self.title = "Message Info" + ViewControllerUtilities.setUpDefaultSessionStyle( + for: self, + title: "Message Info", + hasCustomBackButton: false + ) attachments.forEach { let mediaPreviewView: MediaPreviewView = MediaPreviewView( From d4957de7464133d9c3287be54fb51043095c22d8 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 24 Jan 2023 15:53:07 +1100 Subject: [PATCH 15/44] update media info view --- .../MediaInfoVC+MediaInfoView.swift | 37 ++++++++++++------- .../MediaInfoVC+MediaPreviewView.swift | 5 +++ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift index a39c82c1e..866bf7bcb 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift @@ -8,7 +8,7 @@ extension MediaInfoVC { final class MediaInfoView: UIView { private static let cornerRadius: CGFloat = 8 - private let attachment: Attachment + private var attachment: Attachment? // MARK: - UI @@ -54,12 +54,13 @@ extension MediaInfoVC { // MARK: - Lifecycle - init(attachment: Attachment) { + init(attachment: Attachment?) { self.attachment = attachment super.init(frame: CGRect.zero) self.accessibilityLabel = "Media info" setUpViewHierarchy() + update(attachment: attachment) } override init(frame: CGRect) { @@ -90,7 +91,6 @@ extension MediaInfoVC { return result }() - fileIdLabel.text = attachment.serverId let fileIdContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileIdTitleLabel, fileIdLabel ]) fileIdContainerStackView.axis = .vertical container.addSubview(fileIdContainerStackView) @@ -105,7 +105,6 @@ extension MediaInfoVC { return result }() - fileTypeLabel.text = attachment.contentType let fileTypeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileTypeTitleLabel, fileTypeLabel ]) fileTypeContainerStackView.axis = .vertical container.addSubview(fileTypeContainerStackView) @@ -121,7 +120,6 @@ extension MediaInfoVC { return result }() - fileSizeLabel.text = OWSFormat.formatFileSize(attachment.byteCount) let fileSizeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileSizeTitleLabel, fileSizeLabel ]) fileSizeContainerStackView.axis = .vertical container.addSubview(fileSizeContainerStackView) @@ -138,17 +136,13 @@ extension MediaInfoVC { return result }() - resolutionLabel.text = { - guard let width = attachment.width, let height = attachment.height else { return "N/A" } - return "\(width)×\(height)" - }() let resolutionContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ resolutionTitleLabel, resolutionLabel ]) resolutionContainerStackView.axis = .vertical container.addSubview(resolutionContainerStackView) resolutionContainerStackView.pin(.leading, to: .leading, of: container) resolutionContainerStackView.pin(.top, to: .bottom, of: fileTypeContainerStackView, withInset: Values.mediumSpacing) - // File Size + // Duration let durationTitleLabel: UILabel = { let result = UILabel() result.font = .boldSystemFont(ofSize: Values.mediumFontSize) @@ -157,10 +151,6 @@ extension MediaInfoVC { return result }() - durationLabel.text = { - guard let duration = attachment.duration else { return "N/A" } - return "\(duration)" - }() let durationContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ durationTitleLabel, durationLabel ]) durationContainerStackView.axis = .vertical container.addSubview(durationContainerStackView) @@ -172,5 +162,24 @@ extension MediaInfoVC { backgroundView.addSubview(container) container.pin(to: backgroundView, withInset: Values.largeSpacing) } + + // MARK: - Interaction + public func update(attachment: Attachment?) { + guard let attachment: Attachment = attachment else { return } + + self.attachment = attachment + + fileIdLabel.text = attachment.serverId + fileTypeLabel.text = attachment.contentType + fileSizeLabel.text = OWSFormat.formatFileSize(attachment.byteCount) + resolutionLabel.text = { + guard let width = attachment.width, let height = attachment.height else { return "N/A" } + return "\(width)×\(height)" + }() + durationLabel.text = { + guard let duration = attachment.duration else { return "N/A" } + return "\(duration)" + }() + } } } diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift index 2b7fee601..f504e0261 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -34,6 +34,7 @@ extension MediaInfoVC { result.layer.cornerRadius = 14 result.set(.width, to: 28) result.set(.height, to: 28) + result.addTarget(self, action: #selector(showMediaFullScreen), for: .touchUpInside) return result }() @@ -71,5 +72,9 @@ extension MediaInfoVC { mediaView.loadMedia() } + // MARK: - Interaction + @objc func showMediaFullScreen() { + + } } } From fbc2eb35ec062a6d76ae237b72af161f6ff3dfb3 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 24 Jan 2023 17:08:32 +1100 Subject: [PATCH 16/44] wip: carousel media preview view --- .../MediaInfoVC+MediaInfoView.swift | 2 +- .../MediaInfoVC+MediaPreviewView.swift | 4 +-- .../Media Viewing & Editing/MediaInfoVC.swift | 32 ++++++++++++++----- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift index 866bf7bcb..f75c96ba5 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift @@ -54,7 +54,7 @@ extension MediaInfoVC { // MARK: - Lifecycle - init(attachment: Attachment?) { + init(attachment: Attachment? = nil) { self.attachment = attachment super.init(frame: CGRect.zero) diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift index f504e0261..c2bc0b69b 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -59,8 +59,8 @@ extension MediaInfoVC { } private func setUpViewHierarchy() { - set(.width, to: 293) - set(.height, to: 293) + set(.width, to: MediaInfoVC.mediaSize) + set(.height, to: MediaInfoVC.mediaSize) addSubview(mediaView) mediaView.pin(to: self) diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 17a4666dd..33a808d62 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -5,10 +5,14 @@ import SessionUIKit import SessionUtilitiesKit final class MediaInfoVC: BaseVC { + internal static let mediaSize: CGFloat = 293 private let attachments: [Attachment] private let isOutgoing: Bool + // MARK: - UI + private lazy var mediaInfoView: MediaInfoView = MediaInfoView() + // MARK: - Initialization init(attachments: [Attachment], isOutgoing: Bool) { @@ -36,18 +40,30 @@ final class MediaInfoVC: BaseVC { hasCustomBackButton: false ) + let mediaStackView: UIStackView = UIStackView() + mediaStackView.axis = .horizontal + attachments.forEach { let mediaPreviewView: MediaPreviewView = MediaPreviewView( attachment: $0, isOutgoing: isOutgoing) - let mediaInfoView: MediaInfoView = MediaInfoView(attachment: $0) - - let stackView: UIStackView = UIStackView(arrangedSubviews: [ mediaPreviewView, mediaInfoView ]) - stackView.axis = .vertical - stackView.spacing = Values.largeSpacing - - self.view.addSubview(stackView) - stackView.center(in: self.view) + mediaStackView.addArrangedSubview(mediaPreviewView) } + + let scrollView: UIScrollView = UIScrollView() + scrollView.isPagingEnabled = true + scrollView.set(.width, to: Self.mediaSize) + scrollView.set(.height, to: Self.mediaSize) + scrollView.contentSize = CGSize(width: Self.mediaSize * CGFloat(attachments.count), height: Self.mediaSize) + scrollView.addSubview(mediaStackView) + + mediaInfoView.update(attachment: attachments[0]) + + let stackView: UIStackView = UIStackView(arrangedSubviews: [ scrollView, mediaInfoView ]) + stackView.axis = .vertical + stackView.spacing = Values.largeSpacing + + self.view.addSubview(stackView) + stackView.center(in: self.view) } } From 3f33caf91aee3eac1927dacbe4ff31bceb3df898 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 25 Jan 2023 14:52:44 +1100 Subject: [PATCH 17/44] wip: carousel view --- Session.xcodeproj/project.pbxproj | 4 ++ .../MediaInfoVC+MediaInfoView.swift | 2 +- .../Media Viewing & Editing/MediaInfoVC.swift | 11 ++- Session/Shared/SessionCarouselView.swift | 68 +++++++++++++++++++ 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 Session/Shared/SessionCarouselView.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 920ed78ab..2ce2419ee 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -112,6 +112,7 @@ 7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */; }; 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; 7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */; }; + 7B3A39322980D02B002FE4AC /* SessionCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; @@ -1181,6 +1182,7 @@ 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = ""; }; + 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselView.swift; sourceTree = ""; }; 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -2584,6 +2586,7 @@ FD52090828B59411006098F6 /* ScreenLockUI.swift */, FD37EA0828AA2D27003AE748 /* SessionTableViewModel.swift */, FD37EA0628AA2CCA003AE748 /* SessionTableViewController.swift */, + 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */, ); path = Shared; sourceTree = ""; @@ -5765,6 +5768,7 @@ FD71163828E2C50700B47552 /* SessionTableViewModel.swift in Sources */, FD71164A28E3EA5B00B47552 /* DismissType.swift in Sources */, C328251F25CA3A900062D0A7 /* QuoteView.swift in Sources */, + 7B3A39322980D02B002FE4AC /* SessionCarouselView.swift in Sources */, FD37E9CC28A1E578003AE748 /* AppearanceViewController.swift in Sources */, B8EB20F02640F7F000773E52 /* OpenGroupInvitationView.swift in Sources */, C328254025CA55880062D0A7 /* ContextMenuVC.swift in Sources */, diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift index f75c96ba5..866bf7bcb 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift @@ -54,7 +54,7 @@ extension MediaInfoVC { // MARK: - Lifecycle - init(attachment: Attachment? = nil) { + init(attachment: Attachment?) { self.attachment = attachment super.init(frame: CGRect.zero) diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 33a808d62..46af1170a 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -11,7 +11,7 @@ final class MediaInfoVC: BaseVC { private let isOutgoing: Bool // MARK: - UI - private lazy var mediaInfoView: MediaInfoView = MediaInfoView() + private lazy var mediaInfoView: MediaInfoView = MediaInfoView(attachment: nil) // MARK: - Initialization @@ -50,11 +50,18 @@ final class MediaInfoVC: BaseVC { mediaStackView.addArrangedSubview(mediaPreviewView) } + let contentWidth: CGFloat = Self.mediaSize * CGFloat(attachments.count) + let contentHeight: CGFloat = Self.mediaSize + mediaStackView.set(.width, to: contentWidth) + mediaStackView.set(.height, to: contentHeight) + let scrollView: UIScrollView = UIScrollView() scrollView.isPagingEnabled = true + scrollView.showsVerticalScrollIndicator = false + scrollView.showsHorizontalScrollIndicator = false scrollView.set(.width, to: Self.mediaSize) scrollView.set(.height, to: Self.mediaSize) - scrollView.contentSize = CGSize(width: Self.mediaSize * CGFloat(attachments.count), height: Self.mediaSize) + scrollView.contentSize = CGSize(width: contentWidth, height: contentHeight) scrollView.addSubview(mediaStackView) mediaInfoView.update(attachment: attachments[0]) diff --git a/Session/Shared/SessionCarouselView.swift b/Session/Shared/SessionCarouselView.swift new file mode 100644 index 000000000..47277d99a --- /dev/null +++ b/Session/Shared/SessionCarouselView.swift @@ -0,0 +1,68 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +final class SessionCarouselView: UIView { + private let slices: [UIView] + private let sliceSize: CGSize + + // MARK: - Settings + public var showPageControl: Bool = true { + didSet { + self.pageControl.isHidden = !showPageControl + } + } + + // MARK: - UI + private lazy var scrollView: UIScrollView = { + let result: UIScrollView = UIScrollView() + result.isPagingEnabled = true + result.showsHorizontalScrollIndicator = false + result.showsVerticalScrollIndicator = false + result.contentSize = CGSize( + width: self.sliceSize.width * CGFloat(self.slices.count), + height: self.sliceSize.height + ) + + return result + }() + + private lazy var pageControl: UIPageControl = { + let result: UIPageControl = UIPageControl() + result.numberOfPages = self.slices.count + + return result + }() + + // MARK: - Lifecycle + init(slices: [UIView], sliceSize: CGSize) { + self.slices = slices + self.sliceSize = sliceSize + + super.init(frame: CGRect.zero) + setUpViewHierarchy() + } + + override init(frame: CGRect) { + preconditionFailure("Use init(attachment:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(attachment:) instead.") + } + + private func setUpViewHierarchy() { + let stackView: UIStackView = UIStackView(arrangedSubviews: self.slices) + stackView.axis = .horizontal + stackView.set(.width, to: self.sliceSize.width * CGFloat(self.slices.count)) + stackView.set(.height, to: self.sliceSize.height) + + addSubview(self.scrollView) + scrollView.pin(to: self) + scrollView.addSubview(stackView) + + addSubview(self.pageControl) + self.pageControl.center(.horizontal, in: self) + self.pageControl.pin(.bottom, to: .bottom, of: self) + } +} From 31d081ff49d319dde9aed00b2cf7b9a309faee0b Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 25 Jan 2023 15:54:49 +1100 Subject: [PATCH 18/44] fix carousel view for media preview views --- .../Media Viewing & Editing/MediaInfoVC.swift | 39 ++++++++----------- Session/Shared/SessionCarouselView.swift | 10 ++++- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 46af1170a..efcf4574e 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -12,6 +12,22 @@ final class MediaInfoVC: BaseVC { // MARK: - UI private lazy var mediaInfoView: MediaInfoView = MediaInfoView(attachment: nil) + private lazy var mediaCarouselView: SessionCarouselView = { + let result: SessionCarouselView = SessionCarouselView( + slices: self.attachments.map { + MediaPreviewView( + attachment: $0, + isOutgoing: self.isOutgoing + ) + }, + sliceSize: CGSize( + width: Self.mediaSize, + height: Self.mediaSize + ) + ) + + return result + }() // MARK: - Initialization @@ -43,30 +59,9 @@ final class MediaInfoVC: BaseVC { let mediaStackView: UIStackView = UIStackView() mediaStackView.axis = .horizontal - attachments.forEach { - let mediaPreviewView: MediaPreviewView = MediaPreviewView( - attachment: $0, - isOutgoing: isOutgoing) - mediaStackView.addArrangedSubview(mediaPreviewView) - } - - let contentWidth: CGFloat = Self.mediaSize * CGFloat(attachments.count) - let contentHeight: CGFloat = Self.mediaSize - mediaStackView.set(.width, to: contentWidth) - mediaStackView.set(.height, to: contentHeight) - - let scrollView: UIScrollView = UIScrollView() - scrollView.isPagingEnabled = true - scrollView.showsVerticalScrollIndicator = false - scrollView.showsHorizontalScrollIndicator = false - scrollView.set(.width, to: Self.mediaSize) - scrollView.set(.height, to: Self.mediaSize) - scrollView.contentSize = CGSize(width: contentWidth, height: contentHeight) - scrollView.addSubview(mediaStackView) - mediaInfoView.update(attachment: attachments[0]) - let stackView: UIStackView = UIStackView(arrangedSubviews: [ scrollView, mediaInfoView ]) + let stackView: UIStackView = UIStackView(arrangedSubviews: [ mediaCarouselView, mediaInfoView ]) stackView.axis = .vertical stackView.spacing = Values.largeSpacing diff --git a/Session/Shared/SessionCarouselView.swift b/Session/Shared/SessionCarouselView.swift index 47277d99a..add5434b2 100644 --- a/Session/Shared/SessionCarouselView.swift +++ b/Session/Shared/SessionCarouselView.swift @@ -2,7 +2,7 @@ import Foundation -final class SessionCarouselView: UIView { +final class SessionCarouselView: UIView, UIScrollViewDelegate { private let slices: [UIView] private let sliceSize: CGSize @@ -16,6 +16,7 @@ final class SessionCarouselView: UIView { // MARK: - UI private lazy var scrollView: UIScrollView = { let result: UIScrollView = UIScrollView() + result.delegate = self result.isPagingEnabled = true result.showsHorizontalScrollIndicator = false result.showsVerticalScrollIndicator = false @@ -59,10 +60,17 @@ final class SessionCarouselView: UIView { addSubview(self.scrollView) scrollView.pin(to: self) + scrollView.set(.width, to: self.sliceSize.width) + scrollView.set(.height, to: self.sliceSize.height) scrollView.addSubview(stackView) addSubview(self.pageControl) self.pageControl.center(.horizontal, in: self) self.pageControl.pin(.bottom, to: .bottom, of: self) } + + // MARK: - UIScrollViewDelegate + func scrollViewDidScroll(_ scrollView: UIScrollView) { + + } } From 42b27cd2bfe85c54a2d9de68dc166c552b68afb4 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 27 Jan 2023 15:55:42 +1100 Subject: [PATCH 19/44] WIP: carousel view loop --- .../MediaInfoVC+MediaPreviewView.swift | 23 ++++++- Session/Shared/SessionCarouselView.swift | 67 +++++++++++++++++-- SessionUIKit/Utilities/UIView+Utilities.swift | 10 +++ 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift index c2bc0b69b..a5bb0f1ca 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -55,7 +55,28 @@ extension MediaInfoVC { } required init?(coder: NSCoder) { - preconditionFailure("Use init(attachment:) instead.") + guard let attachment = coder.decodeObject(forKey: "attachment") as? Attachment else { + print("No attachment") + return nil + } + guard let isOutgoing = coder.decodeObject(forKey: "isOutgoing") as? Bool else { + print("No isOutgoing") + return nil + } + + self.attachment = attachment + self.isOutgoing = isOutgoing + + super.init(coder: coder) + self.accessibilityLabel = "Media info" + setUpViewHierarchy() + } + + override func encode(with coder: NSCoder) { + super.encode(with: coder) +// coder.encode(self.attachment, forKey: "attachment") + coder.encode(self.isOutgoing, forKey: "isOutgoing") + } private func setUpViewHierarchy() { diff --git a/Session/Shared/SessionCarouselView.swift b/Session/Shared/SessionCarouselView.swift index add5434b2..f8c1f9747 100644 --- a/Session/Shared/SessionCarouselView.swift +++ b/Session/Shared/SessionCarouselView.swift @@ -3,8 +3,9 @@ import Foundation final class SessionCarouselView: UIView, UIScrollViewDelegate { - private let slices: [UIView] + private let slicesForLoop: [UIView] private let sliceSize: CGSize + private let sliceCount: Int // MARK: - Settings public var showPageControl: Bool = true { @@ -21,7 +22,7 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { result.showsHorizontalScrollIndicator = false result.showsVerticalScrollIndicator = false result.contentSize = CGSize( - width: self.sliceSize.width * CGFloat(self.slices.count), + width: self.sliceSize.width * CGFloat(self.slicesForLoop.count), height: self.sliceSize.height ) @@ -30,14 +31,22 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { private lazy var pageControl: UIPageControl = { let result: UIPageControl = UIPageControl() - result.numberOfPages = self.slices.count + result.numberOfPages = self.sliceCount + result.currentPage = 0 return result }() // MARK: - Lifecycle init(slices: [UIView], sliceSize: CGSize) { - self.slices = slices + self.sliceCount = slices.count + if self.sliceCount > 1, let copyOfFirstSlice: UIView = slices.first?.copyView(), let copyOfLastSlice: UIView = slices.last?.copyView() { + self.slicesForLoop = [copyOfLastSlice] + .appending(contentsOf: slices) + .appending(copyOfFirstSlice) + } else { + self.slicesForLoop = slices + } self.sliceSize = sliceSize super.init(frame: CGRect.zero) @@ -53,9 +62,9 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { } private func setUpViewHierarchy() { - let stackView: UIStackView = UIStackView(arrangedSubviews: self.slices) + let stackView: UIStackView = UIStackView(arrangedSubviews: self.slicesForLoop) stackView.axis = .horizontal - stackView.set(.width, to: self.sliceSize.width * CGFloat(self.slices.count)) + stackView.set(.width, to: self.sliceSize.width * CGFloat(self.slicesForLoop.count)) stackView.set(.height, to: self.sliceSize.height) addSubview(self.scrollView) @@ -63,6 +72,13 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { scrollView.set(.width, to: self.sliceSize.width) scrollView.set(.height, to: self.sliceSize.height) scrollView.addSubview(stackView) + scrollView.setContentOffset( + CGPoint( + x: Int(self.sliceSize.width) * (self.sliceCount > 1 ? 1 : 0), + y: 0 + ), + animated: false + ) addSubview(self.pageControl) self.pageControl.center(.horizontal, in: self) @@ -71,6 +87,43 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { // MARK: - UIScrollViewDelegate func scrollViewDidScroll(_ scrollView: UIScrollView) { - + let pageIndex: Int = { + let maybeCurrentPageIndex: Int = Int(round(scrollView.contentOffset.x/sliceSize.width)) + if self.sliceCount > 1 { + if maybeCurrentPageIndex == 0 { + return pageControl.numberOfPages - 1 + } + if maybeCurrentPageIndex == self.slicesForLoop.count - 1 { + return 0 + } + return maybeCurrentPageIndex - 1 + } + return maybeCurrentPageIndex + }() + + pageControl.currentPage = pageIndex + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + if pageControl.currentPage == 0 { + scrollView.setContentOffset( + CGPoint( + x: Int(self.sliceSize.width) * 1, + y: 0 + ), + animated: false + ) + } + + if pageControl.currentPage == pageControl.numberOfPages - 1 { + let realLastIndex: Int = self.slicesForLoop.count - 2 + scrollView.setContentOffset( + CGPoint( + x: Int(self.sliceSize.width) * realLastIndex, + y: 0 + ), + animated: false + ) + } } } diff --git a/SessionUIKit/Utilities/UIView+Utilities.swift b/SessionUIKit/Utilities/UIView+Utilities.swift index 7d37a2185..2ce766308 100644 --- a/SessionUIKit/Utilities/UIView+Utilities.swift +++ b/SessionUIKit/Utilities/UIView+Utilities.swift @@ -72,4 +72,14 @@ public extension UIView { return result } + + func copyView() -> T? { + do { + return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)) as? T + } catch { + print("\(error)") + return nil + } + + } } From 838969efa6db7ea62663bda00e1b87899b00afd1 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 30 Jan 2023 14:07:27 +1100 Subject: [PATCH 20/44] WIP: encapsulate carousel view --- Session.xcodeproj/project.pbxproj | 4 + .../Media Viewing & Editing/MediaInfoVC.swift | 30 +++-- Session/Shared/SessionCarouselView+Info.swift | 39 ++++++ Session/Shared/SessionCarouselView.swift | 113 +++++++++++++----- 4 files changed, 148 insertions(+), 38 deletions(-) create mode 100644 Session/Shared/SessionCarouselView+Info.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 2a8dc0f3a..9b7220317 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -110,6 +110,7 @@ 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */; }; 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; 7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */; }; + 7B2561C429874851005C086C /* SessionCarouselView+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */; }; 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; 7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */; }; 7B3A39322980D02B002FE4AC /* SessionCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */; }; @@ -1179,6 +1180,7 @@ 7B1D74AB27BDE7510030B423 /* Promise+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+Timeout.swift"; sourceTree = ""; }; 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timer+MainThread.swift"; sourceTree = ""; }; 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = ""; }; + 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCarouselView+Info.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = ""; }; @@ -2587,6 +2589,7 @@ FD37EA0828AA2D27003AE748 /* SessionTableViewModel.swift */, FD37EA0628AA2CCA003AE748 /* SessionTableViewController.swift */, 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */, + 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */, ); path = Shared; sourceTree = ""; @@ -5580,6 +5583,7 @@ buildActionMask = 2147483647; files = ( FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */, + 7B2561C429874851005C086C /* SessionCarouselView+Info.swift in Sources */, FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */, FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */, B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */, diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index efcf4574e..044d31844 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -14,17 +14,27 @@ final class MediaInfoVC: BaseVC { private lazy var mediaInfoView: MediaInfoView = MediaInfoView(attachment: nil) private lazy var mediaCarouselView: SessionCarouselView = { let result: SessionCarouselView = SessionCarouselView( - slices: self.attachments.map { - MediaPreviewView( - attachment: $0, - isOutgoing: self.isOutgoing + info: SessionCarouselView.Info( + slices: self.attachments.map { + MediaPreviewView( + attachment: $0, + isOutgoing: self.isOutgoing + ) + }, + sliceSize: CGSize( + width: Self.mediaSize, + height: Self.mediaSize + ), + shouldShowPageControl: true, + pageControlHeight: 10, + shouldShowArrows: true, + arrowsSize: CGSize( + width: 20, + height: 30 ) - }, - sliceSize: CGSize( - width: Self.mediaSize, - height: Self.mediaSize ) ) + result.set(.height, to: Self.mediaSize) return result }() @@ -63,9 +73,11 @@ final class MediaInfoVC: BaseVC { let stackView: UIStackView = UIStackView(arrangedSubviews: [ mediaCarouselView, mediaInfoView ]) stackView.axis = .vertical + stackView.alignment = .center stackView.spacing = Values.largeSpacing self.view.addSubview(stackView) - stackView.center(in: self.view) + stackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: self.view) + stackView.center(.vertical, in: self.view) } } diff --git a/Session/Shared/SessionCarouselView+Info.swift b/Session/Shared/SessionCarouselView+Info.swift new file mode 100644 index 000000000..8aef9cda8 --- /dev/null +++ b/Session/Shared/SessionCarouselView+Info.swift @@ -0,0 +1,39 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionUIKit +import SessionUtilitiesKit + +extension SessionCarouselView { + public struct Info { + let slices: [UIView] + let sliceSize: CGSize + let sliceCount: Int + let shouldShowPageControl: Bool + let pageControlHeight: CGFloat + let pageControlScale: CGFloat // This is to control the size of the dots + let shouldShowArrows: Bool + let arrowsSize: CGSize + + // MARK: - Initialization + + init( + slices: [UIView] = [], + sliceSize: CGSize = .zero, + shouldShowPageControl: Bool = true, + pageControlHeight: CGFloat = 0, + pageControlScale: CGFloat = 1, + shouldShowArrows: Bool = true, + arrowsSize: CGSize = .zero + ) { + self.slices = slices + self.sliceSize = sliceSize + self.sliceCount = slices.count + self.shouldShowPageControl = shouldShowPageControl && (self.sliceCount > 1) + self.pageControlHeight = pageControlHeight + self.pageControlScale = pageControlScale + self.shouldShowArrows = shouldShowArrows && (self.sliceCount > 1) + self.arrowsSize = arrowsSize + } + } +} diff --git a/Session/Shared/SessionCarouselView.swift b/Session/Shared/SessionCarouselView.swift index f8c1f9747..39accc837 100644 --- a/Session/Shared/SessionCarouselView.swift +++ b/Session/Shared/SessionCarouselView.swift @@ -1,18 +1,12 @@ // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit +import SessionUIKit +import SessionUtilitiesKit final class SessionCarouselView: UIView, UIScrollViewDelegate { private let slicesForLoop: [UIView] - private let sliceSize: CGSize - private let sliceCount: Int - - // MARK: - Settings - public var showPageControl: Bool = true { - didSet { - self.pageControl.isHidden = !showPageControl - } - } + private let info: SessionCarouselView.Info // MARK: - UI private lazy var scrollView: UIScrollView = { @@ -22,8 +16,8 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { result.showsHorizontalScrollIndicator = false result.showsVerticalScrollIndicator = false result.contentSize = CGSize( - width: self.sliceSize.width * CGFloat(self.slicesForLoop.count), - height: self.sliceSize.height + width: self.info.sliceSize.width * CGFloat(self.slicesForLoop.count), + height: self.info.sliceSize.height ) return result @@ -31,23 +25,52 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { private lazy var pageControl: UIPageControl = { let result: UIPageControl = UIPageControl() - result.numberOfPages = self.sliceCount + result.numberOfPages = self.info.sliceCount result.currentPage = 0 + result.isHidden = !self.info.shouldShowPageControl + result.set(.height, to: self.info.pageControlHeight) + result.transform = CGAffineTransform(scaleX: self.info.pageControlScale, y: self.info.pageControlScale) + + return result + }() + + private lazy var arrowLeft: UIButton = { + let result = UIButton(type: .custom) + result.setImage(UIImage(systemName: "chevron.left")?.withRenderingMode(.alwaysTemplate), for: .normal) + result.addTarget(self, action: #selector(scrollToPreviousSlice), for: .touchUpInside) + result.themeTintColor = .textPrimary + result.set(.width, to: self.info.arrowsSize.width) + result.set(.height, to: self.info.arrowsSize.height) + result.isHidden = !self.info.shouldShowArrows + + return result + }() + + private lazy var arrowRight: UIButton = { + let result = UIButton(type: .custom) + result.setImage(UIImage(systemName: "chevron.right")?.withRenderingMode(.alwaysTemplate), for: .normal) + result.addTarget(self, action: #selector(scrollToNextSlice), for: .touchUpInside) + result.themeTintColor = .textPrimary + result.set(.width, to: self.info.arrowsSize.width) + result.set(.height, to: self.info.arrowsSize.height) + result.isHidden = !self.info.shouldShowArrows return result }() // MARK: - Lifecycle - init(slices: [UIView], sliceSize: CGSize) { - self.sliceCount = slices.count - if self.sliceCount > 1, let copyOfFirstSlice: UIView = slices.first?.copyView(), let copyOfLastSlice: UIView = slices.last?.copyView() { + init(info: SessionCarouselView.Info) { + self.info = info + if self.info.sliceCount > 1, + let copyOfFirstSlice: UIView = self.info.slices.first?.copyView(), + let copyOfLastSlice: UIView = self.info.slices.last?.copyView() + { self.slicesForLoop = [copyOfLastSlice] - .appending(contentsOf: slices) + .appending(contentsOf: self.info.slices) .appending(copyOfFirstSlice) } else { - self.slicesForLoop = slices + self.slicesForLoop = self.info.slices } - self.sliceSize = sliceSize super.init(frame: CGRect.zero) setUpViewHierarchy() @@ -62,19 +85,22 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { } private func setUpViewHierarchy() { + set(.width, to: self.info.sliceSize.width + Values.largeSpacing + 2 * self.info.arrowsSize.width) + set(.height, to: self.info.sliceSize.height) + let stackView: UIStackView = UIStackView(arrangedSubviews: self.slicesForLoop) stackView.axis = .horizontal - stackView.set(.width, to: self.sliceSize.width * CGFloat(self.slicesForLoop.count)) - stackView.set(.height, to: self.sliceSize.height) + stackView.set(.width, to: self.info.sliceSize.width * CGFloat(self.slicesForLoop.count)) + stackView.set(.height, to: self.info.sliceSize.height) addSubview(self.scrollView) - scrollView.pin(to: self) - scrollView.set(.width, to: self.sliceSize.width) - scrollView.set(.height, to: self.sliceSize.height) + scrollView.center(in: self) + scrollView.set(.width, to: self.info.sliceSize.width) + scrollView.set(.height, to: self.info.sliceSize.height) scrollView.addSubview(stackView) scrollView.setContentOffset( CGPoint( - x: Int(self.sliceSize.width) * (self.sliceCount > 1 ? 1 : 0), + x: Int(self.info.sliceSize.width) * (self.info.sliceCount > 1 ? 1 : 0), y: 0 ), animated: false @@ -83,13 +109,21 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { addSubview(self.pageControl) self.pageControl.center(.horizontal, in: self) self.pageControl.pin(.bottom, to: .bottom, of: self) + + addSubview(self.arrowLeft) + self.arrowLeft.pin(.leading, to: .leading, of: self) + self.arrowLeft.center(.vertical, in: self) + + addSubview(self.arrowRight) + self.arrowRight.pin(.trailing, to: .trailing, of: self) + self.arrowRight.center(.vertical, in: self) } // MARK: - UIScrollViewDelegate func scrollViewDidScroll(_ scrollView: UIScrollView) { let pageIndex: Int = { - let maybeCurrentPageIndex: Int = Int(round(scrollView.contentOffset.x/sliceSize.width)) - if self.sliceCount > 1 { + let maybeCurrentPageIndex: Int = Int(round(scrollView.contentOffset.x/self.info.sliceSize.width)) + if self.info.sliceCount > 1 { if maybeCurrentPageIndex == 0 { return pageControl.numberOfPages - 1 } @@ -108,7 +142,7 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { if pageControl.currentPage == 0 { scrollView.setContentOffset( CGPoint( - x: Int(self.sliceSize.width) * 1, + x: Int(self.info.sliceSize.width) * 1, y: 0 ), animated: false @@ -119,11 +153,32 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { let realLastIndex: Int = self.slicesForLoop.count - 2 scrollView.setContentOffset( CGPoint( - x: Int(self.sliceSize.width) * realLastIndex, + x: Int(self.info.sliceSize.width) * realLastIndex, y: 0 ), animated: false ) } } + + // MARK: - Interaction + @objc func scrollToNextSlice() { + self.scrollView.setContentOffset( + CGPoint( + x: self.scrollView.contentOffset.x + self.info.sliceSize.width, + y: 0 + ), + animated: true + ) + } + + @objc func scrollToPreviousSlice() { + self.scrollView.setContentOffset( + CGPoint( + x: self.scrollView.contentOffset.x - self.info.sliceSize.width, + y: 0 + ), + animated: true + ) + } } From 7e164ecf04d4084adf23a1ee1f6f145492e76e0f Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 30 Jan 2023 17:08:01 +1100 Subject: [PATCH 21/44] wrap up carousel view --- .../MediaInfoVC+MediaPreviewView.swift | 29 ++++--------- .../Media Viewing & Editing/MediaInfoVC.swift | 21 ++++++---- Session/Shared/SessionCarouselView+Info.swift | 42 ++++++++++++++++--- Session/Shared/SessionCarouselView.swift | 21 ++++++++-- SessionUIKit/Utilities/UIView+Utilities.swift | 10 ----- 5 files changed, 74 insertions(+), 49 deletions(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift index a5bb0f1ca..3bac68e67 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -55,28 +55,7 @@ extension MediaInfoVC { } required init?(coder: NSCoder) { - guard let attachment = coder.decodeObject(forKey: "attachment") as? Attachment else { - print("No attachment") - return nil - } - guard let isOutgoing = coder.decodeObject(forKey: "isOutgoing") as? Bool else { - print("No isOutgoing") - return nil - } - - self.attachment = attachment - self.isOutgoing = isOutgoing - - super.init(coder: coder) - self.accessibilityLabel = "Media info" - setUpViewHierarchy() - } - - override func encode(with coder: NSCoder) { - super.encode(with: coder) -// coder.encode(self.attachment, forKey: "attachment") - coder.encode(self.isOutgoing, forKey: "isOutgoing") - + preconditionFailure("Use init(attachment:) instead.") } private func setUpViewHierarchy() { @@ -94,8 +73,14 @@ extension MediaInfoVC { } // MARK: - Interaction + @objc func showMediaFullScreen() { } + + // MARK: - Copy + func copyView() -> MediaPreviewView { + return MediaPreviewView(attachment: self.attachment, isOutgoing: self.isOutgoing) + } } } diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 044d31844..5c8353b1c 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -13,20 +13,27 @@ final class MediaInfoVC: BaseVC { // MARK: - UI private lazy var mediaInfoView: MediaInfoView = MediaInfoView(attachment: nil) private lazy var mediaCarouselView: SessionCarouselView = { + let slices: [MediaPreviewView] = self.attachments.map { + MediaPreviewView( + attachment: $0, + isOutgoing: self.isOutgoing + ) + } let result: SessionCarouselView = SessionCarouselView( info: SessionCarouselView.Info( - slices: self.attachments.map { - MediaPreviewView( - attachment: $0, - isOutgoing: self.isOutgoing - ) - }, + slices: slices, + copyOfFirstSlice: slices.first?.copyView(), + copyOfLastSlice: slices.last?.copyView(), sliceSize: CGSize( width: Self.mediaSize, height: Self.mediaSize ), shouldShowPageControl: true, - pageControlHeight: 10, + pageControlStyle: SessionCarouselView.PageControlStyle( + size: .medium, + backgroundColor: .init(white: 0, alpha: 0.4), + bottomInset: Values.mediumSpacing + ), shouldShowArrows: true, arrowsSize: CGSize( width: 20, diff --git a/Session/Shared/SessionCarouselView+Info.swift b/Session/Shared/SessionCarouselView+Info.swift index 8aef9cda8..f87d69c86 100644 --- a/Session/Shared/SessionCarouselView+Info.swift +++ b/Session/Shared/SessionCarouselView+Info.swift @@ -7,11 +7,12 @@ import SessionUtilitiesKit extension SessionCarouselView { public struct Info { let slices: [UIView] + let copyOfFirstSlice: UIView? + let copyOfLastSlice: UIView? let sliceSize: CGSize let sliceCount: Int let shouldShowPageControl: Bool - let pageControlHeight: CGFloat - let pageControlScale: CGFloat // This is to control the size of the dots + let pageControlStyle: PageControlStyle let shouldShowArrows: Bool let arrowsSize: CGSize @@ -19,21 +20,50 @@ extension SessionCarouselView { init( slices: [UIView] = [], + copyOfFirstSlice: UIView? = nil, + copyOfLastSlice: UIView? = nil, sliceSize: CGSize = .zero, shouldShowPageControl: Bool = true, - pageControlHeight: CGFloat = 0, - pageControlScale: CGFloat = 1, + pageControlStyle: PageControlStyle, shouldShowArrows: Bool = true, arrowsSize: CGSize = .zero ) { self.slices = slices + self.copyOfFirstSlice = copyOfFirstSlice + self.copyOfLastSlice = copyOfLastSlice self.sliceSize = sliceSize self.sliceCount = slices.count self.shouldShowPageControl = shouldShowPageControl && (self.sliceCount > 1) - self.pageControlHeight = pageControlHeight - self.pageControlScale = pageControlScale + self.pageControlStyle = pageControlStyle self.shouldShowArrows = shouldShowArrows && (self.sliceCount > 1) self.arrowsSize = arrowsSize } } + + public struct PageControlStyle { + enum DotSize: CGFloat { + case mini = 0.5 + case medium = 0.8 + case original = 1 + } + + let height: CGFloat? + let size: DotSize + let backgroundColor: UIColor + let bottomInset: CGFloat + + // MARK: - Initialization + + init( + height: CGFloat? = nil, + size: DotSize = .original, + backgroundColor: UIColor = .clear, + bottomInset: CGFloat = 0 + ) { + self.height = height + self.size = size + self.backgroundColor = backgroundColor + self.bottomInset = bottomInset + } + } } diff --git a/Session/Shared/SessionCarouselView.swift b/Session/Shared/SessionCarouselView.swift index 39accc837..2d102fc3a 100644 --- a/Session/Shared/SessionCarouselView.swift +++ b/Session/Shared/SessionCarouselView.swift @@ -28,8 +28,10 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { result.numberOfPages = self.info.sliceCount result.currentPage = 0 result.isHidden = !self.info.shouldShowPageControl - result.set(.height, to: self.info.pageControlHeight) - result.transform = CGAffineTransform(scaleX: self.info.pageControlScale, y: self.info.pageControlScale) + result.transform = CGAffineTransform( + scaleX: self.info.pageControlStyle.size.rawValue, + y: self.info.pageControlStyle.size.rawValue + ) return result }() @@ -59,11 +61,12 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { }() // MARK: - Lifecycle + init(info: SessionCarouselView.Info) { self.info = info if self.info.sliceCount > 1, - let copyOfFirstSlice: UIView = self.info.slices.first?.copyView(), - let copyOfLastSlice: UIView = self.info.slices.last?.copyView() + let copyOfFirstSlice: UIView = self.info.copyOfFirstSlice, + let copyOfLastSlice: UIView = self.info.copyOfLastSlice { self.slicesForLoop = [copyOfLastSlice] .appending(contentsOf: self.info.slices) @@ -120,6 +123,7 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { } // MARK: - UIScrollViewDelegate + func scrollViewDidScroll(_ scrollView: UIScrollView) { let pageIndex: Int = { let maybeCurrentPageIndex: Int = Int(round(scrollView.contentOffset.x/self.info.sliceSize.width)) @@ -139,6 +143,14 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + setCorrectCotentOffsetIfNeeded(scrollView) + } + + func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + setCorrectCotentOffsetIfNeeded(scrollView) + } + + private func setCorrectCotentOffsetIfNeeded(_ scrollView: UIScrollView) { if pageControl.currentPage == 0 { scrollView.setContentOffset( CGPoint( @@ -162,6 +174,7 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { } // MARK: - Interaction + @objc func scrollToNextSlice() { self.scrollView.setContentOffset( CGPoint( diff --git a/SessionUIKit/Utilities/UIView+Utilities.swift b/SessionUIKit/Utilities/UIView+Utilities.swift index 2ce766308..7d37a2185 100644 --- a/SessionUIKit/Utilities/UIView+Utilities.swift +++ b/SessionUIKit/Utilities/UIView+Utilities.swift @@ -72,14 +72,4 @@ public extension UIView { return result } - - func copyView() -> T? { - do { - return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(NSKeyedArchiver.archivedData(withRootObject:self, requiringSecureCoding:false)) as? T - } catch { - print("\(error)") - return nil - } - - } } From 2b38862524bdfe2e0da6b8034cba3bff3e1994ae Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 30 Jan 2023 17:16:35 +1100 Subject: [PATCH 22/44] optimise round corner for carousel view --- .../Message Cells/Content Views/MediaAlbumView.swift | 3 ++- .../Message Cells/Content Views/MediaView.swift | 5 +++-- .../MediaInfoVC+MediaPreviewView.swift | 3 ++- Session/Media Viewing & Editing/MediaInfoVC.swift | 3 ++- Session/Shared/SessionCarouselView+Info.swift | 5 ++++- Session/Shared/SessionCarouselView.swift | 2 ++ 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift index 9835f9920..a4cdf7e67 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift @@ -28,7 +28,8 @@ public class MediaAlbumView: UIStackView { MediaView( mediaCache: mediaCache, attachment: $0, - isOutgoing: isOutgoing + isOutgoing: isOutgoing, + cornerRadius: VisibleMessageCell.largeCornerRadius ) } diff --git a/Session/Conversations/Message Cells/Content Views/MediaView.swift b/Session/Conversations/Message Cells/Content Views/MediaView.swift index 214c22f1d..507b72917 100644 --- a/Session/Conversations/Message Cells/Content Views/MediaView.swift +++ b/Session/Conversations/Message Cells/Content Views/MediaView.swift @@ -47,7 +47,8 @@ public class MediaView: UIView { public required init( mediaCache: NSCache? = nil, attachment: Attachment, - isOutgoing: Bool + isOutgoing: Bool, + cornerRadius: CGFloat ) { self.mediaCache = mediaCache self.attachment = attachment @@ -58,7 +59,7 @@ public class MediaView: UIView { themeBackgroundColor = .backgroundSecondary clipsToBounds = true layer.masksToBounds = true - layer.cornerRadius = VisibleMessageCell.largeCornerRadius + layer.cornerRadius = cornerRadius createContents() } diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift index 3bac68e67..f30e5d8a9 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -16,7 +16,8 @@ extension MediaInfoVC { private lazy var mediaView: MediaView = { let result: MediaView = MediaView.init( attachment: attachment, - isOutgoing: isOutgoing + isOutgoing: isOutgoing, + cornerRadius: 0 ) return result diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 5c8353b1c..48398a6d8 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -38,7 +38,8 @@ final class MediaInfoVC: BaseVC { arrowsSize: CGSize( width: 20, height: 30 - ) + ), + cornerRadius: 8 ) ) result.set(.height, to: Self.mediaSize) diff --git a/Session/Shared/SessionCarouselView+Info.swift b/Session/Shared/SessionCarouselView+Info.swift index f87d69c86..62e7427c4 100644 --- a/Session/Shared/SessionCarouselView+Info.swift +++ b/Session/Shared/SessionCarouselView+Info.swift @@ -15,6 +15,7 @@ extension SessionCarouselView { let pageControlStyle: PageControlStyle let shouldShowArrows: Bool let arrowsSize: CGSize + let cornerRadius: CGFloat // MARK: - Initialization @@ -26,7 +27,8 @@ extension SessionCarouselView { shouldShowPageControl: Bool = true, pageControlStyle: PageControlStyle, shouldShowArrows: Bool = true, - arrowsSize: CGSize = .zero + arrowsSize: CGSize = .zero, + cornerRadius: CGFloat = 0 ) { self.slices = slices self.copyOfFirstSlice = copyOfFirstSlice @@ -37,6 +39,7 @@ extension SessionCarouselView { self.pageControlStyle = pageControlStyle self.shouldShowArrows = shouldShowArrows && (self.sliceCount > 1) self.arrowsSize = arrowsSize + self.cornerRadius = cornerRadius } } diff --git a/Session/Shared/SessionCarouselView.swift b/Session/Shared/SessionCarouselView.swift index 2d102fc3a..207433f5b 100644 --- a/Session/Shared/SessionCarouselView.swift +++ b/Session/Shared/SessionCarouselView.swift @@ -19,6 +19,8 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { width: self.info.sliceSize.width * CGFloat(self.slicesForLoop.count), height: self.info.sliceSize.height ) + result.layer.cornerRadius = self.info.cornerRadius + result.layer.masksToBounds = true return result }() From 78cea3ac294d983366bc839178c862f149e8ba1a Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 31 Jan 2023 11:06:06 +1100 Subject: [PATCH 23/44] implement carousel view delegate --- Session.xcodeproj/project.pbxproj | 4 ++ .../MediaInfoVC+MediaPreviewView.swift | 29 +------------ .../Media Viewing & Editing/MediaInfoVC.swift | 42 ++++++++++++++++--- Session/Shared/SessionCarouselView.swift | 3 ++ .../Shared/SessionCarouselViewDelegate.swift | 7 ++++ 5 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 Session/Shared/SessionCarouselViewDelegate.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6903a6d95..f351b3090 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -114,6 +114,7 @@ 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; 7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */; }; 7B3A39322980D02B002FE4AC /* SessionCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */; }; + 7B3A3934298882D6002FE4AC /* SessionCarouselViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A3933298882D6002FE4AC /* SessionCarouselViewDelegate.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; @@ -1185,6 +1186,7 @@ 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = ""; }; 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselView.swift; sourceTree = ""; }; + 7B3A3933298882D6002FE4AC /* SessionCarouselViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselViewDelegate.swift; sourceTree = ""; }; 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -2590,6 +2592,7 @@ FD37EA0628AA2CCA003AE748 /* SessionTableViewController.swift */, 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */, 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */, + 7B3A3933298882D6002FE4AC /* SessionCarouselViewDelegate.swift */, ); path = Shared; sourceTree = ""; @@ -5702,6 +5705,7 @@ FD71164228E2C85A00B47552 /* TransitionType.swift in Sources */, FD848B9828422F1A000E298B /* Date+Utilities.swift in Sources */, FD37E9DB28A244E9003AE748 /* ThemePreviewView.swift in Sources */, + 7B3A3934298882D6002FE4AC /* SessionCarouselViewDelegate.swift in Sources */, B85357C323A1BD1200AAF6CD /* SeedVC.swift in Sources */, 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */, 7B9F71C928470667006DFE7B /* ReactionListSheet.swift in Sources */, diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift index f30e5d8a9..2ca703e6e 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaPreviewView.swift @@ -23,23 +23,6 @@ extension MediaInfoVC { return result }() - private lazy var fullScreenButton: UIButton = { - let result: UIButton = UIButton(type: .custom) - result.setImage( - UIImage(systemName: "arrow.up.left.and.arrow.down.right")? - .withRenderingMode(.alwaysTemplate), - for: .normal - ) - result.themeTintColor = .textPrimary - result.backgroundColor = .init(white: 0, alpha: 0.4) - result.layer.cornerRadius = 14 - result.set(.width, to: 28) - result.set(.height, to: 28) - result.addTarget(self, action: #selector(showMediaFullScreen), for: .touchUpInside) - - return result - }() - // MARK: - Lifecycle init(attachment: Attachment, isOutgoing: Bool) { @@ -66,20 +49,12 @@ extension MediaInfoVC { addSubview(mediaView) mediaView.pin(to: self) - addSubview(fullScreenButton) - fullScreenButton.pin(.trailing, to: .trailing, of: self, withInset: -Values.smallSpacing) - fullScreenButton.pin(.bottom, to: .bottom, of: self, withInset: -Values.smallSpacing) - mediaView.loadMedia() } - // MARK: - Interaction - - @objc func showMediaFullScreen() { - - } - // MARK: - Copy + + /// This function is used to make sure the carousel view contains this class can loop infinitely func copyView() -> MediaPreviewView { return MediaPreviewView(attachment: self.attachment, isOutgoing: self.isOutgoing) } diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 48398a6d8..769216173 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -4,8 +4,9 @@ import UIKit import SessionUIKit import SessionUtilitiesKit -final class MediaInfoVC: BaseVC { +final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { internal static let mediaSize: CGFloat = 293 + internal static let arrowSize: CGSize = CGSize(width: 20, height: 30) private let attachments: [Attachment] private let isOutgoing: Bool @@ -35,14 +36,29 @@ final class MediaInfoVC: BaseVC { bottomInset: Values.mediumSpacing ), shouldShowArrows: true, - arrowsSize: CGSize( - width: 20, - height: 30 - ), + arrowsSize: Self.arrowSize, cornerRadius: 8 ) ) result.set(.height, to: Self.mediaSize) + result.delegate = self + + return result + }() + + private lazy var fullScreenButton: UIButton = { + let result: UIButton = UIButton(type: .custom) + result.setImage( + UIImage(systemName: "arrow.up.left.and.arrow.down.right")? + .withRenderingMode(.alwaysTemplate), + for: .normal + ) + result.themeTintColor = .textPrimary + result.backgroundColor = .init(white: 0, alpha: 0.4) + result.layer.cornerRadius = 14 + result.set(.width, to: 28) + result.set(.height, to: 28) + result.addTarget(self, action: #selector(showMediaFullScreen), for: .touchUpInside) return result }() @@ -79,6 +95,10 @@ final class MediaInfoVC: BaseVC { mediaInfoView.update(attachment: attachments[0]) + mediaCarouselView.addSubview(fullScreenButton) + fullScreenButton.pin(.trailing, to: .trailing, of: mediaCarouselView, withInset: -(Values.smallSpacing + Self.arrowSize.width + Values.largeSpacing)) + fullScreenButton.pin(.bottom, to: .bottom, of: mediaCarouselView, withInset: -Values.smallSpacing) + let stackView: UIStackView = UIStackView(arrangedSubviews: [ mediaCarouselView, mediaInfoView ]) stackView.axis = .vertical stackView.alignment = .center @@ -88,4 +108,16 @@ final class MediaInfoVC: BaseVC { stackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: self.view) stackView.center(.vertical, in: self.view) } + + // MARK: - Interaction + + @objc func showMediaFullScreen() { + + } + + // MARK: - SessionCarouselViewDelegate + + func carouselViewDidScrollToNewSlice(currentPage: Int) { + mediaInfoView.update(attachment: attachments[currentPage]) + } } diff --git a/Session/Shared/SessionCarouselView.swift b/Session/Shared/SessionCarouselView.swift index 207433f5b..6111f6df0 100644 --- a/Session/Shared/SessionCarouselView.swift +++ b/Session/Shared/SessionCarouselView.swift @@ -7,6 +7,7 @@ import SessionUtilitiesKit final class SessionCarouselView: UIView, UIScrollViewDelegate { private let slicesForLoop: [UIView] private let info: SessionCarouselView.Info + var delegate: SessionCarouselViewDelegate? // MARK: - UI private lazy var scrollView: UIScrollView = { @@ -146,10 +147,12 @@ final class SessionCarouselView: UIView, UIScrollViewDelegate { func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { setCorrectCotentOffsetIfNeeded(scrollView) + delegate?.carouselViewDidScrollToNewSlice(currentPage: pageControl.currentPage) } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { setCorrectCotentOffsetIfNeeded(scrollView) + delegate?.carouselViewDidScrollToNewSlice(currentPage: pageControl.currentPage) } private func setCorrectCotentOffsetIfNeeded(_ scrollView: UIScrollView) { diff --git a/Session/Shared/SessionCarouselViewDelegate.swift b/Session/Shared/SessionCarouselViewDelegate.swift new file mode 100644 index 000000000..8cb2e19eb --- /dev/null +++ b/Session/Shared/SessionCarouselViewDelegate.swift @@ -0,0 +1,7 @@ +// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +public protocol SessionCarouselViewDelegate: AnyObject { + func carouselViewDidScrollToNewSlice(currentPage: Int) +} From d322c38458f4f25e4d8808c7f8e4e2bdf851f316 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 3 Feb 2023 17:00:42 +1100 Subject: [PATCH 24/44] WIP: show media full screen --- Session/Media Viewing & Editing/MediaInfoVC.swift | 3 +++ .../Notifications/PushNotificationAPI.swift | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 769216173..804854ae1 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -10,6 +10,7 @@ final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { private let attachments: [Attachment] private let isOutgoing: Bool + private var currentPage: Int = 0 // MARK: - UI private lazy var mediaInfoView: MediaInfoView = MediaInfoView(attachment: nil) @@ -112,12 +113,14 @@ final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { // MARK: - Interaction @objc func showMediaFullScreen() { + let attachment = self.attachments[self.currentPage] } // MARK: - SessionCarouselViewDelegate func carouselViewDidScrollToNewSlice(currentPage: Int) { + self.currentPage = currentPage mediaInfoView.update(attachment: attachments[currentPage]) } } diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index 11499c28f..be56e92bf 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -28,7 +28,7 @@ public final class PushNotificationAPI : NSObject { } // MARK: - Settings - public static let server = "https://live.apns.getsession.org" + public static let server = "https://dev.apns.getsession.org" public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private static let maxRetryCount: UInt = 4 From aee9d46e581f1551a64ae2a1eccf28230b65f552 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 9 Feb 2023 14:14:35 +1100 Subject: [PATCH 25/44] feat: show media full screen --- .../ConversationVC+Interaction.swift | 5 +++- .../Media Viewing & Editing/MediaInfoVC.swift | 27 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 222bc2eea..bd4e43249 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1561,7 +1561,10 @@ extension ConversationVC: func info(_ cellViewModel: MessageViewModel) { let mediaInfoVC = MediaInfoVC( attachments: (cellViewModel.attachments ?? []), - isOutgoing: (cellViewModel.variant == .standardOutgoing) + isOutgoing: (cellViewModel.variant == .standardOutgoing), + threadId: self.viewModel.threadData.threadId, + threadVariant: self.viewModel.threadData.threadVariant, + interactionId: cellViewModel.id ) navigationController?.pushViewController(mediaInfoVC, animated: true) } diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 804854ae1..3486e3dbc 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -10,6 +10,10 @@ final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { private let attachments: [Attachment] private let isOutgoing: Bool + private let threadId: String + private let threadVariant: SessionThread.Variant + private let interactionId: Int64 + private var currentPage: Int = 0 // MARK: - UI @@ -66,7 +70,16 @@ final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { // MARK: - Initialization - init(attachments: [Attachment], isOutgoing: Bool) { + init( + attachments: [Attachment], + isOutgoing: Bool, + threadId: String, + threadVariant: SessionThread.Variant, + interactionId: Int64 + ) { + self.threadId = threadId + self.threadVariant = threadVariant + self.interactionId = interactionId self.isOutgoing = isOutgoing self.attachments = attachments super.init(nibName: nil, bundle: nil) @@ -114,7 +127,17 @@ final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { @objc func showMediaFullScreen() { let attachment = self.attachments[self.currentPage] - + let viewController: UIViewController? = MediaGalleryViewModel.createDetailViewController( + for: self.threadId, + threadVariant: self.threadVariant, + interactionId: self.interactionId, + selectedAttachmentId: attachment.id, + options: [ .sliderEnabled ] + ) + if let viewController: UIViewController = viewController { + viewController.transitioningDelegate = nil + self.present(viewController, animated: true) + } } // MARK: - SessionCarouselViewDelegate From 18453405822470fa42a061f0da7a8211ce43f62c Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 13 Feb 2023 14:22:54 +1100 Subject: [PATCH 26/44] WIP: refactor on the call UI to add switching function of caller and callee's video views --- .../Calls/Call Management/SessionCall.swift | 4 + Session/Calls/CallVC.swift | 88 +++++++++++++++++-- .../Calls/Views & Modals/CallVideoView.swift | 5 +- .../Calls/WebRTCSession+UI.swift | 4 + 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 4c3250429..de3f64f48 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -356,6 +356,10 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { webRTCSession.attachLocalRenderer(renderer) } + func removeLocalVideoRenderer(_ renderer: RTCVideoRenderer) { + webRTCSession.removeLocalRenderer(renderer) + } + // MARK: - Delegate public func webRTCIsConnected() { diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 49152e981..cc6609c5e 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -8,6 +8,9 @@ import SessionMessagingKit import SessionUtilitiesKit final class CallVC: UIViewController, VideoPreviewDelegate { + static let floatingVideoViewWidth: CGFloat = UIDevice.current.isIPad ? 160 : 80 + static let floatingVideoViewHeight: CGFloat = UIDevice.current.isIPad ? 346: 173 + let call: SessionCall var latestKnownAudioOutputDeviceName: String? var durationTimer: Timer? @@ -23,25 +26,92 @@ final class CallVC: UIViewController, VideoPreviewDelegate { // MARK: - UI Components - private lazy var localVideoView: LocalVideoView = { + private lazy var floatingLocalVideoView: LocalVideoView = { let result = LocalVideoView() result.clipsToBounds = true result.themeBackgroundColor = .backgroundSecondary result.isHidden = !call.isVideoEnabled result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10 result.layer.masksToBounds = true - result.set(.width, to: LocalVideoView.width) - result.set(.height, to: LocalVideoView.height) - result.makeViewDraggable() + result.set(.width, to: Self.floatingVideoViewWidth) + result.set(.height, to: Self.floatingVideoViewHeight) return result }() - private lazy var remoteVideoView: RemoteVideoView = { + private lazy var floatingRemoteVideoView: RemoteVideoView = { + let result = RemoteVideoView() + result.clipsToBounds = true + result.themeBackgroundColor = .backgroundSecondary + result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10 + result.layer.masksToBounds = true + result.set(.width, to: Self.floatingVideoViewWidth) + result.set(.height, to: Self.floatingVideoViewHeight) + + return result + }() + + private lazy var fullScreenLocalVideoView: LocalVideoView = { + let result = LocalVideoView() + result.alpha = 0 + result.themeBackgroundColor = .backgroundPrimary + result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleFullScreenVideoViewTapped))) + + return result + }() + + private lazy var fullScreenRemoteVideoView: RemoteVideoView = { let result = RemoteVideoView() result.alpha = 0 result.themeBackgroundColor = .backgroundPrimary - result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleRemoteVieioViewTapped))) + result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleFullScreenVideoViewTapped))) + + return result + }() + + private lazy var switchVideoButton: UIButton = { + let result = UIButton(type: .custom) + result.setImage( + UIImage(named: "ic_switch_camera")? + .withRenderingMode(.alwaysTemplate), + for: .normal + ) + result.themeTintColor = .textPrimary + result.addTarget(self, action: #selector(switchVideo), for: UIControl.Event.touchUpInside) + result.set(.width, to: 20) + result.set(.height, to: 20) + + return result + }() + + private lazy var switchVideoButtonContainer: UIView = { + let result = UIView() + result.themeBackgroundColor = .backgroundPrimary + result.layer.cornerRadius = UIDevice.current.isIPad ? 12 : 6 + result.layer.masksToBounds = true + result.addSubview(switchVideoButton) + switchVideoButton.pin(.leading, to: .leading, of: result, withInset: Values.smallSpacing) + switchVideoButton.pin(.trailing, to: .trailing, of: result, withInset: Values.smallSpacing) + switchVideoButton.pin(.bottom, to: .bottom, of: result, withInset: Values.smallSpacing) + switchVideoButton.pin(.top, to: .top, of: result, withInset: Values.largeSpacing) + + return result + }() + + private lazy var floatingViewContainer: UIView = { + let result = UIView() + result.makeViewDraggable() + result.addSubview(switchVideoButtonContainer) + switchVideoButtonContainer.pin(.trailing, to: .trailing, of: result) + switchVideoButtonContainer.pin(.bottom, to: .bottom, of: result) + + result.addSubview(floatingLocalVideoView) + floatingLocalVideoView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top], to: result) + floatingLocalVideoView.pin(.bottom, to: .bottom, of: result, withInset: 36) + + result.addSubview(floatingRemoteVideoView) + floatingRemoteVideoView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top], to: result) + floatingRemoteVideoView.pin(.bottom, to: .bottom, of: result, withInset: 36) return result }() @@ -584,6 +654,10 @@ final class CallVC: UIViewController, VideoPreviewDelegate { call.isVideoEnabled = true } + @objc private func switchVideo() { + + } + @objc private func switchCamera() { cameraManager.switchCamera() } @@ -645,7 +719,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { } } - @objc private func handleRemoteVieioViewTapped(gesture: UITapGestureRecognizer) { + @objc private func handleFullScreenVideoViewTapped(gesture: UITapGestureRecognizer) { let isHidden = callDurationLabel.alpha < 0.5 UIView.animate(withDuration: 0.5) { diff --git a/Session/Calls/Views & Modals/CallVideoView.swift b/Session/Calls/Views & Modals/CallVideoView.swift index 899732f66..d2e8894c0 100644 --- a/Session/Calls/Views & Modals/CallVideoView.swift +++ b/Session/Calls/Views & Modals/CallVideoView.swift @@ -87,10 +87,7 @@ class RemoteVideoView: TargetView { // MARK: LocalVideoView class LocalVideoView: TargetView { - - static let width: CGFloat = UIDevice.current.isIPad ? 160 : 80 - static let height: CGFloat = UIDevice.current.isIPad ? 346: 173 - + override func renderFrame(_ frame: RTCVideoFrame?) { super.renderFrame(frame) DispatchMainThreadSafe { diff --git a/SessionMessagingKit/Calls/WebRTCSession+UI.swift b/SessionMessagingKit/Calls/WebRTCSession+UI.swift index 305d1bfb7..2594d8a15 100644 --- a/SessionMessagingKit/Calls/WebRTCSession+UI.swift +++ b/SessionMessagingKit/Calls/WebRTCSession+UI.swift @@ -6,6 +6,10 @@ extension WebRTCSession { localVideoTrack.add(renderer) } + public func removeLocalRenderer(_ renderer: RTCVideoRenderer) { + localVideoTrack.remove(renderer) + } + public func attachRemoteRenderer(_ renderer: RTCVideoRenderer) { remoteVideoTrack?.add(renderer) } From 0ccfdcd24d1f3b2d50f2d025ff130676b94aaab0 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 14 Feb 2023 16:34:27 +1100 Subject: [PATCH 27/44] WIP: switch video view --- Session/Calls/CallVC.swift | 79 ++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index cc6609c5e..707b72ebc 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -24,6 +24,13 @@ final class CallVC: UIViewController, VideoPreviewDelegate { return result }() + enum FloatingViewVideoSource { + case local + case remote + } + + var floatingViewVideoSource: FloatingViewVideoSource = .local + // MARK: - UI Components private lazy var floatingLocalVideoView: LocalVideoView = { @@ -41,6 +48,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { private lazy var floatingRemoteVideoView: RemoteVideoView = { let result = RemoteVideoView() + result.alpha = 0 result.clipsToBounds = true result.themeBackgroundColor = .backgroundSecondary result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10 @@ -91,8 +99,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { result.layer.masksToBounds = true result.addSubview(switchVideoButton) switchVideoButton.pin(.leading, to: .leading, of: result, withInset: Values.smallSpacing) - switchVideoButton.pin(.trailing, to: .trailing, of: result, withInset: Values.smallSpacing) - switchVideoButton.pin(.bottom, to: .bottom, of: result, withInset: Values.smallSpacing) + switchVideoButton.pin(.trailing, to: .trailing, of: result, withInset: -Values.smallSpacing) + switchVideoButton.pin(.bottom, to: .bottom, of: result, withInset: -Values.smallSpacing) switchVideoButton.pin(.top, to: .top, of: result, withInset: Values.largeSpacing) return result @@ -100,6 +108,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { private lazy var floatingViewContainer: UIView = { let result = UIView() + result.isHidden = true result.makeViewDraggable() result.addSubview(switchVideoButtonContainer) switchVideoButtonContainer.pin(.trailing, to: .trailing, of: result) @@ -107,11 +116,11 @@ final class CallVC: UIViewController, VideoPreviewDelegate { result.addSubview(floatingLocalVideoView) floatingLocalVideoView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top], to: result) - floatingLocalVideoView.pin(.bottom, to: .bottom, of: result, withInset: 36) + floatingLocalVideoView.pin(.bottom, to: .bottom, of: result, withInset: -36) result.addSubview(floatingRemoteVideoView) floatingRemoteVideoView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top], to: result) - floatingRemoteVideoView.pin(.bottom, to: .bottom, of: result, withInset: 36) + floatingRemoteVideoView.pin(.bottom, to: .bottom, of: result, withInset: -36) return result }() @@ -335,7 +344,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { self.call.remoteVideoStateDidChange = { isEnabled in DispatchQueue.main.async { UIView.animate(withDuration: 0.25) { - self.remoteVideoView.alpha = isEnabled ? 1 : 0 + let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView + remoteVideoView.alpha = isEnabled ? 1 : 0 } if self.callInfoLabel.alpha < 0.5 { @@ -445,13 +455,16 @@ final class CallVC: UIViewController, VideoPreviewDelegate { view.addSubview(profilePictureContainer) // Remote video view - call.attachRemoteVideoRenderer(remoteVideoView) - view.addSubview(remoteVideoView) - remoteVideoView.translatesAutoresizingMaskIntoConstraints = false - remoteVideoView.pin(to: view) + call.attachRemoteVideoRenderer(fullScreenRemoteVideoView) + view.addSubview(fullScreenRemoteVideoView) + fullScreenRemoteVideoView.translatesAutoresizingMaskIntoConstraints = false + fullScreenRemoteVideoView.pin(to: view) // Local video view - call.attachLocalVideoRenderer(localVideoView) + call.attachLocalVideoRenderer(floatingLocalVideoView) + view.addSubview(fullScreenLocalVideoView) + fullScreenLocalVideoView.translatesAutoresizingMaskIntoConstraints = false + fullScreenLocalVideoView.pin(to: view) // Fade view view.addSubview(fadeView) @@ -501,12 +514,12 @@ final class CallVC: UIViewController, VideoPreviewDelegate { callDurationLabel.center(in: callInfoLabelContainer) } - private func addLocalVideoView() { + private func addFloatingVideoView() { let safeAreaInsets = UIApplication.shared.keyWindow?.safeAreaInsets - CurrentAppContext().mainWindow?.addSubview(localVideoView) - localVideoView.autoPinEdge(toSuperviewEdge: .right, withInset: Values.smallSpacing) + CurrentAppContext().mainWindow?.addSubview(floatingViewContainer) + floatingViewContainer.autoPinEdge(toSuperviewEdge: .right, withInset: Values.smallSpacing) let topMargin = (safeAreaInsets?.top ?? 0) + Values.veryLargeSpacing - localVideoView.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin) + floatingViewContainer.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin) } override func viewDidAppear(_ animated: Bool) { @@ -515,7 +528,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { if (call.isVideoEnabled && shouldRestartCamera) { cameraManager.start() } shouldRestartCamera = true - addLocalVideoView() + addFloatingVideoView() + let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView remoteVideoView.alpha = (call.isRemoteVideoEnabled ? 1 : 0) } @@ -524,7 +538,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { if (call.isVideoEnabled && shouldRestartCamera) { cameraManager.stop() } - localVideoView.removeFromSuperview() + floatingViewContainer.removeFromSuperview() } // MARK: - Orientation @@ -571,7 +585,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { self.callInfoLabel.text = "Call Ended" UIView.animate(withDuration: 0.25) { - self.remoteVideoView.alpha = 0 + let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView + remoteVideoView.alpha = 0 self.operationPanel.alpha = 1 self.responsePanel.alpha = 1 self.callInfoLabel.alpha = 1 @@ -629,7 +644,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { @objc private func operateCamera() { if (call.isVideoEnabled) { - localVideoView.isHidden = true + floatingViewContainer.isHidden = true cameraManager.stop() videoButton.themeTintColor = .textPrimary videoButton.themeBackgroundColor = .backgroundSecondary @@ -645,7 +660,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { } func cameraDidConfirmTurningOn() { - localVideoView.isHidden = false + floatingViewContainer.isHidden = false cameraManager.prepare() cameraManager.start() videoButton.themeTintColor = .backgroundSecondary @@ -655,7 +670,31 @@ final class CallVC: UIViewController, VideoPreviewDelegate { } @objc private func switchVideo() { - + if self.floatingViewVideoSource == .remote { + call.removeRemoteVideoRenderer(self.floatingRemoteVideoView) + call.removeLocalVideoRenderer(self.fullScreenLocalVideoView) + + self.floatingRemoteVideoView.alpha = 0 + self.floatingLocalVideoView.alpha = 1 + self.fullScreenRemoteVideoView.alpha = 1 + self.fullScreenLocalVideoView.alpha = 0 + + self.floatingViewVideoSource = .local + call.attachRemoteVideoRenderer(self.fullScreenRemoteVideoView) + call.attachLocalVideoRenderer(self.floatingLocalVideoView) + } else { + call.removeRemoteVideoRenderer(self.fullScreenRemoteVideoView) + call.removeLocalVideoRenderer(self.floatingLocalVideoView) + + self.floatingRemoteVideoView.alpha = 1 + self.floatingLocalVideoView.alpha = 0 + self.fullScreenRemoteVideoView.alpha = 0 + self.fullScreenLocalVideoView.alpha = 1 + + self.floatingViewVideoSource = .remote + call.attachRemoteVideoRenderer(self.floatingRemoteVideoView) + call.attachLocalVideoRenderer(self.fullScreenLocalVideoView) + } } @objc private func switchCamera() { From 1b35085015449d29864dbb3224759c39490a1a66 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 15 Feb 2023 15:32:40 +1100 Subject: [PATCH 28/44] fix local video view --- Session/Calls/CallVC.swift | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 707b72ebc..b829ea3ce 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -35,9 +35,9 @@ final class CallVC: UIViewController, VideoPreviewDelegate { private lazy var floatingLocalVideoView: LocalVideoView = { let result = LocalVideoView() + result.alpha = 0 result.clipsToBounds = true result.themeBackgroundColor = .backgroundSecondary - result.isHidden = !call.isVideoEnabled result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10 result.layer.masksToBounds = true result.set(.width, to: Self.floatingVideoViewWidth) @@ -86,22 +86,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { ) result.themeTintColor = .textPrimary result.addTarget(self, action: #selector(switchVideo), for: UIControl.Event.touchUpInside) - result.set(.width, to: 20) - result.set(.height, to: 20) - - return result - }() - - private lazy var switchVideoButtonContainer: UIView = { - let result = UIView() - result.themeBackgroundColor = .backgroundPrimary - result.layer.cornerRadius = UIDevice.current.isIPad ? 12 : 6 - result.layer.masksToBounds = true - result.addSubview(switchVideoButton) - switchVideoButton.pin(.leading, to: .leading, of: result, withInset: Values.smallSpacing) - switchVideoButton.pin(.trailing, to: .trailing, of: result, withInset: -Values.smallSpacing) - switchVideoButton.pin(.bottom, to: .bottom, of: result, withInset: -Values.smallSpacing) - switchVideoButton.pin(.top, to: .top, of: result, withInset: Values.largeSpacing) + result.set(.width, to: 60) + result.set(.height, to: 60) return result }() @@ -110,17 +96,12 @@ final class CallVC: UIViewController, VideoPreviewDelegate { let result = UIView() result.isHidden = true result.makeViewDraggable() - result.addSubview(switchVideoButtonContainer) - switchVideoButtonContainer.pin(.trailing, to: .trailing, of: result) - switchVideoButtonContainer.pin(.bottom, to: .bottom, of: result) result.addSubview(floatingLocalVideoView) - floatingLocalVideoView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top], to: result) - floatingLocalVideoView.pin(.bottom, to: .bottom, of: result, withInset: -36) + floatingLocalVideoView.pin(to: result) result.addSubview(floatingRemoteVideoView) - floatingRemoteVideoView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top], to: result) - floatingRemoteVideoView.pin(.bottom, to: .bottom, of: result, withInset: -36) + floatingRemoteVideoView.pin(to: result) return result }() @@ -477,6 +458,12 @@ final class CallVC: UIViewController, VideoPreviewDelegate { minimizeButton.pin(.left, to: .left, of: view) minimizeButton.pin(.top, to: .top, of: view, withInset: 32) + // Swap button + view.addSubview(switchVideoButton) + switchVideoButton.translatesAutoresizingMaskIntoConstraints = false + switchVideoButton.pin(.right, to: .right, of: view) + switchVideoButton.pin(.top, to: .top, of: view, withInset: 32) + // Title label view.addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false @@ -661,6 +648,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { func cameraDidConfirmTurningOn() { floatingViewContainer.isHidden = false + let localVideoView: LocalVideoView = self.floatingViewVideoSource == .local ? self.floatingLocalVideoView : self.fullScreenLocalVideoView + localVideoView.alpha = 1 cameraManager.prepare() cameraManager.start() videoButton.themeTintColor = .backgroundSecondary From 335bfb687466e459e2afddb9e306b091e94189ec Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 15 Feb 2023 16:31:06 +1100 Subject: [PATCH 29/44] hiding video view if needed --- Session/Calls/CallVC.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index b829ea3ce..2b5db2e01 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -664,8 +664,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { call.removeLocalVideoRenderer(self.fullScreenLocalVideoView) self.floatingRemoteVideoView.alpha = 0 - self.floatingLocalVideoView.alpha = 1 - self.fullScreenRemoteVideoView.alpha = 1 + self.floatingLocalVideoView.alpha = call.isVideoEnabled ? 1 : 0 + self.fullScreenRemoteVideoView.alpha = call.isRemoteVideoEnabled ? 1 : 0 self.fullScreenLocalVideoView.alpha = 0 self.floatingViewVideoSource = .local @@ -675,10 +675,10 @@ final class CallVC: UIViewController, VideoPreviewDelegate { call.removeRemoteVideoRenderer(self.fullScreenRemoteVideoView) call.removeLocalVideoRenderer(self.floatingLocalVideoView) - self.floatingRemoteVideoView.alpha = 1 + self.floatingRemoteVideoView.alpha = call.isRemoteVideoEnabled ? 1 : 0 self.floatingLocalVideoView.alpha = 0 self.fullScreenRemoteVideoView.alpha = 0 - self.fullScreenLocalVideoView.alpha = 1 + self.fullScreenLocalVideoView.alpha = call.isVideoEnabled ? 1 : 0 self.floatingViewVideoSource = .remote call.attachRemoteVideoRenderer(self.floatingRemoteVideoView) From 5a35907cd3398b96bca6075c1eff472db5c75405 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 16 Feb 2023 10:34:03 +1100 Subject: [PATCH 30/44] implement new UI/UX design --- Session/Calls/CallVC.swift | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 2b5db2e01..8e385ff12 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -77,32 +77,41 @@ final class CallVC: UIViewController, VideoPreviewDelegate { return result }() - private lazy var switchVideoButton: UIButton = { - let result = UIButton(type: .custom) - result.setImage( - UIImage(named: "ic_switch_camera")? - .withRenderingMode(.alwaysTemplate), - for: .normal - ) - result.themeTintColor = .textPrimary - result.addTarget(self, action: #selector(switchVideo), for: UIControl.Event.touchUpInside) - result.set(.width, to: 60) - result.set(.height, to: 60) - - return result - }() - private lazy var floatingViewContainer: UIView = { let result = UIView() result.isHidden = true + result.themeBackgroundColor = .backgroundSecondary result.makeViewDraggable() + let noVideoIcon: UIImageView = UIImageView( + image: UIImage(systemName: "video.slash")? + .withRenderingMode(.alwaysTemplate) + ) + noVideoIcon.themeTintColor = .textPrimary + noVideoIcon.set(.width, to: 30) + noVideoIcon.set(.height, to: 20) + result.addSubview(noVideoIcon) + noVideoIcon.center(in: result) + result.addSubview(floatingLocalVideoView) floatingLocalVideoView.pin(to: result) result.addSubview(floatingRemoteVideoView) floatingRemoteVideoView.pin(to: result) + let swappingVideoIcon: UIImageView = UIImageView( + image: UIImage(systemName: "arrow.2.squarepath")? + .withRenderingMode(.alwaysTemplate) + ) + swappingVideoIcon.themeTintColor = .textPrimary + swappingVideoIcon.set(.height, to: 20) + swappingVideoIcon.set(.width, to: 20) + result.addSubview(swappingVideoIcon) + swappingVideoIcon.pin(.top, to: .top, of: result, withInset: Values.mediumSpacing) + swappingVideoIcon.pin(.trailing, to: .trailing, of: result, withInset: -Values.mediumSpacing) + + result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(switchVideo))) + return result }() @@ -458,12 +467,6 @@ final class CallVC: UIViewController, VideoPreviewDelegate { minimizeButton.pin(.left, to: .left, of: view) minimizeButton.pin(.top, to: .top, of: view, withInset: 32) - // Swap button - view.addSubview(switchVideoButton) - switchVideoButton.translatesAutoresizingMaskIntoConstraints = false - switchVideoButton.pin(.right, to: .right, of: view) - switchVideoButton.pin(.top, to: .top, of: view, withInset: 32) - // Title label view.addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false From acd494c29d90fe49b4f7dee33f7d833e4ad028fb Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Thu, 16 Feb 2023 11:38:55 +1100 Subject: [PATCH 31/44] minor adjustment for UI/UX --- Session/Calls/CallVC.swift | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index 8e385ff12..f98901d45 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -36,10 +36,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { private lazy var floatingLocalVideoView: LocalVideoView = { let result = LocalVideoView() result.alpha = 0 - result.clipsToBounds = true result.themeBackgroundColor = .backgroundSecondary - result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10 - result.layer.masksToBounds = true result.set(.width, to: Self.floatingVideoViewWidth) result.set(.height, to: Self.floatingVideoViewHeight) @@ -49,10 +46,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { private lazy var floatingRemoteVideoView: RemoteVideoView = { let result = RemoteVideoView() result.alpha = 0 - result.clipsToBounds = true result.themeBackgroundColor = .backgroundSecondary - result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10 - result.layer.masksToBounds = true result.set(.width, to: Self.floatingVideoViewWidth) result.set(.height, to: Self.floatingVideoViewHeight) @@ -80,6 +74,9 @@ final class CallVC: UIViewController, VideoPreviewDelegate { private lazy var floatingViewContainer: UIView = { let result = UIView() result.isHidden = true + result.clipsToBounds = true + result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10 + result.layer.masksToBounds = true result.themeBackgroundColor = .backgroundSecondary result.makeViewDraggable() @@ -88,8 +85,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { .withRenderingMode(.alwaysTemplate) ) noVideoIcon.themeTintColor = .textPrimary - noVideoIcon.set(.width, to: 30) - noVideoIcon.set(.height, to: 20) + noVideoIcon.set(.width, to: 34) + noVideoIcon.set(.height, to: 28) result.addSubview(noVideoIcon) noVideoIcon.center(in: result) @@ -104,11 +101,11 @@ final class CallVC: UIViewController, VideoPreviewDelegate { .withRenderingMode(.alwaysTemplate) ) swappingVideoIcon.themeTintColor = .textPrimary - swappingVideoIcon.set(.height, to: 20) - swappingVideoIcon.set(.width, to: 20) + swappingVideoIcon.set(.width, to: 16) + swappingVideoIcon.set(.height, to: 12) result.addSubview(swappingVideoIcon) - swappingVideoIcon.pin(.top, to: .top, of: result, withInset: Values.mediumSpacing) - swappingVideoIcon.pin(.trailing, to: .trailing, of: result, withInset: -Values.mediumSpacing) + swappingVideoIcon.pin(.top, to: .top, of: result, withInset: Values.smallSpacing) + swappingVideoIcon.pin(.trailing, to: .trailing, of: result, withInset: -Values.smallSpacing) result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(switchVideo))) From 1f181f77813d4b828243f338250e47e4c28c334a Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 3 Apr 2023 08:55:22 +0800 Subject: [PATCH 32/44] revert changes of PN server link --- .../Sending & Receiving/Notifications/PushNotificationAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index be56e92bf..11499c28f 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -28,7 +28,7 @@ public final class PushNotificationAPI : NSObject { } // MARK: - Settings - public static let server = "https://dev.apns.getsession.org" + public static let server = "https://live.apns.getsession.org" public static let serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private static let maxRetryCount: UInt = 4 From 53293fbb25a678ef335db2262684d2ad8a24b42d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 3 Apr 2023 09:04:27 +0800 Subject: [PATCH 33/44] add localised string for message info title --- Session/Media Viewing & Editing/MediaInfoVC.swift | 2 +- Session/Meta/Translations/de.lproj/Localizable.strings | 1 + Session/Meta/Translations/en.lproj/Localizable.strings | 1 + Session/Meta/Translations/es.lproj/Localizable.strings | 1 + Session/Meta/Translations/fa.lproj/Localizable.strings | 1 + Session/Meta/Translations/fi.lproj/Localizable.strings | 1 + Session/Meta/Translations/fr.lproj/Localizable.strings | 1 + Session/Meta/Translations/hi.lproj/Localizable.strings | 1 + Session/Meta/Translations/hr.lproj/Localizable.strings | 1 + Session/Meta/Translations/id-ID.lproj/Localizable.strings | 1 + Session/Meta/Translations/it.lproj/Localizable.strings | 1 + Session/Meta/Translations/ja.lproj/Localizable.strings | 1 + Session/Meta/Translations/nl.lproj/Localizable.strings | 1 + Session/Meta/Translations/pl.lproj/Localizable.strings | 1 + Session/Meta/Translations/pt_BR.lproj/Localizable.strings | 1 + Session/Meta/Translations/ru.lproj/Localizable.strings | 1 + Session/Meta/Translations/si.lproj/Localizable.strings | 1 + Session/Meta/Translations/sk.lproj/Localizable.strings | 1 + Session/Meta/Translations/sv.lproj/Localizable.strings | 1 + Session/Meta/Translations/th.lproj/Localizable.strings | 1 + Session/Meta/Translations/vi-VN.lproj/Localizable.strings | 1 + Session/Meta/Translations/zh-Hant.lproj/Localizable.strings | 1 + Session/Meta/Translations/zh_CN.lproj/Localizable.strings | 1 + 23 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index 3486e3dbc..bad2c84b9 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -100,7 +100,7 @@ final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { ViewControllerUtilities.setUpDefaultSessionStyle( for: self, - title: "Message Info", + title: "message_info_title".localized(), hasCustomBackButton: false ) diff --git a/Session/Meta/Translations/de.lproj/Localizable.strings b/Session/Meta/Translations/de.lproj/Localizable.strings index 49c107776..fce62bebb 100644 --- a/Session/Meta/Translations/de.lproj/Localizable.strings +++ b/Session/Meta/Translations/de.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index bdb714be0..56a1a29b6 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/es.lproj/Localizable.strings b/Session/Meta/Translations/es.lproj/Localizable.strings index 09ebb1cdd..bab6433cf 100644 --- a/Session/Meta/Translations/es.lproj/Localizable.strings +++ b/Session/Meta/Translations/es.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/fa.lproj/Localizable.strings b/Session/Meta/Translations/fa.lproj/Localizable.strings index eb1709fa1..29524b38b 100644 --- a/Session/Meta/Translations/fa.lproj/Localizable.strings +++ b/Session/Meta/Translations/fa.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/fi.lproj/Localizable.strings b/Session/Meta/Translations/fi.lproj/Localizable.strings index b091ffa89..402b82a1a 100644 --- a/Session/Meta/Translations/fi.lproj/Localizable.strings +++ b/Session/Meta/Translations/fi.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/fr.lproj/Localizable.strings b/Session/Meta/Translations/fr.lproj/Localizable.strings index 4a5a86009..080af5ad0 100644 --- a/Session/Meta/Translations/fr.lproj/Localizable.strings +++ b/Session/Meta/Translations/fr.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/hi.lproj/Localizable.strings b/Session/Meta/Translations/hi.lproj/Localizable.strings index 4d806cdd4..3fc0363a6 100644 --- a/Session/Meta/Translations/hi.lproj/Localizable.strings +++ b/Session/Meta/Translations/hi.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/hr.lproj/Localizable.strings b/Session/Meta/Translations/hr.lproj/Localizable.strings index db60adb82..f96c491d4 100644 --- a/Session/Meta/Translations/hr.lproj/Localizable.strings +++ b/Session/Meta/Translations/hr.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/id-ID.lproj/Localizable.strings b/Session/Meta/Translations/id-ID.lproj/Localizable.strings index f4efd1d7c..ed7d282e1 100644 --- a/Session/Meta/Translations/id-ID.lproj/Localizable.strings +++ b/Session/Meta/Translations/id-ID.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/it.lproj/Localizable.strings b/Session/Meta/Translations/it.lproj/Localizable.strings index 0bcbf954c..a6e934940 100644 --- a/Session/Meta/Translations/it.lproj/Localizable.strings +++ b/Session/Meta/Translations/it.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/ja.lproj/Localizable.strings b/Session/Meta/Translations/ja.lproj/Localizable.strings index 19e9f3293..4d86b7896 100644 --- a/Session/Meta/Translations/ja.lproj/Localizable.strings +++ b/Session/Meta/Translations/ja.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/nl.lproj/Localizable.strings b/Session/Meta/Translations/nl.lproj/Localizable.strings index 6742b9a47..b6310b0dd 100644 --- a/Session/Meta/Translations/nl.lproj/Localizable.strings +++ b/Session/Meta/Translations/nl.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/pl.lproj/Localizable.strings b/Session/Meta/Translations/pl.lproj/Localizable.strings index c1993a882..8b74b57b4 100644 --- a/Session/Meta/Translations/pl.lproj/Localizable.strings +++ b/Session/Meta/Translations/pl.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings index 75b127fda..32a538ea3 100644 --- a/Session/Meta/Translations/pt_BR.lproj/Localizable.strings +++ b/Session/Meta/Translations/pt_BR.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/ru.lproj/Localizable.strings b/Session/Meta/Translations/ru.lproj/Localizable.strings index 365fb0202..fea80b230 100644 --- a/Session/Meta/Translations/ru.lproj/Localizable.strings +++ b/Session/Meta/Translations/ru.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/si.lproj/Localizable.strings b/Session/Meta/Translations/si.lproj/Localizable.strings index 85a38ec79..1e294a931 100644 --- a/Session/Meta/Translations/si.lproj/Localizable.strings +++ b/Session/Meta/Translations/si.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/sk.lproj/Localizable.strings b/Session/Meta/Translations/sk.lproj/Localizable.strings index cf855ce15..64d08961e 100644 --- a/Session/Meta/Translations/sk.lproj/Localizable.strings +++ b/Session/Meta/Translations/sk.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/sv.lproj/Localizable.strings b/Session/Meta/Translations/sv.lproj/Localizable.strings index d2d5f6517..b3966c761 100644 --- a/Session/Meta/Translations/sv.lproj/Localizable.strings +++ b/Session/Meta/Translations/sv.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/th.lproj/Localizable.strings b/Session/Meta/Translations/th.lproj/Localizable.strings index e938e2e5f..3ee4bc381 100644 --- a/Session/Meta/Translations/th.lproj/Localizable.strings +++ b/Session/Meta/Translations/th.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings index 69bbfbce0..181870f19 100644 --- a/Session/Meta/Translations/vi-VN.lproj/Localizable.strings +++ b/Session/Meta/Translations/vi-VN.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings index 34d432adf..1c2883282 100644 --- a/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh-Hant.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; diff --git a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings index 07c9da213..0450db608 100644 --- a/Session/Meta/Translations/zh_CN.lproj/Localizable.strings +++ b/Session/Meta/Translations/zh_CN.lproj/Localizable.strings @@ -618,3 +618,4 @@ "context_menu_resync" = "Resync"; "GIPHY_PERMISSION_TITLE" = "Search GIFs?"; "GIPHY_PERMISSION_MESSAGE" = "Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs."; +"message_info_title" = "Message Info"; From 63f49615b5ce83e7599a591aba9adb60f039f69d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Mon, 3 Apr 2023 09:22:31 +0800 Subject: [PATCH 34/44] fix an issue where info action will show when there is no attachments --- Session/Conversations/Context Menu/ContextMenuVC+Action.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift index 55b4fca4a..797a57801 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+Action.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+Action.swift @@ -214,6 +214,8 @@ extension ContextMenuVC { return !currentThreadIsMessageRequest }() + let shouldShowInfo: Bool = (cellViewModel.attachments?.isEmpty == false) + let generatedActions: [Action] = [ (canRetry ? Action.retry(cellViewModel, delegate) : nil), (canReply ? Action.reply(cellViewModel, delegate) : nil), @@ -223,7 +225,7 @@ extension ContextMenuVC { (canDelete ? Action.delete(cellViewModel, delegate) : nil), (canBan ? Action.ban(cellViewModel, delegate) : nil), (canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate) : nil), - Action.info(cellViewModel, delegate), + (shouldShowInfo ? Action.info(cellViewModel, delegate) : nil), ] .appending(contentsOf: (shouldShowEmojiActions ? recentEmojis : []).map { Action.react(cellViewModel, $0, delegate) }) .appending(Action.emojiPlusButton(cellViewModel, delegate)) From 2ff85ebda13bfe920bed5fc7657f18a736a2b3e0 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 5 Apr 2023 14:36:20 +0800 Subject: [PATCH 35/44] fix UI issues --- Podfile.lock | 2 +- .../MediaInfoVC+MediaInfoView.swift | 20 ++++++++++++------- .../Media Viewing & Editing/MediaInfoVC.swift | 6 +++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 0239f9c29..9cd0a2706 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -242,6 +242,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 2bf7639359fecebe56e9757d88f4eb48864652d2 +PODFILE CHECKSUM: 97324ae5888b01db2f2adc4dcc239e2e7d6867f7 COCOAPODS: 1.11.3 diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift index 866bf7bcb..f79809e48 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift @@ -6,9 +6,10 @@ import SessionUtilitiesKit extension MediaInfoVC { final class MediaInfoView: UIView { - private static let cornerRadius: CGFloat = 8 + private static let cornerRadius: CGFloat = 12 private var attachment: Attachment? + private let width: CGFloat = MediaInfoVC.mediaSize - 2 * MediaInfoVC.arrowSize.width // MARK: - UI @@ -80,7 +81,7 @@ extension MediaInfoVC { backgroundView.pin(to: self) let container: UIView = UIView() - container.set(.width, to: 245) + container.set(.width, to: self.width) // File ID let fileIdTitleLabel: UILabel = { @@ -93,6 +94,7 @@ extension MediaInfoVC { }() let fileIdContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileIdTitleLabel, fileIdLabel ]) fileIdContainerStackView.axis = .vertical + fileIdContainerStackView.spacing = 6 container.addSubview(fileIdContainerStackView) fileIdContainerStackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top ], to: container) @@ -107,9 +109,10 @@ extension MediaInfoVC { }() let fileTypeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileTypeTitleLabel, fileTypeLabel ]) fileTypeContainerStackView.axis = .vertical + fileTypeContainerStackView.spacing = 6 container.addSubview(fileTypeContainerStackView) fileTypeContainerStackView.pin(.leading, to: .leading, of: container) - fileTypeContainerStackView.pin(.top, to: .bottom, of: fileIdContainerStackView, withInset: Values.mediumSpacing) + fileTypeContainerStackView.pin(.top, to: .bottom, of: fileIdContainerStackView, withInset: Values.largeSpacing) // File Size let fileSizeTitleLabel: UILabel = { @@ -122,9 +125,10 @@ extension MediaInfoVC { }() let fileSizeContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ fileSizeTitleLabel, fileSizeLabel ]) fileSizeContainerStackView.axis = .vertical + fileSizeContainerStackView.spacing = 6 container.addSubview(fileSizeContainerStackView) fileSizeContainerStackView.pin(.trailing, to: .trailing, of: container) - fileSizeContainerStackView.pin(.top, to: .bottom, of: fileIdContainerStackView, withInset: Values.mediumSpacing) + fileSizeContainerStackView.pin(.top, to: .bottom, of: fileIdContainerStackView, withInset: Values.largeSpacing) fileSizeContainerStackView.set(.width, to: 90) // Resolution @@ -138,9 +142,10 @@ extension MediaInfoVC { }() let resolutionContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ resolutionTitleLabel, resolutionLabel ]) resolutionContainerStackView.axis = .vertical + resolutionContainerStackView.spacing = 6 container.addSubview(resolutionContainerStackView) resolutionContainerStackView.pin(.leading, to: .leading, of: container) - resolutionContainerStackView.pin(.top, to: .bottom, of: fileTypeContainerStackView, withInset: Values.mediumSpacing) + resolutionContainerStackView.pin(.top, to: .bottom, of: fileTypeContainerStackView, withInset: Values.largeSpacing) // Duration let durationTitleLabel: UILabel = { @@ -153,9 +158,10 @@ extension MediaInfoVC { }() let durationContainerStackView: UIStackView = UIStackView(arrangedSubviews: [ durationTitleLabel, durationLabel ]) durationContainerStackView.axis = .vertical + durationContainerStackView.spacing = 6 container.addSubview(durationContainerStackView) durationContainerStackView.pin(.trailing, to: .trailing, of: container) - durationContainerStackView.pin(.top, to: .bottom, of: fileSizeContainerStackView, withInset: Values.mediumSpacing) + durationContainerStackView.pin(.top, to: .bottom, of: fileSizeContainerStackView, withInset: Values.largeSpacing) durationContainerStackView.set(.width, to: 90) container.pin(.bottom, to: .bottom, of: durationContainerStackView) @@ -178,7 +184,7 @@ extension MediaInfoVC { }() durationLabel.text = { guard let duration = attachment.duration else { return "N/A" } - return "\(duration)" + return floor(duration).formatted(format: .hoursMinutesSeconds) }() } } diff --git a/Session/Media Viewing & Editing/MediaInfoVC.swift b/Session/Media Viewing & Editing/MediaInfoVC.swift index bad2c84b9..26711c83b 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC.swift @@ -5,7 +5,7 @@ import SessionUIKit import SessionUtilitiesKit final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { - internal static let mediaSize: CGFloat = 293 + internal static let mediaSize: CGFloat = UIScreen.main.bounds.width - 2 * Values.veryLargeSpacing internal static let arrowSize: CGSize = CGSize(width: 20, height: 30) private let attachments: [Attachment] @@ -110,7 +110,7 @@ final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { mediaInfoView.update(attachment: attachments[0]) mediaCarouselView.addSubview(fullScreenButton) - fullScreenButton.pin(.trailing, to: .trailing, of: mediaCarouselView, withInset: -(Values.smallSpacing + Self.arrowSize.width + Values.largeSpacing)) + fullScreenButton.pin(.trailing, to: .trailing, of: mediaCarouselView, withInset: -(Values.smallSpacing + Values.veryLargeSpacing)) fullScreenButton.pin(.bottom, to: .bottom, of: mediaCarouselView, withInset: -Values.smallSpacing) let stackView: UIStackView = UIStackView(arrangedSubviews: [ mediaCarouselView, mediaInfoView ]) @@ -120,7 +120,7 @@ final class MediaInfoVC: BaseVC, SessionCarouselViewDelegate { self.view.addSubview(stackView) stackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: self.view) - stackView.center(.vertical, in: self.view) + stackView.pin(.top, to: .top, of: self.view, withInset: Values.veryLargeSpacing) } // MARK: - Interaction From 16d4d0128e0e17b8c0d615026c950826c32ea0ac Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 5 Apr 2023 14:57:25 +0800 Subject: [PATCH 36/44] add a new format for video duration --- .../MediaInfoVC+MediaInfoView.swift | 2 +- SessionUtilitiesKit/General/String+Utilities.swift | 9 +++++++++ SessionUtilitiesKit/General/TimeInterval+Utilities.swift | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift index f79809e48..25c0f5774 100644 --- a/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift +++ b/Session/Media Viewing & Editing/MediaInfoVC+MediaInfoView.swift @@ -184,7 +184,7 @@ extension MediaInfoVC { }() durationLabel.text = { guard let duration = attachment.duration else { return "N/A" } - return floor(duration).formatted(format: .hoursMinutesSeconds) + return floor(duration).formatted(format: .videoDuration) }() } } diff --git a/SessionUtilitiesKit/General/String+Utilities.swift b/SessionUtilitiesKit/General/String+Utilities.swift index 39bbbb913..44074f0da 100644 --- a/SessionUtilitiesKit/General/String+Utilities.swift +++ b/SessionUtilitiesKit/General/String+Utilities.swift @@ -84,6 +84,15 @@ public extension String { let secondsPerWeek: TimeInterval = (secondsPerDay * 7) switch format { + case .videoDuration: + let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60)) + let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60)) + let hours: Int = Int(duration / 3600) + + guard hours > 0 else { return String(format: "%02ld:%02ld", minutes, seconds) } + + return String(format: "%ld:%02ld:%02ld", hours, minutes, seconds) + case .hoursMinutesSeconds: let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60)) let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60)) diff --git a/SessionUtilitiesKit/General/TimeInterval+Utilities.swift b/SessionUtilitiesKit/General/TimeInterval+Utilities.swift index 89b3cc596..c0c668374 100644 --- a/SessionUtilitiesKit/General/TimeInterval+Utilities.swift +++ b/SessionUtilitiesKit/General/TimeInterval+Utilities.swift @@ -7,6 +7,7 @@ public extension TimeInterval { case short case long case hoursMinutesSeconds + case videoDuration } func formatted(format: DurationFormat) -> String { From 93ff94186a81987d207bece951d69c65b8a4783b Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 11 Apr 2023 11:01:40 +0800 Subject: [PATCH 37/44] fix an issue when quoting messages with multiple attachments, the quoted attachment doesn't stay the same. --- SessionMessagingKit/Shared Models/MessageViewModel.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 2bc7157fe..e917eab57 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -744,7 +744,12 @@ public extension MessageViewModel { ) AND \(quote[.timestampMs]) = \(interaction[.timestampMs]) ) - LEFT JOIN \(InteractionAttachment.self) ON \(interaction[.id]) = \(interactionAttachment[.interactionId]) + LEFT JOIN \(InteractionAttachment.self) ON \(interactionAttachment[.attachmentId]) = ( + SELECT \(interactionAttachment[.attachmentId]) + FROM \(InteractionAttachment.self) + WHERE \(interaction[.id]) = \(interactionAttachment[.interactionId]) + LIMIT 1 + ) ) AS \(ViewModel.quoteKey) ON \(quote[.interactionId]) = \(interaction[.id]) LEFT JOIN \(Attachment.self) AS \(ViewModel.quoteAttachmentKey) ON \(ViewModel.quoteAttachmentKey).\(attachmentIdColumnLiteral) = \(quote[.attachmentId]) LEFT JOIN \(LinkPreview.self) ON ( From b270133f68546999fc7b92d1e25499978f0757f8 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 11 Apr 2023 13:10:11 +1000 Subject: [PATCH 38/44] Fixed a couple of issues with seed node requests Updated the seed node certificates to use the new 10 year ones Fixed an issue where multiple seed node requests could be triggered at once Increased the version & build numbers --- Session.xcodeproj/project.pbxproj | 72 ++++++------ .../Certificates/public-loki-foundation.crt | 24 ---- .../Certificates/public-loki-foundation.der | Bin 1047 -> 0 bytes Session/Meta/Certificates/seed1-10y.crt | 24 ++++ Session/Meta/Certificates/seed1-10y.der | Bin 0 -> 1041 bytes Session/Meta/Certificates/seed2-10y.crt | 24 ++++ Session/Meta/Certificates/seed2-10y.der | Bin 0 -> 1041 bytes Session/Meta/Certificates/seed3-10y.crt | 24 ++++ Session/Meta/Certificates/seed3-10y.der | Bin 0 -> 1041 bytes Session/Meta/Certificates/storage-seed-1.crt | 25 ---- Session/Meta/Certificates/storage-seed-1.der | Bin 1061 -> 0 bytes Session/Meta/Certificates/storage-seed-3.crt | 25 ---- Session/Meta/Certificates/storage-seed-3.der | Bin 1061 -> 0 bytes Session/Meta/Session-Info.plist | 6 +- SessionSnodeKit/SnodeAPI.swift | 91 +++++++++------ SessionUtilitiesKit/Networking/HTTP.swift | 110 ++++++++++++------ 16 files changed, 237 insertions(+), 188 deletions(-) delete mode 100644 Session/Meta/Certificates/public-loki-foundation.crt delete mode 100644 Session/Meta/Certificates/public-loki-foundation.der create mode 100644 Session/Meta/Certificates/seed1-10y.crt create mode 100644 Session/Meta/Certificates/seed1-10y.der create mode 100644 Session/Meta/Certificates/seed2-10y.crt create mode 100644 Session/Meta/Certificates/seed2-10y.der create mode 100644 Session/Meta/Certificates/seed3-10y.crt create mode 100644 Session/Meta/Certificates/seed3-10y.der delete mode 100644 Session/Meta/Certificates/storage-seed-1.crt delete mode 100644 Session/Meta/Certificates/storage-seed-1.der delete mode 100644 Session/Meta/Certificates/storage-seed-3.crt delete mode 100644 Session/Meta/Certificates/storage-seed-3.der diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 398efc33d..6b0ff02cb 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -182,9 +182,6 @@ B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; }; B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */; }; B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */; }; - B81D25C426157F40004D1FE1 /* storage-seed-3.crt in Resources */ = {isa = PBXBuildFile; fileRef = B81D25B926157F20004D1FE1 /* storage-seed-3.crt */; }; - B81D25C526157F40004D1FE1 /* storage-seed-1.crt in Resources */ = {isa = PBXBuildFile; fileRef = B81D25B726157F20004D1FE1 /* storage-seed-1.crt */; }; - B81D25C626157F40004D1FE1 /* public-loki-foundation.crt in Resources */ = {isa = PBXBuildFile; fileRef = B81D25B826157F20004D1FE1 /* public-loki-foundation.crt */; }; B82149C125D605C6009C0F2A /* InfoBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82149C025D605C6009C0F2A /* InfoBanner.swift */; }; B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D2825C7A4B400488AB4 /* InputView.swift */; }; B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3225C7A8C600488AB4 /* InputViewButton.swift */; }; @@ -428,9 +425,6 @@ C38EF407255B6DF7007E1867 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3E9255B6DF6007E1867 /* Toast.swift */; }; C38EF40B255B6DF7007E1867 /* TappableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3ED255B6DF6007E1867 /* TappableStackView.swift */; }; C38EF48A255B7E3F007E1867 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; }; - C3A01E05261D24C400290BEB /* public-loki-foundation.der in Resources */ = {isa = PBXBuildFile; fileRef = C3A01E02261D24C400290BEB /* public-loki-foundation.der */; }; - C3A01E06261D24C400290BEB /* storage-seed-1.der in Resources */ = {isa = PBXBuildFile; fileRef = C3A01E03261D24C400290BEB /* storage-seed-1.der */; }; - C3A01E07261D24C400290BEB /* storage-seed-3.der in Resources */ = {isa = PBXBuildFile; fileRef = C3A01E04261D24C400290BEB /* storage-seed-3.der */; }; C3A3A171256E1D25004D228D /* SSKReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */; }; C3A71D0B2558989C0043A11F /* MessageWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D0A2558989C0043A11F /* MessageWrapper.swift */; }; C3A71D1E25589AC30043A11F /* WebSocketProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A71D1C25589AC30043A11F /* WebSocketProto.swift */; }; @@ -609,6 +603,12 @@ FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; }; FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; }; + FD29A11D29E4EB71001923B4 /* seed1-10y.der in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11729E4EB71001923B4 /* seed1-10y.der */; }; + FD29A11E29E4EB71001923B4 /* seed2-10y.der in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11829E4EB71001923B4 /* seed2-10y.der */; }; + FD29A11F29E4EB71001923B4 /* seed3-10y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11929E4EB71001923B4 /* seed3-10y.crt */; }; + FD29A12029E4EB71001923B4 /* seed1-10y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11A29E4EB71001923B4 /* seed1-10y.crt */; }; + FD29A12129E4EB71001923B4 /* seed3-10y.der in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11B29E4EB71001923B4 /* seed3-10y.der */; }; + FD29A12229E4EB71001923B4 /* seed2-10y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11C29E4EB71001923B4 /* seed2-10y.crt */; }; FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; @@ -1266,9 +1266,6 @@ B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = ""; }; B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedConversationCell.swift; sourceTree = ""; }; B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerVC.swift; sourceTree = ""; }; - B81D25B726157F20004D1FE1 /* storage-seed-1.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "storage-seed-1.crt"; sourceTree = ""; }; - B81D25B826157F20004D1FE1 /* public-loki-foundation.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "public-loki-foundation.crt"; sourceTree = ""; }; - B81D25B926157F20004D1FE1 /* storage-seed-3.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "storage-seed-3.crt"; sourceTree = ""; }; B82149C025D605C6009C0F2A /* InfoBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoBanner.swift; sourceTree = ""; }; B8269D2825C7A4B400488AB4 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = ""; }; B8269D3225C7A8C600488AB4 /* InputViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputViewButton.swift; sourceTree = ""; }; @@ -1542,9 +1539,6 @@ C396469D2509D3F400B0B9F5 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; C396469E2509D40400B0B9F5 /* vi-VN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "vi-VN"; path = "vi-VN.lproj/Localizable.strings"; sourceTree = ""; }; C396469F2509D41100B0B9F5 /* id-ID */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "id-ID"; path = "id-ID.lproj/Localizable.strings"; sourceTree = ""; }; - C3A01E02261D24C400290BEB /* public-loki-foundation.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "public-loki-foundation.der"; sourceTree = ""; }; - C3A01E03261D24C400290BEB /* storage-seed-1.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "storage-seed-1.der"; sourceTree = ""; }; - C3A01E04261D24C400290BEB /* storage-seed-3.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "storage-seed-3.der"; sourceTree = ""; }; C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKReachabilityManager.swift; sourceTree = ""; }; C3A71D0A2558989C0043A11F /* MessageWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageWrapper.swift; sourceTree = ""; }; C3A71D1C25589AC30043A11F /* WebSocketProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSocketProto.swift; sourceTree = ""; }; @@ -1699,6 +1693,12 @@ FD23EA6028ED0B260058676E /* CombineExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineExtensions.swift; sourceTree = ""; }; FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; + FD29A11729E4EB71001923B4 /* seed1-10y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed1-10y.der"; sourceTree = ""; }; + FD29A11829E4EB71001923B4 /* seed2-10y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed2-10y.der"; sourceTree = ""; }; + FD29A11929E4EB71001923B4 /* seed3-10y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed3-10y.crt"; sourceTree = ""; }; + FD29A11A29E4EB71001923B4 /* seed1-10y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed1-10y.crt"; sourceTree = ""; }; + FD29A11B29E4EB71001923B4 /* seed3-10y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed3-10y.der"; sourceTree = ""; }; + FD29A11C29E4EB71001923B4 /* seed2-10y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed2-10y.crt"; sourceTree = ""; }; FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = ""; }; @@ -2366,12 +2366,12 @@ B81D260326158DF5004D1FE1 /* Certificates */ = { isa = PBXGroup; children = ( - B81D25B826157F20004D1FE1 /* public-loki-foundation.crt */, - C3A01E02261D24C400290BEB /* public-loki-foundation.der */, - B81D25B726157F20004D1FE1 /* storage-seed-1.crt */, - C3A01E03261D24C400290BEB /* storage-seed-1.der */, - B81D25B926157F20004D1FE1 /* storage-seed-3.crt */, - C3A01E04261D24C400290BEB /* storage-seed-3.der */, + FD29A11A29E4EB71001923B4 /* seed1-10y.crt */, + FD29A11729E4EB71001923B4 /* seed1-10y.der */, + FD29A11C29E4EB71001923B4 /* seed2-10y.crt */, + FD29A11829E4EB71001923B4 /* seed2-10y.der */, + FD29A11929E4EB71001923B4 /* seed3-10y.crt */, + FD29A11B29E4EB71001923B4 /* seed3-10y.der */, ); path = Certificates; sourceTree = ""; @@ -4692,15 +4692,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - B81D25C526157F40004D1FE1 /* storage-seed-1.crt in Resources */, - B81D25C426157F40004D1FE1 /* storage-seed-3.crt in Resources */, - B81D25C626157F40004D1FE1 /* public-loki-foundation.crt in Resources */, 4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */, 4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */, + FD29A11D29E4EB71001923B4 /* seed1-10y.der in Resources */, 34CF078A203E6B78005C4D61 /* end_call_tone_cept.caf in Resources */, C3CA3AA2255CDADA00F4C6D4 /* english.txt in Resources */, B6F509971AA53F760068F56A /* Localizable.strings in Resources */, - C3A01E05261D24C400290BEB /* public-loki-foundation.der in Resources */, B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */, 34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */, 7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */, @@ -4710,13 +4707,13 @@ 34C3C78D20409F320000134C /* Opening.m4r in Resources */, C3CA3AB4255CDAE600F4C6D4 /* japanese.txt in Resources */, B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */, + FD29A12129E4EB71001923B4 /* seed3-10y.der in Resources */, 34CF0787203E6B78005C4D61 /* busy_tone_ansi.caf in Resources */, 45A2F005204473A3002E978A /* NewMessage.aifc in Resources */, 45B74A882044AAB600CD42F8 /* aurora.aifc in Resources */, 45B74A742044AAB600CD42F8 /* aurora-quiet.aifc in Resources */, 7B0EFDF4275490EA00FFAAE7 /* ringing.mp3 in Resources */, 45B74A852044AAB600CD42F8 /* bamboo.aifc in Resources */, - C3A01E06261D24C400290BEB /* storage-seed-1.der in Resources */, 45B74A782044AAB600CD42F8 /* bamboo-quiet.aifc in Resources */, 45B74A7B2044AAB600CD42F8 /* chord.aifc in Resources */, 45B74A812044AAB600CD42F8 /* chord-quiet.aifc in Resources */, @@ -4730,10 +4727,12 @@ B8FF8E7425C10FC3004D1F22 /* GeoLite2-Country-Locations-English in Resources */, B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */, 45B74A872044AAB600CD42F8 /* complete-quiet.aifc in Resources */, + FD29A11F29E4EB71001923B4 /* seed3-10y.crt in Resources */, 45B74A772044AAB600CD42F8 /* hello.aifc in Resources */, 45B74A7C2044AAB600CD42F8 /* hello-quiet.aifc in Resources */, 7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */, 45B74A792044AAB600CD42F8 /* input.aifc in Resources */, + FD29A12029E4EB71001923B4 /* seed1-10y.crt in Resources */, C3CA3ABE255CDB0D00F4C6D4 /* portuguese.txt in Resources */, 45B74A8C2044AAB600CD42F8 /* input-quiet.aifc in Resources */, 45B74A7A2044AAB600CD42F8 /* keys.aifc in Resources */, @@ -4745,7 +4744,8 @@ 45B74A822044AAB600CD42F8 /* pulse.aifc in Resources */, C3CA3AC8255CDB2900F4C6D4 /* spanish.txt in Resources */, B8FF8E6225C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 in Resources */, - C3A01E07261D24C400290BEB /* storage-seed-3.der in Resources */, + FD29A11E29E4EB71001923B4 /* seed2-10y.der in Resources */, + FD29A12229E4EB71001923B4 /* seed2-10y.crt in Resources */, 45B74A802044AAB600CD42F8 /* pulse-quiet.aifc in Resources */, 45B74A8B2044AAB600CD42F8 /* synth.aifc in Resources */, 45B74A752044AAB600CD42F8 /* synth-quiet.aifc in Resources */, @@ -6052,7 +6052,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 399; + CURRENT_PROJECT_VERSION = 400; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6077,7 +6077,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.2.11; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6125,7 +6125,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 399; + CURRENT_PROJECT_VERSION = 400; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6155,7 +6155,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.2.11; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6191,7 +6191,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 399; + CURRENT_PROJECT_VERSION = 400; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6214,7 +6214,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.2.11; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6265,7 +6265,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 399; + CURRENT_PROJECT_VERSION = 400; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6293,7 +6293,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.2.11; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -7193,7 +7193,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 399; + CURRENT_PROJECT_VERSION = 400; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7232,7 +7232,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.2.11; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -7265,7 +7265,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 399; + CURRENT_PROJECT_VERSION = 400; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7304,7 +7304,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.2.10; + MARKETING_VERSION = 2.2.11; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; diff --git a/Session/Meta/Certificates/public-loki-foundation.crt b/Session/Meta/Certificates/public-loki-foundation.crt deleted file mode 100644 index 344a05543..000000000 --- a/Session/Meta/Certificates/public-loki-foundation.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEEzCCAvugAwIBAgIUY9RQqbjhsQEkdeSgV9L0os9xZ7AwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN -ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x -HzAdBgNVBAMMFnB1YmxpYy5sb2tpLmZvdW5kYXRpb24wHhcNMjEwNDA3MDExMDMx -WhcNMjMwNDA3MDExMDMxWjB8MQswCQYDVQQGEwJBVTERMA8GA1UECAwIVmljdG9y -aWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2YWN5IFRl -Y2ggRm91bmRhdGlvbjEfMB0GA1UEAwwWcHVibGljLmxva2kuZm91bmRhdGlvbjCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5dBJSIR5+VNNUxUOo6FG0e -RmZteRqBt50KXGbOi2A23a6sa57pLFh9Yw3hmlWV+QCL7ipG1X4IC55OStgoesf+ -K65VwEMP6Mtq0sSJS3R5TiuV2ZSRdSZTVjUyRXVe5T4Aw6wXVTAbc/HsyS780tDh -GclfDHhonPhZpmTAnSbfMOS+BfOnBNvDxdto0kVh6k5nrGlkT4ECloulHTQF2lwJ -0D6IOtv9AJplPdg6s2c4dY7durOdvr3NNVfvn5PTeRvbEPqzZur4WUUKIPNGu6mY -PxImqd4eUsL0Vod4aAsTIx4YMmCTi0m9W6zJI6nXcK/6a+iiA3+NTNMzEA9gQhEC -AwEAAaOBjDCBiTAdBgNVHQ4EFgQU/zahokxLvvFUpbnM6z/pwS1KsvwwHwYDVR0j -BBgwFoAU/zahokxLvvFUpbnM6z/pwS1KsvwwDwYDVR0TAQH/BAUwAwEB/zAhBgNV -HREEGjAYghZwdWJsaWMubG9raS5mb3VuZGF0aW9uMBMGA1UdJQQMMAoGCCsGAQUF -BwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBql+JvoqpaYrFFTOuDn08U+pdcd3GM7tbI -zRH5LU+YnIpp9aRheek+2COW8DXsIy/kUngETCMLmX6ZaUj/WdHnTDkB0KTgxSHv -ad3ZznKPKZ26qJOklr+0ZWj4J3jHbisSzql6mqq7R2Kp4ESwzwqxvkbykM5RUnmz -Go/3Ol7bpN/ZVwwEkGfD/5rRHf57E/gZn2pBO+zotlQgr7HKRsIXQ2hIXVQqWmPQ -lvfIwrwAZlfES7BARFnHOpyVQxV8uNcV5K5eXzuVFjHBqvq+BtyGhWkP9yKJCHS9 -OUXxch0rzRsH2C/kRVVhEk0pI3qlFiRC8pCJs98SNE9l69EQtG7I ------END CERTIFICATE----- diff --git a/Session/Meta/Certificates/public-loki-foundation.der b/Session/Meta/Certificates/public-loki-foundation.der deleted file mode 100644 index 698980d78d9fa965243b5f6ded8748ba1a93b67e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1047 zcmXqLVi7iIV*0&+nTe5!NhJA7z{(vDH!`Y}K3Ncc>C2+?h3Oj%c-c6$+C196^D;7W zvoaXe7;+nMvN4CUun99ch8hYQ@PjxUJRD(}$tC$knTdu%1_B@ z6C)$TxmcDd9q#j|np`yucx5G$E9aJ$TPfAJeJ)o_+PUrov%BloWY2r46H%MY`*2q1 z)SnF9@3h>m)^Tvp^Ygl)QFZ*E_PWpm&ipS0MIkr#Kk!C|IGuBCBL z?HCTP5f3$xF8=uDq~4!P7amHUjOVGynDZlYS;~RAYWEGE>|_1BoaOf6qqj3IxhB5y zOJ9?j;@`+Lt$V4g3G1yG&I@)OR=58$%u2PrVYNBkqO|YsuFZ4z?LBK6{(k=C%azi% z1%7Q#d-WsIl}q8X+wPS!?1j`;-jfSD^d+plB7<94Sx&+zVREw{~2?- zGQTcKtbA#ALwVW<(>KccPl75~e3ZFo*3HcH_#b)kxsN5|g(VM;D!$LWd-GgTzvkRs zD<&_Qwtq`%#t-$1<9XUb=T=tDTD99fY2^c#4d=Nw?sNMz;ap%)!MHA#l>4s6*D}z>eoKDJ8i9*{NI&2IZF0gx_&H@)jliDenbC> gYiOd7ucmUE#!t8_0?C8W|gy7?>Iu8krcIMv3zpBXbATZB2|y$U()(%D~*j$j@NV z#K^_e#K_2So6~Kg>@1##FS;$3Jjz-h;lph6gl+K$`SO2LSFCyQK+3mjYox;sStE9D z-{+CSje)hV^YS`x>|QY`f$7vOr4>?#RM)-DbG{xj-EB7W?SH5E=I)Y>lIPd(j_b79 zYh1Qopz2eUzj%q^gXhQFr`JTh-GAuIk$2jhg6==+8hj5G{k8~YJ=)!;>|edI&7;_2 z+n(}vrid4>*G1O3PI#+pwBm2ZqFnWtB_>?2vmGM89P2s2=<qD;(;42+8#y9^rJ4P=3lEX&6t#v)=G!*NVYrXx6aS>ja| zrI@FaZYh`>$b+PnStJa^8n7z>B{^APM#ldvtOm?L3Zg?0q@xLY$`J&IQyEni~Ax3DeK{F173P(xd2 zPuZo{{->&LZDx7Pv8Yk|>l4mN=2{KQ_m!Ie__9qlbIr?-Y>nGLh@R3BILsCGX!l(o z_l}bLjk|Q(m^#kaPQ7m9JZ)!$(z@^ei(hDm9-YoR_4|(Z+pDH5ymWByGw1oHS3S17 zRi#YyO@CO*t^DNLt@`8nCwO1d>AkyWT%Xl-%GSvGpirIxFB_*;n@8JsUPeZ4 zRtAGALv903Hs(+kHen{mP(wijeh`O)ha)UAxg@_RGtp4UKma7f&co@Onv;}YT9lV+ zsA`}L664~L@vlhDQwS)^EK5wTR0v5;&QNg6FU?CyEXmBzGn6%u1}S6a5h+ehO)=6- zPc11E#!t8_0?C8W|gy7?>Iu8X1`wMTzqoBXbATZB2|y$U()(%D~*j$j@NV z#K^_e#K_37*Ut9d?NY(?BE|Ll;^)WLZPES3;q|-l{Xgy|yOa`f;VYGqw@x+Pu2>;m zeLQxjMdG{4w5gph5AI&N$MTH$uZv6TN*QNNWSfiiOE`O+#8Vk zHSNiPxsE3k_wM2QaQMsI%-P`$cRQ{z98^xMo7f|y_LS?>T8VpKi$b1Q=I=4;+EMW) z_}it9DX;&&d;KI(cdHraq;n^JJg>;kU!@!?`+95cuE&;tzdv06x$3ZR#FI^+a4dFcj|oK8$U?=Ql;SAU(_ zq8}4>-#y6U%H+dh{JK^`@vAKpGb01z;>Iq6#&!c)U?j`(v52vV@RhAeooe{_=g+pj z^aRC%)L*{*E(Y=-X=N4(1F;6|3P4FtR+y3TKMSh?GmwJl5CrLH!k%)3K{`}fcnrAM zIJDUqSy|bc8Ihw4n6iP<#mJB{EA`*3uGo1p7r3YWT*r3hJ!|3=v$nU7oZMLTSGlR# z?*0`M{(jquqDeAlY$lr=UuV=zYiys?=f1Q_i&hKrK_8Px+m%9)0x?h`Zs z$F%R!?p>!GpSM2YpTF1 literal 0 HcmV?d00001 diff --git a/Session/Meta/Certificates/seed3-10y.crt b/Session/Meta/Certificates/seed3-10y.crt new file mode 100644 index 000000000..6939129f8 --- /dev/null +++ b/Session/Meta/Certificates/seed3-10y.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDTCCAvWgAwIBAgIUTz5rHKUe+VA9IM6vY6QACc0ORFkwDQYJKoZIhvcNAQEL +BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN +ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x +HTAbBgNVBAMMFHNlZWQzLmdldHNlc3Npb24ub3JnMB4XDTIzMDQwNTAxMjYzMVoX +DTMzMDQwNTAxMjYzMVowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh +MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo +IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQzLmdldHNlc3Npb24ub3JnMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6FgxIk9KmYISL5fk7BLaGAW6lBx8 +b4VL3DjlyrFMz7ZhSbcUcavWyyYB+iJxBRhfQGJ7vbwJZ1AwVJisjDFdiLcWzTF8 +gzZ7LVXH8qlVnqcx0gksrWYFnG3Y2WJrxEBFdD29lP7LVN3xLQdplMitOciqg5jN +oRjtwGo+wzaMW6WNPzgTvxLzPce9Rl3oN4tSK7qlA9VtsyHwOWBMcogv9LC9IUFZ +2yu0RdcxPdlwLwywYtSRt/W87KbAWTcYY1DfN2VA68p9Cip7/dPOokRduMh1peux +swmIybpC/wz/Ql6J6scSOjDUp/2UsIdYIvyP/Dibi4nHRmD+oz9kb+J3AQIDAQAB +o4GKMIGHMB0GA1UdDgQWBBSQAFetDPIzVg9rfgOI7bfaeEHd8TAfBgNVHSMEGDAW +gBSQAFetDPIzVg9rfgOI7bfaeEHd8TAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY +MBaCFHNlZWQzLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G +CSqGSIb3DQEBCwUAA4IBAQCiBNdbKNSHyCZJKvC/V+pHy9E/igwvih2GQ5bNZJFA +daOiKBgaADxaxB4lhtzasr2LdgZdLrn0oONw+wYaui9Z12Yfdr9oWuOgktn8HKLY +oKkJc5EcMYFsd00FnnFcO2U8lQoL6PB/tdcEmpOfqtvShpNhp8SbadSNiqlttvtV +1dqvqSBiRdQm1kz2b8hA6GR6SPzSKlSuwI0J+ZcXEi232EJFbgJ3ESHFVHrhUZro +8A16/WDvZOMWCjOqJsFBw15WzosW9kyNwBtZinXVO3LW/7tVl08PDcarpH4IWjd0 +LDpU7zGjcD/A19tfdfMFTOmETuq40I8xxtlR2NENFOAL +-----END CERTIFICATE----- diff --git a/Session/Meta/Certificates/seed3-10y.der b/Session/Meta/Certificates/seed3-10y.der new file mode 100644 index 0000000000000000000000000000000000000000..0a47fb4a198856595bd0000a741acdeee971fca3 GIT binary patch literal 1041 zcmXqLV&OGtV*0v(nTe5!NyOhSTV|=;&j4G6bL*3rFmRsbbBQ$IW#iOp^Jx3d%gD&h z%3x4s$Zf#M#vIDRCd}j*YA9&H58`m}aD-(hm*f{^CK?JE2!Mpxc{qJjbCU8)i}F$p zRSlFuVq82j{uQZt3IRo#Wr@j^3L&Y<847OsrFkidC7JnohO!3IAZ5%vBE_kxDaLx~ zsU^j!#l=7=z5Jqd137VCBVz*-15*P-BQs;eC~;n6WbS~vt%*?yIj9&}8JL?G`56qF z7`d357#SH}L>Mahd(CVT(x3k1jnFL#)?HI%YVupX?^rxNwbAGNwnWeEB8983omOM~ zrBui&5$}*xy>}01dVoR5j5R%mu^rpR&KlM9+|7y~EE9Z+I`qG6*sJImd0Tsn>vcohn+5tj8sWpHKr-zgi~Bb5I1 z|FM|e-Fe(C;ooBWl>A5Kj7-do42+8#y9^rJ4P=3lEX&6t#v(F-A$%>*C*v^w>^kO- zx7%-3INtqeAPItVE@O+;+xca|bG8JnKgO-s@s>^CMPaGGCT81Z`aD)ZNEdW-dev> zA<6ZM+BKhV`6nD+q*QtQxug}c?m#c+&*|bqy4!C!x#lsI3o0HBsd^YV>%|A&s=o>E zQyz|D3lH=o`E(ve-ISFMY#{ofrr-JhTL*y<&998u;aI#wa?4Hp;K fAGm%yzVtJz&&w9SS355B8y>qEc;h0k$OCQw-DH7= literal 0 HcmV?d00001 diff --git a/Session/Meta/Certificates/storage-seed-1.crt b/Session/Meta/Certificates/storage-seed-1.crt deleted file mode 100644 index 7360d6fca..000000000 --- a/Session/Meta/Certificates/storage-seed-1.crt +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEITCCAwmgAwIBAgIUJsox1ZQPK/6iDsCC+MUJfNAlFuYwDQYJKoZIhvcNAQEL -BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ -TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u -MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQxLmxva2kubmV0d29yazAeFw0yMTA0MDcw -MTE5MjZaFw0yMzA0MDcwMTE5MjZaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI -VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2 -YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMS5sb2tp -Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtWH3Rz8Dd -kEmM7tcBWHrJ/G8drr/+qidboEVYzxpyRjszaDxKXVhx4eBBsAD5RuCWuTuZmM8k -TKEDLtf8xfb5SQ7YNX+346s9NXS5Poy4CIPASiW/QWXgIHFbVdv2hC+cKOP61OLM -OGnOxfig6tQyd6EaCkedpY1DvSa2lPnQSOwC/jXCx6Vboc0zTY5R2bHtNc9hjIFP -F4VClLAQSh2F4R1V9MH5KZMW+CCP6oaJY658W9JYXYRwlLrL2EFOVxHgcxq/6+fw -+axXK9OXJrGZjuA+hiz+L/uAOtE4WuxrSeuNMHSrMtM9QqVn4bBuMJ21mAzfNoMP -OIwgMT9DwUjVAgMBAAGjgZAwgY0wHQYDVR0OBBYEFOubJp9SoXIw+ONiWgkOaW8K -zI/TMB8GA1UdIwQYMBaAFOubJp9SoXIw+ONiWgkOaW8KzI/TMA8GA1UdEwEB/wQF -MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMS5sb2tpLm5ldHdvcmswEwYD -VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAIiHNhNrjYvwXVWs -gacx8T/dpqpu9GE3L17LotgQr4R+IYHpNtcmwOTdtWWFfUTr75OCs+c3DqgRKEoj -lnULOsVcalpAGIvW15/fmZWOf66Dpa4+ljDmAc3SOQiD0gGNtqblgI5zG1HF38QP -hjYRhCZ5CVeGOLucvQ8tVVwQvArPFIkBr0jH9jHVgRWEI2MeI3FsU2H93D4TfGln -N4SmmCfYBqygaaZBWkJEt0bYhn8uGHdU9UY9L2FPtfHVKkmFgO7cASGlvXS7B/TT -/8IgbtM3O8mZc2asmdQhGwoAKz93ryyCd8X2UZJg/IwCSCayOlYZWY2fR4OPQmmV -gxJsm+g= ------END CERTIFICATE----- diff --git a/Session/Meta/Certificates/storage-seed-1.der b/Session/Meta/Certificates/storage-seed-1.der deleted file mode 100644 index fac2672f9279587b77bf59e270bedbeb11d2caac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1061 zcmXqLVo@|`V&+`H%*4pVB%*f8@ahzP?SG5-4mABZ%2{(kRqUAoFB_*;n@8JsUPeZ4 zRtAH{219NGPB!LH7B*of$52B-1AY*PgNGw5Gr1(cC^OMe$Up!j#LmO%o0^l9Us{xx zYN%?U3=-qwk@2rc%~J>{$}CGvu2cv~P0mno%P-AKNi50C&ofjuPy{Js=8-A}nw6NI zs#lzvnqsJzlb@ZbmzP>no?n!0AScdiWN2VwU~XV&XlY~?1>_ndaR-AFn;4al1CEiE zfw_s1pTVGsk&CH`k&$6-MD4}%2kuVr?0I*cF{0|^pM2SM`~R&{k6z#!abBv(&DuD_ z#w#|W@Zkf;4Gcfs9!%S5J#)r+6`zI7de{FP{r1z7?}lmp_Q$JjO-pv#_3YqiKH#Og z-!b)pLSc01?Qbpmb2J|Ry7K6ZMdrDqKNh^YVpP6RipzcO(q8AiYTKs#yx{SM>7VJL z<4dC#o;CLE3%t4Ut?Bv1o<@K1R;MW&1iWNhAIgS)Irvj^ve*xW{#R|C$?IyOFGa+* z6inH5`i7%lxZs0gsr|2?fB3m3T>J8LwT(0T9@w?%{L}y4V0FP@!i>t2JB)kc?X zotCCQ+>mE5ck2wE`)1Aj7Cj1v_Ra@At}-z*GB7S~oM6z{Yak1ZaaleVF&2^6v(@GY zEi5wl@i-}plP@!$>rDS;19_0NGK++PSOazip!6pz%*gnkh1Gx=NExVt1O!>+3}l+{ zB_m;wGF28H11>fWZ8k<$R(57a+<^j^z*lsX0mcrA2wE zhN=e2ATcf;8UKpZJcWRw%(BGfN`;WrnWdgC{$l#(gj^{96zeuO4#k`klG9LCZL+ zp5DIsU{;yw>TS#i{C}*{y{i3HX@#Y*%%N$$!B47+H+7%h6&k`|TC!Yg=P#)n3rr%8 zov{65{o*&H%XGG@=Ty(!S`fGTh3JBVY-Ldy%DcHw`KI(f+OKE1QE&l^MCkJAb?n&> z--rL1o-;we6es`KS__g7tZ4_N_Zii~Ff diff --git a/Session/Meta/Session-Info.plist b/Session/Meta/Session-Info.plist index e186b98c9..704f7b3f3 100644 --- a/Session/Meta/Session-Info.plist +++ b/Session/Meta/Session-Info.plist @@ -66,17 +66,17 @@ NSExceptionDomains - public.loki.foundation + seed1.getsession.org NSExceptionRequiresForwardSecrecy - storage.seed1.loki.network + seed2.getsession.org NSExceptionRequiresForwardSecrecy - storage.seed3.loki.network + seed3.getsession.org NSExceptionRequiresForwardSecrecy diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index eff7c6632..1fb102ea2 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -47,7 +47,14 @@ public final class SnodeAPI { private static let maxRetryCount: UInt = 8 private static let minSwarmSnodeCount = 3 - private static let seedNodePool: Set = Features.useTestnet ? [ "http://public.loki.foundation:38157" ] : [ "https://storage.seed1.loki.network:4433", "https://storage.seed3.loki.network:4433", "https://public.loki.foundation:4433" ] + private static let seedNodePool: Set = (Features.useTestnet ? + [ "http://public.loki.foundation:38157" ] : + [ + "https://seed1.getsession.org:4443", + "https://seed2.getsession.org:4443", + "https://seed3.getsession.org:4443" + ] + ) private static let snodeFailureThreshold = 3 private static let targetSwarmSnodeCount = 2 private static let minSnodePoolCount = 12 @@ -316,46 +323,56 @@ public final class SnodeAPI { if let getSnodePoolPromise = getSnodePoolPromise.wrappedValue { return getSnodePoolPromise } - let promise: Promise> - if snodePool.count < minSnodePoolCount { - promise = getSnodePoolFromSeedNode() - } - else { - promise = getSnodePoolFromSnode().recover2 { _ in - getSnodePoolFromSeedNode() + return getSnodePoolPromise.mutate { result in + /// It was possible for multiple threads to call this at the same time resulting in duplicate promises getting created, while + /// this should no longer be possible (as the `wrappedValue` should now properly be blocked) this is a sanity check + /// to make sure we don't create an additional promise when one already exists + if let previouslyBlockedPromise: Promise> = result { + return previouslyBlockedPromise } - } - - getSnodePoolPromise.mutate { $0 = promise } - promise.map2 { snodePool -> Set in - guard !snodePool.isEmpty else { throw SnodeAPIError.snodePoolUpdatingFailed } - - return snodePool - } - - promise.then2 { snodePool -> Promise> in - let (promise, seal) = Promise>.pending() - - Storage.shared.writeAsync( - updates: { db in - db[.lastSnodePoolRefreshDate] = now - setSnodePool(to: snodePool, db: db) - }, - completion: { _, _ in - seal.fulfill(snodePool) + + let promise: Promise> + + if snodePool.count < minSnodePoolCount { + promise = getSnodePoolFromSeedNode() + } + else { + promise = getSnodePoolFromSnode().recover2 { _ in + getSnodePoolFromSeedNode() } - ) - + } + + promise.map2 { snodePool -> Set in + guard !snodePool.isEmpty else { throw SnodeAPIError.snodePoolUpdatingFailed } + + return snodePool + } + + promise.then2 { snodePool -> Promise> in + let (promise, seal) = Promise>.pending() + + Storage.shared.writeAsync( + updates: { db in + db[.lastSnodePoolRefreshDate] = now + setSnodePool(to: snodePool, db: db) + }, + completion: { _, _ in + seal.fulfill(snodePool) + } + ) + + return promise + } + promise.done2 { _ in + getSnodePoolPromise.mutate { $0 = nil } + } + promise.catch2 { _ in + getSnodePoolPromise.mutate { $0 = nil } + } + + result = promise return promise } - promise.done2 { _ in - getSnodePoolPromise.mutate { $0 = nil } - } - promise.catch2 { _ in - getSnodePoolPromise.mutate { $0 = nil } - } - - return promise } public static func getSessionID(for onsName: String) -> Promise { diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index cbe2ec9de..34494fb1a 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -9,19 +9,19 @@ public enum HTTP { // MARK: Certificates private static let storageSeed1Cert: SecCertificate = { - let path = Bundle.main.path(forResource: "storage-seed-1", ofType: "der")! + let path = Bundle.main.path(forResource: "seed1-10y", ofType: "der")! let data = try! Data(contentsOf: URL(fileURLWithPath: path)) return SecCertificateCreateWithData(nil, data as CFData)! }() - + + private static let storageSeed2Cert: SecCertificate = { + let path = Bundle.main.path(forResource: "seed2-10y", ofType: "der")! + let data = try! Data(contentsOf: URL(fileURLWithPath: path)) + return SecCertificateCreateWithData(nil, data as CFData)! + }() + private static let storageSeed3Cert: SecCertificate = { - let path = Bundle.main.path(forResource: "storage-seed-3", ofType: "der")! - let data = try! Data(contentsOf: URL(fileURLWithPath: path)) - return SecCertificateCreateWithData(nil, data as CFData)! - }() - - private static let publicLokiFoundationCert: SecCertificate = { - let path = Bundle.main.path(forResource: "public-loki-foundation", ofType: "der")! + let path = Bundle.main.path(forResource: "seed3-10y", ofType: "der")! let data = try! Data(contentsOf: URL(fileURLWithPath: path)) return SecCertificateCreateWithData(nil, data as CFData)! }() @@ -37,41 +37,75 @@ public enum HTTP { return completionHandler(.cancelAuthenticationChallenge, nil) } // Mark the seed node certificates as trusted - let certificates = [ storageSeed1Cert, storageSeed3Cert, publicLokiFoundationCert ] + let certificates = [ storageSeed1Cert, storageSeed2Cert, storageSeed3Cert ] guard SecTrustSetAnchorCertificates(trust, certificates as CFArray) == errSecSuccess else { + SNLog("Failed to set seed node certificates.") return completionHandler(.cancelAuthenticationChallenge, nil) } - // We want to make sure that the pinned certification was valid during it's validity - // period (which has now expired) so set the date to validate against to be within the - // valid period - let dateFormatter: DateFormatter = DateFormatter() - dateFormatter.dateFormat = "dd/MM/yyyy HH:mm:ss" - - if let validDate: Date = dateFormatter.date(from: "01/01/2022 12:00:00") { - if SecTrustSetVerifyDate(trust, validDate as CFDate) != errSecSuccess { - SNLog("Unable to set date for seed node certificate validation.") - } - } - else { - SNLog("Unable to set date for seed node certificate validation.") - } - // Check that the presented certificate is one of the seed node certificates - var result: SecTrustResultType = .invalid + var error: CFError? + guard SecTrustEvaluateWithError(trust, &error) else { + // Extract the result for further processing (since we are defaulting to `invalid` we + // don't care if extracting the result type fails) + var result: SecTrustResultType = .invalid + _ = SecTrustGetTrustResult(trust, &result) + + switch result { + case .proceed, .unspecified: + /// Unspecified indicates that evaluation reached an (implicitly trusted) anchor certificate without any evaluation + /// failures, but never encountered any explicitly stated user-trust preference. This is the most common return + /// value. The Keychain Access utility refers to this value as the "Use System Policy," which is the default user setting. + return completionHandler(.useCredential, URLCredential(trust: trust)) + + case .recoverableTrustFailure: + /// A recoverable failure generally suggests that the certificate was mostly valid but something minor didn't line up, + /// iOS has a specific rule which rejects certificates which have a lifetime over 825 days which we don't really care + /// about so if we end up with a single issue which is `OtherTrustValidityPeriod` then we can just allow + /// the request to continue + guard + let validationResult: [String: Any] = SecTrustCopyResult(trust) as? [String: Any], + let details: [String: Any] = (validationResult["TrustResultDetails"] as? [[String: Any]])? + .reduce(into: [:], { result, next in next.forEach { result[$0.key] = $0.value } }), + let otherTrustValidityPeriod: Int = details["OtherTrustValidityPeriod"] as? Int, + details.count == 1, + otherTrustValidityPeriod == 0, + let exceptions: CFData = SecTrustCopyExceptions(trust), + SecTrustSetExceptions(trust, exceptions) + else { + let reason: String = { + guard + let validationResult: [String: Any] = SecTrustCopyResult(trust) as? [String: Any], + let details: [String: Any] = (validationResult["TrustResultDetails"] as? [[String: Any]])? + .reduce(into: [:], { result, next in next.forEach { result[$0.key] = $0.value } }) + else { return "Unknown" } + + return "\(details)" + }() + + SNLog("Failed to handle a recoverable seed certificate trust failure: \(reason)") + return completionHandler(.cancelAuthenticationChallenge, nil) + } + + /// Now that the `trust` has been updated with the exceptions it can ignore we need to try to re-evaluate it + /// to ensure it is now seen as valid + var error2: CFError? = nil + guard SecTrustEvaluateWithError(trust, &error2) else { + SNLog("Seed certificate reevaluation failed due to error: \(String(describing: error2))") + return completionHandler(.cancelAuthenticationChallenge, nil) + } + + /// If the reevaluation succeeded then try to use the credential + /// + /// **Note:** It is still possible for the OS to reject the request (which seems to be happening with an expired + /// certificate) but it _does_ seem to work fine with the 10 year certificate + return completionHandler(.useCredential, URLCredential(trust: trust)) + + default: return completionHandler(.cancelAuthenticationChallenge, nil) + } + } - guard SecTrustEvaluate(trust, &result) == errSecSuccess else { - return completionHandler(.cancelAuthenticationChallenge, nil) - } - switch result { - case .proceed, .unspecified: - // Unspecified indicates that evaluation reached an (implicitly trusted) anchor certificate without - // any evaluation failures, but never encountered any explicitly stated user-trust preference. This - // is the most common return value. The Keychain Access utility refers to this value as the "Use System - // Policy," which is the default user setting. - return completionHandler(.useCredential, URLCredential(trust: trust)) - default: return completionHandler(.cancelAuthenticationChallenge, nil) - } + return completionHandler(.useCredential, URLCredential(trust: trust)) } } From 70ce967df6ce20566969a1e2c83f420405c51519 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 12 Apr 2023 16:50:35 +1000 Subject: [PATCH 39/44] Made a few optimisations and fixes Made a couple of DB query optimisations for the Home and Conversation screens Removed some compiler-complex global generic functions Increased the timeout for file uploads Fixed a few import issues Fixed an issue preventing calls on the simulator from working (disable CallKit on the simulator) Fixed an issue where opening a conversation with a draft would result in a typing indicator notification being sent (if enabled) Fixed a truncation issue on the CallVC --- Session/Calls/CallVC.swift | 5 +- .../ConversationVC+Interaction.swift | 4 + Session/Conversations/ConversationVC.swift | 14 +- .../Settings/OWSMessageTimerView.m | 1 + Session/Utilities/IP2Country.swift | 2 +- .../Database/Models/Attachment.swift | 6 +- .../Database/Models/Interaction.swift | 18 +- .../File Server/FileServerAPI.swift | 25 ++- .../Jobs/Types/GarbageCollectionJob.swift | 2 +- .../Open Groups/OpenGroupAPI.swift | 4 +- .../Open Groups/OpenGroupManager.swift | 6 +- .../Sending & Receiving/MessageReceiver.swift | 14 +- .../Shared Models/MessageViewModel.swift | 110 ++++++----- .../SessionThreadViewModel.swift | 179 ++++++++++-------- .../Utilities/Preferences.swift | 7 + SessionSnodeKit/SnodeAPI.swift | 6 +- SessionUIKit/Utilities/UIView+Utilities.swift | 4 +- SessionUtilitiesKit/General/General.swift | 11 -- SessionUtilitiesKit/General/UIView+OWS.h | 1 - SessionUtilitiesKit/General/UIView+OWS.m | 1 + .../AttachmentTextToolbar.swift | 1 + 21 files changed, 228 insertions(+), 193 deletions(-) diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index f98901d45..44dab72c8 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -413,7 +413,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate { if shouldRestartCamera { cameraManager.prepare() } - touch(call.videoCapturer) + _ = call.videoCapturer // Force the lazy var to instantiate titleLabel.text = self.call.contactName AppEnvironment.shared.callManager.startCall(call) { [weak self] error in DispatchQueue.main.async { @@ -468,7 +468,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate { view.addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.center(.vertical, in: minimizeButton) - titleLabel.center(.horizontal, in: view) + titleLabel.pin(.leading, to: .leading, of: view, withInset: Values.largeSpacing) + titleLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing) // Response Panel view.addSubview(responsePanel) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index a266e5b83..b6b802eb9 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -661,6 +661,10 @@ extension ConversationVC: } func inputTextViewDidChangeContent(_ inputTextView: InputTextView) { + // Note: If there is a 'draft' message then we don't want it to trigger the typing indicator to + // appear (as that is not expected/correct behaviour) + guard !viewIsAppearing else { return } + let newText: String = (inputTextView.text ?? "") if !newText.isEmpty { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index f4e2adf2a..746213a43 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -441,11 +441,6 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - // Flag that the initial layout has been completed (the flag blocks and unblocks a number - // of different behaviours) - didFinishInitialLayout = true - viewIsAppearing = false - if delayFirstResponder || isShowingSearchUI { delayFirstResponder = false @@ -457,7 +452,12 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl } } - recoverInputView() + recoverInputView { [weak self] in + // Flag that the initial layout has been completed (the flag blocks and unblocks a number + // of different behaviours) + self?.didFinishInitialLayout = true + self?.viewIsAppearing = false + } } override func viewWillDisappear(_ animated: Bool) { @@ -1261,7 +1261,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl self.blockedBanner.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: self.view) } - func recoverInputView() { + func recoverInputView(completion: (() -> ())? = nil) { // This is a workaround for an issue where the textview is not scrollable // after the app goes into background and goes back in foreground. DispatchQueue.main.async { diff --git a/Session/Conversations/Settings/OWSMessageTimerView.m b/Session/Conversations/Settings/OWSMessageTimerView.m index bfe57d7e3..ad2a924cf 100644 --- a/Session/Conversations/Settings/OWSMessageTimerView.m +++ b/Session/Conversations/Settings/OWSMessageTimerView.m @@ -6,6 +6,7 @@ #import "OWSMath.h" #import "UIView+OWS.h" #import +#import #import #import #import diff --git a/Session/Utilities/IP2Country.swift b/Session/Utilities/IP2Country.swift index a1f13ebab..ca24549b6 100644 --- a/Session/Utilities/IP2Country.swift +++ b/Session/Utilities/IP2Country.swift @@ -40,7 +40,7 @@ final class IP2Country { private func cacheCountry(for ip: String) -> String { if let result = countryNamesCache[ip] { return result } let ipAsInt = IPv4.toInt(ip) - guard let ipv4TableIndex = given(ipv4Table["network"]!.firstIndex(where: { $0 > ipAsInt }), { $0 - 1 }) else { return "Unknown Country" } // Relies on the array being sorted + guard let ipv4TableIndex = ipv4Table["network"]!.firstIndex(where: { $0 > ipAsInt }).map({ $0 - 1 }) else { return "Unknown Country" } // Relies on the array being sorted let countryID = ipv4Table["registered_country_geoname_id"]![ipv4TableIndex] guard let countryNamesTableIndex = countryNamesTable["geoname_id"]!.firstIndex(of: String(countryID)) else { return "Unknown Country" } let result = countryNamesTable["country_name"]![countryNamesTableIndex] diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index ab5a4024c..17cab5424 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -520,8 +520,7 @@ extension Attachment { \(interaction[.id]) = \(interactionAttachment[.interactionId]) OR ( \(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND - /* Note: This equation MUST match the `linkPreviewFilterLiteral` logic in Interaction.swift */ - (ROUND((\(interaction[.timestampMs]) / 1000 / 100000) - 0.5) * 100000) = \(linkPreview[.timestamp]) + \(Interaction.linkPreviewFilterLiteral) ) ) @@ -566,8 +565,7 @@ extension Attachment { \(interaction[.id]) = \(interactionAttachment[.interactionId]) OR ( \(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND - /* Note: This equation MUST match the `linkPreviewFilterLiteral` logic in Interaction.swift */ - (ROUND((\(interaction[.timestampMs]) / 1000 / 100000) - 0.5) * 100000) = \(linkPreview[.timestamp]) + \(Interaction.linkPreviewFilterLiteral) ) ) diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 0fd032333..a26b052ac 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -29,13 +29,13 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu /// Whenever using this `linkPreview` association make sure to filter the result using /// `.filter(literal: Interaction.linkPreviewFilterLiteral)` to ensure the correct LinkPreview is returned public static let linkPreview = hasOne(LinkPreview.self, using: LinkPreview.interactionForeignKey) - public static func linkPreviewFilterLiteral( - timestampColumn: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) - ) -> SQL { + public static var linkPreviewFilterLiteral: SQL = { + let interaction: TypedTableAlias = TypedTableAlias() let linkPreview: TypedTableAlias = TypedTableAlias() - - return "(ROUND((\(Interaction.self).\(timestampColumn) / 1000 / 100000) - 0.5) * 100000) = \(linkPreview[.timestamp])" - } + let halfResolution: Double = LinkPreview.timstampResolution + + return "(\(interaction[.timestampMs]) BETWEEN (\(linkPreview[.timestamp]) - \(halfResolution)) AND (\(linkPreview[.timestamp]) + \(halfResolution)))" + }() public static let recipientStates = hasMany(RecipientState.self, using: RecipientState.interactionForeignKey) public typealias Columns = CodingKeys @@ -246,10 +246,14 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu public var linkPreview: QueryInterfaceRequest { /// **Note:** This equation **MUST** match the `linkPreviewFilterLiteral` logic + let halfResolution: Double = LinkPreview.timstampResolution let roundedTimestamp: Double = (round(((Double(timestampMs) / 1000) / 100000) - 0.5) * 100000) return request(for: Interaction.linkPreview) - .filter(LinkPreview.Columns.timestamp == roundedTimestamp) + .filter( + (Interaction.Columns.timestampMs >= (LinkPreview.Columns.timestamp - halfResolution)) && + (Interaction.Columns.timestampMs <= (LinkPreview.Columns.timestamp + halfResolution)) + ) } public var recipientStates: QueryInterfaceRequest { diff --git a/SessionMessagingKit/File Server/FileServerAPI.swift b/SessionMessagingKit/File Server/FileServerAPI.swift index 5c0363133..964a09ffe 100644 --- a/SessionMessagingKit/File Server/FileServerAPI.swift +++ b/SessionMessagingKit/File Server/FileServerAPI.swift @@ -19,8 +19,9 @@ public final class FileServerAPI: NSObject { /// exactly will be fine but a single byte more will result in an error public static let maxFileSize = 10_000_000 - /// Standard timeout is 10 seconds which is a little too short fir file upload/download with slightly larger files - public static let fileTimeout: TimeInterval = 30 + /// Standard timeout is 10 seconds which is a little too short for file upload/download with slightly larger files + public static let fileDownloadTimeout: TimeInterval = 30 + public static let fileUploadTimeout: TimeInterval = 60 // MARK: - File Storage @@ -36,7 +37,7 @@ public final class FileServerAPI: NSObject { body: Array(file) ) - return send(request, serverPublicKey: serverPublicKey) + return send(request, serverPublicKey: serverPublicKey, timeout: FileServerAPI.fileUploadTimeout) .decoded(as: FileUploadResponse.self, on: .global(qos: .userInitiated)) } @@ -47,7 +48,7 @@ public final class FileServerAPI: NSObject { endpoint: .fileIndividual(fileId: fileId) ) - return send(request, serverPublicKey: serverPublicKey) + return send(request, serverPublicKey: serverPublicKey, timeout: FileServerAPI.fileDownloadTimeout) } public static func getVersion(_ platform: String) -> Promise { @@ -59,14 +60,18 @@ public final class FileServerAPI: NSObject { ] ) - return send(request, serverPublicKey: serverPublicKey) + return send(request, serverPublicKey: serverPublicKey, timeout: HTTP.timeout) .decoded(as: VersionResponse.self, on: .global(qos: .userInitiated)) .map { response in response.version } } // MARK: - Convenience - private static func send(_ request: Request, serverPublicKey: String) -> Promise { + private static func send( + _ request: Request, + serverPublicKey: String, + timeout: TimeInterval + ) -> Promise { let urlRequest: URLRequest do { @@ -76,7 +81,13 @@ public final class FileServerAPI: NSObject { return Promise(error: error) } - return OnionRequestAPI.sendOnionRequest(urlRequest, to: request.server, with: serverPublicKey, timeout: FileServerAPI.fileTimeout) + return OnionRequestAPI + .sendOnionRequest( + urlRequest, + to: request.server, + with: serverPublicKey, + timeout: timeout + ) .map2 { _, response in guard let response: Data = response else { throw HTTP.Error.parsingFailed } diff --git a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift index 512e61bfd..6c0adf6c0 100644 --- a/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift +++ b/SessionMessagingKit/Jobs/Types/GarbageCollectionJob.swift @@ -141,7 +141,7 @@ public enum GarbageCollectionJob: JobExecutor { FROM \(LinkPreview.self) LEFT JOIN \(Interaction.self) ON ( \(interaction[.linkPreviewUrl]) = \(linkPreview[.url]) AND - \(Interaction.linkPreviewFilterLiteral()) + \(Interaction.linkPreviewFilterLiteral) ) WHERE \(interaction[.id]) IS NULL ) diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 9b4984627..f4af87fe4 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -871,7 +871,7 @@ public enum OpenGroupAPI { ], body: bytes ), - timeout: FileServerAPI.fileTimeout, + timeout: FileServerAPI.fileUploadTimeout, using: dependencies ) .decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, using: dependencies) @@ -891,7 +891,7 @@ public enum OpenGroupAPI { server: server, endpoint: .roomFileIndividual(roomToken, fileId) ), - timeout: FileServerAPI.fileTimeout, + timeout: FileServerAPI.fileDownloadTimeout, using: dependencies ) .map { responseInfo, maybeData in diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index 928ffaaea..e2076f350 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -1083,7 +1083,11 @@ public final class OpenGroupManager: NSObject { } public static func parseOpenGroup(from string: String) -> (room: String, server: String, publicKey: String)? { - guard let url = URL(string: string), let host = url.host ?? given(string.split(separator: "/").first, { String($0) }), let query = url.query else { return nil } + guard + let url = URL(string: string), + let host = (url.host ?? string.split(separator: "/").first.map({ String($0) })), + let query = url.query + else { return nil } // Inputs that should work: // https://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c // https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 2f00d7d4d..20aa57cdc 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -327,10 +327,9 @@ public enum MessageReceiver { if let name = name, !name.isEmpty, name != profile.name { let shouldUpdate: Bool if isCurrentUser { - shouldUpdate = given(UserDefaults.standard[.lastDisplayNameUpdate]) { - sentTimestamp > $0.timeIntervalSince1970 - } - .defaulting(to: true) + shouldUpdate = UserDefaults.standard[.lastDisplayNameUpdate] + .map { sentTimestamp > $0.timeIntervalSince1970 } + .defaulting(to: true) } else { shouldUpdate = true @@ -354,10 +353,9 @@ public enum MessageReceiver { { let shouldUpdate: Bool if isCurrentUser { - shouldUpdate = given(UserDefaults.standard[.lastProfilePictureUpdate]) { - sentTimestamp > $0.timeIntervalSince1970 - } - .defaulting(to: true) + shouldUpdate = UserDefaults.standard[.lastProfilePictureUpdate] + .map { sentTimestamp > $0.timeIntervalSince1970 } + .defaulting(to: true) } else { shouldUpdate = true diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index e2a1917cc..f45b3a6a4 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -637,27 +637,32 @@ public extension MessageViewModel { let interaction: TypedTableAlias = TypedTableAlias() let thread: TypedTableAlias = TypedTableAlias() let openGroup: TypedTableAlias = TypedTableAlias() + let groupMember: TypedTableAlias = TypedTableAlias() let recipientState: TypedTableAlias = TypedTableAlias() let contact: TypedTableAlias = TypedTableAlias() let disappearingMessagesConfig: TypedTableAlias = TypedTableAlias() let profile: TypedTableAlias = TypedTableAlias() let quote: TypedTableAlias = TypedTableAlias() - let interactionAttachment: TypedTableAlias = TypedTableAlias() let linkPreview: TypedTableAlias = TypedTableAlias() - let threadProfileTableLiteral: SQL = SQL(stringLiteral: "threadProfile") - let profileIdColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.id.name) - let profileNicknameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.nickname.name) - let profileNameColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.name.name) - let interactionStateInteractionIdColumnLiteral: SQL = SQL(stringLiteral: RecipientState.Columns.interactionId.name) - let readReceiptTableLiteral: SQL = SQL(stringLiteral: "readReceipt") - let readReceiptReadTimestampMsColumnLiteral: SQL = SQL(stringLiteral: RecipientState.Columns.readTimestampMs.name) - let attachmentIdColumnLiteral: SQL = SQL(stringLiteral: Attachment.Columns.id.name) - let groupMemberModeratorTableLiteral: SQL = SQL(stringLiteral: "groupMemberModerator") - let groupMemberAdminTableLiteral: SQL = SQL(stringLiteral: "groupMemberAdmin") - let groupMemberGroupIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.groupId.name) - let groupMemberProfileIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.profileId.name) - let groupMemberRoleColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.role.name) + let threadProfile: SQL = SQL(stringLiteral: "threadProfile") + let quoteInteraction: SQL = SQL(stringLiteral: "quoteInteraction") + let quoteInteractionAttachment: SQL = SQL(stringLiteral: "quoteInteractionAttachment") + let readReceipt: SQL = SQL(stringLiteral: "readReceipt") + let idColumn: SQL = SQL(stringLiteral: Interaction.Columns.id.name) + let interactionBodyColumn: SQL = SQL(stringLiteral: Interaction.Columns.body.name) + let profileIdColumn: SQL = SQL(stringLiteral: Profile.Columns.id.name) + let nicknameColumn: SQL = SQL(stringLiteral: Profile.Columns.nickname.name) + let nameColumn: SQL = SQL(stringLiteral: Profile.Columns.name.name) + let quoteBodyColumn: SQL = SQL(stringLiteral: Quote.Columns.body.name) + let quoteAttachmentIdColumn: SQL = SQL(stringLiteral: Quote.Columns.attachmentId.name) + let readReceiptInteractionIdColumn: SQL = SQL(stringLiteral: RecipientState.Columns.interactionId.name) + let readTimestampMsColumn: SQL = SQL(stringLiteral: RecipientState.Columns.readTimestampMs.name) + let timestampMsColumn: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) + let authorIdColumn: SQL = SQL(stringLiteral: Interaction.Columns.authorId.name) + let attachmentIdColumn: SQL = SQL(stringLiteral: Attachment.Columns.id.name) + let interactionAttachmentInteractionIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.interactionId.name) + let interactionAttachmentAttachmentIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.attachmentId.name) let numColumnsBeforeLinkedRecords: Int = 20 let finalGroupSQL: SQL = (groupSQL ?? "") @@ -671,7 +676,7 @@ public extension MessageViewModel { IFNULL(\(disappearingMessagesConfig[.isEnabled]), false) AS \(ViewModel.threadHasDisappearingMessagesEnabledKey), \(openGroup[.server]) AS \(ViewModel.threadOpenGroupServerKey), \(openGroup[.publicKey]) AS \(ViewModel.threadOpenGroupPublicKeyKey), - IFNULL(\(threadProfileTableLiteral).\(profileNicknameColumnLiteral), \(threadProfileTableLiteral).\(profileNameColumnLiteral)) AS \(ViewModel.threadContactNameInternalKey), + IFNULL(\(threadProfile).\(nicknameColumn), \(threadProfile).\(nameColumn)) AS \(ViewModel.threadContactNameInternalKey), \(interaction.alias[Column.rowID]) AS \(ViewModel.rowIdKey), \(interaction[.id]), @@ -685,20 +690,30 @@ public extension MessageViewModel { -- Default to 'sending' assuming non-processed interaction when null IFNULL(MIN(\(recipientState[.state])), \(SQL("\(RecipientState.State.sending)"))) AS \(ViewModel.stateKey), - (\(readReceiptTableLiteral).\(readReceiptReadTimestampMsColumnLiteral) IS NOT NULL) AS \(ViewModel.hasAtLeastOneReadReceiptKey), + (\(readReceipt).\(readTimestampMsColumn) IS NOT NULL) AS \(ViewModel.hasAtLeastOneReadReceiptKey), \(recipientState[.mostRecentFailureText]) AS \(ViewModel.mostRecentFailureTextKey), - ( - \(groupMemberModeratorTableLiteral).\(groupMemberProfileIdColumnLiteral) IS NOT NULL OR - \(groupMemberAdminTableLiteral).\(groupMemberProfileIdColumnLiteral) IS NOT NULL + EXISTS ( + SELECT 1 + FROM \(GroupMember.self) + WHERE ( + \(groupMember[.groupId]) = \(interaction[.threadId]) AND + \(groupMember[.profileId]) = \(interaction[.authorId]) AND + \(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND + \(SQL("\(groupMember[.role]) IN \([GroupMember.Role.moderator, GroupMember.Role.admin])")) + ) ) AS \(ViewModel.isSenderOpenGroupModeratorKey), \(ViewModel.profileKey).*, - \(ViewModel.quoteKey).*, + \(quote[.interactionId]), + \(quote[.authorId]), + \(quote[.timestampMs]), + \(quoteInteraction).\(interactionBodyColumn) AS \(quoteBodyColumn), + \(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) AS \(quoteAttachmentIdColumn), \(ViewModel.quoteAttachmentKey).*, \(ViewModel.linkPreviewKey).*, \(ViewModel.linkPreviewAttachmentKey).*, - + \(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey), -- All of the below properties are set in post-query processing but to prevent the @@ -715,54 +730,35 @@ public extension MessageViewModel { FROM \(Interaction.self) JOIN \(SessionThread.self) ON \(thread[.id]) = \(interaction[.threadId]) LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(interaction[.threadId]) - LEFT JOIN \(Profile.self) AS \(threadProfileTableLiteral) ON \(threadProfileTableLiteral).\(profileIdColumnLiteral) = \(interaction[.threadId]) + LEFT JOIN \(Profile.self) AS \(threadProfile) ON \(threadProfile).\(profileIdColumn) = \(interaction[.threadId]) LEFT JOIN \(DisappearingMessagesConfiguration.self) ON \(disappearingMessagesConfig[.threadId]) = \(interaction[.threadId]) LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(interaction[.threadId]) LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId]) - LEFT JOIN ( - SELECT \(quote[.interactionId]), - \(quote[.authorId]), - \(quote[.timestampMs]), - \(interaction[.body]) AS \(Quote.Columns.body), - \(interactionAttachment[.attachmentId]) AS \(Quote.Columns.attachmentId) - FROM \(Quote.self) - LEFT JOIN \(Interaction.self) ON ( - ( - \(quote[.authorId]) = \(interaction[.authorId]) OR ( - \(quote[.authorId]) = \(blindedPublicKey ?? "") AND - \(userPublicKey) = \(interaction[.authorId]) - ) - ) AND - \(quote[.timestampMs]) = \(interaction[.timestampMs]) + LEFT JOIN \(Quote.self) ON \(quote[.interactionId]) = \(interaction[.id]) + LEFT JOIN \(Interaction.self) AS \(quoteInteraction) ON ( + \(quoteInteraction).\(timestampMsColumn) = \(quote[.timestampMs]) AND ( + \(quoteInteraction).\(authorIdColumn) = \(quote[.authorId]) OR ( + \(quoteInteraction).\(authorIdColumn) = '' AND + \(quoteInteraction).\(authorIdColumn) = \(userPublicKey) + ) ) - LEFT JOIN \(InteractionAttachment.self) ON \(interaction[.id]) = \(interactionAttachment[.interactionId]) - ) AS \(ViewModel.quoteKey) ON \(quote[.interactionId]) = \(interaction[.id]) - LEFT JOIN \(Attachment.self) AS \(ViewModel.quoteAttachmentKey) ON \(ViewModel.quoteAttachmentKey).\(attachmentIdColumnLiteral) = \(quote[.attachmentId]) + ) + LEFT JOIN \(InteractionAttachment.self) AS \(quoteInteractionAttachment) ON \(quoteInteractionAttachment).\(interactionAttachmentInteractionIdColumn) = \(quoteInteraction).\(idColumn) + LEFT JOIN \(Attachment.self) AS \(ViewModel.quoteAttachmentKey) ON \(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) + LEFT JOIN \(LinkPreview.self) ON ( \(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND - \(Interaction.linkPreviewFilterLiteral()) + \(Interaction.linkPreviewFilterLiteral) ) - LEFT JOIN \(Attachment.self) AS \(ViewModel.linkPreviewAttachmentKey) ON \(ViewModel.linkPreviewAttachmentKey).\(attachmentIdColumnLiteral) = \(linkPreview[.attachmentId]) + LEFT JOIN \(Attachment.self) AS \(ViewModel.linkPreviewAttachmentKey) ON \(ViewModel.linkPreviewAttachmentKey).\(attachmentIdColumn) = \(linkPreview[.attachmentId]) LEFT JOIN \(RecipientState.self) ON ( -- Ignore 'skipped' states \(SQL("\(recipientState[.state]) != \(RecipientState.State.skipped)")) AND \(recipientState[.interactionId]) = \(interaction[.id]) ) - LEFT JOIN \(RecipientState.self) AS \(readReceiptTableLiteral) ON ( - \(readReceiptTableLiteral).\(readReceiptReadTimestampMsColumnLiteral) IS NOT NULL AND - \(interaction[.id]) = \(readReceiptTableLiteral).\(interactionStateInteractionIdColumnLiteral) - ) - LEFT JOIN \(GroupMember.self) AS \(groupMemberModeratorTableLiteral) ON ( - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND - \(groupMemberModeratorTableLiteral).\(groupMemberGroupIdColumnLiteral) = \(interaction[.threadId]) AND - \(groupMemberModeratorTableLiteral).\(groupMemberProfileIdColumnLiteral) = \(interaction[.authorId]) AND - \(SQL("\(groupMemberModeratorTableLiteral).\(groupMemberRoleColumnLiteral) = \(GroupMember.Role.moderator)")) - ) - LEFT JOIN \(GroupMember.self) AS \(groupMemberAdminTableLiteral) ON ( - \(SQL("\(thread[.variant]) = \(SessionThread.Variant.openGroup)")) AND - \(groupMemberAdminTableLiteral).\(groupMemberGroupIdColumnLiteral) = \(interaction[.threadId]) AND - \(groupMemberAdminTableLiteral).\(groupMemberProfileIdColumnLiteral) = \(interaction[.authorId]) AND - \(SQL("\(groupMemberAdminTableLiteral).\(groupMemberRoleColumnLiteral) = \(GroupMember.Role.admin)")) + LEFT JOIN \(RecipientState.self) AS \(readReceipt) ON ( + \(readReceipt).\(readTimestampMsColumn) IS NOT NULL AND + \(readReceipt).\(readReceiptInteractionIdColumn) = \(interaction[.id]) ) WHERE \(interaction.alias[Column.rowID]) IN \(rowIds) \(finalGroupSQL) diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index 6b20a0fe0..01c249d6f 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -448,7 +448,8 @@ public extension SessionThreadViewModel { let interactionAttachment: TypedTableAlias = TypedTableAlias() let profile: TypedTableAlias = TypedTableAlias() - let interactionTimestampMsColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) + let aggregateInteractionLiteral: SQL = SQL(stringLiteral: "aggregateInteraction") + let timestampMsColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) let interactionStateInteractionIdColumnLiteral: SQL = SQL(stringLiteral: RecipientState.Columns.interactionId.name) let readReceiptTableLiteral: SQL = SQL(stringLiteral: "readReceipt") let readReceiptReadTimestampMsColumnLiteral: SQL = SQL(stringLiteral: RecipientState.Columns.readTimestampMs.name) @@ -459,9 +460,7 @@ public extension SessionThreadViewModel { let interactionAttachmentAttachmentIdColumnLiteral: SQL = SQL(stringLiteral: InteractionAttachment.Columns.attachmentId.name) let interactionAttachmentInteractionIdColumnLiteral: SQL = SQL(stringLiteral: InteractionAttachment.Columns.interactionId.name) let interactionAttachmentAlbumIndexColumnLiteral: SQL = SQL(stringLiteral: InteractionAttachment.Columns.albumIndex.name) - let groupMemberProfileIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.profileId.name) - let groupMemberRoleColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.role.name) - let groupMemberGroupIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.groupId.name) + /// **Note:** The `numColumnsBeforeProfiles` value **MUST** match the number of fields before /// the `ViewModel.contactProfileKey` entry below otherwise the query will fail to @@ -470,124 +469,136 @@ public extension SessionThreadViewModel { /// Explicitly set default values for the fields ignored for search results let numColumnsBeforeProfiles: Int = 12 let numColumnsBetweenProfilesAndAttachmentInfo: Int = 12 // The attachment info columns will be combined - let request: SQLRequest = """ SELECT \(thread.alias[Column.rowID]) AS \(ViewModel.rowIdKey), \(thread[.id]) AS \(ViewModel.threadIdKey), \(thread[.variant]) AS \(ViewModel.threadVariantKey), \(thread[.creationDateTimestamp]) AS \(ViewModel.threadCreationDateTimestampKey), - + (\(SQL("\(thread[.id]) = \(userPublicKey)"))) AS \(ViewModel.threadIsNoteToSelfKey), \(thread[.isPinned]) AS \(ViewModel.threadIsPinnedKey), \(contact[.isBlocked]) AS \(ViewModel.threadIsBlockedKey), \(thread[.mutedUntilTimestamp]) AS \(ViewModel.threadMutedUntilTimestampKey), \(thread[.onlyNotifyForMentions]) AS \(ViewModel.threadOnlyNotifyForMentionsKey), - + (\(typingIndicator[.threadId]) IS NOT NULL) AS \(ViewModel.threadContactIsTypingKey), - \(Interaction.self).\(ViewModel.threadUnreadCountKey), - \(Interaction.self).\(ViewModel.threadUnreadMentionCountKey), - + \(aggregateInteractionLiteral).\(ViewModel.threadUnreadCountKey), + \(aggregateInteractionLiteral).\(ViewModel.threadUnreadMentionCountKey), + \(ViewModel.contactProfileKey).*, \(ViewModel.closedGroupProfileFrontKey).*, \(ViewModel.closedGroupProfileBackKey).*, \(ViewModel.closedGroupProfileBackFallbackKey).*, \(closedGroup[.name]) AS \(ViewModel.closedGroupNameKey), - (\(ViewModel.currentUserIsClosedGroupMemberKey).profileId IS NOT NULL) AS \(ViewModel.currentUserIsClosedGroupMemberKey), - (\(ViewModel.currentUserIsClosedGroupAdminKey).profileId IS NOT NULL) AS \(ViewModel.currentUserIsClosedGroupAdminKey), + + EXISTS ( + SELECT 1 + FROM \(GroupMember.self) + WHERE ( + \(groupMember[.groupId]) = \(closedGroup[.threadId]) AND + \(SQL("\(groupMember[.role]) != \(GroupMember.Role.zombie)")) AND + \(SQL("\(groupMember[.profileId]) = \(userPublicKey)")) + ) + ) AS \(ViewModel.currentUserIsClosedGroupMemberKey), + + EXISTS ( + SELECT 1 + FROM \(GroupMember.self) + WHERE ( + \(groupMember[.groupId]) = \(closedGroup[.threadId]) AND + \(SQL("\(groupMember[.role]) = \(GroupMember.Role.admin)")) AND + \(SQL("\(groupMember[.profileId]) = \(userPublicKey)")) + ) + ) AS \(ViewModel.currentUserIsClosedGroupAdminKey), + \(openGroup[.name]) AS \(ViewModel.openGroupNameKey), \(openGroup[.imageData]) AS \(ViewModel.openGroupProfilePictureDataKey), - - \(Interaction.self).\(ViewModel.interactionIdKey), - \(Interaction.self).\(ViewModel.interactionVariantKey), - \(Interaction.self).\(interactionTimestampMsColumnLiteral) AS \(ViewModel.interactionTimestampMsKey), - \(Interaction.self).\(ViewModel.interactionBodyKey), - + + \(interaction[.id]) AS \(ViewModel.interactionIdKey), + \(interaction[.variant]) AS \(ViewModel.interactionVariantKey), + \(interaction[.timestampMs]) AS \(ViewModel.interactionTimestampMsKey), + \(interaction[.body]) AS \(ViewModel.interactionBodyKey), + -- Default to 'sending' assuming non-processed interaction when null - IFNULL(MIN(\(recipientState[.state])), \(SQL("\(RecipientState.State.sending)"))) AS \(ViewModel.interactionStateKey), + IFNULL(( + SELECT \(recipientState[.state]) + FROM \(RecipientState.self) + WHERE ( + \(recipientState[.interactionId]) = \(interaction[.id]) AND + -- Ignore 'skipped' states + \(SQL("\(recipientState[.state]) = \(RecipientState.State.sending)")) + ) + LIMIT 1 + ), 0) AS \(ViewModel.interactionStateKey), + (\(readReceiptTableLiteral).\(readReceiptReadTimestampMsColumnLiteral) IS NOT NULL) AS \(ViewModel.interactionHasAtLeastOneReadReceiptKey), (\(linkPreview[.url]) IS NOT NULL) AS \(ViewModel.interactionIsOpenGroupInvitationKey), - + -- These 4 properties will be combined into 'Attachment.DescriptionInfo' \(attachment[.id]), \(attachment[.variant]), \(attachment[.contentType]), \(attachment[.sourceFilename]), COUNT(\(interactionAttachment[.interactionId])) AS \(ViewModel.interactionAttachmentCountKey), - + \(interaction[.authorId]), IFNULL(\(ViewModel.contactProfileKey).\(profileNicknameColumnLiteral), \(ViewModel.contactProfileKey).\(profileNameColumnLiteral)) AS \(ViewModel.threadContactNameInternalKey), IFNULL(\(profile[.nickname]), \(profile[.name])) AS \(ViewModel.authorNameInternalKey), \(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey) - + FROM \(SessionThread.self) LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id]) LEFT JOIN \(ThreadTypingIndicator.self) ON \(typingIndicator[.threadId]) = \(thread[.id]) + LEFT JOIN ( - -- Fetch all interaction-specific data in a subquery to be more efficient SELECT \(interaction[.id]) AS \(ViewModel.interactionIdKey), - \(interaction[.threadId]), - \(interaction[.variant]) AS \(ViewModel.interactionVariantKey), - MAX(\(interaction[.timestampMs])) AS \(interactionTimestampMsColumnLiteral), - \(interaction[.body]) AS \(ViewModel.interactionBodyKey), - \(interaction[.authorId]), - \(interaction[.linkPreviewUrl]), - + \(interaction[.threadId]) AS \(ViewModel.threadIdKey), + MAX(\(interaction[.timestampMs])) AS \(timestampMsColumnLiteral), SUM(\(interaction[.wasRead]) = false) AS \(ViewModel.threadUnreadCountKey), SUM(\(interaction[.wasRead]) = false AND \(interaction[.hasMention]) = true) AS \(ViewModel.threadUnreadMentionCountKey) - FROM \(Interaction.self) WHERE \(SQL("\(interaction[.variant]) != \(Interaction.Variant.standardIncomingDeleted)")) GROUP BY \(interaction[.threadId]) - ) AS \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id]) + ) AS \(aggregateInteractionLiteral) ON \(aggregateInteractionLiteral).\(ViewModel.threadIdKey) = \(thread[.id]) - LEFT JOIN \(RecipientState.self) ON ( - -- Ignore 'skipped' states - \(SQL("\(recipientState[.state]) != \(RecipientState.State.skipped)")) AND - \(recipientState[.interactionId]) = \(Interaction.self).\(ViewModel.interactionIdKey) + LEFT JOIN \(Interaction.self) ON ( + \(interaction[.threadId]) = \(thread[.id]) AND + \(interaction[.id]) = \(aggregateInteractionLiteral).\(ViewModel.interactionIdKey) ) + LEFT JOIN \(RecipientState.self) AS \(readReceiptTableLiteral) ON ( - \(readReceiptTableLiteral).\(readReceiptReadTimestampMsColumnLiteral) IS NOT NULL AND - \(Interaction.self).\(ViewModel.interactionIdKey) = \(readReceiptTableLiteral).\(interactionStateInteractionIdColumnLiteral) + \(interaction[.id]) = \(readReceiptTableLiteral).\(interactionStateInteractionIdColumnLiteral) AND + \(readReceiptTableLiteral).\(readReceiptReadTimestampMsColumnLiteral) IS NOT NULL ) LEFT JOIN \(LinkPreview.self) ON ( \(linkPreview[.url]) = \(interaction[.linkPreviewUrl]) AND - \(SQL("\(linkPreview[.variant]) = \(LinkPreview.Variant.openGroupInvitation)")) AND - \(Interaction.linkPreviewFilterLiteral(timestampColumn: interactionTimestampMsColumnLiteral)) + \(Interaction.linkPreviewFilterLiteral) AND + \(SQL("\(linkPreview[.variant]) = \(LinkPreview.Variant.openGroupInvitation)")) ) LEFT JOIN \(InteractionAttachment.self) AS \(firstInteractionAttachmentLiteral) ON ( - \(firstInteractionAttachmentLiteral).\(interactionAttachmentAlbumIndexColumnLiteral) = 0 AND - \(firstInteractionAttachmentLiteral).\(interactionAttachmentInteractionIdColumnLiteral) = \(Interaction.self).\(ViewModel.interactionIdKey) + \(firstInteractionAttachmentLiteral).\(interactionAttachmentInteractionIdColumnLiteral) = \(interaction[.id]) AND + \(firstInteractionAttachmentLiteral).\(interactionAttachmentAlbumIndexColumnLiteral) = 0 ) LEFT JOIN \(Attachment.self) ON \(attachment[.id]) = \(firstInteractionAttachmentLiteral).\(interactionAttachmentAttachmentIdColumnLiteral) - LEFT JOIN \(InteractionAttachment.self) ON \(interactionAttachment[.interactionId]) = \(Interaction.self).\(ViewModel.interactionIdKey) + LEFT JOIN \(InteractionAttachment.self) ON \(interactionAttachment[.interactionId]) = \(interaction[.id]) LEFT JOIN \(Profile.self) ON \(profile[.id]) = \(interaction[.authorId]) - + -- Thread naming & avatar content - + LEFT JOIN \(Profile.self) AS \(ViewModel.contactProfileKey) ON \(ViewModel.contactProfileKey).\(profileIdColumnLiteral) = \(thread[.id]) LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id]) LEFT JOIN \(ClosedGroup.self) ON \(closedGroup[.threadId]) = \(thread[.id]) - LEFT JOIN \(GroupMember.self) AS \(ViewModel.currentUserIsClosedGroupMemberKey) ON ( - \(SQL("\(ViewModel.currentUserIsClosedGroupMemberKey).\(groupMemberRoleColumnLiteral) != \(GroupMember.Role.zombie)")) AND - \(ViewModel.currentUserIsClosedGroupMemberKey).\(groupMemberGroupIdColumnLiteral) = \(closedGroup[.threadId]) AND - \(SQL("\(ViewModel.currentUserIsClosedGroupMemberKey).\(groupMemberProfileIdColumnLiteral) = \(userPublicKey)")) - ) - LEFT JOIN \(GroupMember.self) AS \(ViewModel.currentUserIsClosedGroupAdminKey) ON ( - \(SQL("\(ViewModel.currentUserIsClosedGroupAdminKey).\(groupMemberRoleColumnLiteral) = \(GroupMember.Role.admin)")) AND - \(ViewModel.currentUserIsClosedGroupAdminKey).\(groupMemberGroupIdColumnLiteral) = \(closedGroup[.threadId]) AND - \(SQL("\(ViewModel.currentUserIsClosedGroupAdminKey).\(groupMemberProfileIdColumnLiteral) = \(userPublicKey)")) - ) - + LEFT JOIN \(Profile.self) AS \(ViewModel.closedGroupProfileFrontKey) ON ( \(ViewModel.closedGroupProfileFrontKey).\(profileIdColumnLiteral) = ( SELECT MIN(\(groupMember[.profileId])) FROM \(GroupMember.self) JOIN \(Profile.self) ON \(profile[.id]) = \(groupMember[.profileId]) WHERE ( - \(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)")) AND \(groupMember[.groupId]) = \(closedGroup[.threadId]) AND + \(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)")) AND \(SQL("\(groupMember[.profileId]) != \(userPublicKey)")) ) ) @@ -599,8 +610,8 @@ public extension SessionThreadViewModel { FROM \(GroupMember.self) JOIN \(Profile.self) ON \(profile[.id]) = \(groupMember[.profileId]) WHERE ( - \(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)")) AND \(groupMember[.groupId]) = \(closedGroup[.threadId]) AND + \(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)")) AND \(SQL("\(groupMember[.profileId]) != \(userPublicKey)")) ) ) @@ -610,7 +621,7 @@ public extension SessionThreadViewModel { \(ViewModel.closedGroupProfileBackKey).\(profileIdColumnLiteral) IS NULL AND \(ViewModel.closedGroupProfileBackFallbackKey).\(profileIdColumnLiteral) = \(SQL("\(userPublicKey)")) ) - + WHERE \(thread.alias[Column.rowID]) IN \(rowIds) \(groupSQL) ORDER BY \(orderSQL) @@ -643,14 +654,14 @@ public extension SessionThreadViewModel { let contact: TypedTableAlias = TypedTableAlias() let interaction: TypedTableAlias = TypedTableAlias() - let interactionTimestampMsColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) + let timestampMsColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) return """ LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id]) LEFT JOIN ( SELECT \(interaction[.threadId]), - MAX(\(interaction[.timestampMs])) AS \(interactionTimestampMsColumnLiteral) + MAX(\(interaction[.timestampMs])) AS \(timestampMsColumnLiteral) FROM \(Interaction.self) WHERE \(SQL("\(interaction[.variant]) != \(Interaction.Variant.standardIncomingDeleted)")) GROUP BY \(interaction[.threadId]) @@ -701,7 +712,10 @@ public extension SessionThreadViewModel { let thread: TypedTableAlias = TypedTableAlias() let interaction: TypedTableAlias = TypedTableAlias() - return SQL("\(thread[.isPinned]) DESC, IFNULL(\(interaction[.timestampMs]), (\(thread[.creationDateTimestamp]) * 1000)) DESC") + return SQL(""" + \(thread[.isPinned]) DESC, + CASE WHEN \(interaction[.timestampMs]) IS NOT NULL THEN \(interaction[.timestampMs]) ELSE (\(thread[.creationDateTimestamp]) * 1000) END DESC + """) }() static let messageRequetsOrderSQL: SQL = { @@ -725,6 +739,8 @@ public extension SessionThreadViewModel { let openGroup: TypedTableAlias = TypedTableAlias() let interaction: TypedTableAlias = TypedTableAlias() + let aggregateInteractionLiteral: SQL = SQL(stringLiteral: "aggregateInteraction") + let timestampMsColumnLiteral: SQL = SQL(stringLiteral: Interaction.Columns.timestampMs.name) let closedGroupUserCountTableLiteral: SQL = SQL(stringLiteral: "\(ViewModel.closedGroupUserCountString)_table") let groupMemberGroupIdColumnLiteral: SQL = SQL(stringLiteral: GroupMember.Columns.groupId.name) let profileIdColumnLiteral: SQL = SQL(stringLiteral: Profile.Columns.id.name) @@ -760,12 +776,22 @@ public extension SessionThreadViewModel { \(thread[.onlyNotifyForMentions]) AS \(ViewModel.threadOnlyNotifyForMentionsKey), \(thread[.messageDraft]) AS \(ViewModel.threadMessageDraftKey), - \(Interaction.self).\(ViewModel.threadUnreadCountKey), + \(aggregateInteractionLiteral).\(ViewModel.threadUnreadCountKey), \(ViewModel.contactProfileKey).*, \(closedGroup[.name]) AS \(ViewModel.closedGroupNameKey), \(closedGroupUserCountTableLiteral).\(ViewModel.closedGroupUserCountKey) AS \(ViewModel.closedGroupUserCountKey), - (\(groupMember[.profileId]) IS NOT NULL) AS \(ViewModel.currentUserIsClosedGroupMemberKey), + + EXISTS ( + SELECT 1 + FROM \(GroupMember.self) + WHERE ( + \(groupMember[.groupId]) = \(closedGroup[.threadId]) AND + \(SQL("\(groupMember[.role]) != \(GroupMember.Role.zombie)")) AND + \(SQL("\(groupMember[.profileId]) = \(userPublicKey)")) + ) + ) AS \(ViewModel.currentUserIsClosedGroupMemberKey), + \(openGroup[.name]) AS \(ViewModel.openGroupNameKey), \(openGroup[.server]) AS \(ViewModel.openGroupServerKey), \(openGroup[.roomToken]) AS \(ViewModel.openGroupRoomTokenKey), @@ -773,33 +799,28 @@ public extension SessionThreadViewModel { \(openGroup[.userCount]) AS \(ViewModel.openGroupUserCountKey), \(openGroup[.permissions]) AS \(ViewModel.openGroupPermissionsKey), - \(Interaction.self).\(ViewModel.interactionIdKey), + \(aggregateInteractionLiteral).\(ViewModel.interactionIdKey), \(SQL("\(userPublicKey)")) AS \(ViewModel.currentUserPublicKeyKey) FROM \(SessionThread.self) LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id]) LEFT JOIN ( - -- Fetch all interaction-specific data in a subquery to be more efficient SELECT \(interaction[.id]) AS \(ViewModel.interactionIdKey), - \(interaction[.threadId]), - MAX(\(interaction[.timestampMs])), - + \(interaction[.threadId]) AS \(ViewModel.threadIdKey), + MAX(\(interaction[.timestampMs])) AS \(timestampMsColumnLiteral), SUM(\(interaction[.wasRead]) = false) AS \(ViewModel.threadUnreadCountKey) - FROM \(Interaction.self) - WHERE \(SQL("\(interaction[.threadId]) = \(threadId)")) - ) AS \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id]) - + WHERE ( + \(SQL("\(interaction[.threadId]) = \(threadId)")) AND + \(SQL("\(interaction[.variant]) != \(Interaction.Variant.standardIncomingDeleted)")) + ) + ) AS \(aggregateInteractionLiteral) ON \(aggregateInteractionLiteral).\(ViewModel.threadIdKey) = \(thread[.id]) + LEFT JOIN \(Profile.self) AS \(ViewModel.contactProfileKey) ON \(ViewModel.contactProfileKey).\(profileIdColumnLiteral) = \(thread[.id]) LEFT JOIN \(OpenGroup.self) ON \(openGroup[.threadId]) = \(thread[.id]) LEFT JOIN \(ClosedGroup.self) ON \(closedGroup[.threadId]) = \(thread[.id]) - LEFT JOIN \(GroupMember.self) ON ( - \(SQL("\(groupMember[.role]) = \(GroupMember.Role.standard)")) AND - \(groupMember[.groupId]) = \(closedGroup[.threadId]) AND - \(SQL("\(groupMember[.profileId]) = \(userPublicKey)")) - ) LEFT JOIN ( SELECT \(groupMember[.groupId]), @@ -1583,7 +1604,7 @@ public extension SessionThreadViewModel { FROM \(SessionThread.self) LEFT JOIN \(Contact.self) ON \(contact[.id]) = \(thread[.id]) LEFT JOIN ( - SELECT *, MAX(\(interaction[.timestampMs])) + SELECT \(interaction[.threadId]), MAX(\(interaction[.timestampMs])) FROM \(Interaction.self) GROUP BY \(interaction[.threadId]) ) AS \(Interaction.self) ON \(interaction[.threadId]) = \(thread[.id]) diff --git a/SessionMessagingKit/Utilities/Preferences.swift b/SessionMessagingKit/Utilities/Preferences.swift index 293c529c5..0b3c91209 100644 --- a/SessionMessagingKit/Utilities/Preferences.swift +++ b/SessionMessagingKit/Utilities/Preferences.swift @@ -308,9 +308,16 @@ public enum Preferences { } public static var isCallKitSupported: Bool { +#if targetEnvironment(simulator) + /// The iOS simulator doesn't support CallKit, when receiving a call on the simulator and routing it via CallKit it + /// will immediately trigger a hangup making it difficult to test - instead we just should just avoid using CallKit + /// entirely on the simulator + return false +#else guard let regionCode: String = NSLocale.current.regionCode else { return false } guard !regionCode.contains("CN") && !regionCode.contains("CHN") else { return false } return true +#endif } } diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index 1fb102ea2..f4ce3ab39 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -312,9 +312,9 @@ public final class SnodeAPI { public static func getSnodePool() -> Promise> { loadSnodePoolIfNeeded() let now = Date() - let hasSnodePoolExpired = given(Storage.shared[.lastSnodePoolRefreshDate]) { - now.timeIntervalSince($0) > 2 * 60 * 60 - }.defaulting(to: true) + let hasSnodePoolExpired: Bool = Storage.shared[.lastSnodePoolRefreshDate] + .map { now.timeIntervalSince($0) > 2 * 60 * 60 } + .defaulting(to: true) let snodePool: Set = SnodeAPI.snodePool.wrappedValue guard hasInsufficientSnodes || hasSnodePoolExpired else { diff --git a/SessionUIKit/Utilities/UIView+Utilities.swift b/SessionUIKit/Utilities/UIView+Utilities.swift index 7d37a2185..5e3e5af30 100644 --- a/SessionUIKit/Utilities/UIView+Utilities.swift +++ b/SessionUIKit/Utilities/UIView+Utilities.swift @@ -17,13 +17,13 @@ public extension UIView { class func spacer(withWidth width: CGFloat) -> UIView { let view = UIView() - view.autoSetDimension(.width, toSize: width) + view.set(.width, to: width) return view } class func spacer(withHeight height: CGFloat) -> UIView { let view = UIView() - view.autoSetDimension(.height, toSize: height) + view.set(.height, to: height) return view } diff --git a/SessionUtilitiesKit/General/General.swift b/SessionUtilitiesKit/General/General.swift index 72576fb56..b901af73a 100644 --- a/SessionUtilitiesKit/General/General.swift +++ b/SessionUtilitiesKit/General/General.swift @@ -35,14 +35,3 @@ public func getUserHexEncodedPublicKey(_ db: Database? = nil, dependencies: Depe return "" } - -/// Does nothing, but is never inlined and thus evaluating its argument will never be optimized away. -/// -/// Useful for forcing the instantiation of lazy properties like globals. -@inline(never) -public func touch(_ value: Value) { /* Do nothing */ } - -/// Returns `f(x!)` if `x != nil`, or `nil` otherwise. -public func given(_ x: T?, _ f: (T) throws -> U) rethrows -> U? { return try x.map(f) } - -public func with(_ x: T, _ f: (T) throws -> U) rethrows -> U { return try f(x) } diff --git a/SessionUtilitiesKit/General/UIView+OWS.h b/SessionUtilitiesKit/General/UIView+OWS.h index 70bb155b2..77d68311e 100644 --- a/SessionUtilitiesKit/General/UIView+OWS.h +++ b/SessionUtilitiesKit/General/UIView+OWS.h @@ -2,7 +2,6 @@ // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // -#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/SessionUtilitiesKit/General/UIView+OWS.m b/SessionUtilitiesKit/General/UIView+OWS.m index 490ab1efd..7c831764a 100644 --- a/SessionUtilitiesKit/General/UIView+OWS.m +++ b/SessionUtilitiesKit/General/UIView+OWS.m @@ -5,6 +5,7 @@ #import "UIView+OWS.h" #import "OWSMath.h" +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift index f290c43f5..842fb68c6 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift @@ -5,6 +5,7 @@ import Foundation import UIKit import SessionUIKit +import PureLayout // Coincides with Android's max text message length let kMaxMessageBodyCharacterCount = 2000 From cac2831208ea1acbfc02b29f8cee725996576047 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Apr 2023 09:26:34 +1000 Subject: [PATCH 40/44] Fixed a few issues with albums and attachments Fixed an issue where the JobRunner could schedule dependent jobs which were already running (eg. uploading attachments multiple times) Fixed an issue where the image used for quotes might not be the first in an album message Fixed an issue where sending an album message wouldn't send attachments in the correct order Fixed an issue where album attachments wouldn't be downloaded in the correct order --- .../Database/Models/Attachment.swift | 7 ++-- .../Jobs/Types/MessageSendJob.swift | 1 + .../Visible Messages/VisibleMessage.swift | 13 +++++-- .../Shared Models/MessageViewModel.swift | 6 +++- SessionUtilitiesKit/JobRunner/JobRunner.swift | 34 ++++++++++++++----- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/SessionMessagingKit/Database/Models/Attachment.swift b/SessionMessagingKit/Database/Models/Attachment.swift index 17cab5424..3f661fc20 100644 --- a/SessionMessagingKit/Database/Models/Attachment.swift +++ b/SessionMessagingKit/Database/Models/Attachment.swift @@ -493,6 +493,7 @@ extension Attachment { public let interactionId: Int64 public let state: Attachment.State public let downloadUrl: String? + public let albumIndex: Int } public static func stateInfo(authorId: String, state: State? = nil) -> SQLRequest { @@ -510,7 +511,8 @@ extension Attachment { \(attachment[.id]) AS attachmentId, \(interaction[.id]) AS interactionId, \(attachment[.state]) AS state, - \(attachment[.downloadUrl]) AS downloadUrl + \(attachment[.downloadUrl]) AS downloadUrl, + IFNULL(\(interactionAttachment[.albumIndex]), 0) AS albumIndex FROM \(Attachment.self) @@ -555,7 +557,8 @@ extension Attachment { \(attachment[.id]) AS attachmentId, \(interaction[.id]) AS interactionId, \(attachment[.state]) AS state, - \(attachment[.downloadUrl]) AS downloadUrl + \(attachment[.downloadUrl]) AS downloadUrl, + IFNULL(\(interactionAttachment[.albumIndex]), 0) AS albumIndex FROM \(Attachment.self) diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index b835cbca5..eac85ddfb 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -57,6 +57,7 @@ public enum MessageSendJob: JobExecutor { .stateInfo(interactionId: interactionId) .fetchAll(db) let maybeFileIds: [String?] = allAttachmentStateInfo + .sorted { lhs, rhs in lhs.albumIndex < rhs.albumIndex } .map { Attachment.fileId(for: $0.downloadUrl) } let fileIds: [String] = maybeFileIds.compactMap { $0 } diff --git a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift index 2780ae0eb..7132723b9 100644 --- a/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift +++ b/SessionMessagingKit/Messages/Visible Messages/VisibleMessage.swift @@ -160,14 +160,21 @@ public final class VisibleMessage: Message { // Attachments - let attachments: [Attachment]? = try? Attachment.fetchAll(db, ids: self.attachmentIds) + let attachmentIdIndexes: [String: Int] = (try? InteractionAttachment + .filter(self.attachmentIds.contains(InteractionAttachment.Columns.attachmentId)) + .fetchAll(db)) + .defaulting(to: []) + .reduce(into: [:]) { result, next in result[next.attachmentId] = next.albumIndex } + let attachments: [Attachment] = (try? Attachment.fetchAll(db, ids: self.attachmentIds)) + .defaulting(to: []) + .sorted { lhs, rhs in (attachmentIdIndexes[lhs.id] ?? 0) < (attachmentIdIndexes[rhs.id] ?? 0) } - if !(attachments ?? []).allSatisfy({ $0.state == .uploaded }) { + if !attachments.allSatisfy({ $0.state == .uploaded }) { #if DEBUG preconditionFailure("Sending a message before all associated attachments have been uploaded.") #endif } - let attachmentProtos = (attachments ?? []).compactMap { $0.buildProto() } + let attachmentProtos = attachments.compactMap { $0.buildProto() } dataMessage.setAttachments(attachmentProtos) // Open group invitation diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index f45b3a6a4..0523224ed 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -663,6 +663,7 @@ public extension MessageViewModel { let attachmentIdColumn: SQL = SQL(stringLiteral: Attachment.Columns.id.name) let interactionAttachmentInteractionIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.interactionId.name) let interactionAttachmentAttachmentIdColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.attachmentId.name) + let interactionAttachmentAlbumIndexColumn: SQL = SQL(stringLiteral: InteractionAttachment.Columns.albumIndex.name) let numColumnsBeforeLinkedRecords: Int = 20 let finalGroupSQL: SQL = (groupSQL ?? "") @@ -743,7 +744,10 @@ public extension MessageViewModel { ) ) ) - LEFT JOIN \(InteractionAttachment.self) AS \(quoteInteractionAttachment) ON \(quoteInteractionAttachment).\(interactionAttachmentInteractionIdColumn) = \(quoteInteraction).\(idColumn) + LEFT JOIN \(InteractionAttachment.self) AS \(quoteInteractionAttachment) ON ( + \(quoteInteractionAttachment).\(interactionAttachmentInteractionIdColumn) = \(quoteInteraction).\(idColumn) AND + \(quoteInteractionAttachment).\(interactionAttachmentAlbumIndexColumn) = 0 + ) LEFT JOIN \(Attachment.self) AS \(ViewModel.quoteAttachmentKey) ON \(ViewModel.quoteAttachmentKey).\(attachmentIdColumn) = \(quoteInteractionAttachment).\(interactionAttachmentAttachmentIdColumn) LEFT JOIN \(LinkPreview.self) ON ( diff --git a/SessionUtilitiesKit/JobRunner/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift index e1f1408a8..25d352a1d 100644 --- a/SessionUtilitiesKit/JobRunner/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -591,12 +591,17 @@ private final class JobQueue { } fileprivate func appDidBecomeActive(with jobs: [Job], canStart: Bool) { + let currentlyRunningJobIds: Set = jobsCurrentlyRunning.wrappedValue + queue.mutate { queue in // Avoid re-adding jobs to the queue that are already in it (this can // happen if the user sends the app to the background before the 'onActive' // jobs and then brings it back to the foreground) let jobsNotAlreadyInQueue: [Job] = jobs - .filter { job in !queue.contains(where: { $0.id == job.id }) } + .filter { job in + !currentlyRunningJobIds.contains(job.id ?? -1) && + !queue.contains(where: { $0.id == job.id }) + } queue.append(contentsOf: jobsNotAlreadyInQueue) } @@ -784,14 +789,20 @@ private final class JobQueue { guard dependencyInfo.jobs.isEmpty else { SNLog("[JobRunner] \(queueContext) found job with \(dependencyInfo.jobs.count) dependencies, running those first") - /// Remove all jobs this one is dependant on from the queue and re-insert them at the start of the queue + /// Remove all jobs this one is dependant on that aren't currently running from the queue and re-insert them at the start + /// of the queue /// /// **Note:** We don't add the current job back the the queue because it should only be re-added if it's dependencies /// are successfully completed + let currentlyRunningJobIds: [Int64] = Array(detailsForCurrentlyRunningJobs.wrappedValue.keys) + let dependencyJobsNotCurrentlyRunning: [Job] = dependencyInfo.jobs + .filter { job in !currentlyRunningJobIds.contains(job.id ?? -1) } + .sorted { lhs, rhs in (lhs.id ?? -1) < (rhs.id ?? -1) } + queue.mutate { queue in queue = queue - .filter { !dependencyInfo.jobs.contains($0) } - .inserting(contentsOf: Array(dependencyInfo.jobs), at: 0) + .filter { !dependencyJobsNotCurrentlyRunning.contains($0) } + .inserting(contentsOf: dependencyJobsNotCurrentlyRunning, at: 0) } handleJobDeferred(nextJob) return @@ -960,17 +971,22 @@ private final class JobQueue { default: break } - /// Now that the job has been completed we want to insert any jobs that were dependant on it to the start of the queue (the - /// most likely case is that we want an entire job chain to be completed at the same time rather than being blocked by other - /// unrelated jobs) + /// Now that the job has been completed we want to insert any jobs that were dependant on it, that aren't already running + /// to the start of the queue (the most likely case is that we want an entire job chain to be completed at the same time rather + /// than being blocked by other unrelated jobs) /// /// **Note:** If any of these `dependantJobs` have other dependencies then when they attempt to start they will be /// removed from the queue, replaced by their dependencies if !dependantJobs.isEmpty { + let currentlyRunningJobIds: [Int64] = Array(detailsForCurrentlyRunningJobs.wrappedValue.keys) + let dependantJobsNotCurrentlyRunning: [Job] = dependantJobs + .filter { job in !currentlyRunningJobIds.contains(job.id ?? -1) } + .sorted { lhs, rhs in (lhs.id ?? -1) < (rhs.id ?? -1) } + queue.mutate { queue in queue = queue - .filter { !dependantJobs.contains($0) } - .inserting(contentsOf: dependantJobs, at: 0) + .filter { !dependantJobsNotCurrentlyRunning.contains($0) } + .inserting(contentsOf: dependantJobsNotCurrentlyRunning, at: 0) } } From 2a693df4c1ad890bc774d5d4d89caa4a64df8587 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Apr 2023 09:41:26 +1000 Subject: [PATCH 41/44] Updated the seed node certificates and removed the workaround --- Session.xcodeproj/project.pbxproj | 48 +++++++------- Session/Meta/Certificates/seed1-10y.crt | 24 ------- Session/Meta/Certificates/seed1-10y.der | Bin 1041 -> 0 bytes Session/Meta/Certificates/seed1-2023-2y.crt | 24 +++++++ Session/Meta/Certificates/seed1-2023-2y.der | Bin 0 -> 1041 bytes Session/Meta/Certificates/seed2-10y.crt | 24 ------- Session/Meta/Certificates/seed2-10y.der | Bin 1041 -> 0 bytes Session/Meta/Certificates/seed2-2023-2y.crt | 24 +++++++ Session/Meta/Certificates/seed2-2023-2y.der | Bin 0 -> 1041 bytes Session/Meta/Certificates/seed3-10y.crt | 24 ------- Session/Meta/Certificates/seed3-10y.der | Bin 1041 -> 0 bytes Session/Meta/Certificates/seed3-2023-2y.crt | 24 +++++++ Session/Meta/Certificates/seed3-2023-2y.der | Bin 0 -> 1041 bytes SessionSnodeKit/SnodeAPI.swift | 6 +- SessionUtilitiesKit/Networking/HTTP.swift | 66 +++++++------------- 15 files changed, 122 insertions(+), 142 deletions(-) delete mode 100644 Session/Meta/Certificates/seed1-10y.crt delete mode 100644 Session/Meta/Certificates/seed1-10y.der create mode 100644 Session/Meta/Certificates/seed1-2023-2y.crt create mode 100644 Session/Meta/Certificates/seed1-2023-2y.der delete mode 100644 Session/Meta/Certificates/seed2-10y.crt delete mode 100644 Session/Meta/Certificates/seed2-10y.der create mode 100644 Session/Meta/Certificates/seed2-2023-2y.crt create mode 100644 Session/Meta/Certificates/seed2-2023-2y.der delete mode 100644 Session/Meta/Certificates/seed3-10y.crt delete mode 100644 Session/Meta/Certificates/seed3-10y.der create mode 100644 Session/Meta/Certificates/seed3-2023-2y.crt create mode 100644 Session/Meta/Certificates/seed3-2023-2y.der diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6b0ff02cb..72e17b281 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -603,12 +603,6 @@ FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; }; FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; }; FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; }; - FD29A11D29E4EB71001923B4 /* seed1-10y.der in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11729E4EB71001923B4 /* seed1-10y.der */; }; - FD29A11E29E4EB71001923B4 /* seed2-10y.der in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11829E4EB71001923B4 /* seed2-10y.der */; }; - FD29A11F29E4EB71001923B4 /* seed3-10y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11929E4EB71001923B4 /* seed3-10y.crt */; }; - FD29A12029E4EB71001923B4 /* seed1-10y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11A29E4EB71001923B4 /* seed1-10y.crt */; }; - FD29A12129E4EB71001923B4 /* seed3-10y.der in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11B29E4EB71001923B4 /* seed3-10y.der */; }; - FD29A12229E4EB71001923B4 /* seed2-10y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FD29A11C29E4EB71001923B4 /* seed2-10y.crt */; }; FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */; }; FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */; }; @@ -827,6 +821,12 @@ FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; }; FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; }; FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; }; + FDDCBDA829E776BF00303C38 /* seed2-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */; }; + FDDCBDA929E776BF00303C38 /* seed1-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */; }; + FDDCBDAA29E776BF00303C38 /* seed1-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA429E776BF00303C38 /* seed1-2023-2y.der */; }; + FDDCBDAB29E776BF00303C38 /* seed2-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */; }; + FDDCBDAC29E776BF00303C38 /* seed3-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */; }; + FDDCBDAD29E776BF00303C38 /* seed3-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */; }; FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; }; FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */; }; FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; }; @@ -1693,12 +1693,6 @@ FD23EA6028ED0B260058676E /* CombineExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineExtensions.swift; sourceTree = ""; }; FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; - FD29A11729E4EB71001923B4 /* seed1-10y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed1-10y.der"; sourceTree = ""; }; - FD29A11829E4EB71001923B4 /* seed2-10y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed2-10y.der"; sourceTree = ""; }; - FD29A11929E4EB71001923B4 /* seed3-10y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed3-10y.crt"; sourceTree = ""; }; - FD29A11A29E4EB71001923B4 /* seed1-10y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed1-10y.crt"; sourceTree = ""; }; - FD29A11B29E4EB71001923B4 /* seed3-10y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed3-10y.der"; sourceTree = ""; }; - FD29A11C29E4EB71001923B4 /* seed2-10y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed2-10y.crt"; sourceTree = ""; }; FD2AAAEF28ED57B500A49611 /* SynchronousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronousStorage.swift; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = ""; }; @@ -1906,6 +1900,12 @@ FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = ""; }; FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = ""; }; FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = ""; }; + FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed2-2023-2y.crt"; sourceTree = ""; }; + FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed1-2023-2y.crt"; sourceTree = ""; }; + FDDCBDA429E776BF00303C38 /* seed1-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed1-2023-2y.der"; sourceTree = ""; }; + FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed2-2023-2y.der"; sourceTree = ""; }; + FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed3-2023-2y.crt"; sourceTree = ""; }; + FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed3-2023-2y.der"; sourceTree = ""; }; FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = ""; }; FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = ""; }; FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerError.swift; sourceTree = ""; }; @@ -2366,12 +2366,12 @@ B81D260326158DF5004D1FE1 /* Certificates */ = { isa = PBXGroup; children = ( - FD29A11A29E4EB71001923B4 /* seed1-10y.crt */, - FD29A11729E4EB71001923B4 /* seed1-10y.der */, - FD29A11C29E4EB71001923B4 /* seed2-10y.crt */, - FD29A11829E4EB71001923B4 /* seed2-10y.der */, - FD29A11929E4EB71001923B4 /* seed3-10y.crt */, - FD29A11B29E4EB71001923B4 /* seed3-10y.der */, + FDDCBDA329E776BF00303C38 /* seed1-2023-2y.crt */, + FDDCBDA429E776BF00303C38 /* seed1-2023-2y.der */, + FDDCBDA229E776BF00303C38 /* seed2-2023-2y.crt */, + FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */, + FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */, + FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */, ); path = Certificates; sourceTree = ""; @@ -4694,7 +4694,6 @@ files = ( 4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */, 4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */, - FD29A11D29E4EB71001923B4 /* seed1-10y.der in Resources */, 34CF078A203E6B78005C4D61 /* end_call_tone_cept.caf in Resources */, C3CA3AA2255CDADA00F4C6D4 /* english.txt in Resources */, B6F509971AA53F760068F56A /* Localizable.strings in Resources */, @@ -4702,12 +4701,12 @@ 34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */, 7BFD1A972747689000FB91B9 /* Session-Turn-Server in Resources */, 34C3C78F2040A4F70000134C /* sonarping.mp3 in Resources */, + FDDCBDA929E776BF00303C38 /* seed1-2023-2y.crt in Resources */, 34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */, 45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */, 34C3C78D20409F320000134C /* Opening.m4r in Resources */, C3CA3AB4255CDAE600F4C6D4 /* japanese.txt in Resources */, B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */, - FD29A12129E4EB71001923B4 /* seed3-10y.der in Resources */, 34CF0787203E6B78005C4D61 /* busy_tone_ansi.caf in Resources */, 45A2F005204473A3002E978A /* NewMessage.aifc in Resources */, 45B74A882044AAB600CD42F8 /* aurora.aifc in Resources */, @@ -4719,6 +4718,7 @@ 45B74A812044AAB600CD42F8 /* chord-quiet.aifc in Resources */, 45B74A832044AAB600CD42F8 /* circles.aifc in Resources */, 45B74A892044AAB600CD42F8 /* circles-quiet.aifc in Resources */, + FDDCBDAA29E776BF00303C38 /* seed1-2023-2y.der in Resources */, C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */, 4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */, B8D07405265C683300F77E07 /* ElegantIcons.ttf in Resources */, @@ -4727,14 +4727,15 @@ B8FF8E7425C10FC3004D1F22 /* GeoLite2-Country-Locations-English in Resources */, B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */, 45B74A872044AAB600CD42F8 /* complete-quiet.aifc in Resources */, - FD29A11F29E4EB71001923B4 /* seed3-10y.crt in Resources */, 45B74A772044AAB600CD42F8 /* hello.aifc in Resources */, 45B74A7C2044AAB600CD42F8 /* hello-quiet.aifc in Resources */, 7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */, 45B74A792044AAB600CD42F8 /* input.aifc in Resources */, - FD29A12029E4EB71001923B4 /* seed1-10y.crt in Resources */, + FDDCBDAB29E776BF00303C38 /* seed2-2023-2y.der in Resources */, C3CA3ABE255CDB0D00F4C6D4 /* portuguese.txt in Resources */, 45B74A8C2044AAB600CD42F8 /* input-quiet.aifc in Resources */, + FDDCBDAC29E776BF00303C38 /* seed3-2023-2y.crt in Resources */, + FDDCBDA829E776BF00303C38 /* seed2-2023-2y.crt in Resources */, 45B74A7A2044AAB600CD42F8 /* keys.aifc in Resources */, 45B74A762044AAB600CD42F8 /* keys-quiet.aifc in Resources */, 45B74A862044AAB600CD42F8 /* note.aifc in Resources */, @@ -4744,8 +4745,7 @@ 45B74A822044AAB600CD42F8 /* pulse.aifc in Resources */, C3CA3AC8255CDB2900F4C6D4 /* spanish.txt in Resources */, B8FF8E6225C10DA5004D1F22 /* GeoLite2-Country-Blocks-IPv4 in Resources */, - FD29A11E29E4EB71001923B4 /* seed2-10y.der in Resources */, - FD29A12229E4EB71001923B4 /* seed2-10y.crt in Resources */, + FDDCBDAD29E776BF00303C38 /* seed3-2023-2y.der in Resources */, 45B74A802044AAB600CD42F8 /* pulse-quiet.aifc in Resources */, 45B74A8B2044AAB600CD42F8 /* synth.aifc in Resources */, 45B74A752044AAB600CD42F8 /* synth-quiet.aifc in Resources */, diff --git a/Session/Meta/Certificates/seed1-10y.crt b/Session/Meta/Certificates/seed1-10y.crt deleted file mode 100644 index 57199d80b..000000000 --- a/Session/Meta/Certificates/seed1-10y.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEDTCCAvWgAwIBAgIUWk96HLAovn4uFSI057KhnMxqosowDQYJKoZIhvcNAQEL -BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN -ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x -HTAbBgNVBAMMFHNlZWQxLmdldHNlc3Npb24ub3JnMB4XDTIzMDQwNTAxMjQzNVoX -DTMzMDQwNTAxMjQzNVowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh -MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo -IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQxLmdldHNlc3Npb24ub3JnMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2wlGkR2aDOHoizik4mqvWEwDPOQG -o/Afd/6VqKzo4BpNerVZQNgdMgdLTedZE4FRfetubonYu6iSYALK2iKoGsIlru1u -Q9dUl0abA9v+yg6duh1aHw8oS16JPL0zdq8QevJaTxd0MeDnx4eXfFjtv8L0xO4r -CRFH+H6ATcJy+zhVBcWLjiNPe6mGSHM4trx3hwJY6OuuWX5FkO0tMqj9aKJtJ+l0 -NArra0BZ9MaMwAFE7AxWwyD0jWIcSvwK06eap+6jBcZIr+cr7fPO5mAlT+CoGB68 -yUFwh1wglcVdNPoa1mbFQssCsCRa3MWgpzbMq+KregVzjVEtilwLFjx7FQIDAQAB -o4GKMIGHMB0GA1UdDgQWBBQ1XAjGKhyIU22mYdUEIlzlktogNzAfBgNVHSMEGDAW -gBQ1XAjGKhyIU22mYdUEIlzlktogNzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY -MBaCFHNlZWQxLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G -CSqGSIb3DQEBCwUAA4IBAQC4PRiu4LyxK71Gk+f3dDvjinuE9F0XtAamKfRlLMEo -KxK8dtLrT8p62rME7QiigSv15AmSNyqAp751N/j0th1prOnxBoG38BXKLBDDClri -u91MR4h034G6LIYCiM99ldc8Q5a5WCKu9/9z6CtVxZcNlfe477d6lKHSwb3mQ581 -1Ui3RnpkkU1n4XULI+TW2n/Hb8gN6IyTHFB9y2jb4kdg7N7PZIN8FS3n3XGiup9r -b/Rujkuy7rFW78Q1BuHWrQPbJ3RU2CKh1j5o6mtcJFRqP1PfqWmbuaomam48s5hU -4JEiR9tyxP+ewl/bToFcet+5Lp9wRLxn0afm/3V00WyP ------END CERTIFICATE----- diff --git a/Session/Meta/Certificates/seed1-10y.der b/Session/Meta/Certificates/seed1-10y.der deleted file mode 100644 index 0c89bb8b97a5a1971dd2b876798ae5aee34e390d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1041 zcmXqLV&OGtV*0v(nTe5!NhHd@N@jz`zB)ZoC6niy7S1`7wdj-qFB_*;n@8JsUPeZ4 zRtAGALv903Hs(+kHen{mP(wijeh`O)ha)UAxg@_RGtp4UKma7f&co@Onv;}YT9lV+ zsA`}L664~L@vlhDQwS)^EK5wTR0v5;&QNg6FU?CyEXmBzGn6%u1}S6a5h+ehO)=C< zPc11E#!t8_0?C8W|gy7?>Iu8krcIMv3zpBXbATZB2|y$U()(%D~*j$j@NV z#K^_e#K_2So6~Kg>@1##FS;$3Jjz-h;lph6gl+K$`SO2LSFCyQK+3mjYox;sStE9D z-{+CSje)hV^YS`x>|QY`f$7vOr4>?#RM)-DbG{xj-EB7W?SH5E=I)Y>lIPd(j_b79 zYh1Qopz2eUzj%q^gXhQFr`JTh-GAuIk$2jhg6==+8hj5G{k8~YJ=)!;>|edI&7;_2 z+n(}vrid4>*G1O3PI#+pwBm2ZqFnWtB_>?2vmGM89P2s2=<qD;(;42+8#y9^rJ4P=3lEX&6t#v)=G!*NVYrXx6aS>ja| zrI@FaZYh`>$b+PnStJa^8n7z>B{^APM#ldvtOm?L3Zg?0q@xLY$`J&IQyEni~Ax3DeK{F173P(xd2 zPuZo{{->&LZDx7Pv8Yk|>l4mN=2{KQ_m!Ie__9qlbIr?-Y>nGLh@R3BILsCGX!l(o z_l}bLjk|Q(m^#kaPQ7m9JZ)!$(z@^ei(hDm9-YoR_4|(Z+pDH5ymWByGw1oHS3S17 zRi#YyO@CO*t^DNLt@`8nCwO1FB_*;n@8JsUPeZ4 zRtAGALv903Hs(+kHen{mP(wijeh`O)ha)UAxg@_RGtp4UKma7f&co@Onv;}YT9lV+ zsA`}L664~L@vlhDQwS)^EK5wTR0v5;&QNg6FU?CyEXmBzGn6%u1}S6a5h+ehO)=C< zPc11E#!t8_0?C8W|gy7#bNGnVK0HMFF{{K&~N_JD_fBVpKv7Dn?cY<|amd z27@L>E~X|%Mux+Z(o8etMR@a;G$udZv+te%sx@a-4K}>rT6^XDj5%R!zhyECwJnWv zl-Cq2OAkA>)%>pP+SbH(w@l>MN8h@)>0SKZ)yra&?mYVH%e!Fzxy+ptU&L?tTa__U z*qY^>&l%m9kgvZI3@lx>nltANcfIs^t5NVDX*urGHM}DUUemZFKzo zp;bMbgMEwUw@q!BAGPzF-u)w7^D6GUyqKCL6d#xCv#018kNUpI4M&?!l_fhTo9=S( z^w73&K0Bu=F1hH#BcBhZfopOV+?zf<@ch=ieA2JU=Ys>C8ZLD{l?dSdbKtsu!`V8M zl{5bwKP7Av&(!%vc~acz_e<*%Wto^685kEgb{RCb8^{79S(cAQj76kqnf3bgcli+p z6%!A&d^yRwHmYR1fjmfBnMJ}ttO2_MP?D1sW@P-&!fL<_q#!y3K{}eSryOCB4pkN& z11>fWZ8k<$R(57akhc^ADDe*q1(f^SN>0a$NbW5`$C_@nb$Xo zdjFec!j&F5zwp|pO&uwawfW4Q@DLYCLcqM?3~*jQ*}={MA~n?k=A*Ejqewm+2TzXR_pMt z=*hj6_C8+y+o2cPkI(1M$oY9MY1ZB9-730y!7FUfEvgVnme|m*ne8ZZ4> lD%MXr`*`mx-_Cyl8KR%r=AQW&dm${>fgy$0dYb1nYXCB*lso_c literal 0 HcmV?d00001 diff --git a/Session/Meta/Certificates/seed2-10y.crt b/Session/Meta/Certificates/seed2-10y.crt deleted file mode 100644 index bf14073c2..000000000 --- a/Session/Meta/Certificates/seed2-10y.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEDTCCAvWgAwIBAgIUXkVaUNO/G727mNeaiso9MjvBEm4wDQYJKoZIhvcNAQEL -BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN -ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x -HTAbBgNVBAMMFHNlZWQyLmdldHNlc3Npb24ub3JnMB4XDTIzMDQwNTAxMjI0MloX -DTMzMDQwNTAxMjI0MlowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh -MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo -IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQyLmdldHNlc3Npb24ub3JnMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvT493tt1EWdyIa++X59ffrQt+ghK -+3Hv/guCPmR0FxPUeVnayoLbeKgbe8dduThh7nlmlYnpwbulvDnMF/rRpX51AZiT -A8UGktBzGXi17/D/X71EXGqlM41QZfVm5MCdQcghvbwO8MP0nWmbV4DdiNYAwSNh -fpGMEiblCvKtGN71clTkOW+8Moq4eOxT9tKIlOv97uvkUS21NgmSzsj453hrb6oj -XR3rtW264zn99+Gv83rDE1jk0qfDjxCkaUb0BvRDREc+1q3p8GZ6euEFBM3AcXe7 -Yl0qbJgIXd5I+W5nMJJCyJHPTxQNvS+uJqL4kLvdwQRFAkwEM+t9GCH1PQIDAQAB -o4GKMIGHMB0GA1UdDgQWBBQOdqxllTHj+fmGjmdgIXBl+k0PRDAfBgNVHSMEGDAW -gBQOdqxllTHj+fmGjmdgIXBl+k0PRDAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY -MBaCFHNlZWQyLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G -CSqGSIb3DQEBCwUAA4IBAQBkmmX+mopdnhzQC5b5rgbU7wVhlDaG7eJCRgUvqkYm -Pbv6XFfvtshykhw2BjSyQetofJaBh5KOR7g0MGRSn3AqRPBeEpXfkBI9urhqFwBF -F5atmp1rTCeHuAS6w4mL6rmj7wHl2CRSom7czRdUCNM+Tu1iK6xOrtOLwQ1H1ps1 -KK3siJb3W0eKykHnheQPn77RulVBNLz1yedEUTVkkuVhzSUj5yc8tiwrcagwWX6m -BlfVCJgsBbrJ754rg0AJ0k59wriRamimcUIBvKIo3g3UhJHDI8bt4+SvsRYkSmbi -rzVthAlJjSlRA28X/OLnknWcgEdkGhu0F1tkBtVjIQXd ------END CERTIFICATE----- diff --git a/Session/Meta/Certificates/seed2-10y.der b/Session/Meta/Certificates/seed2-10y.der deleted file mode 100644 index d4cfa66fcbaa12ee437400d974d0cee416562dfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1041 zcmXqLV&OGtV*0v(nTe5!NhHoSD&X>d>AkyWT%Xl-%GSvGpirIxFB_*;n@8JsUPeZ4 zRtAGALv903Hs(+kHen{mP(wijeh`O)ha)UAxg@_RGtp4UKma7f&co@Onv;}YT9lV+ zsA`}L664~L@vlhDQwS)^EK5wTR0v5;&QNg6FU?CyEXmBzGn6%u1}S6a5h+ehO)=6- zPc11E#!t8_0?C8W|gy7?>Iu8X1`wMTzqoBXbATZB2|y$U()(%D~*j$j@NV z#K^_e#K_37*Ut9d?NY(?BE|Ll;^)WLZPES3;q|-l{Xgy|yOa`f;VYGqw@x+Pu2>;m zeLQxjMdG{4w5gph5AI&N$MTH$uZv6TN*QNNWSfiiOE`O+#8Vk zHSNiPxsE3k_wM2QaQMsI%-P`$cRQ{z98^xMo7f|y_LS?>T8VpKi$b1Q=I=4;+EMW) z_}it9DX;&&d;KI(cdHraq;n^JJg>;kU!@!?`+95cuE&;tzdv06x$3ZR#FI^+a4dFcj|oK8$U?=Ql;SAU(_ zq8}4>-#y6U%H+dh{JK^`@vAKpGb01z;>Iq6#&!c)U?j`(v52vV@RhAeooe{_=g+pj z^aRC%)L*{*E(Y=-X=N4(1F;6|3P4FtR+y3TKMSh?GmwJl5CrLH!k%)3K{`}fcnrAM zIJDUqSy|bc8Ihw4n6iP<#mJB{EA`*3uGo1p7r3YWT*r3hJ!|3=v$nU7oZMLTSGlR# z?*0`M{(jquqDeAlY$lr=UuV=zYiys?=f1Q_i&hKrK_8Px+m%9)0x?h`Zs z$F%R!?p>!GpSM2YpTF1 diff --git a/Session/Meta/Certificates/seed2-2023-2y.crt b/Session/Meta/Certificates/seed2-2023-2y.crt new file mode 100644 index 000000000..fea4fd4f5 --- /dev/null +++ b/Session/Meta/Certificates/seed2-2023-2y.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDTCCAvWgAwIBAgIUaPiMYcZh7cZZfacCni2NwT5DKh4wDQYJKoZIhvcNAQEL +BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN +ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x +HTAbBgNVBAMMFHNlZWQyLmdldHNlc3Npb24ub3JnMB4XDTIzMDQxMjEyNTY0NVoX +DTI1MDQxMTEyNTY0NVowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh +MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo +IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQyLmdldHNlc3Npb24ub3JnMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh2UcfW0I+1QWRa3cj7RnMGelYkGK +7l4V6q7je1IkudXBNretkvVF1NCpfZ8dz72JmdGPJ5/uIEW15HDD2L63OmSDVPhA +2JCb/NqmXfeO91lyxgb0sDnN1UH0wzuS75aBjaQ0nXQV3ffmqKnNNv0HK+LTMFD+ +Dv2yGDtZTWH6H3VzPLCvHHYXVdyuQHwchAcNQar5k4dbdEIcYIV+ANccPg7iQ81a +ITZ9bCeACdMqbB9gILq21KWdkxCu1fwSXs/B6n+U4UpJyv87fprvAyU3HqQhqlU7 +dHnzA1dPn8D4a/3CMYZogVm8USNjv4HmWIwKbYDX+VahvuZwEi6+pwEurQIDAQAB +o4GKMIGHMB0GA1UdDgQWBBRxVM4+gFFipZFAg+Fs4x580js+2TAfBgNVHSMEGDAW +gBRxVM4+gFFipZFAg+Fs4x580js+2TAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY +MBaCFHNlZWQyLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G +CSqGSIb3DQEBCwUAA4IBAQBIFj6hsOgNVr2kZufimTxoT1TE8uvycIWyt04q6/nP +8h33u/sHuNPdnr2UewqRyDRFefxrGlqBUQAQJVyzJGIlju/HTZaBnVB0H2smCRtK +ZRHAJ/cwcnAp+STjqgPqt1ZZ6JcfFwJZID4pPmrW8WaQNAtQPi2Ly2JLQ+Ym5wus +aGxGjbDRQSWGmUpg5TE+XdDsHeJtCl6HAEjvtXfq1uzKedRzmqYfIa8Rd7b2tmuy +dN27swR4DRJOK4rAxHnI8jt7GKVtPXnYfRuk2+0dVZ4CD6qHw+CO5mcdCabnflgT +XS8BYlOvkAyVbtmZNAacoUZvPRx3o186BMJoK2coQyFN +-----END CERTIFICATE----- diff --git a/Session/Meta/Certificates/seed2-2023-2y.der b/Session/Meta/Certificates/seed2-2023-2y.der new file mode 100644 index 0000000000000000000000000000000000000000..acc374d570287fb320e687996b01e0d3a8b18ac5 GIT binary patch literal 1041 zcmXqLV&OGtV*0v(nTe5!NhIS(PvWt}x5pxDmov@N?LBDctR-i_%f_kI=F#?@mywa1 zmBFCOklTQhjX9KsO_<3s)KJiXAH?C{;Rwr2F3B&-Of(cS5C93W^Kkm6<|O5p7UiWH zsv0PR#JG55{3}xP6atDe%Mz0-6+%*zGZftNOY>3^OEUBG3}p?ZLCTnUM2b^WQ;hV| zQ%j0di;IC$dih1^26E!OM#csvhDL@)re-FlQ9!OKkZTC#4yfCj7?qHNijkFpxrvdV z!Jvtei>Zl`k)b_RrZ$)3cZitl+B^MQ(hbs=COLM!ixYjd?s0XH%Fe3?&9<+d^wsss zg_X7QWzX;JoO!Wdef~QI*R4+q4&T_f-72Ly8eMcawy5q;KLc`O;#W4eMpf#6$0_ zbEuJNVdr&R^>cE2bcvHpLTeqvbs0OpN6u%X6wPXL)EhW2Yvsr%DD2vHW$E0>0_(2+ z5sEv1@KybkhhCni{#)10de5wCF1JK+Rj74I@gWdA*6*p|^4xhGILd4J=x zh#s!ohU-7W7VdjiAf&f%IiucMCT2zk#>I_Y2950ovcO1|E?~+AMi(Q4hnU^M4KH}Z_AW_#{%EF6hJVPBPp>}}v~Jq&r}g^h z`A@RncmHPJary4Ny;G{WCY~^Ht^AWM71bEXAfOtvStUue@BMM#X^nFOO60TEIHkQ( z1rMlyHz+F5{HgMI74xg@VUaJU%ZoEbD%fe-WnKH2Ho=5Dz)rXObdtC8Gqva3Ycg`& zdN*8jRBfB-mGIQiF80D3*+;otaqSEq@3)q}y7uN&<(1-D%j6Z;3zl#Dwk>;8$=%(X zSt@vi{It6c9H~6<$+}u%X|8SMjaum?x8KT!&STMahd(CVT(x3k1jnFL#)?HI%YVupX?^rxNwbAGNwnWeEB8983omOM~ zrBui&5$}*xy>}01dVoR5j5R%mu^rpR&KlM9+|7y~EE9Z+I`qG6*sJImd0Tsn>vcohn+5tj8sWpHKr-zgi~Bb5I1 z|FM|e-Fe(C;ooBWl>A5Kj7-do42+8#y9^rJ4P=3lEX&6t#v(F-A$%>*C*v^w>^kO- zx7%-3INtqeAPItVE@O+;+xca|bG8JnKgO-s@s>^CMPaGGCT81Z`aD)ZNEdW-dev> zA<6ZM+BKhV`6nD+q*QtQxug}c?m#c+&*|bqy4!C!x#lsI3o0HBsd^YV>%|A&s=o>E zQyz|D3lH=o`E(ve-ISFMY#{ofrr-JhTL*y<&998u;aI#wa?4Hp;K fAGm%yzVtJz&&w9SS355B8y>qEc;h0k$OCQw-DH7= diff --git a/Session/Meta/Certificates/seed3-2023-2y.crt b/Session/Meta/Certificates/seed3-2023-2y.crt new file mode 100644 index 000000000..8f9654cdb --- /dev/null +++ b/Session/Meta/Certificates/seed3-2023-2y.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDTCCAvWgAwIBAgIULagRXXdxagFp2IRBaWWNeO5dK+IwDQYJKoZIhvcNAQEL +BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN +ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x +HTAbBgNVBAMMFHNlZWQzLmdldHNlc3Npb24ub3JnMB4XDTIzMDQxMjEyNTY1M1oX +DTI1MDQxMTEyNTY1M1owejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh +MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo +IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQzLmdldHNlc3Npb24ub3JnMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23lBHUMU8xl3ZBPhQJuupNk9pqAW +8UvqyMX2BYWVc6bGpgRiqnf2Rc58Ol9jSM4VT29jXHD+PXXQLIvoZmni/5fbdkZl +zFAvnPFoWf4g4xCdREEpJ7m/sWh8aG6Bf7Eh+sTP6qaspJUPo5q4ovUd4tUoTt7f +bVlnzncXI1z2bhrmxWR8ahl9SwMjd/qKZMFKL3o12f4xhYu0Jfp1aFeKdrRImfZR +X6hzXM6uUe5X+/3mrmKvYCVnNoNCwsdyxTZp4JYXCqhG/g29CbWDFTTqxWVXySFK ++mujbHfWIBvRheYvO9x7Wb2jsPq5VbyP1MoqxPThKjF+FeCfU7X0+Fy+3QIDAQAB +o4GKMIGHMB0GA1UdDgQWBBRXwt1MJe73lcOBv+JHmjqWyypB2DAfBgNVHSMEGDAW +gBRXwt1MJe73lcOBv+JHmjqWyypB2DAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY +MBaCFHNlZWQzLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G +CSqGSIb3DQEBCwUAA4IBAQAb+5FUjLXfgF0QmeBJrpC4B+3gIyw6QGTnbMXM5zVt +zKANoZxeQesZXkSGDTlszI4XnBs/bDzf87AROxDuT0guxt33+PhyXNw+9FdV3CAG +t/8FyRMPyJI8xog0mlPgjVqSw2PGjXtj2uVEkB7gkm6+AoPUfZYdPOplezrpvRES +tMVbjsxxiMiOQAOm1bS69dC16xQ6bZ8++QNZXPhj9o1a+tQCb71Bp2sYI66hCfmy +DRSJEDW7fCPb/da1D8cN68qr5vxIJjm5cWaF4xlN9pc9pywssTbPYhPSluravRDg +qyqfraj2YhdDNOSRj/U6IuYbL+jKWuaTcrEFYyNExxkq +-----END CERTIFICATE----- diff --git a/Session/Meta/Certificates/seed3-2023-2y.der b/Session/Meta/Certificates/seed3-2023-2y.der new file mode 100644 index 0000000000000000000000000000000000000000..e61a11ae8ff27dd16f95b625cd10beadb95b77e0 GIT binary patch literal 1041 zcmXqLV&OGtV*0v(nTe5!Nkn&rU~G9|7Gvg(7RSug-imjz+K&u)**LY@JlekVGBR?r zG8j}DavN~6F^96S2{So{8VVZlgE$;K9ATNsCHX~}iH1T30w5uF9!}rXoTU8HqP$c? zRRd*^7#EL>e?@AZLO@YwSz>afLP%6)%(QmA+Q;U}!TgH;Ks{EVl zxf-kZWRG*A{`tu<1^;YIFX(i?NXvZmfBNk*x70HM`g1;JME+BFEHKx_QB!^A{*4(m z8F`KM8x?;YIsaH?wm2 zudbAXUiww0H~$&7c5hMrRhkjrRkp=r=C{E36~!^<)&;%`|NZybx}^09s_ACUPKS;c z9W~2*Fio6mh1);gy_{Q{MNM8EO$|S(==CdmaZdR)1?h{e&-AVDR7dVzyy4f*&^`TE zPH7$a@=(jLPV~Y2;H_VN#O%Av#LURRxVW*)pt0RR78uF0d@N!tBH@Sb`l!D9KJ{?p z{zvY!tfrmTa=c+650X}9kuVTzz^(w4!Jjwb9WM;N3-m4(NE zi;Y8@jggg=otY6ix_~Jg7+s7E(!VE$^lZJ~5GyeAf#NO_)f^vrY9 z+%pS!7tV=ud@UL0(#C6^RZqz`X40mR(;jY<(?al{??=Cv#-XkK}K?QNOM* = (Features.useTestnet ? [ "http://public.loki.foundation:38157" ] : [ - "https://seed1.getsession.org:4443", - "https://seed2.getsession.org:4443", - "https://seed3.getsession.org:4443" + "https://seed1.getsession.org:4433", + "https://seed2.getsession.org:4433", + "https://seed3.getsession.org:4433" ] ) private static let snodeFailureThreshold = 3 diff --git a/SessionUtilitiesKit/Networking/HTTP.swift b/SessionUtilitiesKit/Networking/HTTP.swift index 34494fb1a..1c5f586c0 100644 --- a/SessionUtilitiesKit/Networking/HTTP.swift +++ b/SessionUtilitiesKit/Networking/HTTP.swift @@ -8,20 +8,24 @@ public enum HTTP { private static let snodeURLSessionDelegate = SnodeURLSessionDelegateImplementation() // MARK: Certificates + + /// **Note:** These certificates will need to be regenerated and replaced at the start of April 2025, iOS has a restriction after iOS 13 + /// where certificates can have a maximum lifetime of 825 days (https://support.apple.com/en-au/HT210176) as a result we + /// can't use the 10 year certificates that the other platforms use private static let storageSeed1Cert: SecCertificate = { - let path = Bundle.main.path(forResource: "seed1-10y", ofType: "der")! + let path = Bundle.main.path(forResource: "seed1-2023-2y", ofType: "der")! let data = try! Data(contentsOf: URL(fileURLWithPath: path)) return SecCertificateCreateWithData(nil, data as CFData)! }() private static let storageSeed2Cert: SecCertificate = { - let path = Bundle.main.path(forResource: "seed2-10y", ofType: "der")! + let path = Bundle.main.path(forResource: "seed2-2023-2y", ofType: "der")! let data = try! Data(contentsOf: URL(fileURLWithPath: path)) return SecCertificateCreateWithData(nil, data as CFData)! }() private static let storageSeed3Cert: SecCertificate = { - let path = Bundle.main.path(forResource: "seed3-10y", ofType: "der")! + let path = Bundle.main.path(forResource: "seed3-2023-2y", ofType: "der")! let data = try! Data(contentsOf: URL(fileURLWithPath: path)) return SecCertificateCreateWithData(nil, data as CFData)! }() @@ -60,48 +64,24 @@ public enum HTTP { case .recoverableTrustFailure: /// A recoverable failure generally suggests that the certificate was mostly valid but something minor didn't line up, - /// iOS has a specific rule which rejects certificates which have a lifetime over 825 days which we don't really care - /// about so if we end up with a single issue which is `OtherTrustValidityPeriod` then we can just allow - /// the request to continue - guard - let validationResult: [String: Any] = SecTrustCopyResult(trust) as? [String: Any], - let details: [String: Any] = (validationResult["TrustResultDetails"] as? [[String: Any]])? - .reduce(into: [:], { result, next in next.forEach { result[$0.key] = $0.value } }), - let otherTrustValidityPeriod: Int = details["OtherTrustValidityPeriod"] as? Int, - details.count == 1, - otherTrustValidityPeriod == 0, - let exceptions: CFData = SecTrustCopyExceptions(trust), - SecTrustSetExceptions(trust, exceptions) - else { - let reason: String = { - guard - let validationResult: [String: Any] = SecTrustCopyResult(trust) as? [String: Any], - let details: [String: Any] = (validationResult["TrustResultDetails"] as? [[String: Any]])? - .reduce(into: [:], { result, next in next.forEach { result[$0.key] = $0.value } }) - else { return "Unknown" } - - return "\(details)" - }() - - SNLog("Failed to handle a recoverable seed certificate trust failure: \(reason)") - return completionHandler(.cancelAuthenticationChallenge, nil) - } + /// while we don't want to recover in this case it's probably a good idea to include the reason in the logs to simplify + /// debugging if it does end up happening + let reason: String = { + guard + let validationResult: [String: Any] = SecTrustCopyResult(trust) as? [String: Any], + let details: [String: Any] = (validationResult["TrustResultDetails"] as? [[String: Any]])? + .reduce(into: [:], { result, next in next.forEach { result[$0.key] = $0.value } }) + else { return "Unknown" } + + return "\(details)" + }() - /// Now that the `trust` has been updated with the exceptions it can ignore we need to try to re-evaluate it - /// to ensure it is now seen as valid - var error2: CFError? = nil - guard SecTrustEvaluateWithError(trust, &error2) else { - SNLog("Seed certificate reevaluation failed due to error: \(String(describing: error2))") - return completionHandler(.cancelAuthenticationChallenge, nil) - } + SNLog("Failed to validate a seed certificate with a recoverable error: \(reason)") + return completionHandler(.cancelAuthenticationChallenge, nil) - /// If the reevaluation succeeded then try to use the credential - /// - /// **Note:** It is still possible for the OS to reject the request (which seems to be happening with an expired - /// certificate) but it _does_ seem to work fine with the 10 year certificate - return completionHandler(.useCredential, URLCredential(trust: trust)) - - default: return completionHandler(.cancelAuthenticationChallenge, nil) + default: + SNLog("Failed to validate a seed certificate with an unrecoverable error.") + return completionHandler(.cancelAuthenticationChallenge, nil) } } From 5cbb4f632d5e3757ad87ec985d53704961b301c7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Apr 2023 11:03:47 +1000 Subject: [PATCH 42/44] Fix for PR comment --- SessionMessagingKit/Shared Models/MessageViewModel.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SessionMessagingKit/Shared Models/MessageViewModel.swift b/SessionMessagingKit/Shared Models/MessageViewModel.swift index 0523224ed..c7261aafa 100644 --- a/SessionMessagingKit/Shared Models/MessageViewModel.swift +++ b/SessionMessagingKit/Shared Models/MessageViewModel.swift @@ -739,7 +739,9 @@ public extension MessageViewModel { LEFT JOIN \(Interaction.self) AS \(quoteInteraction) ON ( \(quoteInteraction).\(timestampMsColumn) = \(quote[.timestampMs]) AND ( \(quoteInteraction).\(authorIdColumn) = \(quote[.authorId]) OR ( - \(quoteInteraction).\(authorIdColumn) = '' AND + -- A users outgoing message is stored in some cases using their standard id + -- but the quote will use their blinded id so handle that case + \(quote[.authorId]) = \(blindedPublicKey ?? "''") AND \(quoteInteraction).\(authorIdColumn) = \(userPublicKey) ) ) From 699a8200aef76a6f770bc11a68f1d411ac29324a Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Apr 2023 11:12:14 +1000 Subject: [PATCH 43/44] Updated the port being used for the seed nodes --- SessionSnodeKit/SnodeAPI.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index bc043aa4f..d3a542f03 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -50,9 +50,9 @@ public final class SnodeAPI { private static let seedNodePool: Set = (Features.useTestnet ? [ "http://public.loki.foundation:38157" ] : [ - "https://seed1.getsession.org:4433", - "https://seed2.getsession.org:4433", - "https://seed3.getsession.org:4433" + "https://seed1.getsession.org:4432", + "https://seed2.getsession.org:4432", + "https://seed3.getsession.org:4432" ] ) private static let snodeFailureThreshold = 3 From b5004f38df1dd1048bc69b7f70772ffe180d4264 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 13 Apr 2023 12:09:19 +1000 Subject: [PATCH 44/44] Increased build and version numbers --- Session.xcodeproj/project.pbxproj | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 44aceb456..5910fddad 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -111,11 +111,11 @@ 7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1D74AF27C365960030B423 /* Timer+MainThread.swift */; }; 7B2561C22978B307005C086C /* MediaInfoVC+MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */; }; 7B2561C429874851005C086C /* SessionCarouselView+Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */; }; + 7B2E985829AC227C001792D7 /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */; }; 7B3A392E2977791E002FE4AC /* MediaInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */; }; 7B3A3930297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */; }; 7B3A39322980D02B002FE4AC /* SessionCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */; }; 7B3A3934298882D6002FE4AC /* SessionCarouselViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3A3933298882D6002FE4AC /* SessionCarouselViewDelegate.swift */; }; - 7B2E985829AC227C001792D7 /* UIContextualAction+Theming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */; }; 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; @@ -1188,11 +1188,11 @@ 7B2561C12978B307005C086C /* MediaInfoVC+MediaInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaInfoView.swift"; sourceTree = ""; }; 7B2561C329874851005C086C /* SessionCarouselView+Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionCarouselView+Info.swift"; sourceTree = ""; }; 7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; + 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; 7B3A392D2977791E002FE4AC /* MediaInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInfoVC.swift; sourceTree = ""; }; 7B3A392F297A3919002FE4AC /* MediaInfoVC+MediaPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaInfoVC+MediaPreviewView.swift"; sourceTree = ""; }; 7B3A39312980D02B002FE4AC /* SessionCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselView.swift; sourceTree = ""; }; 7B3A3933298882D6002FE4AC /* SessionCarouselViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCarouselViewDelegate.swift; sourceTree = ""; }; - 7B2E985729AC227C001792D7 /* UIContextualAction+Theming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Theming.swift"; sourceTree = ""; }; 7B46AAAE28766DF4001AF2DC /* AllMediaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllMediaViewController.swift; sourceTree = ""; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = ""; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = ""; }; @@ -6076,7 +6076,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 401; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6101,7 +6101,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.11; + MARKETING_VERSION = 2.2.12; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6149,7 +6149,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 401; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6179,7 +6179,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.11; + MARKETING_VERSION = 2.2.12; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -6215,7 +6215,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 401; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -6238,7 +6238,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.11; + MARKETING_VERSION = 2.2.12; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6289,7 +6289,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 401; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6317,7 +6317,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.11; + MARKETING_VERSION = 2.2.12; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -7217,7 +7217,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 401; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7256,7 +7256,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.2.11; + MARKETING_VERSION = 2.2.12; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -7289,7 +7289,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 400; + CURRENT_PROJECT_VERSION = 401; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -7328,7 +7328,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 2.2.11; + MARKETING_VERSION = 2.2.12; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session;