Bläddra i källkod

show email address in search for contact section, highlight matching strings

cyberta 4 år sedan
förälder
incheckning
c6bc4ee6a3

+ 34 - 1
DcCore/DcCore/Extensions/String+Extensions.swift

@@ -1,4 +1,6 @@
 import Foundation
+import UIKit
+
 public extension String {
     
 	static func localized(_ stringID: String) -> String {
@@ -23,5 +25,36 @@ public extension String {
     func containsCharacters() -> Bool {
         return !trimmingCharacters(in: [" "]).isEmpty
     }
-    
+
+    func containsExact(subSequence: String?) -> [Int] {
+        guard let searchText = subSequence else {
+            return []
+        }
+        if searchText.count > count {
+            return []
+        }
+
+        if let range = range(of: searchText, options: .caseInsensitive) {
+            let index: Int = distance(from: startIndex, to: range.lowerBound)
+            var indexes: [Int] = []
+            for i in index..<(index + searchText.count) {
+                indexes.append(i)
+            }
+            return indexes
+        }
+        return []
+    }
+
+    func boldAt(indexes: [Int], fontSize: CGFloat) -> NSAttributedString {
+        let attributedText = NSMutableAttributedString(string: self)
+
+        for index in indexes {
+            if index < 0 || count <= index {
+                break
+            }
+            attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontSize), range: NSRange(location: index, length: 1))
+        }
+        return attributedText
+    }
+
 }

+ 149 - 0
DcCore/DcCore/ViewModel/ContactCellViewModel.swift

@@ -0,0 +1,149 @@
+import Foundation
+import DcCore
+
+protocol AvatarCellViewModel {
+    var type: CellModel { get }
+    var title: String { get }
+    var titleHighlightIndexes: [Int] { get }
+    var subtitle: String { get }
+    var subtitleHighlightIndexes: [Int] { get }
+}
+
+enum CellModel {
+    case contact(ContactCellData)
+    case chat(ChatCellData)
+    case deaddrop(DeaddropCellData)
+    case profile
+}
+
+struct ContactCellData {
+    let contactId: Int
+    let chatId: Int?
+}
+
+struct ChatCellData {
+    let chatId: Int
+    let summary: DcLot
+    let unreadMessages: Int
+}
+
+struct DeaddropCellData {
+    let chatId: Int
+    let msgId: Int
+    let summary: DcLot
+}
+
+class ContactCellViewModel: AvatarCellViewModel {
+
+    private let contact: DcContact
+
+    var type: CellModel
+    var title: String {
+        return contact.displayName
+    }
+    var subtitle: String {
+        return contact.email
+    }
+
+    var avartarTitle: String {
+        return DcUtils.getInitials(inputName: title)
+    }
+
+    var titleHighlightIndexes: [Int]
+    var subtitleHighlightIndexes: [Int]
+
+    init(contactData: ContactCellData, titleHighlightIndexes: [Int] = [], subtitleHighlightIndexes: [Int] = []) {
+        type = CellModel.contact(contactData)
+        self.titleHighlightIndexes = titleHighlightIndexes
+        self.subtitleHighlightIndexes = subtitleHighlightIndexes
+        self.contact = DcContact(id: contactData.contactId)
+    }
+}
+
+class ProfileViewModel: AvatarCellViewModel {
+    var type: CellModel {
+        return CellModel.profile
+    }
+
+    var title: String
+
+    private let contact: DcContact
+
+    var titleHighlightIndexes: [Int] {
+        return []
+    }
+
+    var subtitle: String
+
+    var subtitleHighlightIndexes: [Int] {
+        return []
+    }
+
+    init(context: DcContext) {
+        contact = DcContact(id: Int(DC_CONTACT_ID_SELF))
+        title = context.displayname ?? String.localized("pref_your_name")
+        subtitle = context.addr ?? ""
+    }
+}
+
+class ChatCellViewModel: AvatarCellViewModel {
+
+    private let chat: DcChat
+
+    private var summary: DcLot
+
+    var type: CellModel
+    var title: String {
+        return chat.name
+    }
+
+    var subtitle: String {
+        let result1 = summary.text1 ?? ""
+        let result2 = summary.text2 ?? ""
+        let result: String
+        if !result1.isEmpty, !result2.isEmpty {
+            result = "\(result1): \(result2)"
+        } else {
+            result = "\(result1)\(result2)"
+        }
+        return result
+    }
+
+    var titleHighlightIndexes: [Int]
+    var subtitleHighlightIndexes: [Int]
+
+    init(dcContext: DcContext, chatData: ChatCellData, titleHighlightIndexes: [Int] = [], subtitleHighlightIndexes: [Int] = []) {
+        self.type = CellModel.chat(chatData)
+        self.titleHighlightIndexes = titleHighlightIndexes
+        self.subtitleHighlightIndexes = subtitleHighlightIndexes
+        self.summary = chatData.summary
+        self.chat = dcContext.getChat(chatId: chatData.chatId)
+    }
+
+    init(dcContext: DcContext, deaddropCellData cellData: DeaddropCellData) {
+        self.type = CellModel.deaddrop(cellData)
+        self.titleHighlightIndexes = []
+        self.subtitleHighlightIndexes = []
+        self.chat = dcContext.getChat(chatId: cellData.chatId)
+        self.summary = cellData.summary
+    }
+}
+
+extension ContactCellViewModel {
+    static func make(contactId: Int, searchText: String?, dcContext: DcContext) -> ContactCellViewModel {
+        let contact = DcContact(id: contactId)
+        let nameIndexes = contact.displayName.containsExact(subSequence: searchText)
+        let emailIndexes = contact.email.containsExact(subSequence: searchText)
+        let chatId: Int? = dcContext.getChatIdByContactIdOld(contactId)
+            // contact contains searchText
+        let viewModel = ContactCellViewModel(
+            contactData: ContactCellData(
+                contactId: contact.id,
+                chatId: chatId
+            ),
+            titleHighlightIndexes: nameIndexes,
+            subtitleHighlightIndexes: emailIndexes
+        )
+        return viewModel
+    }
+}

+ 17 - 5
DcShare/Controller/ChatListController.swift

@@ -48,13 +48,11 @@ class ChatListController: UITableViewController {
 
     override func viewDidLoad() {
         super.viewDidLoad()
-        //chatList = dcContext.getChatlist(flags: DC_GCL_ADD_ALLDONE_HINT | DC_GCL_FOR_FORWARDING | DC_GCL_NO_SPECIALS, queryString: nil, queryId: 0)
         navigationItem.searchController = searchController
         tableView.register(ChatListCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
         tableView.rowHeight = 64
         tableView.tableHeaderView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: Double.leastNormalMagnitude))
         tableView.tableFooterView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: Double.leastNormalMagnitude))
-
     }
 
     override func numberOfSections(in tableView: UITableView) -> Int {
@@ -70,9 +68,8 @@ class ChatListController: UITableViewController {
             fatalError("could not deque TableViewCell")
         }
 
-        if let chatId = viewModel.getChatId(section: indexPath.section, row: indexPath.row) {
-            cell.updateCell(chatId: chatId)
-        }
+        let cellData = viewModel.cellDataFor(section: indexPath.section, row: indexPath.row)
+        cell.updateCell(cellViewModel: cellData)
 
         return cell
     }
@@ -81,6 +78,21 @@ class ChatListController: UITableViewController {
         if let chatId = viewModel.getChatId(section: indexPath.section, row: indexPath.row) {
             chatListDelegate?.onChatSelected(chatId: chatId)
         }
+
+        let cellData = viewModel.cellDataFor(section: indexPath.section, row: indexPath.row)
+        switch cellData.type {
+        case .chat(let data):
+            chatListDelegate?.onChatSelected(chatId: data.chatId)
+        case .contact(let data):
+             if let chatId = data.chatId {
+                chatListDelegate?.onChatSelected(chatId: chatId)
+            } else {
+                let chatId = dcContext.createChatByContactId(contactId: data.contactId)
+                chatListDelegate?.onChatSelected(chatId: chatId)
+            }
+        default:
+            fatalError("Other types are not allowed in Share contact search")
+        }
     }
 
     override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {

+ 51 - 17
DcShare/View/ChatListCell.swift

@@ -13,17 +13,35 @@ class ChatListCell: UITableViewCell {
         return badge
     }()
 
-    let titleLabel: UILabel = {
+    lazy var stackView: UIStackView = {
+        let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
+        stackView.axis = .vertical
+        stackView.translatesAutoresizingMaskIntoConstraints = false
+        stackView.alignment = .leading
+        stackView.clipsToBounds = true
+        return stackView
+    }()
+
+    lazy var titleLabel: UILabel = {
         let label = UILabel()
         label.font = UIFont.preferredFont(forTextStyle: .headline)
         label.adjustsFontForContentSizeCategory = true
         label.lineBreakMode = .byTruncatingTail
         label.textColor = DcColors.defaultTextColor
-        label.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1), for: NSLayoutConstraint.Axis.horizontal)
         label.translatesAutoresizingMaskIntoConstraints = false
         return label
     }()
 
+    lazy var subtitleLabel: UILabel = {
+           let label = UILabel()
+           label.textColor = DcColors.middleGray
+           label.lineBreakMode = .byTruncatingTail
+           label.font = .preferredFont(forTextStyle: .subheadline)
+           label.adjustsFontForContentSizeCategory = true
+        label.translatesAutoresizingMaskIntoConstraints = false
+           return label
+       }()
+
     override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
         super.init(style: style, reuseIdentifier: reuseIdentifier)
         selectionStyle = .none
@@ -49,11 +67,11 @@ class ChatListCell: UITableViewCell {
             avatar.constraintCenterYTo(contentView),
         ])
 
-        contentView.addSubview(titleLabel)
+        contentView.addSubview(stackView)
         contentView.addConstraints([
-            titleLabel.constraintCenterYTo(contentView),
-            titleLabel.constraintToTrailingOf(avatar, paddingLeading: margin),
-            titleLabel.constraintAlignTrailingTo(contentView)
+            stackView.constraintCenterYTo(contentView),
+            stackView.constraintToTrailingOf(avatar, paddingLeading: margin),
+            stackView.constraintAlignTrailingTo(contentView),
         ])
     }
 
@@ -76,17 +94,33 @@ class ChatListCell: UITableViewCell {
       }
 
     // use this update-method to update cell in cellForRowAt whenever it is possible - other set-methods will be set private in progress
-    func updateCell(chatId: Int) {
-        let chat = DcContext.shared.getChat(chatId: chatId)
-        titleLabel.text = chat.name
-        backgroundColor = DcColors.contactCellBackgroundColor
-        contentView.backgroundColor = DcColors.contactCellBackgroundColor
+      func updateCell(cellViewModel: AvatarCellViewModel) {
+          // subtitle
+          switch cellViewModel.type {
+          case .chat(let chatData):
+              let chat = DcContext.shared.getChat(chatId: chatData.chatId)
+              titleLabel.attributedText = cellViewModel.title.boldAt(indexes: cellViewModel.titleHighlightIndexes, fontSize: titleLabel.font.pointSize)
+              if let img = chat.profileImage {
+                  resetBackupImage()
+                  setImage(img)
+              } else {
+                  setBackupImage(name: chat.name, color: chat.color)
+              }
+            subtitleLabel.attributedText = nil
+
+          case .contact(let contactData):
+              let contact = DcContact(id: contactData.contactId)
+              titleLabel.attributedText = cellViewModel.title.boldAt(indexes: cellViewModel.titleHighlightIndexes, fontSize: titleLabel.font.pointSize)
+              if let profileImage = contact.profileImage {
+                  avatar.setImage(profileImage)
+              } else {
+                setBackupImage(name: cellViewModel.title, color: contact.color)
+              }
+              subtitleLabel.attributedText = cellViewModel.subtitle.boldAt(indexes: cellViewModel.subtitleHighlightIndexes,
+                                                                           fontSize: subtitleLabel.font.pointSize)
+          default:
+            return
 
-        if let img = chat.profileImage {
-            resetBackupImage()
-            setImage(img)
-        } else {
-            setBackupImage(name: chat.name, color: chat.color)
         }
-    }
+      }
 }

+ 39 - 0
DcShare/ViewModel/ChatListViewModel.swift

@@ -59,6 +59,45 @@ class ChatListViewModel: NSObject {
         return chatList.getChatId(index: row)
     }
 
+    func cellDataFor(section: Int, row: Int) -> AvatarCellViewModel {
+        if showSearchResults {
+            switch searchResultSections[section] {
+            case .chats:
+                return makeChatCellViewModel(index: row, searchText: searchText)
+            case .contacts:
+                return ContactCellViewModel.make(contactId: searchResultContactIds[row], searchText: searchText, dcContext: dcContext)
+            }
+        }
+        return makeChatCellViewModel(index: row, searchText: "")
+    }
+
+    func makeChatCellViewModel(index: Int, searchText: String) -> AvatarCellViewModel {
+        let list: DcChatlist = searchResultChatList ?? chatList
+               let chatId = list.getChatId(index: index)
+               let summary = list.getSummary(index: index)
+
+               let chat = dcContext.getChat(chatId: chatId)
+               let unreadMessages = dcContext.getUnreadMessages(chatId: chatId)
+
+               var chatTitleIndexes: [Int] = []
+               if searchText.containsCharacters() {
+                   let chatName = chat.name
+                   chatTitleIndexes = chatName.containsExact(subSequence: searchText)
+               }
+
+               let viewModel = ChatCellViewModel(
+                   dcContext: dcContext,
+                   chatData: ChatCellData(
+                       chatId: chatId,
+                       summary: summary,
+                       unreadMessages: unreadMessages
+                   ),
+                   titleHighlightIndexes: chatTitleIndexes
+               )
+               return viewModel
+    }
+
+
     var numberOfSections: Int {
         if showSearchResults {
             return searchResultSections.count

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

@@ -25,6 +25,7 @@
 		304F5E44244F571C00462538 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A9FB14A1FB061E2001FEA36 /* Assets.xcassets */; };
 		3057027F24C5B2F800D84EFC /* ChatListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3057027E24C5B2F800D84EFC /* ChatListViewModel.swift */; };
 		3057028724C5C88300D84EFC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; };
+		3057028C24C5E7B600D84EFC /* ContactCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE77838E23E4276D0093EABD /* ContactCellViewModel.swift */; };
 		305961CC2346125100C80F33 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961822346125000C80F33 /* UIView+Extensions.swift */; };
 		305961CD2346125100C80F33 /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961832346125000C80F33 /* UIEdgeInsets+Extensions.swift */; };
 		305961CF2346125100C80F33 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961852346125000C80F33 /* UIColor+Extensions.swift */; };
@@ -1355,6 +1356,7 @@
 				30E8F2442449C64100CE2C90 /* ChatListCell.swift in Sources */,
 				30E8F2132447285600CE2C90 /* ShareViewController.swift in Sources */,
 				30E8F253244DAD0E00CE2C90 /* SendingController.swift in Sources */,
+				3057028C24C5E7B600D84EFC /* ContactCellViewModel.swift in Sources */,
 				30E8F2422448B77600CE2C90 /* ChatListController.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 0 - 31
deltachat-ios/Extensions/String+Extension.swift

@@ -9,25 +9,6 @@ extension String {
         return String(self[idx1..<idx2])
     }
 
-    func containsExact(subSequence: String?) -> [Int] {
-        guard let searchText = subSequence else {
-            return []
-        }
-        if searchText.count > count {
-            return []
-        }
-
-        if let range = range(of: searchText, options: .caseInsensitive) {
-            let index: Int = distance(from: startIndex, to: range.lowerBound)
-            var indexes: [Int] = []
-            for i in index..<(index + searchText.count) {
-                indexes.append(i)
-            }
-            return indexes
-        }
-        return []
-    }
-
     // O(n) - returns indexes of subsequences -> can be used to highlight subsequence within string
     func contains(subSequence: String) -> [Int] {
         if subSequence.count > count {
@@ -58,18 +39,6 @@ extension String {
         return self[index(startIndex, offsetBy: i)]
     }
 
-    func boldAt(indexes: [Int], fontSize: CGFloat) -> NSAttributedString {
-        let attributedText = NSMutableAttributedString(string: self)
-
-        for index in indexes {
-            if index < 0 || count <= index {
-                break
-            }
-            attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontSize), range: NSRange(location: index, length: 1))
-        }
-        return attributedText
-    }
-
     func bold(fontSize: CGFloat) -> NSAttributedString {
         let attributedText = NSMutableAttributedString(string: self)
         attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontSize), range: NSRange(location: 0, length: count - 1))