231 lines
8.6 KiB
Swift
231 lines
8.6 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
|
||
|
|
||
|
private struct Constants {
|
||
|
static let FriendRequestsSection = 0
|
||
|
}
|
||
|
|
||
|
protocol FriendListDataSourceDelegate: class {
|
||
|
func friendListDataSourceBeginUpdates()
|
||
|
func friendListDataSourceEndUpdates()
|
||
|
|
||
|
func friendListDataSourceInsertRowsAtIndexPaths(_ indexPaths: [IndexPath])
|
||
|
func friendListDataSourceDeleteRowsAtIndexPaths(_ indexPaths: [IndexPath])
|
||
|
func friendListDataSourceReloadRowsAtIndexPaths(_ indexPaths: [IndexPath])
|
||
|
|
||
|
func friendListDataSourceInsertSections(_ sections: IndexSet)
|
||
|
func friendListDataSourceDeleteSections(_ sections: IndexSet)
|
||
|
func friendListDataSourceReloadSections(_ sections: IndexSet)
|
||
|
|
||
|
func friendListDataSourceReloadTable()
|
||
|
}
|
||
|
|
||
|
enum FriendListObject {
|
||
|
case request(OCTFriendRequest)
|
||
|
case friend(OCTFriend)
|
||
|
}
|
||
|
|
||
|
class FriendListDataSource: NSObject {
|
||
|
weak var delegate: FriendListDataSourceDelegate?
|
||
|
|
||
|
fileprivate let avatarManager: AvatarManager
|
||
|
fileprivate let dateFormatter: DateFormatter
|
||
|
|
||
|
fileprivate let requests: Results<OCTFriendRequest>?
|
||
|
fileprivate let friends: Results<OCTFriend>
|
||
|
|
||
|
fileprivate var requestsToken: RLMNotificationToken?
|
||
|
fileprivate var friendsToken: RLMNotificationToken?
|
||
|
|
||
|
/// In case if requests is nil friend requests won't be shown.
|
||
|
init(theme: Theme, friends: Results<OCTFriend>, requests: Results<OCTFriendRequest>? = nil) {
|
||
|
self.avatarManager = AvatarManager(theme: theme)
|
||
|
self.dateFormatter = DateFormatter(type: .relativeDateAndTime)
|
||
|
|
||
|
self.requests = requests
|
||
|
self.friends = friends
|
||
|
|
||
|
super.init()
|
||
|
|
||
|
addNotificationBlocks()
|
||
|
}
|
||
|
|
||
|
deinit {
|
||
|
requestsToken?.invalidate()
|
||
|
friendsToken?.invalidate()
|
||
|
}
|
||
|
|
||
|
func numberOfSections() -> Int {
|
||
|
if isRequestsSectionVisible() {
|
||
|
return 2
|
||
|
}
|
||
|
|
||
|
// friends only
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
func numberOfRowsInSection(_ section: Int) -> Int {
|
||
|
if section == Constants.FriendRequestsSection && isRequestsSectionVisible() {
|
||
|
return requests!.count
|
||
|
}
|
||
|
else {
|
||
|
return friends.count
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func modelAtIndexPath(_ indexPath: IndexPath) -> FriendListCellModel {
|
||
|
let model = FriendListCellModel()
|
||
|
|
||
|
switch objectAtIndexPath(indexPath) {
|
||
|
case .request(let request):
|
||
|
model.avatar = avatarManager.avatarFromString("", diameter: CGFloat(FriendListCell.Constants.AvatarSize))
|
||
|
model.topText = request.publicKey
|
||
|
model.bottomText = request.message ?? ""
|
||
|
model.multilineBottomtext = true
|
||
|
model.hideStatus = true
|
||
|
|
||
|
model.accessibilityLabel = String(localized: "contact_request")
|
||
|
model.accessibilityValue = ""
|
||
|
|
||
|
if let message = request.message {
|
||
|
model.accessibilityValue += String(localized: "add_contact_default_message_title") + ": " + message + ", "
|
||
|
}
|
||
|
model.accessibilityValue += String(localized: "public_key") + ": " + request.publicKey
|
||
|
|
||
|
case .friend(let friend):
|
||
|
if let data = friend.avatarData {
|
||
|
model.avatar = UIImage(data: data)
|
||
|
}
|
||
|
else {
|
||
|
model.avatar = avatarManager.avatarFromString(friend.nickname, diameter: CGFloat(FriendListCell.Constants.AvatarSize))
|
||
|
}
|
||
|
model.topText = friend.nickname
|
||
|
|
||
|
model.status = UserStatus(connectionStatus: friend.connectionStatus, userStatus: friend.status)
|
||
|
model.connectionstatus = ConnectionStatus(connectionStatus: friend.connectionStatus)
|
||
|
|
||
|
model.accessibilityLabel = friend.nickname
|
||
|
model.accessibilityValue = model.status.toString()
|
||
|
|
||
|
if friend.isConnected {
|
||
|
model.bottomText = friend.statusMessage ?? ""
|
||
|
model.accessibilityValue += ", Status: \(model.bottomText)"
|
||
|
}
|
||
|
else if let date = friend.lastSeenOnline() {
|
||
|
model.bottomText = String(localized: "contact_last_seen", dateFormatter.string(from: date))
|
||
|
model.accessibilityValue += ", " + model.bottomText
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return model
|
||
|
}
|
||
|
|
||
|
func objectAtIndexPath(_ indexPath: IndexPath) -> FriendListObject {
|
||
|
if indexPath.section == Constants.FriendRequestsSection && isRequestsSectionVisible() {
|
||
|
return .request(requests![indexPath.row])
|
||
|
}
|
||
|
else {
|
||
|
return .friend(friends[indexPath.row])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func sectionIndexTitles() -> [String] {
|
||
|
// TODO fix when Realm will support sections
|
||
|
let array = [String]()
|
||
|
return array
|
||
|
}
|
||
|
|
||
|
func titleForHeaderInSection(_ section: Int) -> String? {
|
||
|
if !isRequestsSectionVisible() {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if section == Constants.FriendRequestsSection {
|
||
|
return String(localized: "contact_requests_section")
|
||
|
}
|
||
|
else {
|
||
|
return String(localized: "contacts_title")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private extension FriendListDataSource {
|
||
|
func addNotificationBlocks() {
|
||
|
requestsToken = requests?.addNotificationBlock { [unowned self] change in
|
||
|
switch change {
|
||
|
case .initial:
|
||
|
break
|
||
|
case .update(let requests, let deletions, let insertions, let modifications):
|
||
|
guard let requests = requests else {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if deletions.count > 0 {
|
||
|
// reloading data on request removal/friend insertion to synchronize requests/friends
|
||
|
self.delegate?.friendListDataSourceReloadTable()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
self.delegate?.friendListDataSourceBeginUpdates()
|
||
|
|
||
|
let countAfter = requests.count
|
||
|
let countBefore = countAfter - insertions.count + deletions.count
|
||
|
|
||
|
if countBefore == 0 && countAfter > 0 {
|
||
|
self.delegate?.friendListDataSourceInsertSections(IndexSet(integer: 0))
|
||
|
}
|
||
|
else if countBefore > 0 && countAfter == 0 {
|
||
|
self.delegate?.friendListDataSourceDeleteSections(IndexSet(integer: 0))
|
||
|
}
|
||
|
else {
|
||
|
self.delegate?.friendListDataSourceDeleteRowsAtIndexPaths(deletions.map { IndexPath(row: $0, section: 0)} )
|
||
|
self.delegate?.friendListDataSourceInsertRowsAtIndexPaths(insertions.map { IndexPath(row: $0, section: 0)} )
|
||
|
self.delegate?.friendListDataSourceReloadRowsAtIndexPaths(modifications.map { IndexPath(row: $0, section: 0)} )
|
||
|
}
|
||
|
|
||
|
self.delegate?.friendListDataSourceEndUpdates()
|
||
|
case .error(let error):
|
||
|
fatalError("\(error)")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
friendsToken = friends.addNotificationBlock { [unowned self] change in
|
||
|
switch change {
|
||
|
case .initial:
|
||
|
break
|
||
|
case .update(_, let deletions, let insertions, let modifications):
|
||
|
if insertions.count > 0 {
|
||
|
// reloading data on request removal/friend insertion to synchronize requests/friends
|
||
|
self.delegate?.friendListDataSourceReloadTable()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
let section = self.isRequestsSectionVisible() ? 1 : 0
|
||
|
|
||
|
let deletions = deletions.map { IndexPath(row: $0, section: section) }
|
||
|
let insertions = insertions.map { IndexPath(row: $0, section: section) }
|
||
|
let modifications = modifications.map { IndexPath(row: $0, section: section) }
|
||
|
|
||
|
self.delegate?.friendListDataSourceBeginUpdates()
|
||
|
self.delegate?.friendListDataSourceDeleteRowsAtIndexPaths(deletions)
|
||
|
self.delegate?.friendListDataSourceInsertRowsAtIndexPaths(insertions)
|
||
|
self.delegate?.friendListDataSourceReloadRowsAtIndexPaths(modifications)
|
||
|
self.delegate?.friendListDataSourceEndUpdates()
|
||
|
case .error(let error):
|
||
|
fatalError("\(error)")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func isRequestsSectionVisible() -> Bool {
|
||
|
guard let requests = requests else {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return requests.count > 0
|
||
|
}
|
||
|
}
|