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
// sets output device to whatever is currently available (either spk / headset)
self.audioService.startAVAudioSession()
// disables hardware decoding
self.videoService.setDecodingAccelerated(withState: false)
prepareVideoAcceleration()
// requests permission to use the camera
// will enumerate and add devices once permission has been granted
......@@ -161,6 +160,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
guard let currentAccount = self.accountService.currentAccount else {
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
}
self.reloadDataFor(account: currentAccount)
......@@ -219,6 +222,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
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
fileprivate func startDaemon() {
......
......@@ -24,6 +24,7 @@
#import <map>
#import <string>
#import <vector>
struct AVFrame;
@interface Utils : NSObject
......@@ -35,4 +36,6 @@
+ (NSData*)dataFromVectorOfUInt8:(std::vector<uint8_t>)vectorOfUInt8;
+ (std::vector<uint8_t>)vectorOfUInt8FromData:(NSData*)data;
+ (std::vector<std::map<std::string, std::string>>)arrayOfDictionnarisToVectorOfMap:(NSArray*)dictionaries;
+ (UIImage*)convertHardwareDecodedFrameToImage:(const AVFrame*)frame;
+ (AVFrame*)configureHardwareDecodedFrame:(AVFrame*)frame fromImageBuffer: (CVImageBufferRef) image;
@end
......@@ -20,6 +20,9 @@
*/
#import "Utils.h"
extern "C" {
#include <libavutil/frame.h>
}
@implementation Utils
......@@ -105,4 +108,26 @@
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
......@@ -20,6 +20,7 @@
#import <Foundation/Foundation.h>
#import <AVFoundation/AVCaptureOutput.h>
#import <AVFoundation/AVFoundation.h>
@protocol VideoAdapterDelegate;
......@@ -30,9 +31,13 @@
- (void)addVideoDeviceWithName:(NSString*)deviceName withDevInfo:(NSDictionary*)deviceInfoDict;
- (void)registerSinkTargetWithSinkId:sinkId withWidth:(NSInteger)w withHeight:(NSInteger)h;
- (void)removeSinkTargetWithSinkId:(NSString*)sinkId;
- (void)writeOutgoingHardwareDecodedFrameWithBuffer:(CVImageBufferRef)image;
- (void)writeOutgoingFrameWithImage:(UIImage*)image;
- (void)setDecodingAccelerated:(BOOL)state;
- (BOOL)getDecodingAccelerated;
- (void)switchInput:(NSString*)deviceName;
- (void)switchInput:(NSString*)deviceName forCall:(NSString*) callID;
- (void)setEncodingAccelerated:(BOOL)state;
- (BOOL)getEncodingAccelerated;
@end
......@@ -27,6 +27,7 @@
#include <functional>
#include <AVFoundation/AVFoundation.h>
#include <mutex>
#import "Utils.h"
using namespace DRing;
......@@ -36,11 +37,26 @@ struct Renderer
std::condition_variable frameCv;
bool isRendering;
std::mutex renderMutex;
AVSinkTarget avtarget;
SinkTarget target;
SinkTarget::FrameBufferPtr daemonFramePtr_;
int width;
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() {
target.pull = [this](std::size_t bytes) {
std::lock_guard<std::mutex> lk(renderMutex);
......@@ -146,8 +162,13 @@ static id <VideoAdapterDelegate> _delegate;
auto renderer = std::make_shared<Renderer>();
renderer->width = static_cast<int>(w);
renderer->height = static_cast<int>(h);
renderer->bindSinkFunctions();
DRing::registerSinkTarget(_sinkId, renderer->target);
if(self.getDecodingAccelerated) {
renderer->bindAVSinkFunctions();
DRing::registerAVSinkTarget(_sinkId, renderer->avtarget);
} else {
renderer->bindSinkFunctions();
DRing::registerSinkTarget(_sinkId, renderer->target);
}
renderers.insert(std::make_pair(_sinkId, renderer));
}
......@@ -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;
uint8_t* buf = (uint8_t*)DRing::obtainFrame(capacity);
if (buf){
......@@ -196,6 +227,18 @@ static id <VideoAdapterDelegate> _delegate;
DRing::setDecodingAccelerated(state);
}
- (BOOL)getDecodingAccelerated {
return DRing::getDecodingAccelerated();
}
- (void)setEncodingAccelerated:(BOOL)state {
DRing::setEncodingAccelerated(state);
}
- (BOOL)getEncodingAccelerated {
return DRing::getEncodingAccelerated();
}
- (void)switchInput:(NSString*)deviceName {
DRing::switchInput(std::string([deviceName UTF8String]));
}
......
......@@ -57,6 +57,7 @@ internal enum Asset {
internal static let ringLogo = ImageAsset(name: "ring_logo")
internal static let scan = ImageAsset(name: "scan")
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 shareButton = ImageAsset(name: "share_button")
internal static let stopCall = ImageAsset(name: "stop_call")
......
......@@ -291,6 +291,13 @@ internal enum L10n {
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 {
/// Contact added
internal static let contactAdded = L10n.tr("Localizable", "generatedMessage.contactAdded")
......
......@@ -41,7 +41,7 @@ class ContactViewModel: ViewModel, Stateable {
private let accountService: AccountsService
private let conversationService: ConversationsService
private let nameService: NameService
lazy var tableSection : Observable<[SectionModel<String, ContactActions>]> = {
lazy var tableSection: Observable<[SectionModel<String, ContactActions>]> = {
let jamiSettings =
[SectionModel(model: "ProfileInfoCell",
items:
......
......@@ -57,6 +57,8 @@ class ConversationsCoordinator: Coordinator, StateableResponsive, ConversationNa
self.createNewAccount()
case .showDialpad(let inCall):
self.showDialpad(inCall: inCall)
case .showGeneralSettings:
self.showGeneralSettings()
default:
break
}
......@@ -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) {
let conversationViewModel = ConversationViewModel(with: self.injectionBag)
guard let account = accountService.currentAccount else {
......
......@@ -55,6 +55,10 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
@IBOutlet weak var settingsButton: UIButton!
@IBOutlet weak var dialpadButton: UIButton!
@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
var accounPicker = UIPickerView()
......@@ -151,21 +155,22 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
}
}).disposed(by: self.disposeBag)
let imageScanSearch = UIImage(asset: Asset.qrCodeScan) as UIImage?
let scanButton = UIButton(type: UIButtonType.custom) as UIButton
scanButton.setImage(imageScanSearch, for: .normal)
let scanButtonItem = UIBarButtonItem(customView: scanButton)
scanButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
let imageSettings = UIImage(asset: Asset.settings) as UIImage?
let generalSettingsButton = UIButton(type: UIButtonType.system) as UIButton
generalSettingsButton.setImage(imageSettings, for: .normal)
generalSettingsButton.contentMode = .scaleAspectFill
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
self.openScan()
})
.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)
.subscribe(onNext: { [unowned self] in
self.contactPicker.delegate = self
......@@ -178,15 +183,23 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
if let account = currentAccount {
let accountSip = account.type == AccountType.sip
self.navigationItem
.rightBarButtonItem = accountSip ? contactsButtonItem : scanButtonItem
.rightBarButtonItem = accountSip ? nil : settingsButtonItem
self.dialpadButtonShadow.isHidden = !accountSip
self.phoneBookButton.isHidden = !accountSip
self.qrScanButton.isHidden = accountSip
}
}).disposed(by: disposeBag)
self.navigationItem.rightBarButtonItem = scanButtonItem
if let account = self.viewModel.currentAccount,
account.type == AccountType.sip {
self.navigationItem.rightBarButtonItem = contactsButtonItem
self.navigationItem.rightBarButtonItem = settingsButtonItem
if let account = self.viewModel.currentAccount {
if account.type == AccountType.sip {
self.navigationItem.rightBarButtonItem = nil
self.qrScanButton.isHidden = true
self.phoneBookButton.isHidden = false
} else {
self.qrScanButton.isHidden = false
self.phoneBookButton.isHidden = true
}
}
//create accounts button
......@@ -388,13 +401,13 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
self.searchBar.tintColor = UIColor.jamiMain
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.searchBar.layer.shadowOffset = CGSize(width: 0.0, height: 2.5)
self.searchBar.layer.shadowOpacity = 0.2
self.searchBar.layer.shadowRadius = 3
self.searchBar.layer.masksToBounds = false
self.searchBarShadow.layer.shadowColor = UIColor.black.cgColor
self.searchBarShadow.layer.shadowOffset = CGSize(width: 0.0, height: 2.5)
self.searchBarShadow.layer.shadowOpacity = 0.2
self.searchBarShadow.layer.shadowRadius = 3
self.searchBarShadow.layer.masksToBounds = false
//Bind the SearchBar to the ViewModel
self.searchBar.rx.text.orEmpty
......@@ -404,12 +417,14 @@ class SmartlistViewController: UIViewController, StoryboardBased, ViewModelBased
//Show Cancel button
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)
//Hide Cancel button
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)
//Cancel button event
......@@ -566,23 +581,10 @@ extension SmartlistViewController: CNContactPickerDelegate {
}
func setNumberFromContact(contactNumber: String) {
//UPDATE YOUR NUMBER SELECTION LOGIC AND PERFORM ACTION WITH THE SELECTED NUMBER
var contactNumber = contactNumber.replacingOccurrences(of: "-", with: "")
contactNumber = contactNumber.replacingOccurrences(of: "(", with: "")
contactNumber = contactNumber.replacingOccurrences(of: ")", with: "")
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) {
......
......@@ -401,4 +401,8 @@ class SmartlistViewModel: Stateable, ViewModel {
func showDialpad() {
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"/>
<constraint firstItem="XAa-ES-xki" firstAttribute="bottom" secondItem="f8H-BQ-kNd" secondAttribute="bottom" id="JGK-tS-24D"/>
<constraint firstItem="XAa-ES-xki" firstAttribute="leading" secondItem="f8H-BQ-kNd" secondAttribute="leading" id="Kob-5x-TQ2"/>
<constraint firstAttribute="trailing" secondItem="OaU-bt-R2w" secondAttribute="trailing" id="Nht-yV-tfZ"/>
<constraint firstItem="XAa-ES-xki" firstAttribute="top" secondItem="OaU-bt-R2w" secondAttribute="bottom" id="dHt-YB-mRk"/>
</constraints>
<viewLayoutGuide key="safeArea" id="f8H-BQ-kNd"/>
</view>
<extendedEdge key="edgesForExtendedLayout"/>
<connections>
<outlet property="doneButton" destination="NE7-nL-DvW" id="a2F-oe-Fon"/>
<outlet property="settingsTable" destination="XAa-ES-xki" id="Af8-lt-Nsn"/>
<outlet property="tilteLabel" destination="fm1-VN-ppT" id="ycX-1U-ihE"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="1qI-Sw-ka9" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-108" y="137.18140929535232"/>
</scene>
</scenes>
<resources>
<image name="cross" width="40" height="40"/>
</resources>
</document>
/*
* Copyright (C) 2019 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@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 Reusable
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class GeneralSettingsViewController: UIViewController, StoryboardBased, ViewModelBased {
var viewModel: GeneralSettingsViewModel!
let disposeBag = DisposeBag()
@IBOutlet weak var doneButton: UIButton!
@IBOutlet weak var tilteLabel: UILabel!
@IBOutlet weak var settingsTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.applyL10n()
self.setUpTable()
doneButton.rx.tap
.subscribe(onNext: { [unowned self] in
self.dismiss(animated: true, completion: nil)
})
.disposed(by: self.disposeBag)
}
func setUpTable() {
self.settingsTable.estimatedRowHeight = 35
self.settingsTable.rowHeight = UITableViewAutomaticDimension
self.settingsTable.tableFooterView = UIView()
self.setUpDataSource()
}
func applyL10n() {
tilteLabel.text = L10n.GeneralSettings.title
}
private func setUpDataSource() {
let configureCell: (TableViewSectionedDataSource, UITableView, IndexPath, GeneralSettingsSection.Item)
-> UITableViewCell = {
( dataSource: TableViewSectionedDataSource<GeneralSettingsSection>,
tableView: UITableView,
indexPath: IndexPath,
item: GeneralSettingsSection.Item) in
switch dataSource[indexPath] {
case .hardwareAcceleration:
let cell = DisposableCell()
//L10n.CreateProfile.title
//"generalSettings.title" = "General settings";
//"generalSettings.videoAcceleration" = "Enable video acceleration";
cell.textLabel?.text = L10n.GeneralSettings.videoAcceleration
let switchView = UISwitch()
cell.selectionStyle = .none
switchView.frame = CGRect(x: self.view.frame.size.width - 63,
y: cell.frame.size.height * 0.5 - 15,
width: 49, height: 30)
cell.contentView
.addSubview(switchView)
switchView.setOn(self.viewModel.hardwareAccelerationEnabled.value,
animated: false)
self.viewModel.hardwareAccelerationEnabled
.asObservable()
.observeOn(MainScheduler.instance)
.bind(to: switchView.rx.value)
.disposed(by: cell.disposeBag)
switchView.rx.value
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] (enable) in
self?.viewModel.togleHardwareAcceleration(enable: enable)
}).disposed(by: cell.disposeBag)
return cell
case .sectionHeader(let title):
let cell = UITableViewCell()
cell.textLabel?.text = title
cell.backgroundColor = UIColor.jamiNavigationBar
cell.selectionStyle = .none
cell.heightAnchor.constraint(equalToConstant: 35).isActive = true
return cell
}
}
let settingsItemDataSource = RxTableViewSectionedReloadDataSource<GeneralSettingsSection>(configureCell: configureCell)
self.viewModel.generalSettings
.bind(to: settingsTable.rx.items(dataSource: settingsItemDataSource))
.disposed(by: disposeBag)
}