Commit 0eb233d5 authored by Kateryna Kostiuk's avatar Kateryna Kostiuk

video: hardware decoding/encoding

Change-Id: Ia86d0a42802438cc9eaba15b768eee4f4a01a941
parent caf0e652
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "62366028200914C1002598C1"
BuildableName = "libring"
BlueprintName = "libring"
ReferencedContainer = "container:Ring.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "62366028200914C1002598C1"
BuildableName = "libring"
BlueprintName = "libring"
ReferencedContainer = "container:Ring.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "AVLOGLEVEL"
value = "40"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "62366028200914C1002598C1"
BuildableName = "libring"
BlueprintName = "libring"
ReferencedContainer = "container:Ring.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
...@@ -117,8 +117,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD ...@@ -117,8 +117,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// sets output device to whatever is currently available (either spk / headset) // sets output device to whatever is currently available (either spk / headset)
self.audioService.startAVAudioSession() self.audioService.startAVAudioSession()
// disables hardware decoding prepareVideoAcceleration()
self.videoService.setDecodingAccelerated(withState: false)
// requests permission to use the camera // requests permission to use the camera
// will enumerate and add devices once permission has been granted // will enumerate and add devices once permission has been granted
...@@ -161,6 +160,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD ...@@ -161,6 +160,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
} }
guard let currentAccount = self.accountService.currentAccount else { guard let currentAccount = self.accountService.currentAccount else {
self.log.error("Can't get current account!") self.log.error("Can't get current account!")
//if we don't have any account means it is first run, so enable hardware acceleration
self.videoService.setDecodingAccelerated(withState: true)
self.videoService.setEncodingAccelerated(withState: true)
UserDefaults.standard.set(true, forKey: hardareAccelerationKey)
return return
} }
self.reloadDataFor(account: currentAccount) self.reloadDataFor(account: currentAccount)
...@@ -219,6 +222,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD ...@@ -219,6 +222,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
self.clearBadgeNumber() self.clearBadgeNumber()
} }
func prepareVideoAcceleration() {
// we want enable hardware acceleration by default so if key does not exists,
// means it was not disabled by user
let keyExists = UserDefaults.standard.object(forKey: hardareAccelerationKey) != nil
let enable = keyExists ? UserDefaults.standard.bool(forKey: hardareAccelerationKey) : false
self.videoService.setDecodingAccelerated(withState: enable)
self.videoService.setEncodingAccelerated(withState: enable)
guard let codecInfoPath = Bundle.main.path(forResource: "encoder",
ofType: ".json") else {return}
guard let destPath =
NSSearchPathForDirectoriesInDomains(.documentDirectory,
.userDomainMask,
true).first else {return}
let fileManager = FileManager.default
guard let fullDestPath = NSURL(fileURLWithPath: destPath)
.appendingPathComponent("encoder.json") else {return}
let fullDestPathString = fullDestPath.path
do {
try fileManager.copyItem(atPath: codecInfoPath,
toPath: fullDestPathString)
} catch { }
}
// MARK: - Ring Daemon // MARK: - Ring Daemon
fileprivate func startDaemon() { fileprivate func startDaemon() {
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#import <map> #import <map>
#import <string> #import <string>
#import <vector> #import <vector>
struct AVFrame;
@interface Utils : NSObject @interface Utils : NSObject
...@@ -35,4 +36,6 @@ ...@@ -35,4 +36,6 @@
+ (NSData*)dataFromVectorOfUInt8:(std::vector<uint8_t>)vectorOfUInt8; + (NSData*)dataFromVectorOfUInt8:(std::vector<uint8_t>)vectorOfUInt8;
+ (std::vector<uint8_t>)vectorOfUInt8FromData:(NSData*)data; + (std::vector<uint8_t>)vectorOfUInt8FromData:(NSData*)data;
+ (std::vector<std::map<std::string, std::string>>)arrayOfDictionnarisToVectorOfMap:(NSArray*)dictionaries; + (std::vector<std::map<std::string, std::string>>)arrayOfDictionnarisToVectorOfMap:(NSArray*)dictionaries;
+ (UIImage*)convertHardwareDecodedFrameToImage:(const AVFrame*)frame;
+ (AVFrame*)configureHardwareDecodedFrame:(AVFrame*)frame fromImageBuffer: (CVImageBufferRef) image;
@end @end
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
*/ */
#import "Utils.h" #import "Utils.h"
extern "C" {
#include <libavutil/frame.h>
}
@implementation Utils @implementation Utils
...@@ -105,4 +108,26 @@ ...@@ -105,4 +108,26 @@
return vector; return vector;
} }
+ (UIImage*)convertHardwareDecodedFrameToImage:(const AVFrame*)frame {
if ((CVPixelBufferRef)frame->data[3]) {
CIImage *image = [CIImage imageWithCVPixelBuffer: (CVPixelBufferRef)frame->data[3]];
UIImage * imageUI = [UIImage imageWithCIImage:image];
return imageUI;
}
return [[UIImage alloc] init];
}
+ (AVFrame*)configureHardwareDecodedFrame:(AVFrame*)frame fromImageBuffer:(CVImageBufferRef) image {
//get dimensions
CVPixelBufferLockBaseAddress(image,0);
size_t width = CVPixelBufferGetWidth(image);
size_t height = CVPixelBufferGetHeight(image);
CVPixelBufferUnlockBaseAddress(image,0);
frame->data[3] = (uint8_t *)image;
frame->format = AV_PIX_FMT_VIDEOTOOLBOX;
frame->width = static_cast<int>(width);
frame->height = static_cast<int>(height);
return frame;
}
@end @end
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <AVFoundation/AVCaptureOutput.h> #import <AVFoundation/AVCaptureOutput.h>
#import <AVFoundation/AVFoundation.h>
@protocol VideoAdapterDelegate; @protocol VideoAdapterDelegate;
...@@ -30,9 +31,13 @@ ...@@ -30,9 +31,13 @@
- (void)addVideoDeviceWithName:(NSString*)deviceName withDevInfo:(NSDictionary*)deviceInfoDict; - (void)addVideoDeviceWithName:(NSString*)deviceName withDevInfo:(NSDictionary*)deviceInfoDict;
- (void)registerSinkTargetWithSinkId:sinkId withWidth:(NSInteger)w withHeight:(NSInteger)h; - (void)registerSinkTargetWithSinkId:sinkId withWidth:(NSInteger)w withHeight:(NSInteger)h;
- (void)removeSinkTargetWithSinkId:(NSString*)sinkId; - (void)removeSinkTargetWithSinkId:(NSString*)sinkId;
- (void)writeOutgoingHardwareDecodedFrameWithBuffer:(CVImageBufferRef)image;
- (void)writeOutgoingFrameWithImage:(UIImage*)image; - (void)writeOutgoingFrameWithImage:(UIImage*)image;
- (void)setDecodingAccelerated:(BOOL)state; - (void)setDecodingAccelerated:(BOOL)state;
- (BOOL)getDecodingAccelerated;
- (void)switchInput:(NSString*)deviceName; - (void)switchInput:(NSString*)deviceName;
- (void)switchInput:(NSString*)deviceName forCall:(NSString*) callID; - (void)switchInput:(NSString*)deviceName forCall:(NSString*) callID;
- (void)setEncodingAccelerated:(BOOL)state;
- (BOOL)getEncodingAccelerated;
@end @end
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <functional> #include <functional>
#include <AVFoundation/AVFoundation.h> #include <AVFoundation/AVFoundation.h>
#include <mutex> #include <mutex>
#import "Utils.h"
using namespace DRing; using namespace DRing;
...@@ -36,11 +37,26 @@ struct Renderer ...@@ -36,11 +37,26 @@ struct Renderer
std::condition_variable frameCv; std::condition_variable frameCv;
bool isRendering; bool isRendering;
std::mutex renderMutex; std::mutex renderMutex;
AVSinkTarget avtarget;
SinkTarget target; SinkTarget target;
SinkTarget::FrameBufferPtr daemonFramePtr_; SinkTarget::FrameBufferPtr daemonFramePtr_;
int width; int width;
int height; int height;
void bindAVSinkFunctions() {
avtarget.push = [this](std::unique_ptr<DRing::VideoFrame> frame) {
if(!VideoAdapter.delegate) {
return;
}
@autoreleasepool {
UIImage *image = [Utils
convertHardwareDecodedFrameToImage: std::move(frame->pointer())];
isRendering = true;
[VideoAdapter.delegate writeFrameWithImage: image];
isRendering = false;
}
};
}
void bindSinkFunctions() { void bindSinkFunctions() {
target.pull = [this](std::size_t bytes) { target.pull = [this](std::size_t bytes) {
std::lock_guard<std::mutex> lk(renderMutex); std::lock_guard<std::mutex> lk(renderMutex);
...@@ -146,8 +162,13 @@ static id <VideoAdapterDelegate> _delegate; ...@@ -146,8 +162,13 @@ static id <VideoAdapterDelegate> _delegate;
auto renderer = std::make_shared<Renderer>(); auto renderer = std::make_shared<Renderer>();
renderer->width = static_cast<int>(w); renderer->width = static_cast<int>(w);
renderer->height = static_cast<int>(h); renderer->height = static_cast<int>(h);
renderer->bindSinkFunctions(); if(self.getDecodingAccelerated) {
DRing::registerSinkTarget(_sinkId, renderer->target); renderer->bindAVSinkFunctions();
DRing::registerAVSinkTarget(_sinkId, renderer->avtarget);
} else {
renderer->bindSinkFunctions();
DRing::registerSinkTarget(_sinkId, renderer->target);
}
renderers.insert(std::make_pair(_sinkId, renderer)); renderers.insert(std::make_pair(_sinkId, renderer));
} }
...@@ -162,7 +183,17 @@ static id <VideoAdapterDelegate> _delegate; ...@@ -162,7 +183,17 @@ static id <VideoAdapterDelegate> _delegate;
} }
} }
- (void)writeOutgoingFrameWithImage:(UIImage*)image { - (void)writeOutgoingHardwareDecodedFrameWithBuffer:(CVImageBufferRef)image {
auto frame = DRing::getNewFrame();
if(!frame) {
return;
}
auto avframe = frame->pointer();
[Utils configureHardwareDecodedFrame:(AVFrame*)avframe fromImageBuffer: image];
DRing::publishFrame();
}
- (void)writeOutgoingFrameWithImage:(UIImage *)image {
unsigned capacity = 4 * image.size.width * image.size.height; unsigned capacity = 4 * image.size.width * image.size.height;
uint8_t* buf = (uint8_t*)DRing::obtainFrame(capacity); uint8_t* buf = (uint8_t*)DRing::obtainFrame(capacity);
if (buf){ if (buf){
...@@ -196,6 +227,18 @@ static id <VideoAdapterDelegate> _delegate; ...@@ -196,6 +227,18 @@ static id <VideoAdapterDelegate> _delegate;
DRing::setDecodingAccelerated(state); DRing::setDecodingAccelerated(state);
} }
- (BOOL)getDecodingAccelerated {
return DRing::getDecodingAccelerated();
}
- (void)setEncodingAccelerated:(BOOL)state {
DRing::setEncodingAccelerated(state);
}
- (BOOL)getEncodingAccelerated {
return DRing::getEncodingAccelerated();
}
- (void)switchInput:(NSString*)deviceName { - (void)switchInput:(NSString*)deviceName {
DRing::switchInput(std::string([deviceName UTF8String])); DRing::switchInput(std::string([deviceName UTF8String]));
} }
......
...@@ -57,6 +57,7 @@ internal enum Asset { ...@@ -57,6 +57,7 @@ internal enum Asset {
internal static let ringLogo = ImageAsset(name: "ring_logo") internal static let ringLogo = ImageAsset(name: "ring_logo")
internal static let scan = ImageAsset(name: "scan") internal static let scan = ImageAsset(name: "scan")
internal static let sendButton = ImageAsset(name: "send_button") internal static let sendButton = ImageAsset(name: "send_button")
internal static let settings = ImageAsset(name: "settings")
internal static let settingsIcon = ImageAsset(name: "settings_icon") internal static let settingsIcon = ImageAsset(name: "settings_icon")
internal static let shareButton = ImageAsset(name: "share_button") internal static let shareButton = ImageAsset(name: "share_button")
internal static let stopCall = ImageAsset(name: "stop_call") internal static let stopCall = ImageAsset(name: "stop_call")
......
...@@ -291,6 +291,13 @@ internal enum L10n { ...@@ -291,6 +291,13 @@ internal enum L10n {
internal static let readableStatusSuccess = L10n.tr("Localizable", "dataTransfer.readableStatusSuccess") internal static let readableStatusSuccess = L10n.tr("Localizable", "dataTransfer.readableStatusSuccess")
} }
internal enum GeneralSettings {
/// General settings
internal static let title = L10n.tr("Localizable", "generalSettings.title")
/// Enable video acceleration
internal static let videoAcceleration = L10n.tr("Localizable", "generalSettings.videoAcceleration")
}
internal enum GeneratedMessage { internal enum GeneratedMessage {
/// Contact added /// Contact added
internal static let contactAdded = L10n.tr("Localizable", "generatedMessage.contactAdded") internal static let contactAdded = L10n.tr("Localizable", "generatedMessage.contactAdded")
......
...@@ -41,7 +41,7 @@ class ContactViewModel: ViewModel, Stateable { ...@@ -41,7 +41,7 @@ class ContactViewModel: ViewModel, Stateable {
private let accountService: AccountsService private let accountService: AccountsService
private let conversationService: ConversationsService private let conversationService: ConversationsService
private let nameService: NameService private let nameService: NameService
lazy var tableSection : Observable<[SectionModel<String, ContactActions>]> = { lazy var tableSection: Observable<[SectionModel<String, ContactActions>]> = {
let jamiSettings = let jamiSettings =
[SectionModel(model: "ProfileInfoCell", [SectionModel(model: "ProfileInfoCell",
items: items:
......
...@@ -57,6 +57,8 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa ...@@ -57,6 +57,8 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa
self.createNewAccount() self.createNewAccount()
case .showDialpad(let inCall): case .showDialpad(let inCall):
self.showDialpad(inCall: inCall) self.showDialpad(inCall: inCall)
case .showGeneralSettings:
self.showGeneralSettings()
default: default:
break break
} }
...@@ -109,6 +111,11 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa ...@@ -109,6 +111,11 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa
} }
} }
func showGeneralSettings() {
let settingsViewController = GeneralSettingsViewController.instantiate(with: self.injectionBag)
self.present(viewController: settingsViewController, withStyle: .present, withAnimation: true, disposeBag: self.disposeBag)
}
func puchConversation(participantId: String) { func puchConversation(participantId: String) {
let conversationViewModel = ConversationViewModel(with: self.injectionBag) let conversationViewModel = ConversationViewModel(with: self.injectionBag)
guard let account = accountService.currentAccount else { guard let account = accountService.currentAccount else {
......
...@@ -55,6 +55,10 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased ...@@ -55,6 +55,10 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
@IBOutlet weak var settingsButton: UIButton! @IBOutlet weak var settingsButton: UIButton!
@IBOutlet weak var dialpadButton: UIButton! @IBOutlet weak var dialpadButton: UIButton!
@IBOutlet weak var dialpadButtonShadow: UIView! @IBOutlet weak var dialpadButtonShadow: UIView!
@IBOutlet weak var searchBarShadow: UIView!
@IBOutlet weak var qrScanButton: UIButton!
@IBOutlet weak var phoneBookButton: UIButton!
@IBOutlet weak var scanButtonLeadingConstraint: NSLayoutConstraint!
// account selection // account selection
var accounPicker = UIPickerView() var accounPicker = UIPickerView()
...@@ -151,21 +155,22 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased ...@@ -151,21 +155,22 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
} }
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
let imageScanSearch = UIImage(asset: Asset.qrCodeScan) as UIImage? let imageSettings = UIImage(asset: Asset.settings) as UIImage?
let scanButton = UIButton(type: UIButtonType.custom) as UIButton let generalSettingsButton = UIButton(type: UIButtonType.system) as UIButton
scanButton.setImage(imageScanSearch, for: .normal) generalSettingsButton.setImage(imageSettings, for: .normal)
let scanButtonItem = UIBarButtonItem(customView: scanButton) generalSettingsButton.contentMode = .scaleAspectFill
scanButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance) let settingsButtonItem = UIBarButtonItem(customView: generalSettingsButton)
generalSettingsButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] in
self.viewModel.showGeneralSettings()
})
.disposed(by: self.disposeBag)
qrScanButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] in .subscribe(onNext: { [unowned self] in
self.openScan() self.openScan()
}) })
.disposed(by: self.disposeBag) .disposed(by: self.disposeBag)
let imageOpenPhoneContacts = UIImage(asset: Asset.phoneBook) as UIImage?
let phoneBookButton = UIButton(type: UIButtonType.custom) as UIButton
phoneBookButton.setImage(imageOpenPhoneContacts, for: .normal)
phoneBookButton.contentMode = .scaleAspectFill
let contactsButtonItem = UIBarButtonItem(customView: phoneBookButton)
phoneBookButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance) phoneBookButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] in .subscribe(onNext: { [unowned self] in
self.contactPicker.delegate = self self.contactPicker.delegate = self
...@@ -178,15 +183,23 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased ...@@ -178,15 +183,23 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
if let account = currentAccount { if let account = currentAccount {
let accountSip = account.type == AccountType.sip let accountSip = account.type == AccountType.sip
self.navigationItem self.navigationItem
.rightBarButtonItem = accountSip ? contactsButtonItem : scanButtonItem .rightBarButtonItem = accountSip ? nil : settingsButtonItem
self.dialpadButtonShadow.isHidden = !accountSip self.dialpadButtonShadow.isHidden = !accountSip
self.phoneBookButton.isHidden = !accountSip
self.qrScanButton.isHidden = accountSip
} }
}).disposed(by: disposeBag) }).disposed(by: disposeBag)
self.navigationItem.rightBarButtonItem = scanButtonItem self.navigationItem.rightBarButtonItem = settingsButtonItem
if let account = self.viewModel.currentAccount, if let account = self.viewModel.currentAccount {
account.type == AccountType.sip { if account.type == AccountType.sip {
self.navigationItem.rightBarButtonItem = contactsButtonItem self.navigationItem.rightBarButtonItem = nil
self.qrScanButton.isHidden = true
self.phoneBookButton.isHidden = false
} else {
self.qrScanButton.isHidden = false
self.phoneBookButton.isHidden = true
}
} }
//create accounts button //create accounts button
...@@ -388,13 +401,13 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased ...@@ -388,13 +401,13 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
self.searchBar.tintColor = UIColor.jamiMain self.searchBar.tintColor = UIColor.jamiMain
self.searchBar.barTintColor = UIColor.jamiNavigationBar self.searchBar.barTintColor = UIColor.jamiNavigationBar
self.view.bringSubview(toFront: self.searchBar) self.view.bringSubview(toFront: self.searchBarShadow)
self.searchBar.layer.shadowColor = UIColor.black.cgColor self.searchBarShadow.layer.shadowColor = UIColor.black.cgColor
self.searchBar.layer.shadowOffset = CGSize(width: 0.0, height: 2.5) self.searchBarShadow.layer.shadowOffset = CGSize(width: 0.0, height: 2.5)
self.searchBar.layer.shadowOpacity = 0.2 self.searchBarShadow.layer.shadowOpacity = 0.2
self.searchBar.layer.shadowRadius = 3 self.searchBarShadow.layer.shadowRadius = 3
self.searchBar.layer.masksToBounds = false self.searchBarShadow.layer.masksToBounds = false
//Bind the SearchBar to the ViewModel //Bind the SearchBar to the ViewModel
self.searchBar.rx.text.orEmpty self.searchBar.rx.text.orEmpty
...@@ -404,12 +417,14 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased ...@@ -404,12 +417,14 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
//Show Cancel button //Show Cancel button
self.searchBar.rx.textDidBeginEditing.subscribe(onNext: { [unowned self] in self.searchBar.rx.textDidBeginEditing.subscribe(onNext: { [unowned self] in
self.searchBar.setShowsCancelButton(true, animated: true) self.scanButtonLeadingConstraint.constant = -40
self.searchBar.setShowsCancelButton(true, animated: false)
}).disposed(by: disposeBag) }).disposed(by: disposeBag)
//Hide Cancel button //Hide Cancel button
self.searchBar.rx.textDidEndEditing.subscribe(onNext: { [unowned self] in self.searchBar.rx.textDidEndEditing.subscribe(onNext: { [unowned self] in
self.searchBar.setShowsCancelButton(false, animated: true) self.scanButtonLeadingConstraint.constant = 10
self.searchBar.setShowsCancelButton(false, animated: false)
}).disposed(by: disposeBag) }).disposed(by: disposeBag)
//Cancel button event //Cancel button event
...@@ -566,23 +581,10 @@ extension SmartlistViewController: CNContactPickerDelegate { ...@@ -566,23 +581,10 @@ extension SmartlistViewController: CNContactPickerDelegate {
} }
func setNumberFromContact(contactNumber: String) { func setNumberFromContact(contactNumber: String) {
//UPDATE YOUR NUMBER SELECTION LOGIC AND PERFORM ACTION WITH THE SELECTED NUMBER
var contactNumber = contactNumber.replacingOccurrences(of: "-", with: "") var contactNumber = contactNumber.replacingOccurrences(of: "-", with: "")
contactNumber = contactNumber.replacingOccurrences(of: "(", with: "") contactNumber = contactNumber.replacingOccurrences(of: "(", with: "")
contactNumber = contactNumber.replacingOccurrences(of: ")", with: "") contactNumber = contactNumber.replacingOccurrences(of: ")", with: "")
self.viewModel.showSipConversation(withNumber: contactNumber) self.viewModel.showSipConversation(withNumber: contactNumber)
// self.viewModel.searchBarText.value = contactNumber
//contactNumber = contactNumber.removeWhitespacesInBetween()
// guard contactNumber.count >= 10 else {
// dismiss(animated: true) {
// // self.popUpMessageError(value: 10, message: "Selected contact does not have a valid number")
// }
// return
// }
//textFieldNumber.text = String(contactNumber.suffix(10))
} }
func contactPickerDidCancel(_ picker: CNContactPickerViewController) { func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
......
...@@ -401,4 +401,8 @@ class SmartlistViewModel: Stateable, ViewModel { ...@@ -401,4 +401,8 @@ class SmartlistViewModel: Stateable, ViewModel {
func showDialpad() { func showDialpad() {
self.stateSubject.onNext(ConversationState.showDialpad(inCall: false)) self.stateSubject.onNext(ConversationState.showDialpad(inCall: false))
} }
func showGeneralSettings() {
self.stateSubject.onNext(ConversationState.showGeneralSettings())
}
} }
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="0KF-lS-cXQ">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--General Settings View Controller-->
<scene sceneID="35j-2a-aAz">
<objects>
<viewController hidesBottomBarWhenPushed="YES" id="0KF-lS-cXQ" customClass="GeneralSettingsViewController" customModule="Ring" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="tdP-kF-vrw">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="OaU-bt-R2w">
<rect key="frame" x="0.0" y="0.0" width="375" height="120"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="General Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fm1-VN-ppT">
<rect key="frame" x="80.5" y="53.5" width="214" height="38.5"/>
<fontDescription key="fontDescription" type="system" weight="thin" pointSize="32"/>
<color key="textColor" red="0.1215686275" green="0.28627450980000002" blue="0.4431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NE7-nL-DvW">
<rect key="frame" x="14" y="60" width="25" height="25"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="R0J-pI-wkz"/>
<constraint firstAttribute="width" constant="25" id="gl1-ki-xOB"/>
</constraints>
<color key="tintColor" red="0.1215686275" green="0.28627450980000002" blue="0.4431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" image="cross"/>
</button>
</subviews>
<constraints>
<constraint firstItem="fm1-VN-ppT" firstAttribute="centerX" secondItem="OaU-bt-R2w" secondAttribute="centerX" id="1kP-cu-cDP"/>
<constraint firstAttribute="bottom" secondItem="NE7-nL-DvW" secondAttribute="bottom" constant="35" id="Hsx-he-cDE"/>
<constraint firstAttribute="height" constant="120" id="JGl-fJ-5S9"/>
<constraint firstItem="fm1-VN-ppT" firstAttribute="centerY" secondItem="NE7-nL-DvW" secondAttribute="centerY" id="TAY-0E-oeR"/>
<constraint firstItem="NE7-nL-DvW" firstAttribute="leading" secondItem="OaU-bt-R2w" secondAttribute="leading" constant="14" id="Ytg-WO-MP4"/>
</constraints>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="XAa-ES-xki">
<rect key="frame" x="0.0" y="120" width="375" height="547"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="OaU-bt-R2w" firstAttribute="top" secondItem="tdP-kF-vrw" secondAttribute="top" id="99o-PS-Hfy"/>
<constraint firstItem="OaU-bt-R2w" firstAttribute="leading" secondItem="tdP-kF-vrw" secondAttribute="leading" id="9Aq-IP-zrF"/>
<constraint firstItem="XAa-ES-xki" firstAttribute="trailing" secondItem="f8H-BQ-kNd" secondAttribute="trailing" id="Ekj-a6-3GP"/>