diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 7b03254a3..d05ceace5 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -574,7 +574,7 @@ B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; }; B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; }; B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; }; - B885D5F62334A32100EE0D8E /* UIView+Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */; }; + B885D5F62334A32100EE0D8E /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */; }; B891105C2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; B891105E2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; B891105F2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; }; @@ -584,8 +584,12 @@ B89841E322B7579F00B1BDC6 /* NewConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */; }; B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */; }; B8B26C91234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */; }; - B8BB82A0238F322400BA5194 /* UIColor+Loki.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB829F238F322400BA5194 /* UIColor+Loki.swift */; }; - B8BB82A2238F356100BA5194 /* CGFloat+Loki.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A1238F356100BA5194 /* CGFloat+Loki.swift */; }; + B8BB82A0238F322400BA5194 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB829F238F322400BA5194 /* Colors.swift */; }; + B8BB82A2238F356100BA5194 /* Values.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A1238F356100BA5194 /* Values.swift */; }; + B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; }; + B8BB82A9238F62FB00BA5194 /* Gradients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A8238F62FB00BA5194 /* Gradients.swift */; }; + B8BB82AB238F669C00BA5194 /* ConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AA238F669C00BA5194 /* ConversationCell.swift */; }; + B8BB82AD238F734800BA5194 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */; }; B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; }; @@ -1390,7 +1394,7 @@ B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = ""; }; B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = ""; }; B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModal.swift; sourceTree = ""; }; - B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraint.swift"; sourceTree = ""; }; + B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; B891105B2320872800F15FCC /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = ""; }; B894D0702339D6F300B4D94D /* DeviceLinkingModalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModalDelegate.swift; sourceTree = ""; }; @@ -1398,8 +1402,12 @@ B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = ""; }; B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionCandidateSelectionView.swift; sourceTree = ""; }; B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionCandidateSelectionViewDelegate.swift; sourceTree = ""; }; - B8BB829F238F322400BA5194 /* UIColor+Loki.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Loki.swift"; sourceTree = ""; }; - B8BB82A1238F356100BA5194 /* CGFloat+Loki.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Loki.swift"; sourceTree = ""; }; + B8BB829F238F322400BA5194 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; + B8BB82A1238F356100BA5194 /* Values.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Values.swift; sourceTree = ""; }; + B8BB82A4238F627000BA5194 /* HomeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeVC.swift; sourceTree = ""; }; + B8BB82A8238F62FB00BA5194 /* Gradients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gradients.swift; sourceTree = ""; }; + B8BB82AA238F669C00BA5194 /* ConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCell.swift; sourceTree = ""; }; + B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePictureView.swift; sourceTree = ""; }; B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = ""; }; B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = ""; }; @@ -2660,6 +2668,9 @@ B86BD0872339A1ED000F5AE3 /* Onboarding */, B86BD08223399ABF000F5AE3 /* Settings */, B86BD0882339A253000F5AE3 /* Utilities */, + B8BB82A4238F627000BA5194 /* HomeVC.swift */, + B8BB82AA238F669C00BA5194 /* ConversationCell.swift */, + B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */, ); path = Loki; sourceTree = ""; @@ -2705,7 +2716,7 @@ B84664F4235022F30083A1CD /* MentionUtilities.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, B8BB82A3238F356800BA5194 /* Style Guide */, - B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */, + B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */, ); path = Utilities; sourceTree = ""; @@ -2713,8 +2724,9 @@ B8BB82A3238F356800BA5194 /* Style Guide */ = { isa = PBXGroup; children = ( - B8BB82A1238F356100BA5194 /* CGFloat+Loki.swift */, - B8BB829F238F322400BA5194 /* UIColor+Loki.swift */, + B8BB829F238F322400BA5194 /* Colors.swift */, + B8BB82A8238F62FB00BA5194 /* Gradients.swift */, + B8BB82A1238F356100BA5194 /* Values.swift */, ); path = "Style Guide"; sourceTree = ""; @@ -3777,7 +3789,7 @@ 4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */, EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */, 45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */, - B8BB82A0238F322400BA5194 /* UIColor+Loki.swift in Sources */, + B8BB82A0238F322400BA5194 /* Colors.swift in Sources */, 34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */, 4CEB78C92178EBAB00F315D2 /* OWSSessionResetJobRecord.m in Sources */, 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */, @@ -3809,7 +3821,8 @@ B80C6B572384A56D00FDBC8B /* DeviceLinksVC.swift in Sources */, 34A8B3512190A40E00218A25 /* MediaAlbumCellView.swift in Sources */, 34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */, - B8BB82A2238F356100BA5194 /* CGFloat+Loki.swift in Sources */, + B8BB82A2238F356100BA5194 /* Values.swift in Sources */, + B8BB82AB238F669C00BA5194 /* ConversationCell.swift in Sources */, 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */, 4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, @@ -3822,6 +3835,7 @@ D221A09A169C9E5E00537ABF /* main.m in Sources */, 3496957221A301A100DCFE74 /* OWSBackup.m in Sources */, B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */, + B8BB82A9238F62FB00BA5194 /* Gradients.swift in Sources */, 34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */, 3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */, 34E5DC8220D8050D00C08145 /* RegistrationUtils.m in Sources */, @@ -3863,6 +3877,7 @@ 4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */, 3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */, 34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */, + B8BB82AD238F734800BA5194 /* ProfilePictureView.swift in Sources */, B8162F0322891AD600D46544 /* FriendRequestView.swift in Sources */, 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */, 34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */, @@ -3880,7 +3895,7 @@ 34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */, B821F2F82272CED3002C88C0 /* DisplayNameVC.swift in Sources */, 34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */, - B885D5F62334A32100EE0D8E /* UIView+Constraint.swift in Sources */, + B885D5F62334A32100EE0D8E /* UIView+Constraints.swift in Sources */, 24BD2609234DA2050008EB0A /* NewPublicChatVC.swift in Sources */, 34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */, 34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */, @@ -3934,6 +3949,7 @@ 340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */, 34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */, 340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */, + B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */, 3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */, 4574A5D61DD6704700C6B692 /* CallService.swift in Sources */, 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 7b8b344e6..d438cb471 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -877,7 +877,7 @@ static NSTimeInterval launchStartedAt; return; } - [SignalApp.sharedApp.homeViewController showNewConversationVC]; +// [SignalApp.sharedApp.homeViewController showNewConversationVC]; completionHandler(YES); }]; @@ -1418,7 +1418,7 @@ static NSTimeInterval launchStartedAt; if (self.backup.hasPendingRestoreDecision) { rootViewController = [BackupRestoreViewController new]; } else { - rootViewController = [HomeViewController new]; + rootViewController = [HomeVC new]; } } else { rootViewController = [[OnboardingController new] initialViewController]; diff --git a/Signal/src/Loki/ConversationCell.swift b/Signal/src/Loki/ConversationCell.swift new file mode 100644 index 000000000..24e49f42c --- /dev/null +++ b/Signal/src/Loki/ConversationCell.swift @@ -0,0 +1,144 @@ + +final class ConversationCell : UITableViewCell { + public var threadViewModel: ThreadViewModel! { didSet { update() } } + + public static let reuseIdentifier = "ConversationCell" + + // MARK: Components + private lazy var unreadMessagesIndicator: UIView = { + let result = UIView() + result.backgroundColor = Colors.accent + return result + }() + + private lazy var profilePictureView = ProfilePictureView() + + private lazy var displayNameLabel: UILabel = { + let result = UILabel() + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.textColor = Colors.text + result.lineBreakMode = .byTruncatingTail + return result + }() + + private lazy var snippetLabel: UILabel = { + let result = UILabel() + result.font = .systemFont(ofSize: Values.smallFontSize) + result.textColor = Colors.text + result.lineBreakMode = .byTruncatingTail + return result + }() + + private lazy var typingIndicatorView = TypingIndicatorView() + + // 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() { + // Make the cell transparent + backgroundColor = .clear + // Set up the unread messages indicator + unreadMessagesIndicator.set(.width, to: Values.accentLineThickness) + // Set up the profile picture view + let profilePictureViewSize = Values.mediumProfilePictureSize + profilePictureView.set(.width, to: profilePictureViewSize) + profilePictureView.set(.height, to: profilePictureViewSize) + profilePictureView.size = profilePictureViewSize + // Set up the label stack view + let snippetLabelContainer = UIView() + snippetLabelContainer.addSubview(snippetLabel) + snippetLabelContainer.addSubview(typingIndicatorView) + let labelStackView = UIStackView(arrangedSubviews: [ UIView.spacer(withHeight: Values.smallSpacing), displayNameLabel, snippetLabelContainer, UIView.spacer(withHeight: Values.smallSpacing) ]) + labelStackView.axis = .vertical + labelStackView.spacing = Values.smallSpacing + // Set up the main stack view + let stackView = UIStackView(arrangedSubviews: [ unreadMessagesIndicator, profilePictureView, labelStackView ]) + stackView.axis = .horizontal + stackView.alignment = .center + stackView.spacing = Values.mediumSpacing + contentView.addSubview(stackView) + // Set up the constraints + unreadMessagesIndicator.pin(.top, to: .top, of: stackView) + unreadMessagesIndicator.pin(.bottom, to: .bottom, of: stackView) + snippetLabel.pin(to: snippetLabelContainer) + typingIndicatorView.pin(.leading, to: .leading, of: snippetLabelContainer) + typingIndicatorView.centerYAnchor.constraint(equalTo: snippetLabel.centerYAnchor).isActive = true + stackView.pin(.leading, to: .leading, of: contentView) + stackView.pin(.top, to: .top, of: contentView) + contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.mediumSpacing) + contentView.pin(.bottom, to: .bottom, of: stackView) + stackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing) // Workaround for weird constraints issue + } + + // MARK: Updating + private func update() { + LokiAPI.populateUserHexEncodedPublicKeyCacheIfNeeded(for: threadViewModel.threadRecord.uniqueId!) // FIXME: This is a terrible place to do this + unreadMessagesIndicator.isHidden = !threadViewModel.hasUnreadMessages + if threadViewModel.hasUnreadMessages { + backgroundColor = UIColor(hex: 0x1B1B1B) + } else { + backgroundColor = .clear + } + if threadViewModel.isGroupThread { + let users = LokiAPI.userHexEncodedPublicKeyCache[threadViewModel.threadRecord.uniqueId!] ?? [] + let randomUsers = users.sorted().prefix(2) // Sort to provide a level of stability + if !randomUsers.isEmpty { + profilePictureView.hexEncodedPublicKey = randomUsers[0] + profilePictureView.additionalHexEncodedPublicKey = randomUsers.count == 2 ? randomUsers[1] : nil + } else { + // TODO: Handle + } + } else { + profilePictureView.hexEncodedPublicKey = threadViewModel.contactIdentifier! + profilePictureView.additionalHexEncodedPublicKey = nil + } + profilePictureView.update() + displayNameLabel.text = getDisplayName() + if SSKEnvironment.shared.typingIndicators.typingRecipientId(forThread: self.threadViewModel.threadRecord) != nil { + snippetLabel.text = "" + typingIndicatorView.isHidden = false + typingIndicatorView.startAnimation() + } else { + snippetLabel.attributedText = getSnippet() + typingIndicatorView.isHidden = true + typingIndicatorView.stopAnimation() + } + } + + private func getDisplayName() -> String { + if threadViewModel.isGroupThread { + if threadViewModel.name.isEmpty { + return NSLocalizedString("New Group", comment: "") + } else { + return threadViewModel.name + } + } else { + if threadViewModel.threadRecord.isNoteToSelf() { + return NSLocalizedString("Note to Self", comment: "") + } else { + let hexEncodedPublicKey = threadViewModel.contactIdentifier! + return DisplayNameUtilities.getPrivateChatDisplayName(for: hexEncodedPublicKey) ?? hexEncodedPublicKey + } + } + } + + private func getSnippet() -> NSMutableAttributedString { + let result = NSMutableAttributedString() + if threadViewModel.isMuted { + result.append(NSAttributedString(string: "\u{e067} ", attributes: [ .font : UIFont.ows_elegantIconsFont(10), .foregroundColor : Colors.unimportant ])) + } + if let rawSnippet = threadViewModel.lastMessageText { + let snippet = MentionUtilities.highlightMentions(in: rawSnippet, threadID: threadViewModel.threadRecord.uniqueId!) + result.append(NSAttributedString(string: snippet, attributes: [ .font : UIFont.systemFont(ofSize: Values.smallFontSize), .foregroundColor : Colors.text ])) + } + return result + } +} diff --git a/Signal/src/Loki/HomeVC.swift b/Signal/src/Loki/HomeVC.swift new file mode 100644 index 000000000..18768828a --- /dev/null +++ b/Signal/src/Loki/HomeVC.swift @@ -0,0 +1,84 @@ + +public final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate { + private var threadViewModelCache: [String:ThreadViewModel] = [:] + + private var threads: YapDatabaseViewMappings = { + let result = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName) + result.setIsReversed(true, forGroup: TSInboxGroup) + return result + }() + + private let uiDatabaseConnection: YapDatabaseConnection = { + let result = OWSPrimaryStorage.shared().newDatabaseConnection() + result.objectCacheLimit = 500 + return result + }() + + // MARK: Settings + public override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } + + // MARK: Components + private lazy var tableView: UITableView = { + let result = UITableView() + result.backgroundColor = .clear + result.separatorStyle = .none + result.register(ConversationCell.self, forCellReuseIdentifier: ConversationCell.reuseIdentifier) + return result + }() + + // MARK: Lifecycle + public override func viewDidLoad() { + // Set gradient background + view.backgroundColor = .clear + let gradient = Gradients.defaultLokiBackground + view.setGradient(gradient) + // Customize title + navigationItem.title = NSLocalizedString("Messages", comment: "") + navigationController?.navigationBar.titleTextAttributes = [ .foregroundColor : Colors.text, .font : UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize) ] + // Set up table view + tableView.dataSource = self + tableView.delegate = self + view.addSubview(tableView) + tableView.pin(to: view) + // Do initial update + uiDatabaseConnection.beginLongLivedReadTransaction() + uiDatabaseConnection.read { transaction in + self.threads.update(with: transaction) + } + tableView.reloadData() + } + + // MARK: Data + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return Int(threads.numberOfItems(inGroup: TSInboxGroup)) + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell + cell.threadViewModel = threadViewModel(at: indexPath.row) + return cell + } + + // MARK: Convenience + private func thread(at index: Int) -> TSThread? { + var thread: TSThread? = nil + uiDatabaseConnection.read { transaction in + thread = ((transaction as YapDatabaseReadTransaction).ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction).object(atRow: UInt(index), inSection: 0, with: self.threads) as! TSThread? + } + return thread + } + + private func threadViewModel(at index: Int) -> ThreadViewModel? { + guard let thread = thread(at: index) else { return nil } + if let cachedThreadViewModel = threadViewModelCache[thread.uniqueId!] { + return cachedThreadViewModel + } else { + var threadViewModel: ThreadViewModel? = nil + uiDatabaseConnection.read { transaction in + threadViewModel = ThreadViewModel(thread: thread, transaction: transaction) + } + threadViewModelCache[thread.uniqueId!] = threadViewModel + return threadViewModel + } + } +} diff --git a/Signal/src/Loki/ProfilePictureView.swift b/Signal/src/Loki/ProfilePictureView.swift new file mode 100644 index 000000000..f712cec63 --- /dev/null +++ b/Signal/src/Loki/ProfilePictureView.swift @@ -0,0 +1,72 @@ + +public final class ProfilePictureView : UIView { + private var imageViewWidthConstraint: NSLayoutConstraint! + private var imageViewHeightConstraint: NSLayoutConstraint! + public var size: CGFloat! + public var hexEncodedPublicKey: String! + public var additionalHexEncodedPublicKey: String? + + // MARK: Components + private lazy var imageView = getImageView() + private lazy var additionalImageView = getImageView() + + // MARK: Lifecycle + public override init(frame: CGRect) { + super.init(frame: frame) + initialize() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + initialize() + } + + private func initialize() { + // Set up image view + addSubview(imageView) + imageView.pin(.leading, to: .leading, of: self) + imageView.pin(.top, to: .top, of: self) + // Set up additional image view + addSubview(additionalImageView) + additionalImageView.pin(.trailing, to: .trailing, of: self) + additionalImageView.pin(.bottom, to: .bottom, of: self) + let additionalImageViewSize = Values.smallProfilePictureSize + additionalImageView.set(.width, to: additionalImageViewSize) + additionalImageView.set(.height, to: additionalImageViewSize) + additionalImageView.layer.cornerRadius = additionalImageViewSize / 2 + } + + // MARK: Updating + public func update() { + if let imageViewWidthConstraint = imageViewWidthConstraint, let imageViewHeightConstraint = imageViewHeightConstraint { + imageView.removeConstraint(imageViewWidthConstraint) + imageView.removeConstraint(imageViewHeightConstraint) + } + let size: CGFloat + if let additionalHexEncodedPublicKey = additionalHexEncodedPublicKey { + size = Values.smallProfilePictureSize + imageViewWidthConstraint = imageView.set(.width, to: size) + imageViewHeightConstraint = imageView.set(.height, to: size) + additionalImageView.isHidden = false + additionalImageView.image = Identicon.generateIcon(string: additionalHexEncodedPublicKey, size: size) + } else { + size = self.size + imageViewWidthConstraint = imageView.pin(.trailing, to: .trailing, of: self) + imageViewHeightConstraint = imageView.pin(.bottom, to: .bottom, of: self) + additionalImageView.isHidden = true + additionalImageView.image = nil + } + imageView.image = Identicon.generateIcon(string: hexEncodedPublicKey, size: size) + imageView.layer.cornerRadius = size / 2 + } + + // MARK: Convenience + private func getImageView() -> UIImageView { + let result = UIImageView() + result.layer.masksToBounds = true + result.backgroundColor = Colors.unimportant + result.layer.borderColor = Colors.border.cgColor + result.layer.borderWidth = Values.profilePictureBorderThickness + return result + } +} diff --git a/Signal/src/Loki/Utilities/Style Guide/CGFloat+Loki.swift b/Signal/src/Loki/Utilities/Style Guide/CGFloat+Loki.swift deleted file mode 100644 index 03c816241..000000000 --- a/Signal/src/Loki/Utilities/Style Guide/CGFloat+Loki.swift +++ /dev/null @@ -1,19 +0,0 @@ - -@objc public extension CGFloat { - - // MARK: - Alpha Values - @objc public static let inactiveElementOpacity = CGFloat(0.6) - - // MARK: - Font Sizes - @objc public static let smallFontSize = CGFloat(13) - @objc public static let regularFontSize = CGFloat(15) - @objc public static let largeFontSize = CGFloat(20) - @objc public static let veryLargeFontSize = CGFloat(25) - @objc public static let massiveFontSize = CGFloat(50) - - // MARK: - Element Sizes - @objc public static let buttonHeight = CGFloat(34) - @objc public static let accentLineThickness = CGFloat(4) - @objc public static let regularAvatarSize = CGFloat(45) - @objc public static let largeAvatarSize = CGFloat(65) -} diff --git a/Signal/src/Loki/Utilities/Style Guide/UIColor+Loki.swift b/Signal/src/Loki/Utilities/Style Guide/Colors.swift similarity index 56% rename from Signal/src/Loki/Utilities/Style Guide/UIColor+Loki.swift rename to Signal/src/Loki/Utilities/Style Guide/Colors.swift index c206876f5..38239f9e6 100644 --- a/Signal/src/Loki/Utilities/Style Guide/UIColor+Loki.swift +++ b/Signal/src/Loki/Utilities/Style Guide/Colors.swift @@ -1,13 +1,19 @@ @objc public extension UIColor { - @objc public convenience init(hex value: UInt) { + @objc convenience init(hex value: UInt) { // Doesn't need to be declared public because the extension is already public let red = CGFloat((value >> 16) & 0xff) / 255 let green = CGFloat((value >> 8) & 0xff) / 255 let blue = CGFloat((value >> 0) & 0xff) / 255 self.init(red: red, green: green, blue: blue, alpha: 1) } +} + +@objc(LKColors) +public final class Colors : NSObject { @objc public static let accent = UIColor(hex: 0x00F782) @objc public static let text = UIColor(hex: 0xFFFFFF) + @objc public static let unimportant = UIColor(hex: 0xD8D8D8) + @objc public static let border = UIColor(hex: 0x979797) } diff --git a/Signal/src/Loki/Utilities/Style Guide/Gradients.swift b/Signal/src/Loki/Utilities/Style Guide/Gradients.swift new file mode 100644 index 000000000..2e26454cb --- /dev/null +++ b/Signal/src/Loki/Utilities/Style Guide/Gradients.swift @@ -0,0 +1,33 @@ + +@objc(LKGradient) +public final class Gradient : NSObject { + public let start: UIColor + public let end: UIColor + + private override init() { preconditionFailure("Use init(start:end:) instead.") } + + @objc public init(start: UIColor, end: UIColor) { + self.start = start + self.end = end + super.init() + } +} + +@objc public extension UIView { + + @objc func setGradient(_ gradient: Gradient) { // Doesn't need to be declared public because the extension is already public + let layer = CAGradientLayer() + layer.frame = UIScreen.main.bounds + layer.colors = [ gradient.start.cgColor, gradient.end.cgColor ] + layer.startPoint = CGPoint(x: 0.5, y: 0) + layer.endPoint = CGPoint(x: 0.5, y: 1) + let index = UInt32((self.layer.sublayers ?? []).count) + self.layer.insertSublayer(layer, at: index) + } +} + +@objc(LKGradients) +public final class Gradients : NSObject { + + @objc public static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex:0x121212)) +} diff --git a/Signal/src/Loki/Utilities/Style Guide/Values.swift b/Signal/src/Loki/Utilities/Style Guide/Values.swift new file mode 100644 index 000000000..d84632dfc --- /dev/null +++ b/Signal/src/Loki/Utilities/Style Guide/Values.swift @@ -0,0 +1,26 @@ + +@objc(LKValues) +public final class Values : NSObject { + + // MARK: - Alpha Values + @objc public static let inactiveElementOpacity = CGFloat(0.6) + + // MARK: - Font Sizes + @objc public static let smallFontSize = CGFloat(13) + @objc public static let mediumFontSize = CGFloat(15) + @objc public static let largeFontSize = CGFloat(20) + @objc public static let veryLargeFontSize = CGFloat(25) + @objc public static let massiveFontSize = CGFloat(50) + + // MARK: - Element Sizes + @objc public static let buttonHeight = CGFloat(34) + @objc public static let accentLineThickness = CGFloat(4) + @objc public static let smallProfilePictureSize = CGFloat(35) + @objc public static let mediumProfilePictureSize = CGFloat(45) + @objc public static let largeProfilePictureSize = CGFloat(65) + @objc public static let profilePictureBorderThickness = CGFloat(1) + + // MARK: - Distances + @objc public static let smallSpacing = CGFloat(8) + @objc public static let mediumSpacing = CGFloat(16) +} diff --git a/Signal/src/Loki/Utilities/UIView+Constraint.swift b/Signal/src/Loki/Utilities/UIView+Constraints.swift similarity index 100% rename from Signal/src/Loki/Utilities/UIView+Constraint.swift rename to Signal/src/Loki/Utilities/UIView+Constraints.swift diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index c7bd82edc..01efc8aaf 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -276,7 +276,7 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { // TODO: Remove this. if (self.homeViewMode == HomeViewMode_Inbox) { - [SignalApp.sharedApp setHomeViewController:self]; +// [SignalApp.sharedApp setHomeViewController:self]; } UIStackView *reminderStackView = [UIStackView new]; diff --git a/Signal/src/environment/SignalApp.h b/Signal/src/environment/SignalApp.h index c46e60674..364795abc 100644 --- a/Signal/src/environment/SignalApp.h +++ b/Signal/src/environment/SignalApp.h @@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN @class AccountManager; @class CallService; @class CallUIAdapter; -@class HomeViewController; +@class HomeVC; @class OWSMessageFetcherJob; @class OWSNavigationController; @class OWSWebRTCCallMessageHandler; @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SignalApp : NSObject -@property (nonatomic, nullable, weak) HomeViewController *homeViewController; +@property (nonatomic, nullable, weak) HomeVC *homeViewController; @property (nonatomic, nullable, weak) OWSNavigationController *signUpFlowNavigationController; - (instancetype)init NS_UNAVAILABLE; diff --git a/Signal/src/environment/SignalApp.m b/Signal/src/environment/SignalApp.m index 05fb721ff..542a5fc11 100644 --- a/Signal/src/environment/SignalApp.m +++ b/Signal/src/environment/SignalApp.m @@ -5,7 +5,6 @@ #import "SignalApp.h" #import "AppDelegate.h" #import "ConversationViewController.h" -#import "HomeViewController.h" #import "Session-Swift.h" #import "SignalsNavigationController.h" #import @@ -118,7 +117,7 @@ NS_ASSUME_NONNULL_BEGIN } } - [self.homeViewController presentThread:thread action:action focusMessageId:focusMessageId animated:isAnimated]; +// [self.homeViewController presentThread:thread action:action focusMessageId:focusMessageId animated:isAnimated]; }); } @@ -146,10 +145,10 @@ NS_ASSUME_NONNULL_BEGIN } } - [self.homeViewController presentThread:thread - action:ConversationViewActionNone - focusMessageId:nil - animated:isAnimated]; +// [self.homeViewController presentThread:thread +// action:ConversationViewActionNone +// focusMessageId:nil +// animated:isAnimated]; }); } @@ -177,12 +176,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)showHomeView { - HomeViewController *homeView = [HomeViewController new]; + HomeVC *homeView = [HomeVC new]; SignalsNavigationController *navigationController = [[SignalsNavigationController alloc] initWithRootViewController:homeView]; AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; appDelegate.window.rootViewController = navigationController; - OWSAssertDebug([navigationController.topViewController isKindOfClass:[HomeViewController class]]); + OWSAssertDebug([navigationController.topViewController isKindOfClass:[HomeVC class]]); // Clear the signUpFlowNavigationController. [self setSignUpFlowNavigationController:nil]; diff --git a/Signal/src/views/TypingIndicatorView.swift b/Signal/src/views/TypingIndicatorView.swift index a90200b19..f9922008b 100644 --- a/Signal/src/views/TypingIndicatorView.swift +++ b/Signal/src/views/TypingIndicatorView.swift @@ -127,9 +127,7 @@ fileprivate func startAnimation() { stopAnimation() - let baseColor = (Theme.isDarkThemeEnabled - ? UIColor(rgbHex: 0xBBBDBE) - : UIColor(rgbHex: 0x636467)) + let baseColor = UIColor.white let timeIncrement: CFTimeInterval = 0.15 var colorValues = [CGColor]() var pathValues = [CGPath]() diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index f8d1e9a55..3ea6f7b20 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2672,3 +2672,8 @@ "Your device was unlinked successfully" = "Your device was unlinked successfully"; "Unnamed Device" = "Unnamed Device"; "Linked device (%@)" = "Linked device (%@)"; + +// MARK: - Redesign +"Messages" = "Messages"; +"Note to Self" = "Note to Self"; +"New Group" = "New Group"; diff --git a/SignalMessaging/Loki/Identicon+ObjC.swift b/SignalMessaging/Loki/Identicon+ObjC.swift index 9b8c81bfd..d24bf7bb8 100644 --- a/SignalMessaging/Loki/Identicon+ObjC.swift +++ b/SignalMessaging/Loki/Identicon+ObjC.swift @@ -1,8 +1,8 @@ @objc(LKIdenticon) -final class Identicon : NSObject { +public final class Identicon : NSObject { - @objc static func generateIcon(string: String, size: CGFloat) -> UIImage { + @objc public static func generateIcon(string: String, size: CGFloat) -> UIImage { let icon = JazzIcon(seed: string) let iconLayer = icon.generateLayer(ofSize: size) let rect = CGRect(origin: CGPoint.zero, size: iconLayer.frame.size)