瀏覽代碼

Merge pull request #1234 from deltachat/show-status

show status/signatures in contact-profiles
bjoern 4 年之前
父節點
當前提交
044a485d43

+ 7 - 0
DcCore/DcCore/DC/Wrapper.swift

@@ -1154,6 +1154,13 @@ public class DcContact {
         return swiftString
     }
 
+    public var status: String {
+        guard let cString = dc_contact_get_status(contactPointer) else { return "" }
+        let swiftString = String(cString: cString)
+        dc_str_unref(cString)
+        return swiftString
+    }
+
     public var isVerified: Bool {
         return dc_contact_is_verified(contactPointer) > 0
     }

+ 4 - 0
deltachat-ios.xcodeproj/project.pbxproj

@@ -154,6 +154,7 @@
 		B20462E62440C99600367A57 /* SettingsAutodelSetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20462E52440C99600367A57 /* SettingsAutodelSetController.swift */; };
 		B21005DB23383664004C70C5 /* SettingsClassicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21005DA23383664004C70C5 /* SettingsClassicViewController.swift */; };
 		B26B3BC7236DC3DC008ED35A /* SwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B26B3BC6236DC3DC008ED35A /* SwitchCell.swift */; };
+		B2C42570265C325C00B95377 /* MultilineLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C4256F265C325C00B95377 /* MultilineLabelCell.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -461,6 +462,7 @@
 		B2B9BC1026245F2200F35832 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		B2B9BC1126245F2200F35832 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
 		B2B9BC1226245F2200F35832 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
+		B2C4256F265C325C00B95377 /* MultilineLabelCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineLabelCell.swift; sourceTree = "<group>"; };
 		C1B60449B860342EE5F2AD54 /* Pods-DcShare.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DcShare.release.xcconfig"; path = "Pods/Target Support Files/Pods-DcShare/Pods-DcShare.release.xcconfig"; sourceTree = "<group>"; };
 		FECB35E2B04CD5F5D02C157A /* Pods-deltachat-iosTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-deltachat-iosTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.release.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -835,6 +837,7 @@
 				70B8882D2091B8550074812E /* ContactCell.swift */,
 				78ED839321D5AF8A00243125 /* QrCodeView.swift */,
 				78ED838221D5379000243125 /* TextFieldCell.swift */,
+				B2C4256F265C325C00B95377 /* MultilineLabelCell.swift */,
 				30260CA6238F02F700D8D52C /* MultilineTextFieldCell.swift */,
 				AE38B31722672DFC00EC37A1 /* ActionCell.swift */,
 				AE25F08F22807AD800CDEA66 /* AvatarSelectionCell.swift */,
@@ -1286,6 +1289,7 @@
 				7A0052C81FBE6CB40048C3BF /* NewContactController.swift in Sources */,
 				AEE56D762253431E007DC082 /* AccountSetupController.swift in Sources */,
 				AE8F503524753DFE007FEE0B /* GalleryViewController.swift in Sources */,
+				B2C42570265C325C00B95377 /* MultilineLabelCell.swift in Sources */,
 				AEE6EC412282DF5700EDC689 /* MailboxViewController.swift in Sources */,
 				AEF53BD5248904BF00D309C1 /* GalleryTimeLabel.swift in Sources */,
 				AEE6EC482283045D00EDC689 /* EditSettingsController.swift in Sources */,

+ 39 - 0
deltachat-ios/Controller/ContactDetailViewController.swift

@@ -88,6 +88,11 @@ class ContactDetailViewController: UITableViewController {
         return cell
     }()
 
+    private lazy var statusCell: MultilineLabelCell = {
+        let cell = MultilineLabelCell()
+        cell.multilineDelegate = self
+        return cell
+    }()
 
     init(dcContext: DcContext, contactId: Int) {
         self.viewModel = ContactDetailViewModel(dcContext: dcContext, contactId: contactId)
@@ -162,6 +167,8 @@ class ContactDetailViewController: UITableViewController {
             case .startChat:
                 return startChatCell
             }
+        case .statusArea:
+            return statusCell
         case .chatActions:
             switch viewModel.chatActionFor(row: row) {
             case .archiveChat:
@@ -188,6 +195,8 @@ class ContactDetailViewController: UITableViewController {
         switch type {
         case .chatOptions:
             handleChatOption(for: indexPath.row)
+        case .statusArea:
+            break
         case .chatActions:
             handleChatAction(for: indexPath.row)
         case .sharedChats:
@@ -238,6 +247,7 @@ class ContactDetailViewController: UITableViewController {
         ephemeralMessagesCell.detailTextLabel?.text = String.localized(viewModel.chatIsEphemeral ? "on" : "off")
         galleryCell.detailTextLabel?.text = String.numberOrNone(viewModel.galleryItemMessageIds.count)
         documentsCell.detailTextLabel?.text = String.numberOrNone(viewModel.documentItemMessageIds.count)
+        statusCell.setText(text: viewModel.contact.status)
     }
 
     // MARK: - actions
@@ -430,3 +440,32 @@ class ContactDetailViewController: UITableViewController {
         }
     }
 }
+
+extension ContactDetailViewController: MultilineLabelCellDelegate {
+    func phoneNumberTapped(number: String) {
+        let sanitizedNumber = number.filter("0123456789".contains)
+        if let phoneURL = URL(string: "tel://\(sanitizedNumber)") {
+            UIApplication.shared.open(phoneURL, options: [:], completionHandler: nil)
+        }
+    }
+
+    func urlTapped(url: URL) {
+        if Utils.isEmail(url: url) {
+            let email = Utils.getEmailFrom(url)
+            let contactId = viewModel.context.createContact(name: "", email: email)
+            let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), email),
+                                          message: nil, preferredStyle: .safeActionSheet)
+            alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { [weak self] _ in
+                guard let self = self else { return }
+                let chatId = self.viewModel.context.createChatByContactId(contactId: contactId)
+                if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
+                    appDelegate.appCoordinator.showChat(chatId: chatId, clearViewControllerStack: true)
+                }
+            }))
+            alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
+            present(alert, animated: true, completion: nil)
+        } else {
+            UIApplication.shared.open(url)
+        }
+    }
+}

+ 90 - 0
deltachat-ios/View/MultilineLabelCell.swift

@@ -0,0 +1,90 @@
+import Foundation
+import UIKit
+import DcCore
+
+class MultilineLabelCell: UITableViewCell {
+    public weak var multilineDelegate: MultilineLabelCellDelegate?
+
+    lazy var label: MessageLabel = {
+        let label = MessageLabel()
+        label.delegate = self
+        label.translatesAutoresizingMaskIntoConstraints = false
+        label.font = UIFont.preferredFont(for: .body, weight: .regular)
+        label.numberOfLines = 0
+        label.lineBreakMode = .byWordWrapping
+        label.isUserInteractionEnabled = true
+        label.enabledDetectors = [.url, .phoneNumber]
+        let attributes: [NSAttributedString.Key: Any] = [
+            NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor,
+            NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
+            NSAttributedString.Key.underlineColor: DcColors.defaultTextColor ]
+        label.setAttributes(attributes, detector: .url)
+        label.setAttributes(attributes, detector: .phoneNumber)
+        return label
+    }()
+
+    init() {
+        super.init(style: .value1, reuseIdentifier: nil)
+        selectionStyle = .none
+        setupViews()
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    func setupViews() {
+        contentView.addSubview(label)
+
+        let margins = contentView.layoutMarginsGuide
+        label.alignLeadingToAnchor(margins.leadingAnchor, paddingLeading: 0)
+        label.alignTrailingToAnchor(margins.trailingAnchor)
+        label.alignTopToAnchor(margins.topAnchor)
+        label.alignBottomToAnchor(margins.bottomAnchor)
+
+        let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
+        gestureRecognizer.numberOfTapsRequired = 1
+        label.addGestureRecognizer(gestureRecognizer)
+    }
+
+    func setText(text: String?) {
+        label.text = text
+    }
+
+    @objc
+    open func handleTapGesture(_ gesture: UIGestureRecognizer) {
+        guard gesture.state == .ended else { return }
+        let touchLocation = gesture.location(in: label)
+        let isHandled = label.handleGesture(touchLocation)
+        if !isHandled {
+            logger.info("status: tapped outside urls or phone numbers")
+        }
+    }
+}
+
+extension MultilineLabelCell: MessageLabelDelegate {
+    public func didSelectAddress(_ addressComponents: [String: String]) {}
+
+    public func didSelectDate(_ date: Date) {}
+
+    public func didSelectPhoneNumber(_ phoneNumber: String) {
+        multilineDelegate?.phoneNumberTapped(number: phoneNumber)
+    }
+
+    public func didSelectURL(_ url: URL) {
+        multilineDelegate?.urlTapped(url: url)
+    }
+
+    public func didSelectTransitInformation(_ transitInformation: [String: String]) {}
+
+    public func didSelectMention(_ mention: String) {}
+
+    public func didSelectHashtag(_ hashtag: String) {}
+
+    public func didSelectCustom(_ pattern: String, match: String?) {}
+}
+
+public protocol MultilineLabelCellDelegate: class {
+    func phoneNumberTapped(number: String)
+    func urlTapped(url: URL)
+}

+ 14 - 3
deltachat-ios/ViewModel/ContactDetailViewModel.swift

@@ -7,6 +7,7 @@ class ContactDetailViewModel {
 
     enum ProfileSections {
         case chatOptions
+        case statusArea
         case sharedChats
         case chatActions
     }
@@ -28,6 +29,7 @@ class ContactDetailViewModel {
 
     var contactId: Int
 
+    // TODO: check if that is too inefficient (each bit read from contact, results in a database-query)
     var contact: DcContact {
         return DcContact(id: contactId)
     }
@@ -54,6 +56,12 @@ class ContactDetailViewModel {
         self.sharedChats = context.getChatlist(flags: 0, queryString: nil, queryId: contactId)
 
         sections.append(.chatOptions)
+
+        let dcContact = DcContact(id: contactId)
+        if !dcContact.status.isEmpty {
+            sections.append(.statusArea)
+        }
+
         if sharedChats.length > 0 && !isSavedMessages && !isDeviceTalk {
             sections.append(.sharedChats)
         }
@@ -132,6 +140,7 @@ class ContactDetailViewModel {
     func numberOfRowsInSection(_ section: Int) -> Int {
         switch sections[section] {
         case .chatOptions: return chatOptions.count
+        case .statusArea: return 1
         case .sharedChats: return sharedChats.length
         case .chatActions: return chatActions.count
         }
@@ -152,10 +161,12 @@ class ContactDetailViewModel {
     }
 
     func titleFor(section: Int) -> String? {
-        if sections[section] == .sharedChats {
-            return String.localized("profile_shared_chats")
+        switch sections[section] {
+        case .chatOptions: return nil
+        case .statusArea: return String.localized("pref_default_status_label")
+        case .sharedChats: return String.localized("profile_shared_chats")
+        case .chatActions: return nil
         }
-        return nil
     }
 
     // returns true if chat is archived after action