Antidote/Antidote/ChatListTableManager.swift

223 lines
8.4 KiB
Swift

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
import Foundation
protocol ChatListTableManagerDelegate: class {
func chatListTableManager(_ manager: ChatListTableManager, didSelectChat chat: OCTChat)
func chatListTableManager(_ manager: ChatListTableManager, presentAlertController controller: UIAlertController)
func chatListTableManagerWasUpdated(_ manager: ChatListTableManager)
}
class ChatListTableManager: NSObject {
weak var delegate: ChatListTableManagerDelegate?
let tableView: UITableView
var isEmpty: Bool {
get {
return chats.count == 0
}
}
fileprivate let theme: Theme
fileprivate let avatarManager: AvatarManager
fileprivate let dateFormatter: DateFormatter
fileprivate let timeFormatter: DateFormatter
fileprivate weak var submanagerChats: OCTSubmanagerChats!
fileprivate let chats: Results<OCTChat>
fileprivate var chatsToken: RLMNotificationToken?
fileprivate let friends: Results<OCTFriend>
fileprivate var friendsToken: RLMNotificationToken?
init(theme: Theme, tableView: UITableView, submanagerChats: OCTSubmanagerChats, submanagerObjects: OCTSubmanagerObjects) {
self.tableView = tableView
self.theme = theme
self.avatarManager = AvatarManager(theme: theme)
self.dateFormatter = DateFormatter(type: .relativeDate)
self.timeFormatter = DateFormatter(type: .time)
self.submanagerChats = submanagerChats
self.chats = submanagerObjects.chats().sortedResultsUsingProperty("lastActivityDateInterval", ascending: false)
self.friends = submanagerObjects.friends()
super.init()
tableView.delegate = self
tableView.dataSource = self
addNotificationBlocks()
}
deinit {
chatsToken?.invalidate()
friendsToken?.invalidate()
}
}
extension ChatListTableManager: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var avatarData: Data?
var nickname = String(localized: "contact_deleted")
var connectionStatus = OCTToxConnectionStatus.none
var userStatus = OCTToxUserStatus.none
let chat = chats[indexPath.row]
let friend = chat.friends.lastObject() as? OCTFriend
if let friend = friend {
avatarData = friend.avatarData
nickname = friend.nickname
connectionStatus = friend.connectionStatus
userStatus = friend.status
}
let model = ChatListCellModel()
if let data = avatarData {
model.avatar = UIImage(data: data)
}
else {
model.avatar = avatarManager.avatarFromString(
nickname,
diameter: CGFloat(ChatListCell.Constants.AvatarSize))
}
model.nickname = nickname
model.message = lastMessage(in: chat, friend: friend)
if let date = chat.lastActivityDate() {
model.dateText = dateTextFromDate(date)
}
model.status = UserStatus(connectionStatus: connectionStatus, userStatus: userStatus)
model.connectionstatus = ConnectionStatus(connectionStatus: connectionStatus)
model.isUnread = chat.hasUnreadMessages()
let cell = tableView.dequeueReusableCell(withIdentifier: ChatListCell.staticReuseIdentifier) as! ChatListCell
cell.setupWithTheme(theme, model: model)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return chats.count
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let alert = UIAlertController(title: String(localized:"delete_chat_title"), message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: String(localized: "alert_cancel"), style: .default, handler: nil))
alert.addAction(UIAlertAction(title: String(localized: "alert_delete"), style: .destructive) { [unowned self] _ -> Void in
let chat = self.chats[indexPath.row]
self.submanagerChats.removeAllMessages(in: chat, removeChat: true)
})
delegate?.chatListTableManager(self, presentAlertController: alert)
}
}
}
extension ChatListTableManager: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let chat = self.chats[indexPath.row]
delegate?.chatListTableManager(self, didSelectChat: chat)
}
}
private extension ChatListTableManager {
func addNotificationBlocks() {
chatsToken = chats.addNotificationBlock { [unowned self] change in
switch change {
case .initial:
break
case .update(_, let deletions, let insertions, let modifications):
// TODO: fix me, this is a hack to avoid the crash
self.tableView.reloadData()
self.tableView.beginUpdates()
/*
self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) },
with: .automatic)
self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) },
with: .automatic)
self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) },
with: .none)
*/
self.tableView.endUpdates()
self.delegate?.chatListTableManagerWasUpdated(self)
case .error(let error):
fatalError("\(error)")
}
}
friendsToken = friends.addNotificationBlock { [unowned self] change in
switch change {
case .initial:
break
case .update(let friends, _, _, let modifications):
guard let friends = friends else {
break
}
for index in modifications {
let friend = friends[index]
let pathsToUpdate = self.tableView.indexPathsForVisibleRows?.filter {
let chat = self.chats[$0.row]
return Int(chat.friends.index(of: friend)) != NSNotFound
}
if let paths = pathsToUpdate {
// TODO: fix me, this crashes
// self.tableView.reloadRows(at: paths, with: .none)
}
}
case .error(let error):
fatalError("\(error)")
}
}
}
func lastMessage(in chat: OCTChat, friend: OCTFriend?) -> String {
guard let message = chat.lastMessage else {
return ""
}
if let friend = friend, friend.isTyping {
return String(localized: "chat_is_typing_text")
}
else if let text = message.messageText {
return text.text ?? ""
}
else if let file = message.messageFile {
let fileName = file.fileName ?? ""
return String(localized: message.isOutgoing() ? "chat_outgoing_file" : "chat_incoming_file") + " \(fileName)"
}
else if let call = message.messageCall {
switch call.callEvent {
case .answered:
let timeString = String(timeInterval: call.callDuration)
return String(localized: "chat_call_finished") + " - \(timeString)"
case .unanswered:
return message.isOutgoing() ? String(localized: "chat_unanwered_call") : String(localized: "chat_missed_call_message")
}
}
return ""
}
func dateTextFromDate(_ date: Date) -> String {
let isToday = (Calendar.current as NSCalendar).compare(Date(), to: date, toUnitGranularity: .day) == .orderedSame
return isToday ? timeFormatter.string(from: date) : dateFormatter.string(from: date)
}
}