mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-31 10:19:23 +00:00
First functional version of remote provisioning
This commit is contained in:
parent
3df38649f6
commit
912ca99436
10 changed files with 400 additions and 104 deletions
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="4514" systemVersion="13B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
|
||||
<dependencies>
|
||||
<deployment defaultVersion="1072" identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3747"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
|
@ -15,6 +14,7 @@
|
|||
<outlet property="createAccountView" destination="44" id="70"/>
|
||||
<outlet property="externalAccountButton" destination="39" id="133"/>
|
||||
<outlet property="externalAccountView" destination="56" id="72"/>
|
||||
<outlet property="remoteProvisioningButton" destination="Kbn-dL-C5h" id="PPk-DJ-nEb"/>
|
||||
<outlet property="validateAccountView" destination="101" id="112"/>
|
||||
<outlet property="welcomeView" destination="21" id="28"/>
|
||||
</connections>
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
<size key="shadowOffset" width="-1" height="-1"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" id="36" userLabel="createAccountButton" customClass="UILinphoneButton">
|
||||
<rect key="frame" x="33" y="141" width="255" height="50"/>
|
||||
<rect key="frame" x="34" y="153" width="255" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
|
||||
<accessibility key="accessibilityConfiguration" label="Create linphone.org account">
|
||||
<bool key="isElement" value="YES"/>
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" id="38" userLabel="connectAccountButton" customClass="UILinphoneButton">
|
||||
<rect key="frame" x="33" y="205" width="255" height="50"/>
|
||||
<rect key="frame" x="34" y="211" width="255" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
|
||||
<accessibility key="accessibilityConfiguration" label="Sign in linphone.org account">
|
||||
<bool key="isElement" value="YES"/>
|
||||
|
|
@ -126,6 +126,24 @@
|
|||
<action selector="onExternalAccountClick:" destination="-1" eventType="touchUpInside" id="43"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" id="Kbn-dL-C5h" userLabel="remoteProvisioningButton" customClass="UILinphoneButton">
|
||||
<rect key="frame" x="34" y="327" width="255" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
|
||||
<accessibility key="accessibilityConfiguration" label="Sign in SIP account">
|
||||
<bool key="isElement" value="YES"/>
|
||||
</accessibility>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
|
||||
<state key="normal" title="Remote Provisioning" backgroundImage="button_background_default.png">
|
||||
<color key="titleColor" red="0.35686274509999999" green="0.39607843139999999" blue="0.43529411759999997" alpha="1" colorSpace="deviceRGB"/>
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<state key="highlighted" backgroundImage="button_background_over.png">
|
||||
<color key="titleColor" red="0.72549019609999998" green="0.76862745099999996" blue="0.79607843140000001" alpha="1" colorSpace="deviceRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onRemoteProvisioningClick:" destination="-1" eventType="touchUpInside" id="7oT-2f-Rrb"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
|
|
@ -273,6 +291,13 @@
|
|||
<action selector="onSignInClick:" destination="-1" eventType="touchUpInside" id="116"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="105" contentMode="left" text="remote provisioning details" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsFontSizeToFit="NO" id="W9Y-Z9-Rmz" userLabel="remoteProvisionDetails">
|
||||
<rect key="frame" x="40" y="242" width="240" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
|
|
@ -410,6 +435,19 @@
|
|||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" id="7Az-KG-XEW" userLabel="remoteProvisionWaitView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="whiteLarge" id="GG1-Sn-aIT" userLabel="activity">
|
||||
<rect key="frame" x="142" y="221" width="37" height="37"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="color" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</activityIndicatorView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.29999999999999999" colorSpace="calibratedWhite"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="button_background_default.png" width="550" height="101"/>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ extern NSString *const kLinphoneLogsUpdate;
|
|||
extern NSString *const kLinphoneSettingsUpdate;
|
||||
extern NSString *const kLinphoneBluetoothAvailabilityUpdate;
|
||||
extern NSString *const kLinphoneConfiguringStateUpdate;
|
||||
extern NSString *const kLinphoneGlobalStateUpdate;
|
||||
|
||||
typedef enum _NetworkType {
|
||||
network_none = 0,
|
||||
|
|
@ -119,6 +120,7 @@ typedef struct _LinphoneManagerSounds {
|
|||
+ (NSString *)getUserAgent;
|
||||
|
||||
|
||||
- (void)resetLinphoneCore;
|
||||
- (void)startLibLinphone;
|
||||
- (void)destroyLibLinphone;
|
||||
- (BOOL)resignActive;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ NSString *const kLinphoneMainViewChange = @"LinphoneMainViewChange";
|
|||
NSString *const kLinphoneLogsUpdate = @"LinphoneLogsUpdate";
|
||||
NSString *const kLinphoneSettingsUpdate = @"LinphoneSettingsUpdate";
|
||||
NSString *const kLinphoneBluetoothAvailabilityUpdate = @"LinphoneBluetoothAvailabilityUpdate";
|
||||
|
||||
NSString *const kLinphoneConfiguringStateUpdate = @"LinphoneConfiguringStateUpdate";
|
||||
NSString *const kLinphoneGlobalStateUpdate = @"LinphoneGlobalStateUpdate";
|
||||
|
||||
|
||||
extern void libmsilbc_init();
|
||||
|
|
@ -285,7 +286,9 @@ struct codec_name_pref_table codec_pref_table[]={
|
|||
if (lStatus) {
|
||||
[LinphoneLogger logc:LinphoneLoggerError format:"cannot un register route change handler [%ld]", lStatus];
|
||||
}
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:kLinphoneGlobalStateUpdate];
|
||||
|
||||
[photoLibrary release];
|
||||
[pendindCallIdFromRemoteNotif release];
|
||||
[super dealloc];
|
||||
|
|
@ -569,6 +572,53 @@ static void linphone_iphone_call_state(LinphoneCore *lc, LinphoneCall* call, Lin
|
|||
static void linphone_iphone_transfer_state_changed(LinphoneCore* lc, LinphoneCall* call, LinphoneCallState state) {
|
||||
}
|
||||
|
||||
#pragma mark - Global state change
|
||||
|
||||
static void linphone_iphone_global_state_changed(LinphoneCore *lc, LinphoneGlobalState gstate, const char *message) {
|
||||
[(LinphoneManager*)linphone_core_get_user_data(lc) onGlobalStateChanged:gstate withMessage:message];
|
||||
}
|
||||
|
||||
-(void)onGlobalStateChanged:(LinphoneGlobalState)state withMessage:(const char*)message {
|
||||
[LinphoneLogger log:LinphoneLoggerLog format:@"onGlobalStateChanged: %d (message: %s)", state, message];
|
||||
|
||||
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithInt:state], @"state",
|
||||
[NSString stringWithUTF8String:message?message:""], @"message",
|
||||
nil];
|
||||
|
||||
// dispatch the notification asynchronously
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void){
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneGlobalStateUpdate object:self userInfo:dict];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
-(void)globalStateChangedNotificationHandler:(NSNotification*)notif {
|
||||
if( (LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneGlobalOn){
|
||||
[self finishCoreConfiguration];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Configuring status changed
|
||||
|
||||
static void linphone_iphone_configuring_status_changed(LinphoneCore *lc, LinphoneConfiguringState status, const char *message) {
|
||||
[(LinphoneManager*)linphone_core_get_user_data(lc) onConfiguringStatusChanged:status withMessage:message];
|
||||
}
|
||||
|
||||
-(void)onConfiguringStatusChanged:(LinphoneConfiguringState)status withMessage:(const char*)message {
|
||||
[LinphoneLogger log:LinphoneLoggerLog format:@"onConfiguringStatusChanged: %d (message: %s)", status, message];
|
||||
|
||||
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithInt:status], @"state",
|
||||
[NSString stringWithUTF8String:message?message:""], @"message",
|
||||
nil];
|
||||
|
||||
// dispatch the notification asynchronously
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void){
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneConfiguringStateUpdate object:self userInfo:dict];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Registration State Functions
|
||||
|
||||
|
|
@ -840,7 +890,9 @@ static LinphoneCoreVTable linphonec_vtable = {
|
|||
.message_received=linphone_iphone_message_received,
|
||||
.dtmf_received=NULL,
|
||||
.transfer_state_changed=linphone_iphone_transfer_state_changed,
|
||||
.is_composing_received = linphone_iphone_is_composing_received
|
||||
.is_composing_received = linphone_iphone_is_composing_received,
|
||||
.configuring_status = linphone_iphone_configuring_status_changed,
|
||||
.global_state_changed = linphone_iphone_global_state_changed
|
||||
};
|
||||
|
||||
//scheduling loop
|
||||
|
|
@ -859,17 +911,88 @@ static LinphoneCoreVTable linphonec_vtable = {
|
|||
}
|
||||
}
|
||||
|
||||
/** Should be called once per linphone_core_new() */
|
||||
- (void)finishCoreConfiguration {
|
||||
|
||||
//get default config from bundle
|
||||
NSString *zrtpSecretsFileName = [LinphoneManager documentFile:@"zrtp_secrets"];
|
||||
const char* lRootCa = [[LinphoneManager bundleFile:@"rootca.pem"] cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
|
||||
linphone_core_set_user_agent(theLinphoneCore,"LinphoneIPhone",
|
||||
[[[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleVersionKey] UTF8String]);
|
||||
[_contactSipField release];
|
||||
_contactSipField = [[self lpConfigStringForKey:@"contact_im_type_value" withDefault:@"SIP"] retain];
|
||||
|
||||
|
||||
fastAddressBook = [[FastAddressBook alloc] init];
|
||||
|
||||
linphone_core_set_root_ca(theLinphoneCore, lRootCa);
|
||||
// Set audio assets
|
||||
const char* lRing = [[LinphoneManager bundleFile:@"ring.wav"] cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
linphone_core_set_ring(theLinphoneCore, lRing);
|
||||
const char* lRingBack = [[LinphoneManager bundleFile:@"ringback.wav"] cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
linphone_core_set_ringback(theLinphoneCore, lRingBack);
|
||||
const char* lPlay = [[LinphoneManager bundleFile:@"hold.wav"] cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
linphone_core_set_play_file(theLinphoneCore, lPlay);
|
||||
|
||||
linphone_core_set_zrtp_secrets_file(theLinphoneCore, [zrtpSecretsFileName cStringUsingEncoding:[NSString defaultCStringEncoding]]);
|
||||
|
||||
[self setupNetworkReachabilityCallback];
|
||||
|
||||
NSString* path = [LinphoneManager bundleFile:@"nowebcamCIF.jpg"];
|
||||
if (path) {
|
||||
const char* imagePath = [path cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
[LinphoneLogger logc:LinphoneLoggerLog format:"Using '%s' as source image for no webcam", imagePath];
|
||||
linphone_core_set_static_picture(theLinphoneCore, imagePath);
|
||||
}
|
||||
|
||||
/*DETECT cameras*/
|
||||
frontCamId= backCamId=nil;
|
||||
char** camlist = (char**)linphone_core_get_video_devices(theLinphoneCore);
|
||||
for (char* cam = *camlist;*camlist!=NULL;cam=*++camlist) {
|
||||
if (strcmp(FRONT_CAM_NAME, cam)==0) {
|
||||
frontCamId = cam;
|
||||
//great set default cam to front
|
||||
linphone_core_set_video_device(theLinphoneCore, cam);
|
||||
}
|
||||
if (strcmp(BACK_CAM_NAME, cam)==0) {
|
||||
backCamId = cam;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (![LinphoneManager isNotIphone3G]){
|
||||
PayloadType *pt=linphone_core_find_payload_type(theLinphoneCore,"SILK",24000,-1);
|
||||
if (pt) {
|
||||
linphone_core_enable_payload_type(theLinphoneCore,pt,FALSE);
|
||||
[LinphoneLogger logc:LinphoneLoggerWarning format:"SILK/24000 and video disabled on old iPhone 3G"];
|
||||
}
|
||||
linphone_core_enable_video(theLinphoneCore, FALSE, FALSE);
|
||||
}
|
||||
|
||||
[LinphoneLogger logc:LinphoneLoggerWarning format:"Linphone [%s] started on [%s]", linphone_core_get_version(), [[UIDevice currentDevice].model cStringUsingEncoding:[NSString defaultCStringEncoding]]];
|
||||
|
||||
|
||||
// Post event
|
||||
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:theLinphoneCore]
|
||||
forKey:@"core"];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneCoreUpdate
|
||||
object:[LinphoneManager instance]
|
||||
userInfo:dict];
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (void)startLibLinphone {
|
||||
if (theLinphoneCore != nil) {
|
||||
[LinphoneLogger logc:LinphoneLoggerLog format:"linphonecore is already created"];
|
||||
return;
|
||||
}
|
||||
|
||||
//get default config from bundle
|
||||
NSString* factoryConfig = [LinphoneManager bundleFile:[LinphoneManager runningOnIpad]?@"linphonerc-factory~ipad":@"linphonerc-factory"];
|
||||
|
||||
NSString* factoryConfig = [LinphoneManager bundleFile:[LinphoneManager runningOnIpad]?@"linphonerc-factory~ipad":@"linphonerc-factory"];
|
||||
NSString *confiFileName = [LinphoneManager documentFile:@".linphonerc"];
|
||||
NSString *zrtpSecretsFileName = [LinphoneManager documentFile:@"zrtp_secrets"];
|
||||
const char* lRootCa = [[LinphoneManager bundleFile:@"rootca.pem"] cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
|
||||
connectivity = none;
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
//log management
|
||||
|
|
@ -900,27 +1023,10 @@ static LinphoneCoreVTable linphonec_vtable = {
|
|||
theLinphoneCore = linphone_core_new (&linphonec_vtable
|
||||
, [confiFileName cStringUsingEncoding:[NSString defaultCStringEncoding]]
|
||||
, [factoryConfig cStringUsingEncoding:[NSString defaultCStringEncoding]]
|
||||
,self);
|
||||
linphone_core_set_user_agent(theLinphoneCore,"LinphoneIPhone",
|
||||
[[[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleVersionKey] UTF8String]);
|
||||
[_contactSipField release];
|
||||
_contactSipField = [[self lpConfigStringForKey:@"contact_im_type_value" withDefault:@"SIP"] retain];
|
||||
|
||||
,self /* user_data */);
|
||||
|
||||
fastAddressBook = [[FastAddressBook alloc] init];
|
||||
|
||||
linphone_core_set_root_ca(theLinphoneCore, lRootCa);
|
||||
// Set audio assets
|
||||
const char* lRing = [[LinphoneManager bundleFile:@"ring.wav"] cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
linphone_core_set_ring(theLinphoneCore, lRing);
|
||||
const char* lRingBack = [[LinphoneManager bundleFile:@"ringback.wav"] cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
linphone_core_set_ringback(theLinphoneCore, lRingBack);
|
||||
const char* lPlay = [[LinphoneManager bundleFile:@"hold.wav"] cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
linphone_core_set_play_file(theLinphoneCore, lPlay);
|
||||
|
||||
linphone_core_set_zrtp_secrets_file(theLinphoneCore, [zrtpSecretsFileName cStringUsingEncoding:[NSString defaultCStringEncoding]]);
|
||||
|
||||
[self setupNetworkReachabilityCallback];
|
||||
/* The core will call the linphone_iphone_configuring_status_changed callback when the remote provisioning is loaded (or skipped).
|
||||
Wait for this to finish the code configuration */
|
||||
|
||||
// start scheduler
|
||||
mIterateTimer = [NSTimer scheduledTimerWithTimeInterval:0.02
|
||||
|
|
@ -928,7 +1034,8 @@ static LinphoneCoreVTable linphonec_vtable = {
|
|||
selector:@selector(iterate)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
//init audio session
|
||||
|
||||
//init audio session
|
||||
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
||||
BOOL bAudioInputAvailable= audioSession.inputAvailable;
|
||||
NSError* err;
|
||||
|
|
@ -937,6 +1044,8 @@ static LinphoneCoreVTable linphonec_vtable = {
|
|||
selector:@selector(audioSessionInterrupted:)
|
||||
name:AVAudioSessionInterruptionNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(globalStateChangedNotificationHandler:) name:kLinphoneGlobalStateUpdate object:nil];
|
||||
|
||||
|
||||
|
||||
if( ![audioSession setActive:NO error: &err] && err ){
|
||||
|
|
@ -951,55 +1060,13 @@ static LinphoneCoreVTable linphonec_vtable = {
|
|||
[error show];
|
||||
[error release];
|
||||
}
|
||||
|
||||
NSString* path = [LinphoneManager bundleFile:@"nowebcamCIF.jpg"];
|
||||
if (path) {
|
||||
const char* imagePath = [path cStringUsingEncoding:[NSString defaultCStringEncoding]];
|
||||
[LinphoneLogger logc:LinphoneLoggerLog format:"Using '%s' as source image for no webcam", imagePath];
|
||||
linphone_core_set_static_picture(theLinphoneCore, imagePath);
|
||||
}
|
||||
|
||||
/*DETECT cameras*/
|
||||
frontCamId= backCamId=nil;
|
||||
char** camlist = (char**)linphone_core_get_video_devices(theLinphoneCore);
|
||||
for (char* cam = *camlist;*camlist!=NULL;cam=*++camlist) {
|
||||
if (strcmp(FRONT_CAM_NAME, cam)==0) {
|
||||
frontCamId = cam;
|
||||
//great set default cam to front
|
||||
linphone_core_set_video_device(theLinphoneCore, cam);
|
||||
}
|
||||
if (strcmp(BACK_CAM_NAME, cam)==0) {
|
||||
backCamId = cam;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (![LinphoneManager isNotIphone3G]){
|
||||
PayloadType *pt=linphone_core_find_payload_type(theLinphoneCore,"SILK",24000,-1);
|
||||
if (pt) {
|
||||
linphone_core_enable_payload_type(theLinphoneCore,pt,FALSE);
|
||||
[LinphoneLogger logc:LinphoneLoggerWarning format:"SILK/24000 and video disabled on old iPhone 3G"];
|
||||
}
|
||||
linphone_core_enable_video(theLinphoneCore, FALSE, FALSE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[LinphoneLogger logc:LinphoneLoggerWarning format:"Linphone [%s] started on [%s]"
|
||||
,linphone_core_get_version()
|
||||
,[[UIDevice currentDevice].model cStringUsingEncoding:[NSString defaultCStringEncoding]]];
|
||||
|
||||
if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]
|
||||
&& [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
|
||||
//go directly to bg mode
|
||||
[self resignActive];
|
||||
}
|
||||
|
||||
|
||||
// Post event
|
||||
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:theLinphoneCore] forKey:@"core"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneCoreUpdate object:[LinphoneManager instance] userInfo:dict];
|
||||
}
|
||||
|
||||
- (void)destroyLibLinphone {
|
||||
|
|
@ -1026,6 +1093,39 @@ static LinphoneCoreVTable linphonec_vtable = {
|
|||
}
|
||||
}
|
||||
|
||||
- (void) resetLinphoneCore {
|
||||
[self destroyLibLinphone];
|
||||
if (theLinphoneCore != nil) {
|
||||
[LinphoneLogger logc:LinphoneLoggerLog format:"linphonecore is already created"];
|
||||
return;
|
||||
}
|
||||
[LinphoneLogger logc:LinphoneLoggerLog format:"Create linphonecore"];
|
||||
|
||||
NSString* factoryConfig = [LinphoneManager bundleFile:[LinphoneManager runningOnIpad]?@"linphonerc-factory~ipad":@"linphonerc-factory"];
|
||||
NSString *confiFileName = [LinphoneManager documentFile:@".linphonerc"];
|
||||
theLinphoneCore = linphone_core_new (&linphonec_vtable
|
||||
, [confiFileName cStringUsingEncoding:[NSString defaultCStringEncoding]]
|
||||
, [factoryConfig cStringUsingEncoding:[NSString defaultCStringEncoding]]
|
||||
,self /* user_data */);
|
||||
|
||||
/* The core will call the linphone_iphone_configuring_status_changed callback when the remote provisioning is loaded (or skipped).
|
||||
Wait for this to finish the code configuration */
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionInterrupted:) name:AVAudioSessionInterruptionNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(globalStateChangedNotificationHandler:) name:kLinphoneGlobalStateUpdate object:nil];
|
||||
connectivity=none;
|
||||
linphone_core_set_network_reachable(theLinphoneCore, FALSE);
|
||||
// start scheduler
|
||||
mIterateTimer = [NSTimer scheduledTimerWithTimeInterval:0.02
|
||||
target:self
|
||||
selector:@selector(iterate)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
static int comp_call_id(const LinphoneCall* call , const char *callid) {
|
||||
if (linphone_call_log_get_call_id(linphone_call_get_call_log(call)) == nil) {
|
||||
ms_error ("no callid for call [%p]", call);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,10 @@ NSTimer *callSecurityTimer;
|
|||
name:kLinphoneRegistrationUpdate
|
||||
object:nil];
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(globalStateUpdate:)
|
||||
name:kLinphoneConfiguringStateUpdate
|
||||
object:nil];
|
||||
[callQualityImage setHidden: true];
|
||||
[callSecurityImage setHidden: true];
|
||||
|
||||
|
|
@ -104,7 +107,9 @@ NSTimer *callSecurityTimer;
|
|||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:kLinphoneRegistrationUpdate
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:kLinphoneConfiguringStateUpdate
|
||||
object:nil];
|
||||
if(callQualityTimer != nil) {
|
||||
[callQualityTimer invalidate];
|
||||
callQualityTimer = nil;
|
||||
|
|
@ -124,15 +129,23 @@ NSTimer *callSecurityTimer;
|
|||
[self proxyConfigUpdate:config];
|
||||
}
|
||||
|
||||
- (void) globalStateUpdate:(NSNotification*) notif {
|
||||
[self registrationUpdate:notif];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)proxyConfigUpdate: (LinphoneProxyConfig*) config {
|
||||
LinphoneRegistrationState state;
|
||||
LinphoneRegistrationState state = LinphoneRegistrationNone;
|
||||
NSString* message = nil;
|
||||
UIImage* image = nil;
|
||||
LinphoneCore* lc = [LinphoneManager getLc];
|
||||
LinphoneGlobalState gstate = linphone_core_get_global_state(lc);
|
||||
|
||||
if (config == NULL) {
|
||||
if( gstate == LinphoneGlobalConfiguring ){
|
||||
message = NSLocalizedString(@"Fetching remote configuration", nil);
|
||||
} else if (config == NULL) {
|
||||
state = LinphoneRegistrationNone;
|
||||
if(![LinphoneManager isLcReady] || linphone_core_is_network_reachable([LinphoneManager getLc]))
|
||||
message = NSLocalizedString(@"No SIP account configured", nil);
|
||||
|
|
|
|||
|
|
@ -112,7 +112,11 @@ static PhoneMainView* phoneMainViewInstance=nil;
|
|||
selector:@selector(textReceived:)
|
||||
name:kLinphoneTextReceived
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(onGlobalStateChanged:)
|
||||
name:kLinphoneGlobalStateUpdate
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(batteryLevelChanged:)
|
||||
name:UIDeviceBatteryLevelDidChangeNotification
|
||||
object:nil];
|
||||
|
|
@ -135,10 +139,13 @@ static PhoneMainView* phoneMainViewInstance=nil;
|
|||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:kLinphoneRegistrationUpdate
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:kLinphoneTextReceived
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:kLinphoneConfiguringStateUpdate
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIDeviceBatteryLevelDidChangeNotification
|
||||
object:nil];
|
||||
[[UIDevice currentDevice] setBatteryMonitoringEnabled:NO];
|
||||
|
|
@ -229,6 +236,19 @@ static PhoneMainView* phoneMainViewInstance=nil;
|
|||
}
|
||||
}
|
||||
|
||||
- (void)onGlobalStateChanged:(NSNotification*)notif {
|
||||
LinphoneConfiguringState state = [[[notif userInfo] valueForKey:@"state"] integerValue];
|
||||
LinphoneAppDelegate *appDelegate = (LinphoneAppDelegate *)[[UIApplication sharedApplication] delegate];
|
||||
if( state == LinphoneGlobalOn && !(appDelegate.started) ){
|
||||
if( [[LinphoneManager instance] lpConfigBoolForKey:@"show_login_view" forSection:@"app"] ){
|
||||
WizardViewController *controller = DYNAMIC_CAST([[PhoneMainView instance] changeCurrentView:[WizardViewController compositeViewDescription]], WizardViewController);
|
||||
if(controller != nil) {
|
||||
[controller handleRemoteProvisioning];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)callUpdate:(NSNotification*)notif {
|
||||
LinphoneCall *call = [[notif.userInfo objectForKey: @"call"] pointerValue];
|
||||
LinphoneCallState state = [[notif.userInfo objectForKey: @"state"] intValue];
|
||||
|
|
@ -321,8 +341,11 @@ static PhoneMainView* phoneMainViewInstance=nil;
|
|||
}
|
||||
}
|
||||
|
||||
- (void)startUp {
|
||||
if ([[LinphoneManager instance] lpConfigBoolForKey:@"enable_first_login_view_preference"] == true) {
|
||||
- (void)startUp {
|
||||
|
||||
if( linphone_core_get_global_state([LinphoneManager getLc]) != LinphoneGlobalOn ){
|
||||
[self changeCurrentView: [DialerViewController compositeViewDescription]];
|
||||
} else if ([[LinphoneManager instance] lpConfigBoolForKey:@"enable_first_login_view_preference"] == true) {
|
||||
// Change to fist login view
|
||||
[self changeCurrentView: [FirstLoginViewController compositeViewDescription]];
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,13 @@
|
|||
#import <XMLRPCConnectionDelegate.h>
|
||||
#import "UICompositeViewController.h"
|
||||
|
||||
@interface WizardViewController : TPMultiLayoutViewController <UITextFieldDelegate, UICompositeViewDelegate, XMLRPCConnectionDelegate, UIGestureRecognizerDelegate> {
|
||||
@interface WizardViewController : TPMultiLayoutViewController
|
||||
<UITextFieldDelegate,
|
||||
UICompositeViewDelegate,
|
||||
XMLRPCConnectionDelegate,
|
||||
UIGestureRecognizerDelegate,
|
||||
UIAlertViewDelegate>
|
||||
{
|
||||
@private
|
||||
UITextField *activeTextField;
|
||||
UIView *currentView;
|
||||
|
|
@ -44,12 +50,15 @@
|
|||
@property (nonatomic, retain) IBOutlet UIButton *createAccountButton;
|
||||
@property (nonatomic, retain) IBOutlet UIButton *connectAccountButton;
|
||||
@property (nonatomic, retain) IBOutlet UIButton *externalAccountButton;
|
||||
@property (retain, nonatomic) IBOutlet UIButton *remoteProvisioningButton;
|
||||
|
||||
|
||||
@property (nonatomic, retain) IBOutlet UIImageView *choiceViewLogoImageView;
|
||||
|
||||
@property (nonatomic, retain) IBOutlet UITapGestureRecognizer *viewTapGestureRecognizer;
|
||||
|
||||
- (void)reset;
|
||||
- (void)handleRemoteProvisioning;
|
||||
|
||||
- (IBAction)onStartClick:(id)sender;
|
||||
- (IBAction)onBackClick:(id)sender;
|
||||
|
|
@ -59,6 +68,7 @@
|
|||
- (IBAction)onConnectAccountClick:(id)sender;
|
||||
- (IBAction)onExternalAccountClick:(id)sender;
|
||||
- (IBAction)onCheckValidationClick:(id)sender;
|
||||
- (IBAction)onRemoteProvisioningClick:(id)sender;
|
||||
|
||||
- (IBAction)onSignInClick:(id)sender;
|
||||
- (IBAction)onSignInExternalClick:(id)sender;
|
||||
|
|
|
|||
|
|
@ -27,14 +27,15 @@
|
|||
#import <XMLRPCRequest.h>
|
||||
|
||||
typedef enum _ViewElement {
|
||||
ViewElement_Username = 100,
|
||||
ViewElement_Password = 101,
|
||||
ViewElement_Password2 = 102,
|
||||
ViewElement_Email = 103,
|
||||
ViewElement_Domain = 104,
|
||||
ViewElement_Label = 200,
|
||||
ViewElement_Error = 201,
|
||||
ViewElement_Username_Error = 404
|
||||
ViewElement_Username = 100,
|
||||
ViewElement_Password = 101,
|
||||
ViewElement_Password2 = 102,
|
||||
ViewElement_Email = 103,
|
||||
ViewElement_Domain = 104,
|
||||
ViewElement_RemoteProvDetails = 105,
|
||||
ViewElement_Label = 200,
|
||||
ViewElement_Error = 201,
|
||||
ViewElement_Username_Error = 404
|
||||
} ViewElement;
|
||||
|
||||
@implementation WizardViewController
|
||||
|
|
@ -102,6 +103,7 @@ typedef enum _ViewElement {
|
|||
|
||||
[viewTapGestureRecognizer release];
|
||||
|
||||
[_remoteProvisioningButton release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +137,11 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
selector:@selector(registrationUpdateEvent:)
|
||||
name:kLinphoneRegistrationUpdate
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(configuringUpdate:)
|
||||
name:kLinphoneConfiguringStateUpdate
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillShow:)
|
||||
name:UIKeyboardWillShowNotification
|
||||
|
|
@ -149,16 +155,19 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:kLinphoneRegistrationUpdate
|
||||
object:nil];
|
||||
|
||||
|
||||
name:kLinphoneRegistrationUpdate
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:kLinphoneConfiguringStateUpdate
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIKeyboardWillShowNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:UIKeyboardWillHideNotification
|
||||
object:nil];
|
||||
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
|
|
@ -191,6 +200,38 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
}
|
||||
}
|
||||
|
||||
- (void)handleRemoteProvisioning {
|
||||
|
||||
[self resetTextFields];
|
||||
|
||||
LinphoneProxyConfig* default_conf = linphone_core_create_proxy_config([LinphoneManager getLc]);
|
||||
const char* identity = linphone_proxy_config_get_identity(default_conf);
|
||||
if( identity ){
|
||||
LinphoneAddress* default_addr = linphone_address_new(identity);
|
||||
if( default_addr ){
|
||||
const char* domain = linphone_address_get_domain(default_addr);
|
||||
|
||||
if( domain && strlen(domain) > 0){
|
||||
UITextField* domainfield = [WizardViewController findTextField:ViewElement_Domain view:externalAccountView];
|
||||
[domainfield setText:[NSString stringWithUTF8String:domain]];
|
||||
}
|
||||
}
|
||||
}
|
||||
[self changeView:externalAccountView back:FALSE animation:TRUE];
|
||||
|
||||
linphone_proxy_config_destroy(default_conf);
|
||||
|
||||
}
|
||||
|
||||
- (void)resetTextFields {
|
||||
[WizardViewController cleanTextField:welcomeView];
|
||||
[WizardViewController cleanTextField:choiceView];
|
||||
[WizardViewController cleanTextField:createAccountView];
|
||||
[WizardViewController cleanTextField:connectAccountView];
|
||||
[WizardViewController cleanTextField:externalAccountView];
|
||||
[WizardViewController cleanTextField:validateAccountView];
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[self clearProxyConfig];
|
||||
[[LinphoneManager instance] lpConfigSetBool:FALSE forKey:@"pushnotification_preference"];
|
||||
|
|
@ -210,12 +251,7 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
[[LinphoneManager instance] lpConfigSetString:@"" forKey:@"stun_preference"];
|
||||
linphone_core_set_stun_server(lc, NULL);
|
||||
linphone_core_set_firewall_policy(lc, LinphonePolicyNoFirewall);
|
||||
[WizardViewController cleanTextField:welcomeView];
|
||||
[WizardViewController cleanTextField:choiceView];
|
||||
[WizardViewController cleanTextField:createAccountView];
|
||||
[WizardViewController cleanTextField:connectAccountView];
|
||||
[WizardViewController cleanTextField:externalAccountView];
|
||||
[WizardViewController cleanTextField:validateAccountView];
|
||||
[self resetTextFields];
|
||||
if ([[LinphoneManager instance] lpConfigBoolForKey:@"hide_wizard_welcome_view_preference"] == true) {
|
||||
[self changeView:choiceView back:FALSE animation:FALSE];
|
||||
} else {
|
||||
|
|
@ -286,6 +322,7 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
// [ Create Btn ]
|
||||
// [ Connect Btn ]
|
||||
// [ External Btn ]
|
||||
// [ Remote Prov ]
|
||||
|
||||
BOOL show_logo = [[LinphoneManager instance] lpConfigBoolForKey:@"show_wizard_logo_in_choice_view_preference"];
|
||||
BOOL show_extern = ![[LinphoneManager instance] lpConfigBoolForKey:@"hide_wizard_custom_account"];
|
||||
|
|
@ -437,9 +474,9 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
[self setDefaultSettings:proxyCfg];
|
||||
}
|
||||
linphone_proxy_config_enable_register(proxyCfg, true);
|
||||
linphone_core_add_auth_info([LinphoneManager getLc], info);
|
||||
linphone_core_add_proxy_config([LinphoneManager getLc], proxyCfg);
|
||||
linphone_core_set_default_proxy([LinphoneManager getLc], proxyCfg);
|
||||
linphone_core_add_auth_info([LinphoneManager getLc], info);
|
||||
}
|
||||
|
||||
- (NSString*)identityFromUsername:(NSString*)username {
|
||||
|
|
@ -614,6 +651,23 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
[self checkAccountValidation:identity];
|
||||
}
|
||||
|
||||
- (IBAction)onRemoteProvisioningClick:(id)sender {
|
||||
UIAlertView* remoteInput = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Enter provisioning URL", @"")
|
||||
message:@""
|
||||
delegate:self
|
||||
cancelButtonTitle:NSLocalizedString(@"Cancel", @"")
|
||||
otherButtonTitles:NSLocalizedString(@"Fetch", @""), nil];
|
||||
remoteInput.alertViewStyle = UIAlertViewStylePlainTextInput;
|
||||
|
||||
UITextField* prov_url = [remoteInput textFieldAtIndex:0];
|
||||
prov_url.keyboardType = UIKeyboardTypeURL;
|
||||
prov_url.text = [[LinphoneManager instance] lpConfigStringForKey:@"config-uri" forSection:@"misc"];
|
||||
prov_url.placeholder = @"URL";
|
||||
|
||||
[remoteInput show];
|
||||
[remoteInput release];
|
||||
}
|
||||
|
||||
- (IBAction)onSignInExternalClick:(id)sender {
|
||||
NSString *username = [WizardViewController findTextField:ViewElement_Username view:contentView].text;
|
||||
NSString *password = [WizardViewController findTextField:ViewElement_Password view:contentView].text;
|
||||
|
|
@ -719,6 +773,55 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
[LinphoneUtils findAndResignFirstResponder:currentView];
|
||||
}
|
||||
|
||||
#pragma mark - UIAlertViewDelegate
|
||||
|
||||
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
|
||||
if (buttonIndex == 1) { /* fetch */
|
||||
NSString* url = [alertView textFieldAtIndex:0].text;
|
||||
if( [url length] > 0 ){
|
||||
// missing prefix will result in http:// being used
|
||||
if( [url rangeOfString:@"://"].location == NSNotFound )
|
||||
url = [NSString stringWithFormat:@"http://%@", url];
|
||||
|
||||
[LinphoneLogger log:LinphoneLoggerLog format:@"Should use remote provisioning URL %@", url];
|
||||
linphone_core_set_provisioning_uri([LinphoneManager getLc], [url UTF8String]);
|
||||
|
||||
[waitView setHidden:false];
|
||||
[[LinphoneManager instance] resetLinphoneCore];
|
||||
}
|
||||
} else {
|
||||
[LinphoneLogger log:LinphoneLoggerLog format:@"Canceled remote provisioning"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configuringUpdate:(NSNotification *)notif {
|
||||
LinphoneConfiguringState status = (LinphoneConfiguringState)[[notif.userInfo valueForKey:@"state"] integerValue];
|
||||
|
||||
[waitView setHidden:true];
|
||||
|
||||
switch (status) {
|
||||
case LinphoneConfiguringSuccessful:
|
||||
[self handleRemoteProvisioning];
|
||||
break;
|
||||
case LinphoneConfiguringFailed:
|
||||
{
|
||||
NSString* error_message = [notif.userInfo valueForKey:@"message"];
|
||||
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Provisioning Load error", nil)
|
||||
message:error_message
|
||||
delegate:nil
|
||||
cancelButtonTitle:NSLocalizedString(@"OK", nil)
|
||||
otherButtonTitles: nil];
|
||||
[alert show];
|
||||
[alert release];
|
||||
break;
|
||||
}
|
||||
|
||||
case LinphoneConfiguringSkipped:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Event Functions
|
||||
|
||||
|
|
@ -914,4 +1017,8 @@ static UICompositeViewDescription *compositeDescription = nil;
|
|||
return YES;
|
||||
}
|
||||
|
||||
- (void)viewDidUnload {
|
||||
[self setRemoteProvisioningButton:nil];
|
||||
[super viewDidUnload];
|
||||
}
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -302,3 +302,4 @@
|
|||
"Enter an address" = "Enter SIP address or phone number...";
|
||||
"%@ is composing..." = "%@ is composing...";
|
||||
"Resend" = "Resend";
|
||||
"Fetching remote configuration" = "Fetching remote configuration";
|
||||
|
|
|
|||
|
|
@ -302,3 +302,4 @@
|
|||
"Enter an address" = "Entrez une addresse SIP ou un numéro de téléphone...";
|
||||
"%@ is composing..." = "%@ rédige un message...";
|
||||
"Resend" = "Renvoyer";
|
||||
"Fetching remote configuration" = "Récupération de la conf. distante";
|
||||
|
|
|
|||
|
|
@ -302,3 +302,4 @@
|
|||
"Enter an address" = "Введите адрес SIP или номер телефона...";
|
||||
"%@ is composing..." = "%@ is composing...";
|
||||
"Resend" = "Resend";
|
||||
"Fetching remote configuration" = "Fetching remote configuration";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue