diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index 7442ac9c9..a62eab26d 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -12,8 +12,9 @@ #import "ActiveChatsViewController.h" #import "DataLayer.h" #import "xmpp.h" +#import "MLNotificationManager.h" +#import "MLXMPPManager.h" #import "MLContactCell.h" -#import "chatViewController.h" #import "MonalAppDelegate.h" #import "MLImageManager.h" #import "MLXEPSlashMeHandler.h" @@ -1047,26 +1048,28 @@ -(void) presentChatWithContact:(MLContact*) contact andCompletion:(monal_id_bloc completion(@NO); return; } - + + //this will open the chat + monal_void_block_t presentator = ^{ + UIViewController* chatView = [[SwiftuiInterface new] makeChatViewFor:contact]; + chatView.ml_disposeCallback = ^{ + [self sheetDismissed]; + }; + [self scrollToContact:contact]; + [self showDetailViewController:chatView sender:self]; + if(completion != nil) + completion(@YES); + }; + //open chat (make sure we have an active buddy for it and add it to our ui, if needed) //but don't animate this if the contact is already present in our list [[DataLayer sharedInstance] addActiveBuddies:contact.contactJid forAccount:contact.accountID]; if([[self getChatArrayForSection:pinnedChats] containsObject:contact] || [[self getChatArrayForSection:unpinnedChats] containsObject:contact]) - { - [self scrollToContact:contact]; - [self performSegueWithIdentifier:@"showConversation" sender:contact]; - if(completion != nil) - completion(@YES); - } + presentator(); else - { [self insertOrMoveContact:contact completion:^(BOOL finished __unused) { - [self scrollToContact:contact]; - [self performSegueWithIdentifier:@"showConversation" sender:contact]; - if(completion != nil) - completion(@YES); + presentator(); }]; - } }]; }]; } @@ -1103,15 +1106,7 @@ -(void) performSegueWithIdentifier:(NSString*) identifier sender:(id) sender -(void) prepareForSegue:(UIStoryboardSegue*) segue sender:(id) sender { DDLogInfo(@"Got segue identifier '%@'", segue.identifier); - if([segue.identifier isEqualToString:@"showConversation"]) - { - UINavigationController* nav = segue.destinationViewController; - chatViewController* chatVC = (chatViewController*)nav.topViewController; - UIBarButtonItem* barButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; - self.navigationItem.backBarButtonItem = barButtonItem; - [chatVC setupWithContact:sender]; - } - else if([segue.identifier isEqualToString:@"showDetails"]) + if([segue.identifier isEqualToString:@"showDetails"]) { UIViewController* detailsViewController = [[SwiftuiInterface new] makeContactDetails:sender]; detailsViewController.ml_disposeCallback = ^{ @@ -1527,6 +1522,9 @@ -(void) dismissRecursorWithViewControllers:(NSMutableArray*) viewControllers ani -(chatViewController* _Nullable) currentChatView { + //TODO: this has to be adapted to the new chatui + return nil; + /* NSArray* controllers = ((UINavigationController*)self.splitViewController.viewControllers[0]).viewControllers; chatViewController* chatView = nil; if(controllers.count > 1) @@ -1534,6 +1532,7 @@ -(chatViewController* _Nullable) currentChatView if(![chatView isKindOfClass:NSClassFromString(@"chatViewController")]) chatView = nil; return chatView; + */ } -(void) scrollToContact:(MLContact*) contact diff --git a/Monal/Classes/ChatView.swift b/Monal/Classes/ChatView.swift new file mode 100644 index 000000000..fa3b03c4d --- /dev/null +++ b/Monal/Classes/ChatView.swift @@ -0,0 +1,465 @@ +// +// ChatView.swift +// Monal +// +// Created by Thilo Molitor on 05.09.24. +// Copyright © 2024 monal-im.org. All rights reserved. +// + +import FrameUp +import ExyteChat +typealias ExyteChatView = ExyteChat.ChatView + + +/* +struct MonalViewDefaults: ViewModifier { + @Binding public var alertPrompt: AlertPrompt? + + public func body(content: Content) -> some View { + content + //TODO: modernize alert prompt usage in all other swiftui files to be in line with this implementation here + //TODO: e.g. non-hardcoded dismiss button text and usage of optionalMappedToBool and dismissCallback + .alert(isPresented: $alertPrompt.optionalMappedToBool()) { + let callback = alertPrompt!.dismissCallback + return Alert(title: alertPrompt!.title, message: alertPrompt!.message, dismissButton:.default(alertPrompt!.dismissLabel, action: { + if let callback = callback { + callback() + } + })) + } + } +} + +private struct AssociatedMonalViewKeys { + static var AlertPromptKey = "ml_alertPromptKey" +} + +extension View { + func addMonalViewDefaults() -> some View { + //see https://medium.com/@marcosantadev/stored-properties-in-swift-extensions-615d4c5a9a58 + modifier(MonalViewDefaults(alertPrompt:Binding( + get: { + print("Getter called...") + return AlertPrompt( + title: Text("No OMEMO keys found"), + message: Text("This contact may not support OMEMO encrypted messages. Please try to enable encryption again in a few seconds, if you think this is wrong."), + dismissLabel: Text("Disable Encryption") + ) +// guard let value = objc_getAssociatedObject(self, &AssociatedMonalViewKeys.AlertPromptKey) as? AlertPrompt else { +// return nil +// } +// return value + }, + set: { + print("Setting: \(String(describing:$0))") + if let value = $0 { + objc_setAssociatedObject(self, &AssociatedMonalViewKeys.AlertPromptKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } else { + objc_setAssociatedObject(self, &AssociatedMonalViewKeys.AlertPromptKey, nil, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + ))) + } +} + +protocol MonalView: View { + associatedtype Content: View + @ViewBuilder var content: Self.Content { get } +} + +extension MonalView { + var body: some View { + content + .addMonalViewDefaults() + } + + func showAlert(_ prompt: AlertPrompt) { + objc_setAssociatedObject(self, &AssociatedMonalViewKeys.AlertPromptKey, prompt, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + content.id(UUID()) + } +} +*/ + + +struct ChatView: View { + @Environment(\.presentationMode) private var presentationMode + + @StateObject var contact: ObservableKVOWrapper + @State private var selectedContactForContactDetails: ObservableKVOWrapper? + @State private var alertPrompt: AlertPrompt? + @State private var confirmationPrompt: ConfirmationPrompt? + @StateObject private var overlay = LoadingOverlayState() + @State var messages: [ChatViewMessage] = [] + private var account: xmpp + + init(contact: ObservableKVOWrapper) { + _contact = StateObject(wrappedValue: contact) + account = contact.obj.account! + } + + private func showCannotEncryptAlert(_ show: Bool) { + if show { + DDLogVerbose("Showing cannot encrypt alert...") + alertPrompt = AlertPrompt( + title: Text("Encryption Not Supported"), + message: Text("This contact does not appear to have any devices that support encryption, please try again later if you think this is wrong."), + dismissLabel: Text("Close") + ) + } else { + alertPrompt = nil + } + } + + private func showShouldDisableEncryptionConfirmation(_ show: Bool) { + if show { + DDLogVerbose("Showing should disable encryption confirmation...") + confirmationPrompt = ConfirmationPrompt( + title: Text("Disable encryption?"), + message: Text("Do you really want to disable encryption for this contact?"), + buttons: [ + .cancel( + Text("No, keep encryption activated"), + action: { } + ), + .destructive( + Text("Yes, deactivate encryption"), + action: { + showCannotEncryptAlert(!contact.obj.toggleEncryption(!contact.isEncrypted)) + } + ) + ] + ) + } else { + confirmationPrompt = nil + } + } + + private func checkOmemoSupport(withAlert showWarning: Bool) { +#if !DISABLE_OMEMO + if DataLayer.sharedInstance().isAccountEnabled(contact.accountID) { + var omemoDeviceForContactFound = false + if !contact.isMuc { + omemoDeviceForContactFound = account.omemo.knownDevices(forAddressName:contact.contactJid).count > 0 + } else { + omemoDeviceForContactFound = false + for participant in DataLayer.sharedInstance().getMembersAndParticipants(ofMuc:contact.contactJid, forAccountID:contact.accountID) { + if let participant_jid = participant["participant_jid"] as? String { + omemoDeviceForContactFound = omemoDeviceForContactFound || account.omemo.knownDevices(forAddressName:participant_jid).count > 0 + } else if let participant_jid = participant["member_jid"] as? String { + omemoDeviceForContactFound = omemoDeviceForContactFound || account.omemo.knownDevices(forAddressName:participant_jid).count > 0 + } + if omemoDeviceForContactFound { + break + } + } + } + if !omemoDeviceForContactFound && contact.isEncrypted { + if HelperTools.isContactBlacklistedForEncryption(contact.obj) { + // this contact was blacklisted for encryption + // --> disable it + contact.isEncrypted = false + DataLayer.sharedInstance().disableEncrypt(forJid:contact.contactJid, andAccountID:contact.accountID) + } else if contact.isMuc && contact.mucType != kMucTypeGroup { + // a channel type muc has OMEMO encryption enabled, but channels don't support encryption + // --> disable it + contact.isEncrypted = false + DataLayer.sharedInstance().disableEncrypt(forJid:contact.contactJid, andAccountID:contact.accountID) + } else if !contact.isMuc || (contact.isMuc && contact.mucType == kMucTypeGroup) { + hideLoadingOverlay(overlay) + + if showWarning { + DDLogWarn("Showing omemo not supported alert for: \(self.contact)") + alertPrompt = AlertPrompt( + title: Text("No OMEMO keys found"), + message: Text("This contact may not support OMEMO encrypted messages. Please try to enable encryption again in a few seconds, if you think this is wrong."), + dismissLabel: Text("Disable Encryption") + ) { + contact.isEncrypted = false + DataLayer.sharedInstance().disableEncrypt(forJid:contact.contactJid, andAccountID:contact.accountID) + } + } else { + DDLogInfo("Trying to fetch omemo keys for: \(self.contact)") + + // we won't do this twice, because the user won't be able to change isEncrypted to YES, + // unless we have omemo devices for that contact + showPromisingLoadingOverlay(overlay, headlineView:Text("Loading OMEMO keys"), descriptionView:Text("")).done { + // request omemo devicelist + account.omemo.subscribeAndFetchDevicelistIfNoSessionExists(forJid:contact.contactJid) + } + } + } + } else { + hideLoadingOverlay(overlay) + } + } +#endif +} + + var body: some View { + ExyteChatView(messages: messages, chatType: .conversation, replyMode: .quote) { draft in + print("sending draft: \(String(describing:draft))") + } messageBuilder: { message, positionInUserGroup, positionInCommentsGroup, showContextMenuClosure, messageActionClosure, showAttachmentClosure in + MessageView(message: ObservableKVOWrapper((message as! ChatViewMessage).message)) + } +// .enableLoadMore(pageSize: 3) { message in +// print("load more messages before: \(String(describing:message))") +// } +// .messageUseMarkdown(messageUseMarkdown: true) + .sheet(item: $selectedContactForContactDetails) { selectedContact in + AnyView(AddTopLevelNavigation(withDelegate:nil, to:ContactDetails(delegate:nil, contact:selectedContact))) + } + //TODO: modernize action sheet usage in all other swiftui files to be in line with this implementation here + //TODO: e.g. same usage like alert prompt below + .actionSheet(isPresented: $confirmationPrompt.optionalMappedToBool()) { + ActionSheet(title: confirmationPrompt!.title, message: confirmationPrompt!.message, buttons: confirmationPrompt!.buttons) + } + //TODO: modernize alert prompt usage in all other swiftui files to be in line with this implementation here + //TODO: e.g. non-hardcoded dismiss button text and usage of optionalMappedToBool and dismissCallback + //somehow the order of alert modifiers is important: they have to come after all sheet modifiers + .alert(isPresented: $alertPrompt.optionalMappedToBool()) { + let callback = alertPrompt!.dismissCallback + return Alert(title: alertPrompt!.title, message: alertPrompt!.message, dismissButton:.default(alertPrompt!.dismissLabel, action: { + if let callback = callback { + callback() + } + })) + } + .toolbar { + ToolbarItem(placement: .principal) { + //make sure to take all space available, otherwise we'll get aligned to the center + //of the navigation bar instead of the leading edge + ZStack { + Color.clear + + HStack { + Button { + selectedContactForContactDetails = contact + } label: { + HStack { + Image(uiImage: contact.avatar) + .resizable() + .scaledToFill() + .frame(width: 35, height: 35) + .clipShape(Circle()) + + VStack(alignment: .leading, spacing: 0) { + Text(contact.contactDisplayName as String) + .fontWeight(.semibold) + .font(.headline) + .foregroundColor(.black) + + if (contact.isTyping as Bool) { + Text("Typing...") + .font(.footnote) + .foregroundColor(Color(hex: "AFB3B8")) + } else if let lastInteractionDate:Date = contact.lastInteractionTime { + Text(HelperTools.formatLastInteraction(lastInteractionDate)) + .font(.footnote) + .foregroundColor(Color(hex: "AFB3B8")) + } + } + } + } + Spacer() + } + } + } + + ToolbarItemGroup(placement: .topBarTrailing) { + if !(contact.isMuc || contact.isSelfChat) { + let activeChats = (UIApplication.shared.delegate as! MonalAppDelegate).activeChats! + let voipProcessor = (UIApplication.shared.delegate as! MonalAppDelegate).voipProcessor! + Button { + if let activeCall = voipProcessor.getActiveCall(with:contact.obj) { + if !DataLayer.sharedInstance().checkCap("urn:xmpp:jingle-message:0", forUser:contact.contactJid, onAccountID:contact.accountID) { + confirmationPrompt = ConfirmationPrompt( + title: Text("Missing Call Support"), + message: Text("Your contact may not support calls. Your call might never reach its destination."), + buttons: [ + .default( + Text("Try nevertheless"), + action: { + activeChats.call(contact.obj, withUIKitSender:nil) + } + ), + .cancel( + Text("Cancel"), + action: { } + ) + ] + ) + } + } else { + activeChats.call(contact.obj, withUIKitSender:nil) + } + } label: { + if let activeCall = voipProcessor.getActiveCall(with:contact.obj) { + Image(systemName: "phone.connection.fill") + } else { + Image(systemName: "phone.fill") + } + } + } + + Button { + guard !HelperTools.isContactBlacklistedForEncryption(contact.obj) else { + return + } + if contact.isEncrypted { + DDLogVerbose("Showing should disable encryption confirmation...") + showShouldDisableEncryptionConfirmation(true) + } else { + showCannotEncryptAlert(!contact.obj.toggleEncryption(!contact.isEncrypted)) + } + } label: { + if contact.isEncrypted { + Label { + Text("Messages are encrypted") + } icon: { + Image(systemName: "lock.fill") + } + } else { + Label { + Text("Messages are NOT encrypted") + } icon: { + Image(systemName: "lock.open.fill") + .foregroundColor(.red) + } + } + } + .disabled( + //disable encryption button on unsupported muc types + (contact.isMuc && contact.mucType != kMucTypeGroup) || + //disable encryption button for special jids + HelperTools.isContactBlacklistedForEncryption(contact.obj) + ) + } + } + .toolbarRole(.editor) //make sure to never show the title of the previous view in the back bar button + .addLoadingOverlay(overlay) + .onAppear { + checkOmemoSupport(withAlert:false) + + //TODO: load messages from db + let dbMessages = DataLayer.sharedInstance().messages(forContact:contact.contactJid, forAccount:contact.accountID) as! [MLMessage] + for msg in dbMessages { + messages.append(ChatViewMessage(msg)) + } +// messages = [ +// ExyteChat.Message( +// id: "123", +// user: ChatViewUser(contact), +// status: .sent, +// createdAt: Date(), +// text: "Dummy message no. one", +// attachments: [], +// recording: nil, +// replyMessage: nil +// ), +// ExyteChat.Message( +// id: "456", +// user: ChatViewUser(contact), +// status: .sent, +// createdAt: Date(), +// text: "Yes, that's really cool :)", +// attachments: [], +// recording: nil, +// replyMessage: nil +// ) +// ] + } + .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("kMonalOmemoFetchingStateUpdate")).receive(on: RunLoop.main)) { notification in + if let xmppAccount = notification.object as? xmpp, let notificationJid = notification.userInfo?["jid"] as? String { + if xmppAccount.accountID == contact.accountID && notificationJid == contact.contactJid { + DDLogDebug("Got omemo fetching update: \(contact) --> \(String(describing:notification.userInfo))") + if let _ = (notification.userInfo?["isFetching"] as? Bool) { + //recheck support and show alert if needed + DDLogVerbose("Rechecking omemo support with alert, if needed...") + checkOmemoSupport(withAlert:true) + } + } + } + } + } +} + +struct MessageView: View { + @StateObject var message: ObservableKVOWrapper + + var body: some View { + let radius = 12.0 + VStack(alignment: .leading, spacing: 0) { + Text(message.messageText as String) + .foregroundColor(.black) + .background { + RoundedRectangle(cornerRadius: radius) + .foregroundColor(.blue) + //.opacity(isReply ? 0.5 : 1) + } + .cornerRadius(radius) + } + } +} + +class ChatViewMessage: ExyteChat.Message { + @Published public var message: MLMessage + + init(_ message: MLMessage) { + self.message = message + super.init(id: message.id, user: ExyteChat.User(id: message.contact.id, name: "", avatarURL: nil, isCurrentUser: false)) + } +} + +// class ChatViewUser: ExyteChat.User { +// private enum CodingKeys: CodingKey { +// case contact +// } +// +// // @Published public var id: String +// // @Published public var name: String +// // @Published public var isCurrentUser: Bool +// +// @Published public var contact: MLContact +// +// init(_ contact: MLContact) { +// super.init(id: contact.id, name: "", avatarURL: nil, isCurrentUser: false) +// self.contact = contact +// //contact.$contactDisplayName.sink { print($0 as String) } +// } +// +// required public init(from decoder: Decoder) throws { +// //let container = try decoder.container(keyedBy: CodingKeys.self) +// //contact = try container.decode(String.self, forKey: .contact) +// try super.init(from: decoder) +// } +// +// // public func encode(to encoder: Encoder) throws { +// // var container = encoder.container(keyedBy: CodingKeys.self) +// // try container.encode(contact, forKey: .contact) +// // try super.encode(to: encoder) +// // } +// } + +/* +public extension ExyteChat.MessageView { + @ViewBuilder + override public var avatarView: some View { + Group { + if showAvatar, let image = (message.user as! ChatViewUser).image { + image + .resizable() + .scaledToFill() + .contentShape(Circle()) + .onTapGesture { + tapAvatarClosure?(message.user, message.id) + } + } else { + Color.clear.viewSize(avatarSize) + } + } + .padding(.horizontal, ExyteChat.MessageView.horizontalAvatarPadding) +// .onSizeChange { size in +// self.avatarViewSize = size +// } + } +}*/ diff --git a/Monal/Classes/SoundSettingsView.swift b/Monal/Classes/SoundSettingsView.swift new file mode 100644 index 000000000..3f989a568 --- /dev/null +++ b/Monal/Classes/SoundSettingsView.swift @@ -0,0 +1,169 @@ +// +// SoundsSettingView.swift +// Monal +// +// Created by 阿栋 on 4/3/24. +// Copyright © 2024 monal-im.org. All rights reserved. +// + +import AVFoundation + +class SoundsDefaultsDB: ObservableObject { + @defaultsDB("Sound") + var soundsEnabled:Bool +} + +struct SoundSettingsView: View { + @ObservedObject var defaultsDB = SoundsDefaultsDB() + let contact: ObservableKVOWrapper + let delegate: SheetDismisserProtocol + + @State private var selectedSound: String + @State private var playSounds: Bool + @State private var audioPlayer: AVAudioPlayer? + @State private var showingSoundPicker = false + @State private var connectedAccounts: [xmpp] + @State private var selectedAccount = -1; + + let sounds: [String] = MLSoundManager.sharedInstance().listBundledSounds() + + + init(contact: ObservableKVOWrapper, delegate: SheetDismisserProtocol) { + self.contact = contact + self.delegate = delegate + + var soundFileName: String + let receiverJID = "Default" + let senderJID = contact?.obj.contactJid.lowercased() ?? "global" + soundFileName = MLSoundManager.sharedInstance().getSoundName(forSenderJID: senderJID, andReceiverJID: receiverJID) + if (!sounds.contains(soundFileName) && soundFileName != "") { + soundFileName = "Custom Sound" + } else if soundFileName == "" { + soundFileName = "System Sound" + } + _selectedSound = State(initialValue: soundFileName) + } + + + var body: some View { + List { + Section { + Toggle(isOn: $defaultsDB.soundsEnabled) { + if contact.isSelfChat { + Text("Play Sounds Globally") + } else { + Text("Play Sounds for this Contact") + } + } + } + + if $defaultsDB.soundsEnabled { + Section { + HStack { + Text("Custom Sound") + .onTapGesture { + self.showingSoundPicker = true + } + + Spacer() + + if selectedSound == "Custom Sound" { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + .sheet(isPresented: $showingSoundPicker) { + let account = selectedAccount == -1 ? nil : self.connectedAccounts[self.selectedAccount] + let receiverJID = selectedAccount == -1 ? "Default" : account!.connectionProperties.identity.jid.lowercased() + let senderJID = contact?.obj.contactJid.lowercased() ?? "global" + LazyClosureView(SoundPickerView(contact: contact, receiverJID: receiverJID, senderJID: senderJID, onSoundPicked: { (url: URL?) in + if (url != nil) { + do { + let soundData = try Data(contentsOf: url!) + self.selectedSound = "Custom Sound" + let soundFileName = url!.lastPathComponent + MLSoundManager.sharedInstance().saveSound(soundData, forSenderJID: senderJID, andReceiverJID: receiverJID, withSoundFileName: soundFileName, isCustomSound: 1) + } catch { + DDLogDebug("Error playing sound: \(error)") + } + } + }, delegate: delegate)) + } + } + } + + + if playSounds { + soundSelectionSection + } + + if playSounds { + Section { + HStack { + Spacer() + Text("Sounds courtesy Emrah") + .foregroundColor(.gray) + Spacer() + } + } + } + } + .navigationBarTitle("Sounds", displayMode: .inline) + .listStyle(GroupedListStyle()) + } + + var soundSelectionSection: some View { + Section(header: Text("SELECT SOUNDS THAT ARE PLAYED WITH NEW MESSAGE NOTIFICATIONS. DEFAULT IS XYLOPHONE.")) { + HStack { + Text("System Sound") + Spacer() + if selectedSound == "System Sound" { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + .contentShape(Rectangle()) + .onTapGesture { + self.selectedSound = "System Sound" + let account = selectedAccount == -1 ? nil : self.connectedAccounts[self.selectedAccount] + let receiverJID = account == nil ? "Default" : account!.connectionProperties.identity.jid.lowercased() + let senderJID = contact?.obj.contactJid.lowercased() ?? "global" + DataLayer.sharedInstance().deleteSound(forAccountId: receiverJID, buddyId: senderJID) + self.audioPlayer?.stop() + } + + ForEach(sounds.filter { $0 != "System Sound" }, id: \.self) { sound in + HStack { + Text(sound) + Spacer() + if sound == selectedSound { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + .contentShape(Rectangle()) + .onTapGesture { + self.selectedSound = sound + self.playSound(soundName: sound) + } + } + } + } + + func playSound(soundName: String) { + guard let url = Bundle.main.url(forResource: soundName, withExtension: "aif", subdirectory: "AlertSounds") else { return } + do { + let soundData = try Data(contentsOf: url) + audioPlayer = try AVAudioPlayer(data: soundData) + audioPlayer?.play() + let account = selectedAccount == -1 ? nil : self.connectedAccounts[self.selectedAccount] + let receiverJID = account == nil ? "Default" : account!.connectionProperties.identity.jid.lowercased() + let senderJID = contact?.obj.contactJid.lowercased() ?? "global" + let soundFileName = self.selectedSound + MLSoundManager.sharedInstance().saveSound(soundData, forSenderJID: senderJID, andReceiverJID: receiverJID, withSoundFileName: soundFileName, isCustomSound: 0) + } catch { + DDLogDebug("Error playing sound: \(error)") + } + } +} + diff --git a/Monal/Classes/SwiftuiHelpers.swift b/Monal/Classes/SwiftuiHelpers.swift index 34d94e208..e21e3a599 100644 --- a/Monal/Classes/SwiftuiHelpers.swift +++ b/Monal/Classes/SwiftuiHelpers.swift @@ -690,6 +690,13 @@ public extension UIViewController { // Interfaces between ObjectiveC/Storyboards and SwiftUI @objc class SwiftuiInterface : NSObject { + @objc + func makeChatView(for contact: MLContact) -> UIViewController { + let host = UIHostingController(rootView:AnyView(EmptyView())) + host.rootView = AnyView(ChatView(contact:ObservableKVOWrapper(contact))) + return host + } + @objc(makeAccountPickerForContacts:andCallType:) func makeAccountPicker(for contacts: [MLContact], and callType: UInt) -> UIViewController { let delegate = SheetDismisserProtocol() diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index 873956623..cd832858e 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -137,7 +137,6 @@ 8418B55E2C7EC6B7006FAF60 /* Quicksy_Country.m in Sources */ = {isa = PBXBuildFile; fileRef = 8418B55C2C7EC6B7006FAF60 /* Quicksy_Country.m */; }; 8418B5632C7ECFD6006FAF60 /* HelperTools+Quicksy_CountryCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 8418B5612C7ECFD6006FAF60 /* HelperTools+Quicksy_CountryCodes.h */; }; 8418B5642C7ECFD6006FAF60 /* HelperTools+Quicksy_CountryCodes.m in Sources */ = {isa = PBXBuildFile; fileRef = 8418B5622C7ECFD6006FAF60 /* HelperTools+Quicksy_CountryCodes.m */; }; - 8418B5672C87E0ED006FAF60 /* ExyteChat in Frameworks */ = {isa = PBXBuildFile; productRef = 8418B5662C87E0ED006FAF60 /* ExyteChat */; }; 841B6F1A297B18720074F9B7 /* AccountPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841B6F19297B18720074F9B7 /* AccountPicker.swift */; }; 841B6F1C297B3CFC0074F9B7 /* AVCallUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841B6F1B297B3CFC0074F9B7 /* AVCallUI.swift */; }; 841EE4302A426F2300D3AF14 /* MLCrashReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 841EE42F2A426F2300D3AF14 /* MLCrashReporter.m */; }; @@ -145,6 +144,7 @@ 8420EA9D2915E5100038FF40 /* OmemoState.m in Sources */ = {isa = PBXBuildFile; fileRef = 8420EA9C2915E5100038FF40 /* OmemoState.m */; }; 842790852A32D16D005C18CC /* CallSounds in Resources */ = {isa = PBXBuildFile; fileRef = 842790842A32D16C005C18CC /* CallSounds */; }; 843AD3AB2AA55CE20036844D /* MLOgHtmlParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843AD3AA2AA55CE20036844D /* MLOgHtmlParser.swift */; }; + 843DC1B22C8E51E3002724FE /* ExyteChat in Frameworks */ = {isa = PBXBuildFile; productRef = 843DC1B12C8E51E3002724FE /* ExyteChat */; }; 8441EFF92921B53500E851E9 /* BackgroundSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8441EFF82921B53500E851E9 /* BackgroundSettings.swift */; }; 844921EA2C29F9A000B99A9C /* MLDelayableTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 844921E92C29F9A000B99A9C /* MLDelayableTimer.m */; }; 844921EC2C29F9BE00B99A9C /* MLDelayableTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 844921EB2C29F9BE00B99A9C /* MLDelayableTimer.h */; }; @@ -153,6 +153,7 @@ 845EFFBD2918721800C1E03E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1E4654624EE517000CA5AAF /* Localizable.strings */; }; 845EFFBE2918723D00C1E03E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1E4654624EE517000CA5AAF /* Localizable.strings */; }; 846DF27C2937FAA600AAB9C0 /* ChatPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846DF27B2937FAA600AAB9C0 /* ChatPlaceholder.swift */; }; + 847311232C893D2600739CC6 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847311222C893D2600739CC6 /* ChatView.swift */; }; 848227912C4A6194003CCA33 /* MLPlaceholderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 848227902C4A6194003CCA33 /* MLPlaceholderViewController.m */; }; 848717F3295ED64600B8D288 /* MLCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 848717F1295ED64500B8D288 /* MLCall.m */; }; 848904A9289C82C30097E19C /* SCRAM.m in Sources */ = {isa = PBXBuildFile; fileRef = 848904A8289C82C30097E19C /* SCRAM.m */; }; @@ -591,6 +592,7 @@ 845836B92C49F36300B11EC5 /* Quicksy Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Quicksy Launch Screen.storyboard"; path = "Monal-iOS/Quicksy Launch Screen.storyboard"; sourceTree = ""; }; 845D636A2AD4AEDA0066EFFB /* MediaViewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaViewer.swift; sourceTree = ""; }; 846DF27B2937FAA600AAB9C0 /* ChatPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPlaceholder.swift; sourceTree = ""; }; + 847311222C893D2600739CC6 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; 848227902C4A6194003CCA33 /* MLPlaceholderViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLPlaceholderViewController.m; sourceTree = ""; }; 848717F1295ED64500B8D288 /* MLCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MLCall.m; path = Classes/MLCall.m; sourceTree = SOURCE_ROOT; }; 848717F2295ED64500B8D288 /* MLCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MLCall.h; path = Classes/MLCall.h; sourceTree = SOURCE_ROOT; }; @@ -785,7 +787,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8418B5672C87E0ED006FAF60 /* ExyteChat in Frameworks */, + 843DC1B22C8E51E3002724FE /* ExyteChat in Frameworks */, 261E542523A0A1D300394F59 /* monalxmpp.framework in Frameworks */, 84F194D12C15197200F0A994 /* FrameUp in Frameworks */, C176F1EC2AF11C31002034E5 /* UserNotifications.framework in Frameworks */, @@ -1007,6 +1009,7 @@ E89DD32425C6626400925F62 /* MLFileTransferTextCell.m */, E89DD31F25C6626300925F62 /* MLFileTransferVideoCell.h */, E89DD32125C6626300925F62 /* MLFileTransferVideoCell.m */, + 847311222C893D2600739CC6 /* ChatView.swift */, ); name = ChatViews; sourceTree = ""; @@ -1547,7 +1550,7 @@ C1F5C7AE2777638B0001F295 /* OrderedCollections */, 841898A92957712000FEC77D /* ViewExtractor */, 84F194D02C15197200F0A994 /* FrameUp */, - 8418B5662C87E0ED006FAF60 /* ExyteChat */, + 843DC1B12C8E51E3002724FE /* ExyteChat */, ); productName = SworIM; productReference = 26080210110ABA4E005E194D /* Monal.app */; @@ -1764,7 +1767,7 @@ 849ADF3D2BACF0360009BCD7 /* XCRemoteSwiftPackageReference "cocoalumberjack" */, 84F194CF2C15197200F0A994 /* XCRemoteSwiftPackageReference "FrameUp" */, 84E231F12C16A9CE00735FB7 /* XCRemoteSwiftPackageReference "SVGView" */, - 8418B5652C87E0ED006FAF60 /* XCRemoteSwiftPackageReference "Chat" */, + 843DC1B02C8E51E3002724FE /* XCLocalSwiftPackageReference "../../ExyteChat" */, ); productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; @@ -2095,6 +2098,7 @@ E8CF9CC726249640001A1952 /* MLSettingsAboutViewController.m in Sources */, C10490492612ED2F0054AC9E /* MLEmoji.swift in Sources */, 20D3611C2C10E12500E46587 /* BoardingCards.swift in Sources */, + 847311232C893D2600739CC6 /* ChatView.swift in Sources */, 3D06A515281FFCC000DDAE90 /* NotificationDebugging.swift in Sources */, 845D636B2AD4AEDA0066EFFB /* MediaViewer.swift in Sources */, 2636C43F177BD58C001CA71F /* XMPPEdit.m in Sources */, @@ -4647,6 +4651,13 @@ }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 843DC1B02C8E51E3002724FE /* XCLocalSwiftPackageReference "../../ExyteChat" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../ExyteChat; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCRemoteSwiftPackageReference section */ 841898A82957712000FEC77D /* XCRemoteSwiftPackageReference "ViewExtractor" */ = { isa = XCRemoteSwiftPackageReference; @@ -4656,14 +4667,6 @@ minimumVersion = 2.0.0; }; }; - 8418B5652C87E0ED006FAF60 /* XCRemoteSwiftPackageReference "Chat" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/exyte/Chat"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.5; - }; - }; 849ADF3D2BACF0360009BCD7 /* XCRemoteSwiftPackageReference "cocoalumberjack" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/cocoalumberjack/cocoalumberjack"; @@ -4708,9 +4711,8 @@ package = 841898A82957712000FEC77D /* XCRemoteSwiftPackageReference "ViewExtractor" */; productName = ViewExtractor; }; - 8418B5662C87E0ED006FAF60 /* ExyteChat */ = { + 843DC1B12C8E51E3002724FE /* ExyteChat */ = { isa = XCSwiftPackageProductDependency; - package = 8418B5652C87E0ED006FAF60 /* XCRemoteSwiftPackageReference "Chat" */; productName = ExyteChat; }; 849ADF3E2BACF0360009BCD7 /* CocoaLumberjack */ = {