Minor refactoring

pull/369/head
Niels Andriesse 4 years ago
parent 8738829358
commit 54fc54e7b9

@ -250,7 +250,6 @@
B8856ED7256F1EB4001CE70E /* OWSPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2F1255B6DBB007E1867 /* OWSPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; };
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; };
B886B4A92398BA1500211ABE /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A82398BA1500211ABE /* QRCode.swift */; };
B88847BC23E10BC6009836D2 /* GroupMembersVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */; };
B88A1AC725C90A4700E6D421 /* TypingIndicatorInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */; };
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */; };
B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0742339EDCF00B4D94D /* NukeDataModal.swift */; };
@ -270,7 +269,7 @@
B8C2B3442563782400551B4D /* ThreadUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = B8C2B33B2563770800551B4D /* ThreadUtil.h */; settings = {ATTRIBUTES = (Public, ); }; };
B8CADAE925AFADF400AAFA15 /* OpenGroupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AAFFCB25AE92150089E6DD /* OpenGroupManager.swift */; };
B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */; };
B8CCF63723961D6D0091D419 /* NewPrivateChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */; };
B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63623961D6D0091D419 /* NewDMVC.swift */; };
B8CCF63F23975CFB0091D419 /* JoinOpenGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63E23975CFB0091D419 /* JoinOpenGroupVC.swift */; };
B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF6422397711F0091D419 /* SettingsVC.swift */; };
B8D0A25025E3678700C1835E /* LinkDeviceVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D0A24F25E3678700C1835E /* LinkDeviceVC.swift */; };
@ -391,7 +390,6 @@
C32C5D24256DD4C0003C73A2 /* MentionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA81255A57FC00E217F9 /* MentionsManager.swift */; };
C32C5D2E256DD4EA003C73A2 /* TSUnreadIndicatorInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2C1255B6DA6007E1867 /* TSUnreadIndicatorInteraction.m */; };
C32C5D37256DD4ED003C73A2 /* TSUnreadIndicatorInteraction.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2BE255B6DA6007E1867 /* TSUnreadIndicatorInteraction.h */; settings = {ATTRIBUTES = (Public, ); }; };
C32C5D40256DD51E003C73A2 /* Storage+VolumeSamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31F812525258FB000DD9FD9 /* Storage+VolumeSamples.swift */; };
C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */; };
C32C5D9C256DD6DC003C73A2 /* OWSOutgoingReceiptManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB6F255A580F00E217F9 /* OWSOutgoingReceiptManager.m */; };
C32C5DA5256DD6E5003C73A2 /* OWSOutgoingReceiptManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDABD255A580100E217F9 /* OWSOutgoingReceiptManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -536,7 +534,6 @@
C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C354E75923FE2A7600CE22E3 /* BaseVC.swift */; };
C35D0DB525AE5F1200B6BF49 /* UIEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35D0DB425AE5F1200B6BF49 /* UIEdgeInsets.swift */; };
C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35E8AAD2485E51D00ACB629 /* IP2Country.swift */; };
C364535C252467900045C478 /* AudioUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C364535B252467900045C478 /* AudioUtilities.swift */; };
C374EEE225DA26740073A857 /* LinkPreviewModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C374EEE125DA26740073A857 /* LinkPreviewModal.swift */; };
C374EEEB25DA3CA70073A857 /* ConversationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */; };
C374EEF425DB31D40073A857 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C374EEF325DB31D40073A857 /* VoiceMessageRecordingView.swift */; };
@ -1237,7 +1234,6 @@
B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = "<group>"; };
B886B4A62398B23E00211ABE /* QRCodeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeVC.swift; sourceTree = "<group>"; };
B886B4A82398BA1500211ABE /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = "<group>"; };
B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMembersVC.swift; sourceTree = "<group>"; };
B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = "<group>"; };
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeDataModal.swift; sourceTree = "<group>"; };
B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = "<group>"; };
@ -1265,7 +1261,7 @@
B8C2B33B2563770800551B4D /* ThreadUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ThreadUtil.h; sourceTree = "<group>"; };
B8C9689023FA1401005F64E0 /* AppMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMode.swift; sourceTree = "<group>"; };
B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Regular.ttf"; sourceTree = "<group>"; };
B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPrivateChatVC.swift; sourceTree = "<group>"; };
B8CCF63623961D6D0091D419 /* NewDMVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDMVC.swift; sourceTree = "<group>"; };
B8CCF638239721E20091D419 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; };
B8CCF63E23975CFB0091D419 /* JoinOpenGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupVC.swift; sourceTree = "<group>"; };
B8CCF6422397711F0091D419 /* SettingsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsVC.swift; sourceTree = "<group>"; };
@ -1309,7 +1305,6 @@
C31D1DDC25217014005D4DA8 /* UserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCell.swift; sourceTree = "<group>"; };
C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionVC.swift; sourceTree = "<group>"; };
C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUtilities.swift; sourceTree = "<group>"; };
C31F812525258FB000DD9FD9 /* Storage+VolumeSamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+VolumeSamples.swift"; sourceTree = "<group>"; };
C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairUtilities.swift; sourceTree = "<group>"; };
C328250E25CA06020062D0A7 /* VoiceMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageView.swift; sourceTree = "<group>"; };
C328251E25CA3A900062D0A7 /* QuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView.swift; sourceTree = "<group>"; };
@ -1536,7 +1531,6 @@
C35D0DB425AE5F1200B6BF49 /* UIEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEdgeInsets.swift; sourceTree = "<group>"; };
C35E8AA22485C72300ACB629 /* SwiftCSV.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftCSV.framework; path = ThirdParty/Carthage/Build/iOS/SwiftCSV.framework; sourceTree = "<group>"; };
C35E8AAD2485E51D00ACB629 /* IP2Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IP2Country.swift; sourceTree = "<group>"; };
C364535B252467900045C478 /* AudioUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioUtilities.swift; sourceTree = "<group>"; };
C374EEE125DA26740073A857 /* LinkPreviewModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewModal.swift; sourceTree = "<group>"; };
C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = "<group>"; };
C374EEF325DB31D40073A857 /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = "<group>"; };
@ -2048,7 +2042,6 @@
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
34D1F0BF1F8EC1760066283D /* MessageRecipientStatusUtils.swift */,
B8544E3223D50E4900299F14 /* SNAppearance.swift */,
C364535B252467900045C478 /* AudioUtilities.swift */,
C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */,
C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */,
C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */,
@ -2692,7 +2685,6 @@
isa = PBXGroup;
children = (
B85A68B02587141A008CC492 /* Storage+Resetting.swift */,
C31F812525258FB000DD9FD9 /* Storage+VolumeSamples.swift */,
);
path = Database;
sourceTree = "<group>";
@ -2866,18 +2858,17 @@
isa = PBXGroup;
children = (
C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */,
B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */,
B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */,
);
path = "Closed Groups";
sourceTree = "<group>";
};
C36096A525AD18D7008B62B2 /* Basic Chats */ = {
C36096A525AD18D7008B62B2 /* DMs */ = {
isa = PBXGroup;
children = (
B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */,
B8CCF63623961D6D0091D419 /* NewDMVC.swift */,
);
path = "Basic Chats";
path = DMs;
sourceTree = "<group>";
};
C36096AF25AD1932008B62B2 /* Sheets & Modals */ = {
@ -3589,11 +3580,11 @@
children = (
C3F0A58F255C8E3D007BE2A3 /* Meta */,
C36096BC25AD1C3E008B62B2 /* Backups */,
C36096A525AD18D7008B62B2 /* Basic Chats */,
C360969C25AD18BA008B62B2 /* Closed Groups */,
B835246C25C38AA20089A44F /* Conversations */,
C32C5D49256DD522003C73A2 /* Database */,
C32B405424A961E1001117B5 /* Dependencies */,
C36096A525AD18D7008B62B2 /* DMs */,
C360968E25AD16E8008B62B2 /* Home */,
C36096BA25AD1B14008B62B2 /* Media Viewing & Editing */,
C36096BB25AD1BBB008B62B2 /* Notifications */,
@ -4869,7 +4860,7 @@
buildActionMask = 2147483647;
files = (
B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */,
B8CCF63723961D6D0091D419 /* NewPrivateChatVC.swift in Sources */,
B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */,
452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */,
3496956E21A301A100DCFE74 /* OWSBackupExportJob.m in Sources */,
4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */,
@ -4880,7 +4871,6 @@
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */,
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */,
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
C364535C252467900045C478 /* AudioUtilities.swift in Sources */,
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */,
34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */,
451A13B11E13DED2000A50FD /* AppNotifications.swift in Sources */,
@ -4968,7 +4958,6 @@
B8D84ECF25E3108A005A043E /* ExpandingAttachmentsButton.swift in Sources */,
B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */,
C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */,
C32C5D40256DD51E003C73A2 /* Storage+VolumeSamples.swift in Sources */,
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
@ -5004,7 +4993,6 @@
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */,
B88847BC23E10BC6009836D2 /* GroupMembersVC.swift in Sources */,
B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */,
B83786802586D296003CE78E /* KeyPairMigrationSheet.swift in Sources */,
B821494F25D4E163009C0F2A /* BodyTextView.swift in Sources */,

@ -1,5 +1,5 @@
@objc(LKEditClosedGroupVC)
@objc(SNEditClosedGroupVC)
final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegate {
private let thread: TSGroupThread
private var name = ""
@ -271,9 +271,9 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
Storage.write(with: { [weak self] transaction in
do {
if !members.contains(getUserHexEncodedPublicKey()) {
try MessageSender.v2_leave(groupPublicKey, using: transaction)
try MessageSender.leave(groupPublicKey, using: transaction)
} else {
try MessageSender.v2_update(groupPublicKey, with: members, name: name, transaction: transaction)
try MessageSender.update(groupPublicKey, with: members, name: name, transaction: transaction)
}
} catch {
DispatchQueue.main.async {

@ -1,156 +0,0 @@
@objc(LKGroupMembersVC)
final class GroupMembersVC : BaseVC, UITableViewDataSource {
private let thread: TSGroupThread
private lazy var members: [String] = {
func getDisplayName(for hexEncodedPublicKey: String) -> String {
return Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ?? hexEncodedPublicKey
}
return GroupUtilities.getClosedGroupMembers(thread).sorted { getDisplayName(for: $0) < getDisplayName(for: $1) }
}()
// MARK: Components
@objc private lazy var tableView: UITableView = {
let result = UITableView()
result.dataSource = self
result.register(Cell.self, forCellReuseIdentifier: "Cell")
result.separatorStyle = .none
result.backgroundColor = .clear
result.showsVerticalScrollIndicator = false
result.alwaysBounceVertical = false
return result
}()
// MARK: Lifecycle
@objc init(thread: TSGroupThread) {
self.thread = thread
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("Using GroupMembersVC.init(nibName:bundle:) isn't allowed. Use GroupMembersVC.init(thread:) instead.") }
override init(nibName: String?, bundle: Bundle?) { fatalError("Using GroupMembersVC.init(nibName:bundle:) isn't allowed. Use GroupMembersVC.init(thread:) instead.") }
override func viewDidLoad() {
super.viewDidLoad()
setUpGradientBackground()
setUpNavBarStyle()
setNavBarTitle("Group Members")
// Set up explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity)
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = "The ability to add members to a closed group is coming soon."
explanationLabel.numberOfLines = 0
explanationLabel.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
// Set up view hierarchy
view.addSubview(explanationLabel)
explanationLabel.pin(.leading, to: .leading, of: view, withInset: Values.largeSpacing)
explanationLabel.pin(.top, to: .top, of: view, withInset: Values.mediumSpacing)
explanationLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing)
let separator = UIView()
separator.backgroundColor = Colors.separator
separator.set(.height, to: Values.separatorThickness)
view.addSubview(separator)
separator.pin(.leading, to: .leading, of: view)
separator.pin(.top, to: .bottom, of: explanationLabel, withInset: Values.mediumSpacing)
separator.pin(.trailing, to: .trailing, of: view)
view.addSubview(tableView)
tableView.pin(.leading, to: .leading, of: view)
tableView.pin(.top, to: .bottom, of: separator)
tableView.pin(.trailing, to: .trailing, of: view)
tableView.pin(.bottom, to: .bottom, of: view)
}
// MARK: Data
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return members.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
let contact = members[indexPath.row]
cell.hexEncodedPublicKey = contact
return cell
}
@objc private func close() {
dismiss(animated: true, completion: nil)
}
}
// MARK: - Cell
private extension GroupMembersVC {
final class Cell : UITableViewCell {
var hexEncodedPublicKey = "" { didSet { update() } }
// MARK: Components
private lazy var profilePictureView = ProfilePictureView()
private lazy var displayNameLabel: UILabel = {
let result = UILabel()
result.textColor = Colors.text
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.lineBreakMode = .byTruncatingTail
return result
}()
private lazy var separator: UIView = {
let result = UIView()
result.backgroundColor = Colors.separator
result.set(.height, to: Values.separatorThickness)
return result
}()
// MARK: Initialization
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViewHierarchy()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
}
private func setUpViewHierarchy() {
// Set the cell background color
backgroundColor = Colors.cellBackground
// Set up the highlight color
let selectedBackgroundView = UIView()
selectedBackgroundView.backgroundColor = .clear // Disabled for now
self.selectedBackgroundView = selectedBackgroundView
// Set up the profile picture image view
let profilePictureViewSize = Values.smallProfilePictureSize
profilePictureView.set(.width, to: profilePictureViewSize)
profilePictureView.set(.height, to: profilePictureViewSize)
profilePictureView.size = profilePictureViewSize
// Set up the main stack view
let stackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ])
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Values.mediumSpacing
stackView.set(.height, to: profilePictureViewSize)
contentView.addSubview(stackView)
stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.mediumSpacing)
stackView.pin(.top, to: .top, of: contentView, withInset: Values.mediumSpacing)
contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.mediumSpacing)
stackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing)
// Set up the separator
addSubview(separator)
separator.pin(.leading, to: .leading, of: self)
separator.pin(.bottom, to: .bottom, of: self)
separator.set(.width, to: UIScreen.main.bounds.width)
}
// MARK: Updating
private func update() {
profilePictureView.publicKey = hexEncodedPublicKey
profilePictureView.update()
displayNameLabel.text = Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ?? hexEncodedPublicKey
}
}
}

@ -86,7 +86,7 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
explanationLabel.text = NSLocalizedString("vc_create_closed_group_empty_state_message", comment: "")
let createNewPrivateChatButton = Button(style: .prominentOutline, size: .large)
createNewPrivateChatButton.setTitle(NSLocalizedString("vc_create_closed_group_empty_state_button_title", comment: ""), for: UIControl.State.normal)
createNewPrivateChatButton.addTarget(self, action: #selector(createNewPrivateChat), for: UIControl.Event.touchUpInside)
createNewPrivateChatButton.addTarget(self, action: #selector(createNewDM), for: UIControl.Event.touchUpInside)
createNewPrivateChatButton.set(.width, to: 196)
let stackView = UIStackView(arrangedSubviews: [ explanationLabel, createNewPrivateChatButton ])
stackView.axis = .vertical
@ -190,8 +190,8 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat
}
}
@objc private func createNewPrivateChat() {
@objc private func createNewDM() {
presentingViewController?.dismiss(animated: true, completion: nil)
SignalApp.shared().homeViewController!.createNewPrivateChat()
SignalApp.shared().homeViewController!.createNewDM()
}
}

@ -809,7 +809,7 @@ CGFloat kIconViewLength = 24;
- (void)editGroup
{
LKEditClosedGroupVC *editClosedGroupVC = [[LKEditClosedGroupVC alloc] initWithThreadID:self.thread.uniqueId];
SNEditClosedGroupVC *editClosedGroupVC = [[SNEditClosedGroupVC alloc] initWithThreadID:self.thread.uniqueId];
[self.navigationController pushViewController:editClosedGroupVC animated:YES completion:nil];
}
@ -858,7 +858,7 @@ CGFloat kIconViewLength = 24;
if (gThread.isClosedGroup) {
NSString *groupPublicKey = [LKGroupUtilities getDecodedGroupID:gThread.groupModel.groupId];
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[SNMessageSender v2_leaveClosedGroupWithPublicKey:groupPublicKey using:transaction error:nil];
[SNMessageSender leaveClosedGroupWithPublicKey:groupPublicKey using:transaction error:nil];
}];
}

@ -1,5 +1,5 @@
final class NewPrivateChatVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate {
final class NewDMVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate {
private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
private var pages: [UIViewController] = []
private var targetVCIndex: Int?
@ -21,13 +21,13 @@ final class NewPrivateChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
private lazy var enterPublicKeyVC: EnterPublicKeyVC = {
let result = EnterPublicKeyVC()
result.newPrivateChatVC = self
result.NewDMVC = self
return result
}()
private lazy var scanQRCodePlaceholderVC: ScanQRCodePlaceholderVC = {
let result = ScanQRCodePlaceholderVC()
result.newPrivateChatVC = self
result.NewDMVC = self
return result
}()
@ -121,18 +121,18 @@ final class NewPrivateChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) {
let hexEncodedPublicKey = string
startNewPrivateChatIfPossible(with: hexEncodedPublicKey)
startNewDMIfPossible(with: hexEncodedPublicKey)
}
fileprivate func startNewPrivateChatIfPossible(with onsNameOrPublicKey: String) {
fileprivate func startNewDMIfPossible(with onsNameOrPublicKey: String) {
if ECKeyPair.isValidHexEncodedPublicKey(candidate: onsNameOrPublicKey) {
startNewPrivateChat(with: onsNameOrPublicKey)
startNewDM(with: onsNameOrPublicKey)
} else {
// This could be an ONS name
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in
SnodeAPI.getSessionID(for: onsNameOrPublicKey).done { sessionID in
modalActivityIndicator.dismiss {
self?.startNewPrivateChat(with: sessionID)
self?.startNewDM(with: sessionID)
}
}.catch { error in
modalActivityIndicator.dismiss {
@ -153,7 +153,7 @@ final class NewPrivateChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
}
}
private func startNewPrivateChat(with sessionID: String) {
private func startNewDM(with sessionID: String) {
let thread = TSContactThread.getOrCreateThread(contactId: sessionID)
presentingViewController?.dismiss(animated: true, completion: nil)
SignalApp.shared().presentConversation(for: thread, action: .compose, animated: false)
@ -161,7 +161,7 @@ final class NewPrivateChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
}
private final class EnterPublicKeyVC : UIViewController {
weak var newPrivateChatVC: NewPrivateChatVC!
weak var NewDMVC: NewDMVC!
private var isKeyboardShowing = false
private var bottomConstraint: NSLayoutConstraint!
@ -226,7 +226,7 @@ private final class EnterPublicKeyVC : UIViewController {
// Next button
let nextButton = Button(style: .prominentOutline, size: .large)
nextButton.setTitle(NSLocalizedString("next", comment: ""), for: UIControl.State.normal)
nextButton.addTarget(self, action: #selector(startNewPrivateChatIfPossible), for: UIControl.Event.touchUpInside)
nextButton.addTarget(self, action: #selector(startNewDMIfPossible), for: UIControl.Event.touchUpInside)
let nextButtonContainer = UIView()
nextButtonContainer.addSubview(nextButton)
nextButton.pin(.leading, to: .leading, of: nextButtonContainer, withInset: 80)
@ -315,17 +315,17 @@ private final class EnterPublicKeyVC : UIViewController {
@objc private func sharePublicKey() {
let shareVC = UIActivityViewController(activityItems: [ getUserHexEncodedPublicKey() ], applicationActivities: nil)
newPrivateChatVC.navigationController!.present(shareVC, animated: true, completion: nil)
NewDMVC.navigationController!.present(shareVC, animated: true, completion: nil)
}
@objc fileprivate func startNewPrivateChatIfPossible() {
@objc fileprivate func startNewDMIfPossible() {
let text = publicKeyTextView.text?.trimmingCharacters(in: .whitespaces) ?? ""
newPrivateChatVC.startNewPrivateChatIfPossible(with: text)
NewDMVC.startNewDMIfPossible(with: text)
}
}
private final class ScanQRCodePlaceholderVC : UIViewController {
weak var newPrivateChatVC: NewPrivateChatVC!
weak var NewDMVC: NewDMVC!
override func viewDidLoad() {
// Remove background color
@ -365,7 +365,7 @@ private final class ScanQRCodePlaceholderVC : UIViewController {
@objc private func requestCameraAccess() {
ows_ask(forCameraPermissions: { [weak self] hasCameraAccess in
if hasCameraAccess {
self?.newPrivateChatVC.handleCameraAccessGranted()
self?.NewDMVC.handleCameraAccessGranted()
} else {
// Do nothing
}

@ -1,17 +0,0 @@
extension Storage {
private static let volumeSamplesCollection = "LokiVolumeSamplesCollection"
public func getVolumeSamples(for attachment: String) -> [Float]? {
var result: [Float]?
Storage.read { transaction in
result = transaction.object(forKey: attachment, inCollection: Storage.volumeSamplesCollection) as? [Float]
}
return result
}
public func setVolumeSamples(for attachment: String, to volumeSamples: [Float], using transaction: Any) {
(transaction as! YapDatabaseReadWriteTransaction).setObject(volumeSamples, forKey: attachment, inCollection: Storage.volumeSamplesCollection)
}
}

@ -66,7 +66,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
explanationLabel.text = NSLocalizedString("vc_home_empty_state_message", comment: "")
let createNewPrivateChatButton = Button(style: .prominentOutline, size: .large)
createNewPrivateChatButton.setTitle(NSLocalizedString("vc_home_empty_state_button_title", comment: ""), for: UIControl.State.normal)
createNewPrivateChatButton.addTarget(self, action: #selector(createNewPrivateChat), for: UIControl.Event.touchUpInside)
createNewPrivateChatButton.addTarget(self, action: #selector(createNewDM), for: UIControl.Event.touchUpInside)
createNewPrivateChatButton.set(.width, to: 196)
let result = UIStackView(arrangedSubviews: [ explanationLabel, createNewPrivateChatButton ])
result.axis = .vertical
@ -138,7 +138,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
dbConnection.read { transaction in
self.threads.update(with: transaction) // Perform the initial update
}
// Pollers
// Start polling if needed (i.e. if the user just created or restored their Session ID)
if OWSIdentityManager.shared().identityKeyPair() != nil {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.startPollerIfNeeded()
@ -188,6 +188,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
}
@objc private func handleYapDatabaseModifiedNotification(_ yapDatabase: YapDatabase) {
// This code is very finicky and crashes easily
AssertIsOnMainThread()
let notifications = dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit
guard !notifications.isEmpty else { return }
@ -360,7 +361,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
let groupID = thread.groupModel.groupId
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
do {
try MessageSender.v2_leave(groupPublicKey, using: transaction)
try MessageSender.leave(groupPublicKey, using: transaction)
} catch {
// TODO: Handle
}
@ -391,13 +392,13 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv
present(navigationController, animated: true, completion: nil)
}
@objc func createNewPrivateChat() {
let newPrivateChatVC = NewPrivateChatVC()
let navigationController = OWSNavigationController(rootViewController: newPrivateChatVC)
@objc func createNewDM() {
let newDMVC = NewDMVC()
let navigationController = OWSNavigationController(rootViewController: newDMVC)
present(navigationController, animated: true, completion: nil)
}
@objc func createNewClosedGroup() {
@objc func createClosedGroup() {
let newClosedGroupVC = NewClosedGroupVC()
let navigationController = OWSNavigationController(rootViewController: newClosedGroupVC)
present(navigationController, animated: true, completion: nil)

@ -17,8 +17,8 @@ final class NewConversationButtonSet : UIView {
// MARK: Components
private lazy var mainButton = NewConversationButton(isMainButton: true, icon: #imageLiteral(resourceName: "Plus").scaled(to: CGSize(width: iconSize, height: iconSize)))
private lazy var createNewPrivateChatButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize)))
private lazy var createNewClosedGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Group").scaled(to: CGSize(width: iconSize, height: iconSize)))
private lazy var newDMButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize)))
private lazy var createClosedGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Group").scaled(to: CGSize(width: iconSize, height: iconSize)))
private lazy var joinOpenGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Globe").scaled(to: CGSize(width: iconSize, height: iconSize)))
// MARK: Initialization
@ -35,22 +35,22 @@ final class NewConversationButtonSet : UIView {
private func setUpViewHierarchy() {
mainButton.accessibilityLabel = "Toggle conversation options button"
mainButton.isAccessibilityElement = true
createNewPrivateChatButton.accessibilityLabel = "Start new one-on-one conversation button"
createNewPrivateChatButton.isAccessibilityElement = true
createNewClosedGroupButton.accessibilityLabel = "Start new closed group button"
createNewClosedGroupButton.isAccessibilityElement = true
newDMButton.accessibilityLabel = "Start new one-on-one conversation button"
newDMButton.isAccessibilityElement = true
createClosedGroupButton.accessibilityLabel = "Start new closed group button"
createClosedGroupButton.isAccessibilityElement = true
joinOpenGroupButton.accessibilityLabel = "Join open group button"
joinOpenGroupButton.isAccessibilityElement = true
let inset = (NewConversationButtonSet.expandedButtonSize - NewConversationButtonSet.collapsedButtonSize) / 2
addSubview(joinOpenGroupButton)
horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset)
verticalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset)
addSubview(createNewPrivateChatButton)
createNewPrivateChatButton.center(.horizontal, in: self)
verticalButtonConstraints[createNewPrivateChatButton] = createNewPrivateChatButton.pin(.top, to: .top, of: self, withInset: inset)
addSubview(createNewClosedGroupButton)
horizontalButtonConstraints[createNewClosedGroupButton] = createNewClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset)
verticalButtonConstraints[createNewClosedGroupButton] = createNewClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset)
addSubview(newDMButton)
newDMButton.center(.horizontal, in: self)
verticalButtonConstraints[newDMButton] = newDMButton.pin(.top, to: .top, of: self, withInset: inset)
addSubview(createClosedGroupButton)
horizontalButtonConstraints[createClosedGroupButton] = createClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset)
verticalButtonConstraints[createClosedGroupButton] = createClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset)
addSubview(mainButton)
mainButton.center(.horizontal, in: self)
mainButton.pin(.bottom, to: .bottom, of: self, withInset: -inset)
@ -63,25 +63,25 @@ final class NewConversationButtonSet : UIView {
let joinOpenGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleJoinOpenGroupButtonTapped))
joinOpenGroupButton.addGestureRecognizer(joinOpenGroupButtonTapGestureRecognizer)
let createNewPrivateChatButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewPrivateChatButtonTapped))
createNewPrivateChatButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer)
newDMButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer)
let createNewClosedGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewClosedGroupButtonTapped))
createNewClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer)
createClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer)
}
// MARK: Interaction
@objc private func handleJoinOpenGroupButtonTapped() { delegate?.joinOpenGroup() }
@objc private func handleCreateNewPrivateChatButtonTapped() { delegate?.createNewPrivateChat() }
@objc private func handleCreateNewClosedGroupButtonTapped() { delegate?.createNewClosedGroup() }
@objc private func handleCreateNewPrivateChatButtonTapped() { delegate?.createNewDM() }
@objc private func handleCreateNewClosedGroupButtonTapped() { delegate?.createClosedGroup() }
private func expand(isUserDragging: Bool) {
let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ]
let buttons = [ joinOpenGroupButton, newDMButton, createClosedGroupButton ]
UIView.animate(withDuration: 0.25, animations: {
buttons.forEach { $0.alpha = 1 }
let inset = (NewConversationButtonSet.expandedButtonSize - NewConversationButtonSet.collapsedButtonSize) / 2
let size = NewConversationButtonSet.collapsedButtonSize
self.joinOpenGroupButton.frame = CGRect(origin: CGPoint(x: inset, y: self.height() - size - inset), size: CGSize(width: size, height: size))
self.createNewPrivateChatButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size))
self.createNewClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size))
self.newDMButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size))
self.createClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size))
}, completion: { _ in
self.isUserDragging = isUserDragging
})
@ -89,7 +89,7 @@ final class NewConversationButtonSet : UIView {
private func collapse(withAnimation isAnimated: Bool) {
isUserDragging = false
let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ]
let buttons = [ joinOpenGroupButton, newDMButton, createClosedGroupButton ]
UIView.animate(withDuration: isAnimated ? 0.25 : 0) {
buttons.forEach { button in
button.alpha = 0
@ -126,12 +126,12 @@ final class NewConversationButtonSet : UIView {
let touchLocationInSelfCoordinates = touch.location(in: self)
mainButton.frame = CGRect(center: touchLocationInSelfCoordinates, size: mainButtonSize)
mainButton.alpha = 1 - (touchLocationInSelfCoordinates.distance(to: mainButtonLocationInSelfCoordinates) / maxDragDistance)
let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ]
let buttons = [ joinOpenGroupButton, newDMButton, createClosedGroupButton ]
let buttonToExpand = buttons.first { button in
var hasUserDraggedBeyondButton = false
if button == joinOpenGroupButton && touch.isLeft(of: joinOpenGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true }
if button == createNewPrivateChatButton && touch.isAbove(createNewPrivateChatButton, with: dragMargin) { hasUserDraggedBeyondButton = true }
if button == createNewClosedGroupButton && touch.isRight(of: createNewClosedGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true }
if button == newDMButton && touch.isAbove(newDMButton, with: dragMargin) { hasUserDraggedBeyondButton = true }
if button == createClosedGroupButton && touch.isRight(of: createClosedGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true }
return button.contains(touch) || hasUserDraggedBeyondButton
}
if let buttonToExpand = buttonToExpand {
@ -148,8 +148,8 @@ final class NewConversationButtonSet : UIView {
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, isUserDragging else { return }
if joinOpenGroupButton.contains(touch) || touch.isLeft(of: joinOpenGroupButton, with: dragMargin) { delegate?.joinOpenGroup() }
else if createNewPrivateChatButton.contains(touch) || touch.isAbove(createNewPrivateChatButton, with: dragMargin) { delegate?.createNewPrivateChat() }
else if createNewClosedGroupButton.contains(touch) || touch.isRight(of: createNewClosedGroupButton, with: dragMargin) { delegate?.createNewClosedGroup() }
else if newDMButton.contains(touch) || touch.isAbove(newDMButton, with: dragMargin) { delegate?.createNewDM() }
else if createClosedGroupButton.contains(touch) || touch.isRight(of: createClosedGroupButton, with: dragMargin) { delegate?.createClosedGroup() }
reset()
}
@ -181,11 +181,11 @@ final class NewConversationButtonSet : UIView {
if joinOpenGroupButton == expandedButton {
horizontalButtonConstraints[joinOpenGroupButton]!.constant = inset
verticalButtonConstraints[joinOpenGroupButton]!.constant = -inset
} else if createNewPrivateChatButton == expandedButton {
verticalButtonConstraints[createNewPrivateChatButton]!.constant = inset
} else if createNewClosedGroupButton == expandedButton {
horizontalButtonConstraints[createNewClosedGroupButton]!.constant = -inset
verticalButtonConstraints[createNewClosedGroupButton]!.constant = -inset
} else if newDMButton == expandedButton {
verticalButtonConstraints[newDMButton]!.constant = inset
} else if createClosedGroupButton == expandedButton {
horizontalButtonConstraints[createClosedGroupButton]!.constant = -inset
verticalButtonConstraints[createClosedGroupButton]!.constant = -inset
}
let size = NewConversationButtonSet.collapsedButtonSize
let frame = CGRect(center: button.center, size: CGSize(width: size, height: size))
@ -203,7 +203,7 @@ final class NewConversationButtonSet : UIView {
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let allButtons = [ mainButton, joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ]
let allButtons = [ mainButton, joinOpenGroupButton, newDMButton, createClosedGroupButton ]
if allButtons.contains(where: { $0.frame.contains(point) }) {
return super.hitTest(point, with: event)
} else {
@ -217,8 +217,8 @@ final class NewConversationButtonSet : UIView {
protocol NewConversationButtonSetDelegate {
func joinOpenGroup()
func createNewPrivateChat()
func createNewClosedGroup()
func createNewDM()
func createClosedGroup()
}
// MARK: Button

@ -658,7 +658,7 @@ static NSTimeInterval launchStartedAt;
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (![self.tsAccountManager isRegisteredAndReady]) { return; }
[SignalApp.sharedApp.homeViewController createNewPrivateChat];
[SignalApp.sharedApp.homeViewController createNewDM];
completionHandler(YES);
}];
}

@ -12,13 +12,19 @@ enum Onboarding {
switch self {
case .register:
userDefaults[.hasViewedSeed] = false
// Set hasSyncedInitialConfiguration to true so that when we hit the home screen a configuration sync
// is triggered (yes, the logic is a bit weird). This is needed so that if the user registers and
// immediately links a device, there'll be a configuration in their swarm.
userDefaults[.hasSyncedInitialConfiguration] = true
case .recover, .link:
userDefaults[.hasViewedSeed] = true
userDefaults[.hasViewedSeed] = true // No need to show it again if the user is restoring or linking
userDefaults[.hasSyncedInitialConfiguration] = false
}
switch self {
case .register, .recover:
// Set both lastDisplayNameUpdate and lastProfilePictureUpdate to the current date, so that
// we don't overwrite what the user set in the display name step with whatever we find in
// their swarm.
userDefaults[.lastDisplayNameUpdate] = Date()
userDefaults[.lastProfilePictureUpdate] = Date()
case .link: break

@ -1,190 +0,0 @@
import Accelerate
import PromiseKit
enum AudioUtilities {
private static let noiseFloor: Float = -80
private struct FileInfo {
let sampleCount: Int
let asset: AVAsset
let track: AVAssetTrack
}
enum Error : LocalizedError {
case noAudioTrack
case noAudioFormatDescription
case loadingFailed
case parsingFailed
var errorDescription: String? {
switch self {
case .noAudioTrack: return "No audio track."
case .noAudioFormatDescription: return "No audio format description."
case .loadingFailed: return "Couldn't load asset."
case .parsingFailed: return "Couldn't parse asset."
}
}
}
static func getVolumeSamples(for audioFileURL: URL, targetSampleCount: Int) -> Promise<[Float]> {
return loadFile(audioFileURL).then { fileInfo in
AudioUtilities.parseSamples(from: fileInfo, with: targetSampleCount)
}
}
private static func loadFile(_ audioFileURL: URL, isRetry: Bool = false) -> Promise<FileInfo> {
let asset = AVURLAsset(url: audioFileURL)
guard let track = asset.tracks(withMediaType: AVMediaType.audio).first else {
if isRetry {
return Promise(error: Error.loadingFailed)
} else {
// Workaround for issue where MP3 files sent by Android get saved as M4A
var newAudioFileURL = audioFileURL.deletingPathExtension()
let fileName = newAudioFileURL.lastPathComponent
newAudioFileURL = newAudioFileURL.deletingLastPathComponent()
newAudioFileURL = newAudioFileURL.appendingPathComponent("\(fileName).mp3")
let fileManager = FileManager.default
if fileManager.fileExists(atPath: newAudioFileURL.path) {
return loadFile(newAudioFileURL, isRetry: true)
} else {
do {
try FileManager.default.copyItem(at: audioFileURL, to: newAudioFileURL)
} catch {
return Promise(error: Error.loadingFailed)
}
return loadFile(newAudioFileURL, isRetry: true)
}
}
}
let (promise, seal) = Promise<FileInfo>.pending()
asset.loadValuesAsynchronously(forKeys: [ #keyPath(AVAsset.duration) ]) {
var nsError: NSError?
let status = asset.statusOfValue(forKey: #keyPath(AVAsset.duration), error: &nsError)
switch status {
case .loaded:
guard let formatDescriptions = track.formatDescriptions as? [CMAudioFormatDescription],
let audioFormatDescription = formatDescriptions.first,
let asbd = CMAudioFormatDescriptionGetStreamBasicDescription(audioFormatDescription)
else { return seal.reject(Error.noAudioFormatDescription) }
let sampleCount = Int((asbd.pointee.mSampleRate) * Float64(asset.duration.value) / Float64(asset.duration.timescale))
let fileInfo = FileInfo(sampleCount: sampleCount, asset: asset, track: track)
seal.fulfill(fileInfo)
default:
print("Couldn't load asset due to error: \(nsError?.localizedDescription ?? "no description provided").")
seal.reject(Error.loadingFailed)
}
}
return promise
}
private static func parseSamples(from fileInfo: FileInfo, with targetSampleCount: Int) -> Promise<[Float]> {
// Prepare the reader
guard let reader = try? AVAssetReader(asset: fileInfo.asset) else { return Promise(error: Error.parsingFailed) }
let range = 0..<fileInfo.sampleCount
reader.timeRange = CMTimeRange(start: CMTime(value: Int64(range.lowerBound), timescale: fileInfo.asset.duration.timescale),
duration: CMTime(value: Int64(range.count), timescale: fileInfo.asset.duration.timescale))
let outputSettings: [String:Any] = [
AVFormatIDKey : Int(kAudioFormatLinearPCM),
AVLinearPCMBitDepthKey : 16,
AVLinearPCMIsBigEndianKey : false,
AVLinearPCMIsFloatKey : false,
AVLinearPCMIsNonInterleaved : false
]
let output = AVAssetReaderTrackOutput(track: fileInfo.track, outputSettings: outputSettings)
output.alwaysCopiesSampleData = false
reader.add(output)
var channelCount = 1
let formatDescriptions = fileInfo.track.formatDescriptions as! [CMAudioFormatDescription]
for audioFormatDescription in formatDescriptions {
guard let asbd = CMAudioFormatDescriptionGetStreamBasicDescription(audioFormatDescription) else {
return Promise(error: Error.parsingFailed)
}
channelCount = Int(asbd.pointee.mChannelsPerFrame)
}
let samplesPerPixel = max(1, channelCount * range.count / targetSampleCount)
let filter = [Float](repeating: 1 / Float(samplesPerPixel), count: samplesPerPixel)
var result = [Float]()
var sampleBuffer = Data()
// Read the file
reader.startReading()
defer { reader.cancelReading() }
while reader.status == .reading {
guard let readSampleBuffer = output.copyNextSampleBuffer(),
let readBuffer = CMSampleBufferGetDataBuffer(readSampleBuffer) else { break }
var readBufferLength = 0
var readBufferPointer: UnsafeMutablePointer<Int8>?
CMBlockBufferGetDataPointer(readBuffer,
atOffset: 0,
lengthAtOffsetOut: &readBufferLength,
totalLengthOut: nil,
dataPointerOut: &readBufferPointer)
sampleBuffer.append(UnsafeBufferPointer(start: readBufferPointer, count: readBufferLength))
CMSampleBufferInvalidate(readSampleBuffer)
let sampleCount = sampleBuffer.count / MemoryLayout<Int16>.size
let downSampledLength = sampleCount / samplesPerPixel
let samplesToProcess = downSampledLength * samplesPerPixel
guard samplesToProcess > 0 else { continue }
processSamples(from: &sampleBuffer,
outputSamples: &result,
samplesToProcess: samplesToProcess,
downSampledLength: downSampledLength,
samplesPerPixel: samplesPerPixel,
filter: filter)
}
// Process any remaining samples
let samplesToProcess = sampleBuffer.count / MemoryLayout<Int16>.size
if samplesToProcess > 0 {
let downSampledLength = 1
let samplesPerPixel = samplesToProcess
let filter = [Float](repeating: 1.0 / Float(samplesPerPixel), count: samplesPerPixel)
processSamples(from: &sampleBuffer,
outputSamples: &result,
samplesToProcess: samplesToProcess,
downSampledLength: downSampledLength,
samplesPerPixel: samplesPerPixel,
filter: filter)
}
guard reader.status == .completed else { return Promise(error: Error.parsingFailed) }
// Return
return Promise { $0.fulfill(result) }
}
private static func processSamples(from sampleBuffer: inout Data, outputSamples: inout [Float], samplesToProcess: Int,
downSampledLength: Int, samplesPerPixel: Int, filter: [Float]) {
sampleBuffer.withUnsafeBytes { (samples: UnsafeRawBufferPointer) in
var processingBuffer = [Float](repeating: 0, count: samplesToProcess)
let sampleCount = vDSP_Length(samplesToProcess)
// Create an UnsafePointer<Int16> from the samples
let unsafeBufferPointer = samples.bindMemory(to: Int16.self)
let unsafePointer = unsafeBufferPointer.baseAddress!
// Convert 16 bit int samples to floats
vDSP_vflt16(unsafePointer, 1, &processingBuffer, 1, sampleCount)
// Take the absolute values to get the amplitude
vDSP_vabs(processingBuffer, 1, &processingBuffer, 1, sampleCount)
// Get the corresponding dB values and clip the results
getdB(from: &processingBuffer)
// Downsample and average
var downSampledData = [Float](repeating: 0, count: downSampledLength)
vDSP_desamp(processingBuffer,
vDSP_Stride(samplesPerPixel),
filter,
&downSampledData,
vDSP_Length(downSampledLength),
vDSP_Length(samplesPerPixel))
// Remove the processed samples
sampleBuffer.removeFirst(samplesToProcess * MemoryLayout<Int16>.size)
// Update the output samples
outputSamples += downSampledData
}
}
static func getdB(from normalizedSamples: inout [Float]) {
// Convert samples to a log scale
var zero: Float = 32768.0
vDSP_vdbcon(normalizedSamples, 1, &zero, &normalizedSamples, 1, vDSP_Length(normalizedSamples.count), 1)
// Clip to [noiseFloor, 0]
var ceil: Float = 0.0
var noiseFloorMutable = AudioUtilities.noiseFloor
vDSP_vclip(normalizedSamples, 1, &noiseFloorMutable, &ceil, &normalizedSamples, 1, vDSP_Length(normalizedSamples.count))
}
}

@ -85,7 +85,7 @@ extension MessageSender {
}
}
public static func v2_update(_ groupPublicKey: String, with members: Set<String>, name: String, transaction: YapDatabaseReadWriteTransaction) throws {
public static func update(_ groupPublicKey: String, with members: Set<String>, name: String, transaction: YapDatabaseReadWriteTransaction) throws {
// Get the group, check preconditions & prepare
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
let threadID = TSGroupThread.threadId(fromGroupId: groupID)
@ -208,8 +208,8 @@ extension MessageSender {
infoMessage.save(with: transaction)
}
@objc(v2_leaveClosedGroupWithPublicKey:using:error:)
public static func v2_leave(_ groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws {
@objc(leaveClosedGroupWithPublicKey:using:error:)
public static func leave(_ groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws {
// Get the group, check preconditions & prepare
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
let threadID = TSGroupThread.threadId(fromGroupId: groupID)

Loading…
Cancel
Save