diff --git a/Sources/ChatFeature/Helpers/CellConfigurator.swift b/Sources/ChatFeature/Helpers/CellConfigurator.swift index c59050a9221da32149bda8432c69d9271f08a0b7..4f0ad3ca31e71223f57f6b9593b918c1f745c2e1 100644 --- a/Sources/ChatFeature/Helpers/CellConfigurator.swift +++ b/Sources/ChatFeature/Helpers/CellConfigurator.swift @@ -54,7 +54,7 @@ extension CellFactory { }, build: { item, collectionView, indexPath in let ft = transfer(item.fileTransferId!) let cell: IncomingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) - let url = FileManager.url(for: ft.name)! + let url = FileManager.url(for: "\(ft.name).\(ft.type)")! var model = AudioMessageCellState( date: item.date, @@ -130,7 +130,7 @@ extension CellFactory { }, build: { item, collectionView, indexPath in let ft = transfer(item.fileTransferId!) let cell: OutgoingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) - let url = FileManager.url(for: ft.name)! + let url = FileManager.url(for: "\(ft.name).\(ft.type)")! var model = AudioMessageCellState( date: item.date, audioURL: url, diff --git a/Sources/ContactFeature/Controllers/ContactController.swift b/Sources/ContactFeature/Controllers/ContactController.swift index 1ef8e96ffba5f58ec2ae2ac6e03adc701e1b3ffb..cf64268edbdd1cad2986105c38d72a74b2273499 100644 --- a/Sources/ContactFeature/Controllers/ContactController.swift +++ b/Sources/ContactFeature/Controllers/ContactController.swift @@ -263,7 +263,7 @@ public final class ContactController: UIViewController { let deleteButton = RowButton() deleteButton.setup( - title: "Delete Connection", + title: Localized.Contact.Delete.Info.title, icon: Asset.settingsDelete.image, style: .delete, separator: false @@ -411,22 +411,23 @@ extension ContactController { private func presentDeleteInfo() { let actionButton = DrawerCapsuleButton(model: .init( - title: "Delete Connection", + title: Localized.Contact.Delete.Info.title, style: .red )) let drawer = DrawerController(with: [ DrawerText( font: Fonts.Mulish.bold.font(size: 26.0), - text: "Delete Connection?", + text: Localized.Contact.Delete.Drawer.title, color: Asset.neutralActive.color, alignment: .left, spacingAfter: 19 ), DrawerText( - text: "This is a silent deletion, \(viewModel.contact.username) will not know you deleted them. This action will remove all information on your phone about this user, including your communications. You #cannot undo this step, and cannot re-add them unless they delete you as a connection as well.#", - spacingAfter: 37 - ), + text: Localized.Contact.Delete.Drawer.description(viewModel.contact.username ?? ""), + spacingAfter: 37, + customAttributes: [.font: Fonts.Mulish.bold.font(size: 16.0)] + ), actionButton ]) diff --git a/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift b/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift index baf38264a85467038888c2b37e618598a7190acd..56043d7556f0d0d4eb19c4e9fae21cd9163c878c 100644 --- a/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift +++ b/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift @@ -25,6 +25,7 @@ public struct ContactListCoordinator: ContactListCoordinating { var sidePresenter: Presenting = SideMenuPresenter() var bottomPresenter: Presenting = BottomPresenter() var fullscreenPresenter: Presenting = FullscreenPresenter() + var replacePresenter: Presenting = ReplacePresenter(mode: .replaceLast) var scanFactory: () -> UIViewController var searchFactory: () -> UIViewController @@ -104,7 +105,7 @@ public extension ContactListCoordinator { func toGroupChat(with info: GroupInfo, from parent: UIViewController) { let screen = groupChatFactory(info) - pushPresenter.present(screen, from: parent) + replacePresenter.present(screen, from: parent) } func toSideMenu(from parent: UIViewController) { diff --git a/Sources/Integration/Session/Session+Chat.swift b/Sources/Integration/Session/Session+Chat.swift index 3e3eb63a7df4c8d2dcc1ab315335f8fb046b6d69..b8d6e02d45f0cb6681fe206a31c117d7807a70c5 100644 --- a/Sources/Integration/Session/Session+Chat.swift +++ b/Sources/Integration/Session/Session+Chat.swift @@ -124,7 +124,7 @@ extension Session { message.date = Date() if let message = try? dbManager.saveMessage(message) { - if let recipientId = message.recipientId { + if let _ = message.recipientId { send(message: message) } else { send(groupMessage: message) @@ -229,36 +229,39 @@ extension Session { let content = transfer.type == "m4a" ? "a voice message" : "an image" - var message = Message( - networkId: nil, - senderId: transfer.contactId, - recipientId: myId, - groupId: nil, - date: transfer.createdAt, - status: .receiving, - isUnread: true, - text: "Sent you \(content)", - replyMessageId: nil, - roundURL: nil, - fileTransferId: transfer.id + var message = try! dbManager.saveMessage( + Message( + networkId: nil, + senderId: transfer.contactId, + recipientId: myId, + groupId: nil, + date: transfer.createdAt, + status: .receiving, + isUnread: true, + text: "Sent you \(content)", + replyMessageId: nil, + roundURL: nil, + fileTransferId: transfer.id + ) ) - message = try! self.dbManager.saveMessage(message) - try! manager.listenDownloadFromTransfer(with: transfer.id) { completed, arrived, total, error in - if let error = error { fatalError(error.localizedDescription) } + if let error = error { + print(error.localizedDescription) + return + } if completed { - guard let rawFile = try? manager.downloadFileFromTransfer(with: transfer.id) else { return } - _ = try! FileManager.store(data: rawFile, name: transfer.name, type: transfer.type) - - var transfer = transfer - transfer.data = rawFile - transfer.progress = 1.0 - _ = try? self.dbManager.saveFileTransfer(transfer) - - message.status = .received - _ = try? self.dbManager.saveMessage(message) + if let data = try? manager.downloadFileFromTransfer(with: transfer.id), + let _ = try? FileManager.store(data: data, name: transfer.name, type: transfer.type) { + var transfer = transfer + transfer.data = data + transfer.progress = 1.0 + message.status = .received + + _ = try? self.dbManager.saveFileTransfer(transfer) + _ = try? self.dbManager.saveMessage(message) + } } else { self.progressTransferWith(tid: transfer.id, arrived: Float(arrived), total: Float(total)) } diff --git a/Sources/Integration/Session/Session.swift b/Sources/Integration/Session/Session.swift index 8a1c20407a7fa24ede9ea75f9468034210dca89f..ae01b853ac2ac24d7e8427f3a868dcb18b34dcde 100644 --- a/Sources/Integration/Session/Session.swift +++ b/Sources/Integration/Session/Session.swift @@ -194,7 +194,8 @@ public final class Session: SessionType { } .store(in: &cancellables) - registerUnfinishedTransfers() + registerUnfinishedUploadTransfers() + registerUnfinishedDownloadTransfers() let query = Contact.Query(authStatus: [.verificationInProgress]) _ = try? dbManager.bulkUpdateContacts(query, .init(authStatus: .verificationFailed)) @@ -239,7 +240,56 @@ public final class Session: SessionType { inappnotifications = true } - private func registerUnfinishedTransfers() { + private func registerUnfinishedDownloadTransfers() { + guard let unfinishedReceivingMessages = try? dbManager.fetchMessages(.init(status: [.receiving])), + let unfinishedReceivingTransfers = try? dbManager.fetchFileTransfers(.init( + id: Set(unfinishedReceivingMessages + .filter { $0.fileTransferId != nil } + .compactMap(\.fileTransferId)))) + else { return } + + let pairs = unfinishedReceivingMessages.compactMap { message -> (Message, FileTransfer)? in + guard let transfer = unfinishedReceivingTransfers.first(where: { ft in + ft.id == message.fileTransferId + }) else { return nil } + + return (message, transfer) + } + + pairs.forEach { message, transfer in + var message = message + var transfer = transfer + + do { + try client.transferManager?.listenDownloadFromTransfer(with: transfer.id) { [weak self] completed, received, total, error in + guard let self = self else { return } + if completed { + transfer.progress = 1.0 + message.status = .received + + if let data = try? self.client.transferManager?.downloadFileFromTransfer(with: transfer.id), + let _ = try? FileManager.store(data: data, name: transfer.name, type: transfer.type) { + transfer.data = data + } + } else { + if error != nil { + message.status = .receivingFailed + } else { + transfer.progress = Float(received)/Float(total) + } + } + + _ = try? self.dbManager.saveFileTransfer(transfer) + _ = try? self.dbManager.saveMessage(message) + } + } catch { + message.status = .receivingFailed + _ = try? self.dbManager.saveMessage(message) + } + } + } + + private func registerUnfinishedUploadTransfers() { guard let unfinishedSendingMessages = try? dbManager.fetchMessages(.init(status: [.sending])), let unfinishedSendingTransfers = try? dbManager.fetchFileTransfers(.init( id: Set(unfinishedSendingMessages @@ -247,12 +297,12 @@ public final class Session: SessionType { .compactMap(\.fileTransferId)))) else { return } - let pairs = unfinishedSendingMessages.map { message -> (Message, FileTransfer) in - let transfer = unfinishedSendingTransfers.first { ft in + let pairs = unfinishedSendingMessages.compactMap { message -> (Message, FileTransfer)? in + guard let transfer = unfinishedSendingTransfers.first(where: { ft in ft.id == message.fileTransferId - } + }) else { return nil } - return (message, transfer!) + return (message, transfer) } pairs.forEach { message, transfer in @@ -260,11 +310,14 @@ public final class Session: SessionType { var transfer = transfer do { - try client.transferManager?.listenUploadFromTransfer(with: transfer.id) { completed, sent, arrived, total, error in + try client.transferManager?.listenUploadFromTransfer(with: transfer.id) { [weak self] completed, sent, arrived, total, error in + guard let self = self else { return } + if completed { transfer.progress = 1.0 message.status = .sent + try? self.client.transferManager?.endTransferUpload(with: transfer.id) } else { if error != nil { message.status = .sendingFailed @@ -410,25 +463,8 @@ public final class Session: SessionType { client.transfers .sink { [unowned self] in - _ = try? dbManager.saveFileTransfer($0) - - let content = $0.type == "m4a" ? "a voice message" : "an image" - - let message = Message( - networkId: $0.id, - senderId: $0.contactId, - recipientId: myId, - groupId: nil, - date: $0.createdAt, - status: .receiving, - isUnread: true, - text: "Sent you \(content)", - replyMessageId: nil, - roundURL: nil, - fileTransferId: $0.id - ) - - _ = try? dbManager.saveMessage(message) + guard let transfer = try? dbManager.saveFileTransfer($0) else { return } + handle(incomingTransfer: transfer) } .store(in: &cancellables) } diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/Shared/AutoGenerated/Strings.swift index 755693f39ac7ba661a99e93b8d64360590ed299b..07f2cee03ff185b2bd7f9fd768d00758302c74a2 100644 --- a/Sources/Shared/AutoGenerated/Strings.swift +++ b/Sources/Shared/AutoGenerated/Strings.swift @@ -455,6 +455,20 @@ public enum Localized { /// Send message public static let send = Localized.tr("Localizable", "contact.confirmed.send") } + public enum Delete { + public enum Drawer { + /// This is a silent deletion, %@ will not know you deleted them. This action will remove all information on your phone about this user, including your communications. You #cannot undo this step, and cannot re-add them unless they delete you as a connection as well.# + public static func description(_ p1: Any) -> String { + return Localized.tr("Localizable", "contact.delete.drawer.description", String(describing: p1)) + } + /// Delete Connection? + public static let title = Localized.tr("Localizable", "contact.delete.drawer.title") + } + public enum Info { + /// Delete Connection + public static let title = Localized.tr("Localizable", "contact.delete.info.title") + } + } public enum Inprogress { /// Your request failed to send public static let failed = Localized.tr("Localizable", "contact.inprogress.failed") diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings index 774e9759242a913fc93e1d9b025ced9ca2b847b9..aebff7b7bcdb9b2c872577060b8f9244300baa90 100644 --- a/Sources/Shared/Resources/en.lproj/Localizable.strings +++ b/Sources/Shared/Resources/en.lproj/Localizable.strings @@ -230,6 +230,15 @@ "contact.nickname" = "Nickname"; +// ContactFeature - Delete + +"contact.delete.info.title" += "Delete Connection"; +"contact.delete.drawer.title" += "Delete Connection?"; +"contact.delete.drawer.description" += "This is a silent deletion, %@ will not know you deleted them. This action will remove all information on your phone about this user, including your communications. You #cannot undo this step, and cannot re-add them unless they delete you as a connection as well.#"; + // ContactFeature - Confirmed "contact.confirmed.clear" diff --git a/Sources/Shared/Views/AvatarView.swift b/Sources/Shared/Views/AvatarView.swift index 93a4789f6b22df1f60d4470c05df1db69e2d9cd6..a1104edb680c869092a0017ef5e8c87310ae7bd5 100644 --- a/Sources/Shared/Views/AvatarView.swift +++ b/Sources/Shared/Views/AvatarView.swift @@ -48,6 +48,12 @@ public final class AvatarView: UIView { public func setupProfile(title: String, image: Data?, size: AvatarView.Size) { iconImageView.image = nil + monogramLabel.text = title + .trimmingCharacters(in: .whitespacesAndNewlines) + .replacingOccurrences(of: " ", with: "") + .prefix(2) + .uppercased() + monogramLabel.text = "\(title.prefix(2))".uppercased() // TODO: What are the font sizes and corner radius for small/medium avatars?