Commit 8cb78b7a authored by Andreas Traczyk's avatar Andreas Traczyk Committed by Kateryna Kostiuk

presence: initial implementation of contact presence indicators

- Subscribes to presence update signals for that have been added.
- Update events can be subscribed to via the presence service.
- A contact's last known presence status can be queried via the presence
  service.

Change-Id: I4e1ed87f980978469b267c34936da25d15cef7eb
Reviewed-by: Kateryna Kostiuk's avatarKateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
parent 9fd7d04b
......@@ -183,6 +183,9 @@
56BBC9D41EDC7A6D00CDAF8B /* libargon2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 56BBC9D31EDC7A6D00CDAF8B /* libargon2.a */; };
56BBC9DF1EDDC9D300CDAF8B /* LookupNameResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9DE1EDDC9D300CDAF8B /* LookupNameResponse.m */; };
56C715FF1F0D36C600770048 /* ContactsAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 56C715FE1F0D36C600770048 /* ContactsAdapter.mm */; };
62A88D371F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */; };
62A88D391F6C323500F8AB18 /* PresenceAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */; };
62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
......@@ -406,6 +409,10 @@
56C715FE1F0D36C600770048 /* ContactsAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ContactsAdapter.mm; sourceTree = "<group>"; };
56C716001F0D36D900770048 /* ContactsAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsAdapterDelegate.swift; sourceTree = "<group>"; };
56C716021F0D466100770048 /* ContactsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsService.swift; sourceTree = "<group>"; };
62A88D351F6C2E5F00F8AB18 /* PresenceAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PresenceAdapter.h; sourceTree = "<group>"; };
62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceAdapterDelegate.swift; sourceTree = "<group>"; };
62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PresenceAdapter.mm; sourceTree = "<group>"; };
62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceService.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
......@@ -553,6 +560,8 @@
56C716001F0D36D900770048 /* ContactsAdapterDelegate.swift */,
1A5DC01D1F355DA70075E8EF /* ContactsAdapterDelegate.swift */,
1A5DC01F1F355DCF0075E8EF /* ContactsService.swift */,
62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */,
62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */,
);
path = Services;
sourceTree = "<group>";
......@@ -583,6 +592,8 @@
1A5DC00D1F3559070075E8EF /* ContactsAdapter.mm */,
563AEC741EA66487003A5641 /* AccountCreation */,
563AEC731EA6627F003A5641 /* NameRegistration */,
62A88D351F6C2E5F00F8AB18 /* PresenceAdapter.h */,
62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */,
);
path = Bridging;
sourceTree = "<group>";
......@@ -1270,6 +1281,7 @@
1A3D28A71F0EB9DB00B524EE /* Bool+String.swift in Sources */,
5516C29F1E71CEFF009D3D2D /* AccountModelHelper.swift in Sources */,
1ABE07D31F0D8FE800D36361 /* Storyboards.swift in Sources */,
62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */,
1A2D18E51F29197100B2C785 /* MessageAccessoryView.swift in Sources */,
1A2D18C61F29180700B2C785 /* ConversationModel.swift in Sources */,
56308BA71EA00E5700660275 /* NameRegistrationResponse.m in Sources */,
......@@ -1289,6 +1301,7 @@
1A2041861F1EA19600C08435 /* CreateAccountViewController.swift in Sources */,
1A2D18C21F29180700B2C785 /* AccountCredentialsModel.swift in Sources */,
1A2D18FF1F29352D00B2C785 /* MeViewModel.swift in Sources */,
62A88D391F6C323500F8AB18 /* PresenceAdapter.mm in Sources */,
1A2D18B71F29164700B2C785 /* SmartlistViewModel.swift in Sources */,
04399AAE1D1C304300E99CD9 /* Utils.mm in Sources */,
56BBC9A31ED714DF00CDAF8B /* ConversationsService.swift in Sources */,
......@@ -1316,6 +1329,7 @@
1A2D19011F29353A00B2C785 /* MeDetailViewModel.swift in Sources */,
1A2D18A41F27EF5200B2C785 /* AppCoordinator.swift in Sources */,
1A2D18C31F29180700B2C785 /* AccountModel.swift in Sources */,
62A88D371F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift in Sources */,
1A2D18EB1F29197100B2C785 /* MessageViewModel.swift in Sources */,
02B22DFF1DF755DB000358C9 /* AccountsService.swift in Sources */,
1A5DC0421F3567DF0075E8EF /* ContactRequestsCoordinator.swift in Sources */,
......
......@@ -27,8 +27,6 @@ import RxSwift
import Chameleon
import Contacts
import Contacts
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
......@@ -38,13 +36,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private let nameService = NameService(withNameRegistrationAdapter: NameRegistrationAdapter())
private let conversationsService = ConversationsService(withMessageAdapter: MessagesAdapter())
private let contactsService = ContactsService(withContactsAdapter: ContactsAdapter())
private let presenceService = PresenceService(withPresenceAdapter: PresenceAdapter())
public lazy var injectionBag: InjectionBag = {
return InjectionBag(withDaemonService: self.daemonService,
withAccountService: self.accountService,
withNameService: self.nameService,
withConversationService: self.conversationsService,
withContactsService: self.contactsService)
withContactsService: self.contactsService,
withPresenceService: self.presenceService)
}()
private lazy var appCoordinator: AppCoordinator = {
return AppCoordinator(with: self.injectionBag)
......@@ -77,12 +77,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if let currentAccount = self.accountService.currentAccount {
self.contactsService.loadContacts(withAccount: currentAccount)
self.contactsService.loadContactRequests(withAccount: currentAccount)
self.presenceService.subscribeBuddies(withAccount: currentAccount, withContacts: self.contactsService.contacts.value)
}
self.window?.rootViewController = self.appCoordinator.rootViewController
self.window?.makeKeyAndVisible()
self.appCoordinator.start()
}.disposed(by: self.disposeBag)
self.window?.rootViewController = self.appCoordinator.rootViewController
self.window?.makeKeyAndVisible()
self.appCoordinator.start()
return true
}
......
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Foundation/Foundation.h>
@protocol PresenceAdapterDelegate;
@interface PresenceAdapter : NSObject
@property (class, nonatomic, weak) id <PresenceAdapterDelegate> delegate;
- (void)subscribeBuddyWithURI:(NSString*)uri WithAccountId:(NSString*)accountId WithFlag:(BOOL)flag;
@end
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "PresenceAdapter.h"
#import "Utils.h"
#import "dring/presencemanager_interface.h"
#import "Ring-Swift.h"
using namespace DRing;
@implementation PresenceAdapter
// Static delegate that will receive the propagated daemon events
static id <PresenceAdapterDelegate> _delegate;
#pragma mark Init
- (id)init {
if (self = [super init]) {
[self registerPresenceHandlers];
}
return self;
}
#pragma mark -
#pragma mark Callbacks registration
- (void)registerPresenceHandlers {
std::map<std::string, std::shared_ptr<CallbackWrapperBase>> presenceHandlers;
// Incoming buddy notification
presenceHandlers.insert(exportable_callback<PresenceSignal::NewBuddyNotification>([&](const std::string& account_id,
const std::string& uri,
int status,
const std::string& lineStatus) {
if(PresenceAdapter.delegate) {
NSString* accountId = [NSString stringWithUTF8String:account_id.c_str()];
NSString* uriString = [NSString stringWithUTF8String:uri.c_str()];
NSString* lineStatusString = [NSString stringWithUTF8String:lineStatus.c_str()];
[PresenceAdapter.delegate newBuddyNotificationWithAccountId:accountId withUri:uriString withStatus:(NSInteger)status withLineStatus:lineStatusString];
}
}));
registerPresHandlers(presenceHandlers);
}
#pragma mark -
- (void)subscribeBuddyWithURI:(NSString*)uri WithAccountId:(NSString*)accountId WithFlag:(BOOL)flag {
DRing::subscribeBuddy(std::string([accountId UTF8String]), std::string([uri UTF8String]), (bool)flag);
}
#pragma mark PresenceAdapterDelegate
+ (id <PresenceAdapterDelegate>)delegate {
return _delegate;
}
+ (void) setDelegate:(id<PresenceAdapterDelegate>)delegate {
_delegate = delegate;
}
#pragma mark -
@end
......@@ -32,3 +32,4 @@
#import "MessagesAdapter.h"
#import "Chameleon/Chameleon.h"
#import "ContactsAdapter.h"
#import "PresenceAdapter.h"
......@@ -28,17 +28,20 @@ class InjectionBag {
let nameService: NameService
let conversationsService: ConversationsService
let contactsService: ContactsService
let presenceService: PresenceService
init (withDaemonService daemonService: DaemonService,
withAccountService accountService: AccountsService,
withNameService nameService: NameService,
withConversationService conversationService: ConversationsService,
withContactsService contactsService: ContactsService) {
withContactsService contactsService: ContactsService,
withPresenceService presenceService: PresenceService) {
self.daemonService = daemonService
self.accountService = accountService
self.nameService = nameService
self.conversationsService = conversationService
self.contactsService = contactsService
self.presenceService = presenceService
}
}
......@@ -30,6 +30,26 @@ class ConversationViewModel: ViewModel {
*/
private let log = SwiftyBeaver.self
//Services
private let conversationsService: ConversationsService
private let accountService: AccountsService
private let nameService: NameService
private let contactsService: ContactsService
private let presenceService: PresenceService
private let injectionBag: InjectionBag
required init(with injectionBag: InjectionBag) {
self.injectionBag = injectionBag
self.accountService = injectionBag.accountService
self.conversationsService = injectionBag.conversationsService
self.nameService = injectionBag.nameService
self.contactsService = injectionBag.contactsService
self.presenceService = injectionBag.presenceService
dateFormatter.dateStyle = .medium
hourFormatter.dateFormat = "HH:mm"
}
var conversation: ConversationModel! {
didSet {
//Create observable from sorted conversations and flatMap them to view models
......@@ -50,20 +70,34 @@ class ConversationViewModel: ViewModel {
let contact = self.contactsService.contact(withRingId: self.conversation.recipientRingId)
if let contact = contact {
if let contact = contact {
self.inviteButtonIsAvailable.onNext(!contact.confirmed)
}
self.contactsService.contactStatus.subscribe(onNext: { contact in
self.inviteButtonIsAvailable.onNext(!contact.confirmed)
}).disposed(by: self.disposeBag)
// subscribe to presence updates for the conversation's associated contact
self.presenceService
.sharedResponseStream
.filter({ presenceUpdateEvent in
return presenceUpdateEvent.eventType == ServiceEventType.presenceUpdated
&& presenceUpdateEvent.getEventInput(.uri) == contact?.ringId
})
.subscribe(onNext: { [unowned self] presenceUpdateEvent in
if let uri: String = presenceUpdateEvent.getEventInput(.uri) {
self.contactPresence.onNext(self.presenceService.contactPresence[uri]!)
}
})
.disposed(by: disposeBag)
if let contactUserName = contact?.userName {
self.userName.onNext(contactUserName)
} else {
let recipientRingId = self.conversation.recipientRingId
//Return an observer for the username lookup
// Return an observer for the username lookup
self.nameService.usernameLookupStatus
.filter({ lookupNameResponse in
return lookupNameResponse.address != nil &&
......@@ -101,25 +135,10 @@ class ConversationViewModel: ViewModel {
var messages: Observable<[MessageViewModel]>!
var userName = BehaviorSubject(value: "")
var inviteButtonIsAvailable = BehaviorSubject(value: true)
//Services
private let conversationsService: ConversationsService
private let accountService: AccountsService
private let nameService: NameService
private let contactsService: ContactsService
private let injectionBag: InjectionBag
required init(with injectionBag: InjectionBag) {
self.injectionBag = injectionBag
self.accountService = injectionBag.accountService
self.conversationsService = injectionBag.conversationsService
self.nameService = injectionBag.nameService
self.contactsService = injectionBag.contactsService
var inviteButtonIsAvailable = BehaviorSubject(value: true)
dateFormatter.dateStyle = .medium
hourFormatter.dateFormat = "HH:mm"
}
var contactPresence = BehaviorSubject(value: false)
var unreadMessages: String {
return self.unreadMessagesCount.description
......
......@@ -30,6 +30,7 @@ class ConversationCell: UITableViewCell, NibReusable {
@IBOutlet weak var newMessagesLabel: UILabel!
@IBOutlet weak var lastMessageDateLabel: UILabel!
@IBOutlet weak var lastMessagePreviewLabel: UILabel!
@IBOutlet weak var presenceIndicator: UIView!
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16F2073" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
......@@ -15,7 +15,7 @@
<rect key="frame" x="0.0" y="0.0" width="358" height="76"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="358" height="75.5"/>
<rect key="frame" x="0.0" y="0.0" width="358" height="76"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_contact_picture" translatesAutoresizingMaskIntoConstraints="NO" id="pFB-Jn-TNP">
......@@ -32,19 +32,19 @@
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Yesterday" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7Yv-cC-LKx">
<rect key="frame" x="281.5" y="30.5" width="60.5" height="14.5"/>
<rect key="frame" x="281" y="31" width="61" height="15"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="12"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Name" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2fJ-Wf-1e0">
<rect key="frame" x="60" y="8" width="217.5" height="39"/>
<rect key="frame" x="60" y="8" width="217" height="39"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Preview" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eug-ak-r49">
<rect key="frame" x="60" y="51" width="217.5" height="17"/>
<rect key="frame" x="60" y="51" width="217" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
......@@ -76,14 +76,29 @@
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" horizontalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="Fpi-20-ZYV" userLabel="Presence Indicator">
<rect key="frame" x="42" y="44" width="14" height="14"/>
<color key="backgroundColor" red="0.0" green="1" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="14" id="0m2-aN-1GT"/>
<constraint firstAttribute="width" constant="14" id="m3b-My-KNY"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="6"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<constraints>
<constraint firstItem="2fJ-Wf-1e0" firstAttribute="leading" secondItem="pFB-Jn-TNP" secondAttribute="trailing" constant="4" id="2NV-6m-dri"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="7Yv-cC-LKx" secondAttribute="bottom" constant="8" id="2O6-wC-voj"/>
<constraint firstItem="eug-ak-r49" firstAttribute="leading" secondItem="pFB-Jn-TNP" secondAttribute="trailing" constant="4" id="9ah-Ed-RlY"/>
<constraint firstItem="pFB-Jn-TNP" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="9mO-5E-3lA"/>
<constraint firstItem="Fpi-20-ZYV" firstAttribute="bottom" secondItem="pFB-Jn-TNP" secondAttribute="bottom" id="BMe-Il-GoN"/>
<constraint firstItem="7Yv-cC-LKx" firstAttribute="leading" secondItem="2fJ-Wf-1e0" secondAttribute="trailing" constant="4" id="BzU-Ya-2ME"/>
<constraint firstItem="JTE-eF-Y5s" firstAttribute="trailing" secondItem="pFB-Jn-TNP" secondAttribute="trailing" id="MgK-cd-QXM"/>
<constraint firstItem="Fpi-20-ZYV" firstAttribute="trailing" secondItem="pFB-Jn-TNP" secondAttribute="trailing" id="Oav-3c-X7k"/>
<constraint firstAttribute="trailing" secondItem="7Yv-cC-LKx" secondAttribute="trailing" constant="16" id="UOx-Og-IuZ"/>
<constraint firstItem="JTE-eF-Y5s" firstAttribute="top" secondItem="pFB-Jn-TNP" secondAttribute="top" id="W3A-IX-eXJ"/>
<constraint firstItem="7Yv-cC-LKx" firstAttribute="top" relation="greaterThanOrEqual" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="Wei-7X-4zv"/>
......@@ -101,6 +116,7 @@
<outlet property="nameLabel" destination="2fJ-Wf-1e0" id="0Mb-yC-vh6"/>
<outlet property="newMessagesIndicator" destination="JTE-eF-Y5s" id="9kR-8x-Zpk"/>
<outlet property="newMessagesLabel" destination="P5S-4k-0yx" id="WlA-Z8-sNC"/>
<outlet property="presenceIndicator" destination="Fpi-20-ZYV" id="RL9-sx-sBF"/>
<outlet property="profileImage" destination="pFB-Jn-TNP" id="zuf-CZ-9wL"/>
</connections>
<point key="canvasLocation" x="70" y="-92"/>
......
......@@ -117,12 +117,22 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: ConversationCell.self)
item.userName.asObservable().observeOn(MainScheduler.instance).bind(to: cell.nameLabel.rx.text).disposed(by: self.disposeBag)
item.userName.asObservable()
.observeOn(MainScheduler.instance)
.bind(to: cell.nameLabel.rx.text)
.disposed(by: self.disposeBag)
cell.newMessagesLabel.text = item.unreadMessages
cell.lastMessageDateLabel.text = item.lastMessageReceivedDate
cell.newMessagesIndicator.isHidden = item.hideNewMessagesLabel
cell.lastMessagePreviewLabel.text = item.lastMessage
item.contactPresence.asObservable()
.observeOn(MainScheduler.instance)
.map { value in !value }
.bind(to: cell.presenceIndicator.rx.isHidden)
.disposed(by: self.disposeBag)
return cell
}
......
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
@objc protocol PresenceAdapterDelegate {
func newBuddyNotification(withAccountId accountId: String,
withUri uri: String,
withStatus status: Int,
withLineStatus lineStatus: String)
}
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import SwiftyBeaver
import RxSwift
class PresenceService {
fileprivate let presenceAdapter: PresenceAdapter
fileprivate let log = SwiftyBeaver.self
var contactPresence: [String: Bool]
fileprivate let disposeBag = DisposeBag()
fileprivate let responseStream = PublishSubject<ServiceEvent>()
var sharedResponseStream: Observable<ServiceEvent>
init(withPresenceAdapter presenceAdapter: PresenceAdapter) {
self.responseStream.disposed(by: disposeBag)
self.sharedResponseStream = responseStream.share()
self.contactPresence = [String: Bool]()
self.presenceAdapter = presenceAdapter
PresenceAdapter.delegate = self
}
func subscribeBuddies(withAccount account: AccountModel, withContacts contacts: [ContactModel]) {
for contact in contacts {
subscribeBuddy(withAccountId: account.id,
withUri: contact.ringId,
withFlag: true)
}
}
func subscribeBuddy(withAccountId accountId: String,
withUri uri: String,
withFlag flag: Bool) {
presenceAdapter.subscribeBuddy(withURI: uri, withAccountId: accountId, withFlag: flag)
contactPresence[uri] = false
}
}
extension PresenceService: PresenceAdapterDelegate {
func newBuddyNotification(withAccountId accountId: String,
withUri uri: String,
withStatus status: Int,
withLineStatus lineStatus: String) {
contactPresence[uri] = status > 0 ? true : false
/*
The subscriber is intended to query the contactPresence dictionary
with the contact's ringId
*/
var event = ServiceEvent(withEventType: .presenceUpdated)
event.addEventInput(.uri, value: uri)
self.responseStream.onNext(event)
log.debug("newBuddyNotification: uri=\(uri), status=\(status)")
}
}
......@@ -28,6 +28,7 @@ enum ServiceEventType {
case accountAdded
case accountsChanged
case registrationStateChanged
case presenceUpdated
}
/**
......@@ -37,6 +38,8 @@ enum ServiceEventInput {
case id
case state
case registrationState
case uri
case presenceStatus
}
/**
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment