223 lines
8.4 KiB
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)
|
|
}
|
|
}
|