From f973a695d8cdce2f31826dda806306aca6081e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Tue, 24 Oct 2017 10:40:39 +0200 Subject: [PATCH] [RN] Add audio route picker Due to the difference in nature, the iOS and Android implementations are completely different: iOS: MPVolumeView is used, which allows us to place a button which will launch a native route picker provided by iOS itself. This view is different depending on the iOS version, with the iOS 11 version being more complete. Android: A completely custom component is used, which displays a bottom sheet with the device categories, not devices individually. This is akin to the sheet in the builtin dialer. --- .../org/jitsi/meet/sdk/AudioModeModule.java | 251 ++++++- .../meet/sdk/BluetoothHeadsetMonitor.java | 2 +- fonts/jitsi.eot | Bin 7552 -> 8684 bytes fonts/jitsi.svg | 3 + fonts/jitsi.ttf | Bin 7396 -> 8528 bytes fonts/jitsi.woff | Bin 7472 -> 8604 bytes fonts/selection.json | 393 ++++++----- ios/sdk/sdk.xcodeproj/project.pbxproj | 4 + ios/sdk/src/MPVolumeViewManager.m | 62 ++ react/features/base/font-icons/jitsi.json | 628 ++++++++++-------- .../components/AudioRoutePickerDialog.js | 178 +++++ .../mobile/audio-mode/components/index.js | 3 + react/features/mobile/audio-mode/index.js | 2 + .../toolbox/components/AudioRouteButton.js | 160 +++++ .../toolbox/components/Toolbox.native.js | 10 + 15 files changed, 1238 insertions(+), 458 deletions(-) create mode 100644 ios/sdk/src/MPVolumeViewManager.m create mode 100644 react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js create mode 100644 react/features/mobile/audio-mode/components/index.js create mode 100644 react/features/toolbox/components/AudioRouteButton.js diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java index 4ffe19e8a..54d34f170 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java @@ -21,6 +21,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Build; @@ -28,13 +29,19 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** * Module implementing a simple API to select the appropriate audio device for a @@ -102,10 +109,53 @@ class AudioModeModule extends ReactContextBaseJavaModule { private final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); + /** + * {@link Runnable} for running audio device detection the main thread. + * This is only used on Android >= M. + */ + private final Runnable onAudioDeviceChangeRunner = new Runnable() { + @TargetApi(Build.VERSION_CODES.M) + @Override + public void run() { + Set devices = new HashSet<>(); + AudioDeviceInfo[] deviceInfos + = audioManager.getDevices(AudioManager.GET_DEVICES_ALL); + + for (AudioDeviceInfo info: deviceInfos) { + switch (info.getType()) { + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + devices.add(DEVICE_BLUETOOTH); + break; + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: + devices.add(DEVICE_EARPIECE); + break; + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + devices.add(DEVICE_SPEAKER); + break; + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + devices.add(DEVICE_HEADPHONES); + break; + } + } + + availableDevices = devices; + Log.d(TAG, "Available audio devices: " + + availableDevices.toString()); + + // Reset user selection + userSelectedDevice = null; + + if (mode != -1) { + updateAudioRoute(mode); + } + } + }; + /** * {@link Runnable} for running update operation on the main thread. */ - private final Runnable mainThreadRunner + private final Runnable updateAudioRouteRunner = new Runnable() { @Override public void run() { @@ -120,6 +170,30 @@ class AudioModeModule extends ReactContextBaseJavaModule { */ private int mode = -1; + /** + * Audio device types. + */ + private static final String DEVICE_BLUETOOTH = "BLUETOOTH"; + private static final String DEVICE_EARPIECE = "EARPIECE"; + private static final String DEVICE_HEADPHONES = "HEADPHONES"; + private static final String DEVICE_SPEAKER = "SPEAKER"; + + /** + * List of currently available audio devices. + */ + private Set availableDevices = Collections.emptySet(); + + /** + * Currently selected device. + */ + private String selectedDevice; + + /** + * User selected device. When null the default is used depending on the + * mode. + */ + private String userSelectedDevice; + /** * Initializes a new module instance. There shall be a single instance of * this module throughout the lifetime of the application. @@ -136,6 +210,20 @@ class AudioModeModule extends ReactContextBaseJavaModule { // Setup runtime device change detection. setupAudioRouteChangeDetection(); + + // Do an initial detection on Android >= M. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + mainThreadHandler.post(onAudioDeviceChangeRunner); + } else { + // On Android < M, detect if we have an earpiece. + PackageManager pm = reactContext.getPackageManager(); + if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + availableDevices.add(DEVICE_EARPIECE); + } + + // Always assume there is a speaker. + availableDevices.add(DEVICE_SPEAKER); + } } /** @@ -155,6 +243,36 @@ class AudioModeModule extends ReactContextBaseJavaModule { return constants; } + /** + * Gets the list of available audio device categories, i.e. 'bluetooth', + * 'earpiece ', 'speaker', 'headphones'. + * + * @param promise a {@link Promise} which will be resolved with an object + * containing a 'devices' key with a list of devices, plus a + * 'selected' key with the selected one. + */ + @ReactMethod + public void getAudioDevices(final Promise promise) { + mainThreadHandler.post(new Runnable() { + @Override + public void run() { + WritableMap map = Arguments.createMap(); + map.putString("selected", selectedDevice); + WritableArray devices = Arguments.createArray(); + for (String device : availableDevices) { + if (mode == VIDEO_CALL && device.equals(DEVICE_EARPIECE)) { + // Skip earpiece when in video call mode. + continue; + } + devices.pushString(device); + } + map.putArray("devices", devices); + + promise.resolve(map); + } + }); + } + /** * Gets the name for this module to be used in the React Native bridge. * @@ -168,9 +286,81 @@ class AudioModeModule extends ReactContextBaseJavaModule { /** * Helper method to trigger an audio route update when devices change. It * makes sure the operation is performed on the main thread. + * + * Only used on Android >= M. */ void onAudioDeviceChange() { - mainThreadHandler.post(mainThreadRunner); + mainThreadHandler.post(onAudioDeviceChangeRunner); + } + + /** + * Helper method to trigger an audio route update when Bluetooth devices are + * connected / disconnected. + * + * Only used on Android < M. Runs on the main thread. + */ + void onBluetoothDeviceChange() { + if (bluetoothHeadsetMonitor.isHeadsetAvailable()) { + availableDevices.add(DEVICE_BLUETOOTH); + } else { + availableDevices.remove(DEVICE_BLUETOOTH); + } + + if (mode != -1) { + updateAudioRoute(mode); + } + } + + /** + * Helper method to trigger an audio route update when a headset is plugged + * or unplugged. + * + * Only used on Android < M. + */ + void onHeadsetDeviceChange() { + mainThreadHandler.post(new Runnable() { + @Override + public void run() { + // XXX: isWiredHeadsetOn is not deprecated when used just for + // knowing if there is a wired headset connected, regardless of + // audio being routed to it. + //noinspection deprecation + if (audioManager.isWiredHeadsetOn()) { + availableDevices.add(DEVICE_HEADPHONES); + } else { + availableDevices.remove(DEVICE_HEADPHONES); + } + + if (mode != -1) { + updateAudioRoute(mode); + } + } + }); + } + + /** + * Sets the user selected audio device as the active audio device. + * + * @param device the desired device which will become active. + */ + @ReactMethod + public void setAudioDevice(final String device) { + mainThreadHandler.post(new Runnable() { + @Override + public void run() { + if (!availableDevices.contains(device)) { + Log.d(TAG, "Audio device not available: " + device); + userSelectedDevice = null; + return; + } + + if (mode != -1) { + Log.d(TAG, "User selected device set to: " + device); + userSelectedDevice = device; + updateAudioRoute(mode); + } + } + }); } /** @@ -278,7 +468,7 @@ class AudioModeModule extends ReactContextBaseJavaModule { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "Wired headset added / removed"); - onAudioDeviceChange(); + onHeadsetDeviceChange(); } }; context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter); @@ -302,6 +492,8 @@ class AudioModeModule extends ReactContextBaseJavaModule { audioManager.abandonAudioFocus(null); audioManager.setSpeakerphoneOn(false); setBluetoothAudioRoute(false); + selectedDevice = null; + userSelectedDevice = null; return true; } @@ -318,31 +510,42 @@ class AudioModeModule extends ReactContextBaseJavaModule { return false; } - boolean useSpeaker = (mode == VIDEO_CALL); + boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH); + boolean earpieceAvailable = availableDevices.contains(DEVICE_EARPIECE); + boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // On Android >= M we use the AudioDeviceCallback API, so turn on - // Bluetooth SCO from the start. - if (audioManager.isBluetoothScoAvailableOffCall()) { - audioManager.startBluetoothSco(); - } + // Pick the desired device based on what's available and the mode. + String audioDevice; + if (bluetoothAvailable) { + audioDevice = DEVICE_BLUETOOTH; + } else if (headsetAvailable) { + audioDevice = DEVICE_HEADPHONES; + } else if (mode == AUDIO_CALL && earpieceAvailable) { + audioDevice = DEVICE_EARPIECE; } else { - // On older Android versions we must set the Bluetooth route - // manually. Also disable the speaker in that case. - setBluetoothAudioRoute( - bluetoothHeadsetMonitor.isHeadsetAvailable()); - if (bluetoothHeadsetMonitor.isHeadsetAvailable()) { - useSpeaker = false; - } + audioDevice = DEVICE_SPEAKER; } - // XXX: isWiredHeadsetOn is not deprecated when used just for knowing if - // there is a wired headset connected, regardless of audio being routed - // to it. - audioManager.setSpeakerphoneOn( - useSpeaker - && !(audioManager.isWiredHeadsetOn() - || audioManager.isBluetoothScoOn())); + // Consider the user's selection + if (userSelectedDevice != null + && availableDevices.contains(userSelectedDevice)) { + audioDevice = userSelectedDevice; + } + + // If the previously selected device and the current default one + // match, do nothing. + if (selectedDevice != null && selectedDevice.equals(audioDevice)) { + return true; + } + + selectedDevice = audioDevice; + Log.d(TAG, "Selected audio device: " + audioDevice); + + // Turn bluetooth on / off + setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH)); + + // Turn speaker on / off + audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER)); return true; } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java index 519809ec8..d4b04a941 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java @@ -71,7 +71,7 @@ class BluetoothHeadsetMonitor { headsetAvailable = (headset != null) && !headset.getConnectedDevices().isEmpty(); - audioModeModule.onAudioDeviceChange(); + audioModeModule.onBluetoothDeviceChange(); } }; diff --git a/fonts/jitsi.eot b/fonts/jitsi.eot index 30085b44ebc54acdee4e01aff603ed98d184e8f4..38ada4e6042ac710af680a4266c599b1b9a86571 100755 GIT binary patch delta 3883 zcma)9eQX=$8Gm1&?Tce4{yd+*WBU@H?YOpM`)r&9r*rCj%uXA+)a_{FlBR{y(9+SU zl+j@$MIg~4jolOti9aTg*mTXLO&L18qyX=k9fi zLnzzH=X*b%_r2$Rp5OC(o@)%fJF0y*fY5Z{Ic-)w{=uo$=XO3^Zz6<#0{4CMC+AK} zzQe~6!XD^<^MScDr{QUW{`cY8@xVim-QR0?`Mc1+jF9KR@ndsGjc@$w6@+{sgR;kA zpyQ}og8l^bdyk)7Jon2eV}|}i(0~1*hv(Ji4 zTc)?Ky+gfYd&m2i(5>}bx8!x7nzO-@<1K5KyeuozI; z#H30ird87?BA|tx^6G1zZCWnK$1xugGT9-VvNJY2YuD1)#rk{`k;smk z&Y|y};w=t`WqE1oYCaE7i^E``)H+pWMM23b)6$qMj{naQPTmgdYfpW}0^Ij!+w#-^qD0v0kMp3af3Dl|QP?(*diqgRIC zdvEwk6o1R-5BgJ9)^1xlqSNbjcw+C1D$|R5FJJx}-bIA)uGf-}43B&zmEx=QM0`KL za>Sl;IWsKSwSgp1riuDNiIidm#%$B}V{=-_DCg&bbT_88UYs^_!H{6ifRS=p71}d4 z_Mp#K78(-E-jmH03c2h(EGsoK6`$|Hu@co(9@87wG8J5{l39>6foOF@DhoJ9b`X20 z53?MaMPEfvqZdGnIHqwo@k4f`<(YI!d8?2O@zCHYo1&9AqQ1+jOypAFyg24UWPC}T zck=xHL}K8??Cgbb7^h-qZIorz_ zbm8!Y+1V3&mq(6U1ltB%DHe}-Jz`0dG8c>U9uKye9}GpYEL9ViG>I_}%A+~-C|W{m zpIDKocpF5DnrVg@ld&n5-aHjCw0J{vAzrIph=e2Ii?!K>1*IL1T%4^fR7T;&UN5LW zS}8-@3m3G-XSBx#Wa-)oA`NYjChcXf_sD3mQYns-rK4o2$19rXN9zsL0KYeo9=TBo zw9bS$q2>Q{%ki6i`p)rS+!{HmMp=mQ{Gi!9LqI`X9Niuzb+xY69!X^t%2cXm^B~X1 z2v({DKg*wl55$+l60Y=iI(4;pBr>i-YO(u|44P=u^{~N?DNa1G{&h2?heeGrOn0 z+}3`*Qs{9y13I_@PKW>QDOHWgXDW?t!u8fl1T27%q()Q~Y0&^Uf)hA~V_SA*TG|CR zxrm@0VhCa|O-`U{HdY!KWe6r&@9ug1fTwPnEERKQBHip z;5W1zUVPQiXCU?_Rutbi1i}jZ$!K+>k>-LK`9^9a0YIz3v=lO+9VjnmyMkM{t96C8 zZg;VZtKVTefQ^Kr%6<+7HUdivn;^;N*lx%j3ddMCt=q~ZBqSI<2OCIZBZXPF+v6%2 zjRXPP2I`UoIYL@#xZSgb!uUXqFSv&T1YD}>XAFX@jOixOOBABfkK$m$G=VYAZ~}3< zklw<8w31i=&~`cbsBTW!m{KAx%w!VvM2WFYSS=%wNGukKj99EUTVbe*=8^*yr?cbY zMcU=83?y^OFrPP>OvZdTJPr8;2&F9Cf`5e`+7%l9KUBhIg(<|hyB)6E+H6xsG#c%$ zJF6k)jNS+ZIacOc3H5^C1%#}a<{_nSGlux6F|3EM-8``PvN2R?Kbl@YhN-gP$7tjF zMh7`w-?%?(?RO|=j6yDN0+E=I2ZiB`#Pwbr5J=J%yG-u-(BML-Z+)gOv@kfdajDU$ z!Vfpbj?Rz6jU|0&)bH=FR)P4Uo%$7dw$X&S`;Wo=7NT{d92%5~R*8wN)xxL2r-Z+f z3+r-4rL3@;H7Y~^M{v~MZsXNgM$Rri7~ zqF^CX_qAA-76`|+j>21^`ZD`)Upi;zIaPIMF1LrZ+u1$2>=)uZpEt@-<3ouPgURIJ z`kK^iO4QkIE;wH(?Alehd!FOE4|I9l<#;@uj>pSxclXB4{6Pmk;5ZlkKh(lCoSlz_~&3mpW-#5b+1_y=pks04AF3+=YNA(`Co>-o=&uV;TUA?~*gTXZ_k z<>D3>gKih6)3FT0iZ9vLJm4LGv6c%2Lp)Aq(}a2nD#Y_lR^#=Qps;y`32ICx-fR-D zGd<$R%#Z6Hh}&LR{5jPMXXT(oewNr0<8&FiOQkU*Tt?Lbp_U%(36-8@cg`Hpp z9*rVYSL$sjz~$oG`oHnTYCpjjWEJ8?xu78VmymNiQIq!cMk(3Y8aX*uA5uLmAMVUfqqZY@r>xOLaSg*xGR3BN0dWZLSbR04o z$-wZj$axFTkcIM*I&bG3fa~frjyJGp_0$LBhmFJkKzg+-ftsPw+0Cc8=*r==xZ-lT zPy?pn#*%s`QmGe_Recr}!p)5bPoLtBb>#Hsw#CK^_sd@y{NvbzOKn??$w;v ztY~A}3GFHEvi8q(fxbvz>qvGy)p11^)JeK#C+ZA#?(SUHJM_c)NAy?qztg{K@EPtk fTXI=ff`&s;7(JdC) delta 2769 zcmaJ@YfKy26~1?7JY!?WhOr0RVDMwW1I8Hecx>zt!!F^GwRr}Ns|6C`yxBaI5W1^8 zuvFM+L$*yADN5S>*s7IUc~dPFRn=yrD6OLHR%)fKwChSyn>J}1DXXozsYuEISk<&O635nRECyg}35hxnL=`ma`nLu(tIMH%{to`kFq8x7qkBYybM_1PI<{Fwl&S3N;S* zwpk~28pdEKOrQ7&YgG;1p^h^D^gGcQI*!hwB?uS595xoih1(&x9E?*+*egK}5^XV3 z>RCw;VH+$h9)57~=HcNh?FV_qvSv-r?}@6{*x;7=Oe?mO9rF6{;NpksAK0dV1=XV= zbO6nuWymd9O$m^K=m|gp;*>0Sj4)A66>FITuT*ySDnT2Fx zArp&b7Ss~$KKIbYTZcF7L|%#QSx6Qg-zJVx^*!y^=Tay|=ja?Vp-R+*P;D?A3D6>h z^%9MudU19B;(GY zo9Epvu5EqPWYH&-%obhdp7|mQP?KX2okA=km=G0F5UtRiLOI;)^@zlwTh$bm&)&%9 zajd>K?DWL?{i}Ipva3#v?`*bg-7(q}JG3*3^SN9ekI-T?;q&*$Jat`@N=tgXU*qiv zR7hQu1f}XWecl~`U>=qeEG%cl@D)AYCV&XQZ#|g0)zGLtaqhy79T(1>P-hIj%7pB2 z$gefACY{d29%kWVlT3`oIBzObZyQ!=8TG2Sw>UFK%7g*Fae{03RipsTm2E1ej1#y- zXY*T}<>gL{EPk7=1e524KRr4u(LA5H*4K9}!Sl2;1RdV@hP_DKz(>M?G3&aL3C-Y`C(B_yktOfD9%1(~R~Oto54K~f_1?oDd|CM_JkjaJL~ZBp|~%;{;I&(2Bf%!Z?ptDGP<6T|$|Kr5^UycTFfG zp~$@?XP+S;T$Sh24anYGnaV zsUj+}B5R;_#nM6LvJI!E6RRVM=~HX(XR|5z;%xWcOb-m46MP*FsZ>M9oKLtxOxZjb z_U{8D1lyn_Gm;=MP!e!60mC{E7BTXSAkAeO3K39{j&I0KBt&DBd)`dkXw-Sq_>`s! zbhUEHGEE&6kOO&OlSSg;lsHYoOJ<;lvT9r_N6I9TN_89^Xlxuf@=BmT>U2i?nhwXB zn`5hYa`}9Yo;7x5&TQX)I@4`TZ;2$vWqCXiHm*I4zHY;5+v`2~)sZ}K&z#kU$JDFV z9lEFGRKK)FwY$f*{byv`*e+GEwUzQ>NSvARC?ana1S`Dt4ci@iE2IbT6x|-Lgqy>1 zkzdGZ-enKd(XOcaZM%ndNpAHQcBlHleshNfItTwM@TSW|G>c_2=^lkP(MlQ`luFU> zZ>Er?N@324WC8-Xnc<=)JvYCkM^+=;BT%314Xz<_aOw1f!!dDsY4GV2 zmEE`^)^g9gl2@tnrqNS9-Ho%OO@#-LgqeK6OrO$Oq$%8CJ&;=AUqo$@Qd`ZK&FkXKed zbNmTAme2Da>@u}|h4c*J3G~@NN`JK>)laG;_07fkf)Tb!wu-Va^6;-K$8;cynL%b| z7QL#rIlp%(jZUB+qDT0r)c2{+=^A>LzChn+D5i!PVQw?`G)PmYnbF*388*&dWbbjM z+%R`f%W9jo!`gG&pXoxnwCc JcDdfg{{_c~fUW=l diff --git a/fonts/jitsi.svg b/fonts/jitsi.svg index f06e4c6c2..bb75dba1f 100755 --- a/fonts/jitsi.svg +++ b/fonts/jitsi.svg @@ -9,10 +9,13 @@ + + + diff --git a/fonts/jitsi.ttf b/fonts/jitsi.ttf index 4f1294da8c7b048cc191a6a3b97ee07a9fb15196..62aed45c0132342e632227fafbfb97a6327efc0c 100755 GIT binary patch delta 3858 zcma)9e{37|75{#H_BpmM{(V0GI6Jn_@%dM6$M)HE5*%>qG?|?yl+^7?V@T6LY1&d~ zRLYNGOH}YjJ89cZ+1L+h0%>DMCr#=mRbrYl)>TMj(>7HWVnZusAciJ}CMKk5N%wr` zfI}$bl=FSx_xrwg@B7~8ecsF8ihn3K5k?3ZPzw<#J#$woWBeo8hM8Z$eedFlg;SF6 zo}&n17UXZ;}0(0vG9|fmwtxOz)qMrdSc=1DO5tg2Kfgd`%f;M zSbF1YU-=QpYw(eirye}L0*1mBNO#r|p(ek+_W1s@Ah^LU0_ovA{_DScv+}0&mig^# z?-1|U-|_xEd~^Ng&Ca$@t=V7=%WL@NTLT5(n(q+%kvr!mT_3sr$xvboyTt3PTKpEC zZAQTW2jjzxgDI3S46+%A7u!iL$R{x$7IOI!oN>^02jd{M*vnJC5o(<(w<8*z^!b>EPNSwMd7)UmdhQ%3IIWRdNbl0YY1*hw#E6)9 zP!(@#AWn6nMMy}6G{}YmsACmRTj)Yo%V;@^mM`!H#*)?eFl{Hbd4`WwG-+zDfN(_J7=MVZbHpXGUcvxpJ=8i()XL? zsw$ML)h(7`o=+%PsVn$d{mh(dB%{&Ya@wg0iULj`P#v;Lf~>Nk0W^ZpU?yJ(_X1e! z!?qv^xt-1AgV!n~3}UiNp-VJofZ#u2vN~_klfoP6q>l;ORZn zU+x*dS}P5>TmcKF!%`siS(p~EU zn*^nx9dZbAFsqzE)oxvEwefH?6v~@TiVN4jD zlG->tM_4Vz@L)^{m)%}TrG%kGVwc0A3H$xY{(f6$N_<=UIvx?%`rQ3nzA0Ys+d&nJ zPgW{ZQNNl zWzU`iJ!A1NdRLCr&}IuA=chRXMH#qhK2BTAw1&huzHLKyGZIr^(;jORxfa03B zcpV@{LNWP$P8l%*qZKyskuNYtC>t`p*f!nREh0)b(0l=2AWe*w<~<(PT{4-J;BT8~ zN)nU@>0-;{nJ<-g4>kCbXEdPTN>%@iNzm=Uxed$`gGBVBB)~bVAXrOtf)ZUJyM_F; za!mm&J34hobz{;_S5iq~E|+SiDztslW*v)06NzYa%xb&Q%|KPGkRGbJTzwZVXx*;b zP`Z$g@I|xPY$`?~vrt-qORB<6_?4NV*PziaU=p^-Bym`<0VxJa+bu2JX5hwq12pYb z!OuYMi`XiD*XWHBP=h;METIs9LqN!ewLFyEZ5|WXHntv9_Q>P=y)_x$2cla~XK z#h|9Qfl$ndMG-i#f&(uO2ukr5A2NBGBg4z#!S%Vp@bd7;#*3|19sY1@;>hA|xUpo= z$Nc_yy$%Ez(;F^!R$FaYyKf2Bw-Bxe70_^paODKq+BJL{iE`4*HL=-gx8xl*i$;YM z^eLd~9ry5SFV_+nN!yi`jRe}pw%x7ktGKlWwgX6yT$(iBqiTA==P~e*(iC>tRx2o6 z*F6evgD%YC!-Ls^h38cD+Y5ym#^GRQ3i&&dJYO_*aL0#J$A;7C;q^7C-Ij<4E>~62mwMCQ7iSHI8wkeaGw>^P+ z%vPk*>HWTe|IYaa_NPuiNt)FI#~&#zhA;W;1UwizRwMJVd{=#6qfuVDsnnCY+6fhL97oD%3-(y%c)@ zuu`uRFW!nu#d{relSktnI6{XFEqABgw*7z5FytqofdZj~rqeAb#Z{r-UMg0`vr|)# zCsQn&Nd=jhFRC#Tr+dE|bQ- zTur6g_){L*RgSvBa)DDl&Z$8D>Tm_sJ&2G0FwO|=;EU9ltD)kDosn4KtXHMMdetxNe z&Z1vpg!6bsRVVHxUQvhCE9$H2*EM;~LCq7Ii)4bFBu|p7f4ToXV*>t-K@fyrT^jYgfd2=n91vRo delta 2854 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 delta 2813 zcmaJ@eN0=|6~Fhr=l5*v*zoL!Z7}|N;0MMSuY^$3Cl}k0|N*n_%wx(o@mFX>c`l2IbdG;bN<-u354(* zSkDnzd;dq@J2ZQW*gl21c0+0p9ew#A*uFr>W+C#s-S-#g4$SUHr~zD1l!$5X!Pn-% z2)1sRHxtqBXWpJWwr~b)Q!u|pk-vQV{M#p9JhcG3seR~2!-!@$)qv0T?}f1i)gl@(uxt0nfBdBX zvk&jl_bm5)57r+%fK_<=L#z2du2}Jdzq(@1TMgj`#{Q*Mr{2MKbsdk+v_MQJZH*J~2^XLkM3t|o%tKp*CA-Eh)QcBb( zK@JjaF;Z%Wrh7}0iiPW~u^<+GU^%0Sn@1;Sx{lmj$>4Bc(L(b#y7L#+-)kgOeBZ@X z`&jd0&q{h8mQ{vrw{&^=%*xFp<5#!O@rtEvO<&v>SBKf~mW%mrY^gi!^W)6QyXq?2 z8ML4_vi7zAjH?;#OG{alrHgbCnNTC@M5rYk zjRk2DLUK-Q_OeiWIUH3YKIy5nC^Yt}A9rUa-R|62H}=1hiKJ5sk=(UYeSN3C^Ysn= zhjh{v_VB!?%e`%cnyLCk(uJza(j#9)0cwg2qO*ua1QVhn3ZfOdQz?gMqaLv&bjwLm zrNZ?>2`AcyqAqV@G_YP$X8K#j+qh=#Npj>Tq+hzc!Cz=DSu!z;ce}oQMz(F z0vca$utDmdAt+Va@uM;2w@Y@Kc{!QPZJ$d2M@bIMzC)H+y zzcD2{9P-Xi)}+&!*dr`_Y?6tw7%!UY)Z>PAT1G?a@hz^rkuqU`Z<62|eibQ5b9GOZ zQpPDXx)%LQ5VMuxxYpo9vvAK-37K1e`@bH)jx5~LM*Y3MbZRf z^b_z<*?_1i;T(2L%bV_k!RK8u-R?}}8bVtqYHnIahD$*r5m%T%rB+d}@6nHGjJNPN9&6FD?x1&kus&qTugs&t}_um;AzY zvQ#L+!odTu2*EZf$&4fj43q@iOu(?ggH?Ca{J9-F&gKV&w`13ZLvpqjpTAwHZ_jFoq zcv2m*4(lG4Q+>-C*Y2I%_TPzZlP{>BS$k@EF(NK3comVi3W61$l5Mx_aQ9@&35F)9$6+l1Kf9-KAc(-yF6;=irY3e(Ew2&0?KQx<{d>Xe9>? z%4X?zzCs~u4Z@NO$pi&(HN#C!nr?nZldML#NT6^@nu4QHC4NFOn`A?D=E~V=hhzHe zmCVBtRoJ{E%EiZB$)i+#=bqDp109QdIx7Rvgqr+7O&`()q^VqDgOFO~Z$wL#RMfRb zyPxEN;V#zz(Zh%^A8^hl(cu5Ek#zajHAV=imK*JRNm}-Cr73}57bJd4%64R~ygco& z?F^mcbl^mi)E*Q%8%S1L^v{vQ!&ImPx9M7?{Dgv)M}?pCNk|g&~}cO z1oFs@WyeR@vGywe;}=XlUm`t2m;(LjAGN>P)M~*QYYSEFl||Sm*()l5l;D3?k?BPg zvw+OZB6?2!!ukEfIdl^J5Z%K+qkcerPB+tw^deS%yur ztL$yAmK*18YguhbJFdN;{kblp%jw?G-O>HChN+3x?5|nVyY;^{)IeAa5<)*gWDGQ_ Ne$!*VVRFBY{|BM_fO-G` diff --git a/fonts/selection.json b/fonts/selection.json index fbd4e2180..8515b7e68 100755 --- a/fonts/selection.json +++ b/fonts/selection.json @@ -1,6 +1,197 @@ { "IcoMoonType": "selection", "icons": [ + { + "icon": { + "paths": [ + "M550 696l-80-82v162zM470 248v162l80-82zM670 328l-184 184 184 184-244 242h-42v-324l-196 196-60-60 238-238-238-238 60-60 196 196v-324h42zM834 286c40 64 62 142 62 222 0 84-24 160-66 226l-50-50c26-52 42-110 42-172s-16-120-42-172zM608 512l98-98c12 30 20 64 20 98s-8 70-20 100z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "bluetooth_searching" + ], + "defaultCode": 57770, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "bluetooth_audio, bluetooth_searching", + "id": 79, + "order": 911, + "prevSize": 24, + "code": 57770, + "name": "bluetooth" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 79 + }, + { + "icon": { + "paths": [ + "M512 42c212 0 384 172 384 384v300c0 70-58 128-128 128h-128v-342h170v-86c0-166-132-298-298-298s-298 132-298 298v86h170v342h-128c-70 0-128-58-128-128v-300c0-212 172-384 384-384z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "headset" + ], + "defaultCode": 58128, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "headset", + "id": 376, + "order": 910, + "prevSize": 24, + "code": 58128, + "name": "headset" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 376 + }, + { + "icon": { + "paths": [ + "M640 512c0-70-58-128-128-128v-86c118 0 214 96 214 214h-86zM810 512c0-166-132-298-298-298v-86c212 0 384 172 384 384h-86zM854 662c24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44l-94 94c62 122 162 220 282 282l94-94c12-12 30-14 44-10 48 16 98 24 152 24z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "phone_in_talk" + ], + "defaultCode": 58909, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "phone_in_talk", + "id": 566, + "order": 912, + "prevSize": 24, + "code": 58909, + "name": "phone-talk" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 566 + }, + { + "icon": { + "paths": [ + "M512 682c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 426c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 342c-46 0-86-40-86-86s40-86 86-86 86 40 86 86-40 86-86 86z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "more_vert" + ], + "defaultCode": 58836, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "more_vert", + "id": 0, + "order": 897, + "prevSize": 24, + "code": 58836, + "name": "thumb-menu" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 40 + }, + { + "icon": { + "paths": [ + "M330.667 554.667c-0.427-14.933 6.4-29.44 17.92-39.253 32 6.827 61.867 20.053 88.747 39.253 0 29.013-23.893 52.907-53.333 52.907s-52.907-23.467-53.333-52.907zM586.667 554.667c26.88-18.773 56.747-32 88.747-38.827 11.52 9.813 18.347 24.32 17.92 38.827 0 29.867-23.893 53.76-53.333 53.76s-53.333-23.893-53.333-53.76v0zM512 384c-118.187-1.707-234.667 27.733-338.347 85.333l-2.987 42.667c0 52.48 12.373 104.107 35.84 151.040 101.12-15.36 203.093-23.040 305.493-23.040s204.373 7.68 305.493 23.040c23.467-46.933 35.84-98.56 35.84-151.040l-2.987-42.667c-103.68-57.6-220.16-87.040-338.347-85.333zM512 85.333c235.641 0 426.667 191.025 426.667 426.667s-191.025 426.667-426.667 426.667c-235.641 0-426.667-191.025-426.667-426.667s191.025-426.667 426.667-426.667z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "ninja" + ], + "grid": 24 + }, + "attrs": [ + {} + ], + "properties": { + "order": 850, + "id": 1, + "name": "ninja", + "prevSize": 24, + "code": 59657 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 41 + }, + { + "icon": { + "paths": [ + "M282 460c62 120 162 220 282 282l94-94c12-12 30-16 44-10 48 16 100 24 152 24 24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "phone" + ], + "defaultCode": 57549, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "call, local_phone, phone", + "id": 2, + "order": 851, + "prevSize": 24, + "code": 57549, + "name": "phone" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 42 + }, + { + "icon": { + "paths": [ + "M810 554h-256v256h-84v-256h-256v-84h256v-256h84v256h256v84z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "add" + ], + "defaultCode": 57669, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "add", + "id": 3, + "order": 896, + "prevSize": 24, + "code": 57669, + "name": "add" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 43 + }, { "icon": { "paths": [ @@ -26,7 +217,7 @@ "prevSize": 32, "code": 59686 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 0 }, @@ -55,7 +246,7 @@ "prevSize": 32, "code": 59682 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 1 }, @@ -84,7 +275,7 @@ "prevSize": 32, "code": 59651 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 2 }, @@ -113,7 +304,7 @@ "prevSize": 32, "code": 59677 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 3 }, @@ -142,7 +333,7 @@ "prevSize": 32, "code": 59676 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 4 }, @@ -168,7 +359,7 @@ "code": 59649, "name": "avatar" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 5 }, @@ -194,7 +385,7 @@ "code": 59653, "name": "hangup" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 6 }, @@ -220,7 +411,7 @@ "code": 59654, "name": "chat" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 7 }, @@ -246,7 +437,7 @@ "code": 59650, "name": "download" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 8 }, @@ -272,7 +463,7 @@ "code": 59655, "name": "edit" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 9 }, @@ -298,7 +489,7 @@ "code": 59656, "name": "share-doc" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 10 }, @@ -324,7 +515,7 @@ "code": 59652, "name": "kick" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 11 }, @@ -350,7 +541,7 @@ "code": 59679, "name": "menu-up" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 12 }, @@ -376,7 +567,7 @@ "code": 59680, "name": "menu-down" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 13 }, @@ -402,7 +593,7 @@ "code": 59659, "name": "full-screen" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 14 }, @@ -428,7 +619,7 @@ "code": 59660, "name": "exit-full-screen" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 15 }, @@ -454,7 +645,7 @@ "code": 59658, "name": "star-full" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 16 }, @@ -480,7 +671,7 @@ "code": 59661, "name": "security" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 17 }, @@ -506,7 +697,7 @@ "code": 59662, "name": "security-locked" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 18 }, @@ -532,7 +723,7 @@ "code": 59663, "name": "reload" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 19 }, @@ -558,7 +749,7 @@ "code": 59664, "name": "microphone" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 20 }, @@ -584,7 +775,7 @@ "code": 59665, "name": "mic-empty" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 21 }, @@ -610,7 +801,7 @@ "code": 59666, "name": "mic-disabled" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 22 }, @@ -636,7 +827,7 @@ "code": 59678, "name": "raised-hand" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 23 }, @@ -662,7 +853,7 @@ "code": 59675, "name": "contactList" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 24 }, @@ -688,7 +879,7 @@ "code": 59667, "name": "link" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 25 }, @@ -714,7 +905,7 @@ "code": 59668, "name": "shared-video" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 26 }, @@ -740,7 +931,7 @@ "code": 59669, "name": "settings" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 27 }, @@ -766,7 +957,7 @@ "code": 59670, "name": "star" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 28 }, @@ -792,7 +983,7 @@ "code": 59681, "name": "switch-camera" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 29 }, @@ -818,7 +1009,7 @@ "code": 59671, "name": "share-desktop" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 30 }, @@ -844,7 +1035,7 @@ "code": 59672, "name": "camera" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 31 }, @@ -870,7 +1061,7 @@ "code": 59673, "name": "camera-disabled" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 32 }, @@ -896,7 +1087,7 @@ "code": 59674, "name": "volume" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 33 }, @@ -925,9 +1116,9 @@ "name": "recDisable", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 36 + "iconIdx": 34 }, { "icon": { @@ -955,9 +1146,9 @@ "name": "recEnable", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 37 + "iconIdx": 35 }, { "icon": { @@ -985,9 +1176,9 @@ "name": "presentation", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 38 + "iconIdx": 36 }, { "icon": { @@ -1011,9 +1202,9 @@ "code": 59685, "name": "dialpad" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 39 + "iconIdx": 37 }, { "icon": { @@ -1037,9 +1228,9 @@ "code": 59683, "name": "visibility" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 40 + "iconIdx": 38 }, { "icon": { @@ -1063,119 +1254,9 @@ "code": 59684, "name": "visibility-off" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 41 - }, - { - "icon": { - "paths": [ - "M512 682c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 426c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 342c-46 0-86-40-86-86s40-86 86-86 86 40 86 86-40 86-86 86z" - ], - "attrs": [], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "more_vert" - ], - "defaultCode": 58836, - "grid": 24 - }, - "attrs": [], - "properties": { - "ligatures": "more_vert", - "id": 0, - "order": 897, - "prevSize": 24, - "code": 58836, - "name": "thumb-menu" - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 42 - }, - { - "icon": { - "paths": [ - "M330.667 554.667c-0.427-14.933 6.4-29.44 17.92-39.253 32 6.827 61.867 20.053 88.747 39.253 0 29.013-23.893 52.907-53.333 52.907s-52.907-23.467-53.333-52.907zM586.667 554.667c26.88-18.773 56.747-32 88.747-38.827 11.52 9.813 18.347 24.32 17.92 38.827 0 29.867-23.893 53.76-53.333 53.76s-53.333-23.893-53.333-53.76v0zM512 384c-118.187-1.707-234.667 27.733-338.347 85.333l-2.987 42.667c0 52.48 12.373 104.107 35.84 151.040 101.12-15.36 203.093-23.040 305.493-23.040s204.373 7.68 305.493 23.040c23.467-46.933 35.84-98.56 35.84-151.040l-2.987-42.667c-103.68-57.6-220.16-87.040-338.347-85.333zM512 85.333c235.641 0 426.667 191.025 426.667 426.667s-191.025 426.667-426.667 426.667c-235.641 0-426.667-191.025-426.667-426.667s191.025-426.667 426.667-426.667z" - ], - "attrs": [ - {} - ], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "ninja" - ], - "grid": 24 - }, - "attrs": [ - {} - ], - "properties": { - "order": 850, - "id": 1, - "name": "ninja", - "prevSize": 24, - "code": 59657 - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 43 - }, - { - "icon": { - "paths": [ - "M282 460c62 120 162 220 282 282l94-94c12-12 30-16 44-10 48 16 100 24 152 24 24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44z" - ], - "attrs": [], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "phone" - ], - "defaultCode": 57549, - "grid": 24 - }, - "attrs": [], - "properties": { - "ligatures": "call, local_phone, phone", - "id": 2, - "order": 851, - "prevSize": 24, - "code": 57549, - "name": "phone" - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 44 - }, - { - "icon": { - "paths": [ - "M810 554h-256v256h-84v-256h-256v-84h256v-256h84v256h256v84z" - ], - "attrs": [], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "add" - ], - "defaultCode": 57669, - "grid": 24 - }, - "attrs": [], - "properties": { - "ligatures": "add", - "id": 3, - "order": 896, - "prevSize": 24, - "code": 57669, - "name": "add" - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 45 + "iconIdx": 39 } ], "height": 1024, diff --git a/ios/sdk/sdk.xcodeproj/project.pbxproj b/ios/sdk/sdk.xcodeproj/project.pbxproj index 312768c78..798980acc 100644 --- a/ios/sdk/sdk.xcodeproj/project.pbxproj +++ b/ios/sdk/sdk.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; }; 0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; }; 0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; }; 0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; }; 0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; }; @@ -32,6 +33,7 @@ 0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JitsiMeetView.h; sourceTree = ""; }; 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = ""; }; 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = ""; }; + 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = ""; }; 0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = ""; }; 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = ""; }; @@ -108,6 +110,7 @@ 0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */, 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */, 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */, + 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */, 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */, 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */, 0BCA495D1EC4B6C600B793EE /* POSIX.m */, @@ -293,6 +296,7 @@ 0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */, 0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */, 0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */, + 0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */, 0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */, 0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */, ); diff --git a/ios/sdk/src/MPVolumeViewManager.m b/ios/sdk/src/MPVolumeViewManager.m new file mode 100644 index 000000000..99f075919 --- /dev/null +++ b/ios/sdk/src/MPVolumeViewManager.m @@ -0,0 +1,62 @@ +/* + * Copyright @ 2017-present Atlassian Pty Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +@import MediaPlayer; + + +@interface MPVolumeViewManager : RCTViewManager +@end + +@implementation MPVolumeViewManager + +RCT_EXPORT_MODULE() + +- (UIView *)view { + MPVolumeView *volumeView = [[MPVolumeView alloc] init]; + volumeView.showsRouteButton = YES; + volumeView.showsVolumeSlider = NO; + + return (UIView *) volumeView; +} + +RCT_EXPORT_METHOD(show:(nonnull NSNumber *)reactTag) { + [self.bridge.uiManager addUIBlock:^( + __unused RCTUIManager *uiManager, + NSDictionary *viewRegistry) { + id view = viewRegistry[reactTag]; + if (![view isKindOfClass:[MPVolumeView class]]) { + RCTLogError(@"Invalid view returned from registry, expecting \ + MPVolumeView, got: %@", view); + } else { + // Simulate a click + UIButton *btn = nil; + for (UIView *buttonView in ((UIView *) view).subviews) { + if ([buttonView isKindOfClass:[UIButton class]]) { + btn = (UIButton *) buttonView; + break; + } + } + if (btn != nil) { + [btn sendActionsForControlEvents:UIControlEventTouchUpInside]; + } + } + }]; +} + +@end diff --git a/react/features/base/font-icons/jitsi.json b/react/features/base/font-icons/jitsi.json index f3b9e25e3..8515b7e68 100644 --- a/react/features/base/font-icons/jitsi.json +++ b/react/features/base/font-icons/jitsi.json @@ -1,6 +1,114 @@ { "IcoMoonType": "selection", "icons": [ + { + "icon": { + "paths": [ + "M550 696l-80-82v162zM470 248v162l80-82zM670 328l-184 184 184 184-244 242h-42v-324l-196 196-60-60 238-238-238-238 60-60 196 196v-324h42zM834 286c40 64 62 142 62 222 0 84-24 160-66 226l-50-50c26-52 42-110 42-172s-16-120-42-172zM608 512l98-98c12 30 20 64 20 98s-8 70-20 100z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "bluetooth_searching" + ], + "defaultCode": 57770, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "bluetooth_audio, bluetooth_searching", + "id": 79, + "order": 911, + "prevSize": 24, + "code": 57770, + "name": "bluetooth" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 79 + }, + { + "icon": { + "paths": [ + "M512 42c212 0 384 172 384 384v300c0 70-58 128-128 128h-128v-342h170v-86c0-166-132-298-298-298s-298 132-298 298v86h170v342h-128c-70 0-128-58-128-128v-300c0-212 172-384 384-384z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "headset" + ], + "defaultCode": 58128, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "headset", + "id": 376, + "order": 910, + "prevSize": 24, + "code": 58128, + "name": "headset" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 376 + }, + { + "icon": { + "paths": [ + "M640 512c0-70-58-128-128-128v-86c118 0 214 96 214 214h-86zM810 512c0-166-132-298-298-298v-86c212 0 384 172 384 384h-86zM854 662c24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44l-94 94c62 122 162 220 282 282l94-94c12-12 30-14 44-10 48 16 98 24 152 24z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "phone_in_talk" + ], + "defaultCode": 58909, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "phone_in_talk", + "id": 566, + "order": 912, + "prevSize": 24, + "code": 58909, + "name": "phone-talk" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 566 + }, + { + "icon": { + "paths": [ + "M512 682c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 426c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 342c-46 0-86-40-86-86s40-86 86-86 86 40 86 86-40 86-86 86z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "more_vert" + ], + "defaultCode": 58836, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "more_vert", + "id": 0, + "order": 897, + "prevSize": 24, + "code": 58836, + "name": "thumb-menu" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 40 + }, { "icon": { "paths": [ @@ -11,24 +119,24 @@ ], "isMulticolor": false, "isMulticolor2": false, - "grid": 24, "tags": [ "ninja" - ] + ], + "grid": 24 }, "attrs": [ {} ], "properties": { - "order": 851, - "id": 121, + "order": 850, + "id": 1, "name": "ninja", - "prevSize": 32, + "prevSize": 24, "code": 59657 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 0 + "iconIdx": 41 }, { "icon": { @@ -47,15 +155,100 @@ "attrs": [], "properties": { "ligatures": "call, local_phone, phone", - "id": 120, - "order": 848, - "prevSize": 32, + "id": 2, + "order": 851, + "prevSize": 24, "code": 57549, "name": "phone" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 41 + "iconIdx": 42 + }, + { + "icon": { + "paths": [ + "M810 554h-256v256h-84v-256h-256v-84h256v-256h84v256h256v84z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "add" + ], + "defaultCode": 57669, + "grid": 24 + }, + "attrs": [], + "properties": { + "ligatures": "add", + "id": 3, + "order": 896, + "prevSize": 24, + "code": 57669, + "name": "add" + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 43 + }, + { + "icon": { + "paths": [ + "M896 0c70.692 0 128 57.308 128 128v768c0 70.692-57.308 128-128 128s-128-57.308-128-128v-768c0-70.692 57.308-128 128-128zM512 256c70.692 0 128 57.308 128 128v512c0 70.692-57.308 128-128 128s-128-57.308-128-128v-512c0-70.692 57.308-128 128-128zM128 640v0c70.692 0 128 57.308 128 128v128c0 70.692-57.308 128-128 128s-128-57.308-128-128v-128c0-70.692 57.308-128 128-128v0z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "gsm-bars-black" + ] + }, + "attrs": [ + {} + ], + "properties": { + "order": 901, + "id": 0, + "name": "gsm-bars-black", + "prevSize": 32, + "code": 59686 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 0 + }, + { + "icon": { + "paths": [ + "M512 85.333c-235.52 0-426.667 191.147-426.667 426.667s191.147 426.667 426.667 426.667 426.667-191.147 426.667-426.667-191.147-426.667-426.667-426.667zM554.667 725.333h-85.333v-256h85.333v256zM554.667 384h-85.333v-85.333h85.333v85.333z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "ic_info_black_24px" + ] + }, + "attrs": [ + {} + ], + "properties": { + "order": 898, + "id": 0, + "name": "info", + "prevSize": 32, + "code": 59682 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 1 }, { "icon": { @@ -76,15 +269,15 @@ {} ], "properties": { - "order": 109, + "order": 856, "id": 0, "name": "mic-camera-combined", "prevSize": 32, "code": 59651 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 1 + "iconIdx": 2 }, { "icon": { @@ -105,15 +298,15 @@ {} ], "properties": { - "order": 104, + "order": 857, "id": 1, "name": "feedback", "prevSize": 32, "code": 59677 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 2 + "iconIdx": 3 }, { "icon": { @@ -134,15 +327,15 @@ {} ], "properties": { - "order": 103, + "order": 858, "id": 2, "name": "toggle-filmstrip", "prevSize": 32, "code": 59676 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 3 + "iconIdx": 4 }, { "icon": { @@ -160,15 +353,15 @@ "attrs": [], "properties": { "id": 3, - "order": 60, + "order": 859, "ligatures": "account_circle", "prevSize": 32, "code": 59649, "name": "avatar" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 4 + "iconIdx": 5 }, { "icon": { @@ -186,15 +379,15 @@ "attrs": [], "properties": { "id": 4, - "order": 849, + "order": 860, "ligatures": "call_end", "prevSize": 32, "code": 59653, "name": "hangup" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 5 + "iconIdx": 6 }, { "icon": { @@ -212,15 +405,15 @@ "attrs": [], "properties": { "id": 5, - "order": 61, + "order": 861, "ligatures": "chat_bubble_outline", "prevSize": 32, "code": 59654, "name": "chat" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 6 + "iconIdx": 7 }, { "icon": { @@ -238,15 +431,15 @@ "attrs": [], "properties": { "id": 6, - "order": 99, + "order": 862, "ligatures": "cloud_download", "prevSize": 32, "code": 59650, "name": "download" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 7 + "iconIdx": 8 }, { "icon": { @@ -264,15 +457,15 @@ "attrs": [], "properties": { "id": 7, - "order": 89, + "order": 863, "ligatures": "create, edit, mode_edit", "prevSize": 32, "code": 59655, "name": "edit" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 8 + "iconIdx": 9 }, { "icon": { @@ -290,15 +483,15 @@ "attrs": [], "properties": { "id": 8, - "order": 85, + "order": 864, "ligatures": "description", "prevSize": 32, "code": 59656, "name": "share-doc" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 9 + "iconIdx": 10 }, { "icon": { @@ -315,16 +508,16 @@ }, "attrs": [], "properties": { - "id": 10, - "order": 98, + "id": 9, + "order": 865, "ligatures": "eject", "prevSize": 32, "code": 59652, "name": "kick" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 10 + "iconIdx": 11 }, { "icon": { @@ -341,16 +534,16 @@ }, "attrs": [], "properties": { - "id": 11, - "order": 106, + "id": 10, + "order": 900, "ligatures": "expand_less", "prevSize": 32, "code": 59679, "name": "menu-up" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 11 + "iconIdx": 12 }, { "icon": { @@ -367,16 +560,16 @@ }, "attrs": [], "properties": { - "id": 12, - "order": 107, + "id": 11, + "order": 867, "ligatures": "expand_more", "prevSize": 32, "code": 59680, "name": "menu-down" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 12 + "iconIdx": 13 }, { "icon": { @@ -393,16 +586,16 @@ }, "attrs": [], "properties": { - "id": 13, - "order": 94, + "id": 12, + "order": 868, "ligatures": "fullscreen", "prevSize": 32, "code": 59659, "name": "full-screen" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 13 + "iconIdx": 14 }, { "icon": { @@ -419,16 +612,16 @@ }, "attrs": [], "properties": { - "id": 14, - "order": 92, + "id": 13, + "order": 869, "ligatures": "fullscreen_exit", "prevSize": 32, "code": 59660, "name": "exit-full-screen" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 14 + "iconIdx": 15 }, { "icon": { @@ -445,16 +638,16 @@ }, "attrs": [], "properties": { - "id": 15, - "order": 101, + "id": 14, + "order": 870, "ligatures": "grade, star", "prevSize": 32, "code": 59658, "name": "star-full" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 15 + "iconIdx": 16 }, { "icon": { @@ -471,16 +664,16 @@ }, "attrs": [], "properties": { - "id": 16, - "order": 66, + "id": 15, + "order": 871, "ligatures": "lock_open", "prevSize": 32, "code": 59661, "name": "security" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 16 + "iconIdx": 17 }, { "icon": { @@ -497,16 +690,16 @@ }, "attrs": [], "properties": { - "id": 17, - "order": 65, + "id": 16, + "order": 872, "ligatures": "lock_outline", "prevSize": 32, "code": 59662, "name": "security-locked" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 17 + "iconIdx": 18 }, { "icon": { @@ -523,16 +716,16 @@ }, "attrs": [], "properties": { - "id": 18, - "order": 67, + "id": 17, + "order": 873, "ligatures": "loop, sync", "prevSize": 32, "code": 59663, "name": "reload" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 18 + "iconIdx": 19 }, { "icon": { @@ -549,16 +742,16 @@ }, "attrs": [], "properties": { - "id": 19, - "order": 68, + "id": 18, + "order": 874, "ligatures": "mic", "prevSize": 32, "code": 59664, "name": "microphone" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 19 + "iconIdx": 20 }, { "icon": { @@ -575,16 +768,16 @@ }, "attrs": [], "properties": { - "id": 20, - "order": 69, + "id": 19, + "order": 875, "ligatures": "mic_none", "prevSize": 32, "code": 59665, "name": "mic-empty" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 20 + "iconIdx": 21 }, { "icon": { @@ -601,16 +794,16 @@ }, "attrs": [], "properties": { - "id": 21, - "order": 70, + "id": 20, + "order": 876, "ligatures": "mic_off", "prevSize": 32, "code": 59666, "name": "mic-disabled" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 21 + "iconIdx": 22 }, { "icon": { @@ -627,16 +820,16 @@ }, "attrs": [], "properties": { - "id": 22, - "order": 105, + "id": 21, + "order": 899, "ligatures": "pan_tool", "prevSize": 32, "code": 59678, "name": "raised-hand" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 22 + "iconIdx": 23 }, { "icon": { @@ -653,16 +846,16 @@ }, "attrs": [], "properties": { - "id": 23, - "order": 100, + "id": 22, + "order": 878, "ligatures": "people_outline", "prevSize": 32, "code": 59675, "name": "contactList" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 23 + "iconIdx": 24 }, { "icon": { @@ -679,16 +872,16 @@ }, "attrs": [], "properties": { - "id": 24, - "order": 87, + "id": 23, + "order": 879, "ligatures": "person_add", "prevSize": 32, "code": 59667, "name": "link" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 24 + "iconIdx": 25 }, { "icon": { @@ -705,16 +898,16 @@ }, "attrs": [], "properties": { - "id": 25, - "order": 82, + "id": 24, + "order": 880, "ligatures": "play_circle_outline", "prevSize": 32, "code": 59668, "name": "shared-video" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 25 + "iconIdx": 26 }, { "icon": { @@ -731,16 +924,16 @@ }, "attrs": [], "properties": { - "id": 26, - "order": 81, + "id": 25, + "order": 881, "ligatures": "settings", "prevSize": 32, "code": 59669, "name": "settings" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 26 + "iconIdx": 27 }, { "icon": { @@ -757,16 +950,16 @@ }, "attrs": [], "properties": { - "id": 27, - "order": 76, + "id": 26, + "order": 882, "ligatures": "star_border", "prevSize": 32, "code": 59670, "name": "star" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 27 + "iconIdx": 28 }, { "icon": { @@ -783,16 +976,16 @@ }, "attrs": [], "properties": { - "id": 28, - "order": 108, + "id": 27, + "order": 883, "ligatures": "switch_camera", "prevSize": 32, "code": 59681, "name": "switch-camera" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 28 + "iconIdx": 29 }, { "icon": { @@ -809,16 +1002,16 @@ }, "attrs": [], "properties": { - "id": 29, - "order": 93, + "id": 28, + "order": 884, "ligatures": "tv", "prevSize": 32, "code": 59671, "name": "share-desktop" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 29 + "iconIdx": 30 }, { "icon": { @@ -835,16 +1028,16 @@ }, "attrs": [], "properties": { - "id": 30, - "order": 77, + "id": 29, + "order": 885, "ligatures": "videocam", "prevSize": 32, "code": 59672, "name": "camera" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 30 + "iconIdx": 31 }, { "icon": { @@ -861,16 +1054,16 @@ }, "attrs": [], "properties": { - "id": 31, - "order": 78, + "id": 30, + "order": 886, "ligatures": "videocam_off", "prevSize": 32, "code": 59673, "name": "camera-disabled" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 31 + "iconIdx": 32 }, { "icon": { @@ -887,138 +1080,17 @@ }, "attrs": [], "properties": { - "id": 32, - "order": 79, + "id": 31, + "order": 887, "ligatures": "volume_up", "prevSize": 32, "code": 59674, "name": "volume" }, - "setIdx": 0, - "setId": 1, - "iconIdx": 32 - }, - { - "icon": { - "paths": [ - "M-0 724.847h196.337v187.951h-196.337v-187.951z", - "M271.842 543.628h196.337v369.169h-196.337v-369.169z", - "M543.656 362.438h196.337v550.36h-196.337v-550.36z", - "M815.47 181.234v731.564h119.56c-14.589-33.025-23.125-71.503-23.232-111.943 0.132-86.42 38.697-163.851 99.656-216.468l0.348-403.153h-196.332z", - "M1087.292-0v533.672c28.874-10.572 62.222-16.73 97.009-16.825 35.717 0.129 69.823 6.614 101.322 18.371l-1.999-535.218h-196.332z", - "M1192.868 584.148c-0.009-0-0.020-0-0.031-0-122.247 0-221.351 98.447-221.372 219.896-0 0.007-0 0.014-0 0.021 0 121.467 99.111 219.935 221.372 219.935 0.011 0 0.021-0 0.032-0 122.248-0.014 221.345-98.477 221.345-219.935 0-0.007-0-0.013-0-0.020-0.021-121.441-99.11-219.883-221.345-219.897zM1194.706 651.393c87.601 0.006 158.614 69.787 158.614 155.866 0 0.006-0 0.012-0 0.019-0.022 86.062-71.026 155.822-158.614 155.828-87.588-0.006-158.593-69.766-158.615-155.826-0-0.007-0-0.014-0-0.020 0-86.079 71.013-155.86 158.613-155.866z", - "M1286.795 668.318l48.348 52.528-236.375 217.567-48.348-52.528 236.375-217.567z" - ], - "width": 1414, - "attrs": [ - {}, - {}, - {}, - {}, - {}, - {}, - {} - ], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "connection-lost" - ], - "grid": 0 - }, - "attrs": [ - {}, - {}, - {}, - {}, - {}, - {}, - {} - ], - "properties": { - "order": 33, - "id": 33, - "name": "connection-lost", - "prevSize": 32, - "code": 59648 - }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 33 }, - { - "icon": { - "paths": [ - "M3.881 813.165h220.26v210.835h-220.26v-210.835z", - "M308.817 609.857h220.27v414.143h-220.27v-414.143z", - "M613.764 406.588h220.268v617.412h-220.268v-617.412z", - "M918.685 203.285h220.265v820.715h-220.265v-820.715z", - "M1223.629 0h220.263v1024h-220.263v-1024z" - ], - "width": 1444, - "attrs": [ - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - } - ], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "connection-2" - ], - "grid": 0 - }, - "attrs": [ - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - } - ], - "properties": { - "order": 37, - "id": 34, - "prevSize": 32, - "code": 58906, - "name": "connection", - "ligatures": "" - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 34 - }, { "icon": { "paths": [ @@ -1037,16 +1109,16 @@ }, "attrs": [], "properties": { - "order": 43, - "id": 35, + "order": 890, + "id": 34, "prevSize": 32, "code": 58899, "name": "recDisable", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 35 + "iconIdx": 34 }, { "icon": { @@ -1067,16 +1139,16 @@ }, "attrs": [], "properties": { - "order": 44, - "id": 36, + "order": 891, + "id": 35, "prevSize": 32, "code": 58900, "name": "recEnable", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 36 + "iconIdx": 35 }, { "icon": { @@ -1097,16 +1169,16 @@ }, "attrs": [], "properties": { - "order": 53, - "id": 37, + "order": 892, + "id": 36, "prevSize": 32, "code": 58883, "name": "presentation", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 37 + "iconIdx": 36 }, { "icon": { @@ -1123,16 +1195,16 @@ }, "attrs": [], "properties": { - "order": 115, + "order": 893, "ligatures": "dialpad", - "id": 38, + "id": 37, "prevSize": 32, "code": 59685, "name": "dialpad" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 38 + "iconIdx": 37 }, { "icon": { @@ -1149,16 +1221,16 @@ }, "attrs": [], "properties": { - "order": 114, + "order": 894, "ligatures": "remove_red_eye, visibility", - "id": 39, + "id": 38, "prevSize": 32, "code": 59683, "name": "visibility" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 39 + "iconIdx": 38 }, { "icon": { @@ -1175,16 +1247,16 @@ }, "attrs": [], "properties": { - "order": 113, + "order": 895, "ligatures": "visibility_off", - "id": 40, + "id": 39, "prevSize": 32, "code": 59684, "name": "visibility-off" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 40 + "iconIdx": 39 } ], "height": 1024, @@ -1212,11 +1284,13 @@ "imagePref": { "prefix": "icon-", "png": true, - "useClassSelector": true + "useClassSelector": true, + "classSelector": ".icon" }, "historySize": 100, "showCodes": false, "search": "", - "showLiga": false + "showLiga": false, + "gridSize": 16 } } \ No newline at end of file diff --git a/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js new file mode 100644 index 000000000..9941af664 --- /dev/null +++ b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js @@ -0,0 +1,178 @@ +// @flow + +import _ from 'lodash'; +import React, { Component } from 'react'; +import { NativeModules } from 'react-native'; +import { connect } from 'react-redux'; + +import { hideDialog, SimpleBottomSheet } from '../../../base/dialog'; + +const AudioMode = NativeModules.AudioMode; + +/** + * Maps each device type to a display name and icon. + * TODO: internationalization. + */ +const deviceInfoMap = { + BLUETOOTH: { + iconName: 'bluetooth', + text: 'Bluetooth', + type: 'BLUETOOTH' + }, + EARPIECE: { + iconName: 'phone-talk', + text: 'Phone', + type: 'EARPIECE' + }, + HEADPHONES: { + iconName: 'headset', + text: 'Headphones', + type: 'HEADPHONES' + }, + SPEAKER: { + iconName: 'volume', + text: 'Speaker', + type: 'SPEAKER' + } +}; + +/** + * Variable to hold the reference to the exported component. This dialog is only + * exported if the {@code AudioMode} module has the capability to get / set + * audio devices. + */ +let DialogType; + +/** + * {@code PasswordRequiredPrompt}'s React {@code Component} prop types. + */ +type Props = { + + /** + * Used for hiding the dialog when the selection was completed. + */ + dispatch: Function +}; + +type State = { + + /** + * Array of available devices. + */ + devices: Array +}; + +/** + * Implements a React {@code Component} which prompts the user when a password + * is required to join a conference. + */ +class AudioRoutePickerDialog extends Component { + state = { + // Available audio devices, it will be set in componentWillMount. + devices: [] + }; + + /** + * Initializes a new {@code PasswordRequiredPrompt} instance. + * + * @param {Props} props - The read-only React {@code Component} props with + * which the new instance is to be initialized. + */ + constructor(props) { + super(props); + + // Bind event handlers so they are only bound once per instance. + this._onCancel = this._onCancel.bind(this); + this._onSubmit = this._onSubmit.bind(this); + } + + /** + * Initializes the device list by querying the {@code AudioMode} module. + * + * @inheritdoc + */ + componentWillMount() { + AudioMode.getAudioDevices().then(({ devices, selected }) => { + const audioDevices = []; + + if (devices) { + for (const device of devices) { + const info = deviceInfoMap[device]; + + if (info) { + info.selected = device === selected; + audioDevices.push(info); + } + } + } + + if (audioDevices) { + // Make sure devices is alphabetically sorted + this.setState({ devices: _.sortBy(audioDevices, 'text') }); + } + }); + } + + /** + * Dispatches a redux action to hide this sheet. + * + * @returns {void} + */ + _hide() { + this.props.dispatch(hideDialog(DialogType)); + } + + _onCancel: () => void; + + /** + * Cancels the dialog by hiding it. + * + * @private + * @returns {void} + */ + _onCancel() { + this._hide(); + } + + _onSubmit: (?Object) => void; + + /** + * Handles the selection of a device on the sheet. The selected device will + * be used by {@code AudioMode}. + * + * @param {Object} device - Object representing the selected device. + * @private + * @returns {void} + */ + _onSubmit(device) { + this._hide(); + AudioMode.setAudioDevice(device.type); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + if (!this.state.devices.length) { + return null; + } + + return ( + + ); + } +} + +// Only export the dialog if we have support for getting / setting audio devices +// in AudioMode. +if (AudioMode.getAudioDevices && AudioMode.setAudioDevice) { + DialogType = connect()(AudioRoutePickerDialog); +} + +export default DialogType; diff --git a/react/features/mobile/audio-mode/components/index.js b/react/features/mobile/audio-mode/components/index.js new file mode 100644 index 000000000..17f03f9ce --- /dev/null +++ b/react/features/mobile/audio-mode/components/index.js @@ -0,0 +1,3 @@ +export { + default as AudioRoutePickerDialog +} from './AudioRoutePickerDialog'; diff --git a/react/features/mobile/audio-mode/index.js b/react/features/mobile/audio-mode/index.js index d43689289..7ce19e0e6 100644 --- a/react/features/mobile/audio-mode/index.js +++ b/react/features/mobile/audio-mode/index.js @@ -1 +1,3 @@ +export * from './components'; + import './middleware'; diff --git a/react/features/toolbox/components/AudioRouteButton.js b/react/features/toolbox/components/AudioRouteButton.js new file mode 100644 index 000000000..63553e58d --- /dev/null +++ b/react/features/toolbox/components/AudioRouteButton.js @@ -0,0 +1,160 @@ +// @flow + +import React, { Component } from 'react'; +import { + findNodeHandle, + requireNativeComponent, + NativeModules, + View +} from 'react-native'; +import { connect } from 'react-redux'; + +import { openDialog } from '../../base/dialog'; +import { AudioRoutePickerDialog } from '../../mobile/audio-mode'; + +import ToolbarButton from './ToolbarButton'; + +/** + * Define the {@code MPVolumeView} React component. It will only be available + * on iOS. + */ +let MPVolumeView; + +if (NativeModules.MPVolumeViewManager) { + MPVolumeView = requireNativeComponent('MPVolumeView', null); +} + +/** + * Style required to hide the {@code MPVolumeView} view, since it's displayed + * programmatically. + */ +const HIDE_VIEW_STYLE = { display: 'none' }; + +type Props = { + + /** + * Used to show the {@code AudioRoutePickerDialog}. + */ + dispatch: Function, + + /** + * The name of the Icon of this {@code AudioRouteButton}. + */ + iconName: string, + + /** + * The style of the Icon of this {@code AudioRouteButton}. + */ + iconStyle: Object, + + /** + * {@code AudioRouteButton} styles. + */ + style: Array<*> | Object, + + /** + * The color underlying the button. + */ + underlayColor: string +}; + +/** + * A toolbar button which triggers an audio route picker when pressed. + */ +class AudioRouteButton extends Component { + _volumeComponent: ?Object; + + /** + * Indicates if there is support for audio device selection via this button. + * + * @returns {boolean} - True if audio device selection is supported, false + * otherwise. + */ + static supported() { + return Boolean(MPVolumeView || AudioRoutePickerDialog); + } + + /** + * Initializes a new {@code AudioRouteButton} instance. + * + * @param {Object} props - The React {@code Component} props to initialize + * the new {@code AudioRouteButton} instance with. + */ + constructor(props) { + super(props); + + /** + * The internal reference to the React {@code MPVolumeView} for + * showing the volume control view. + * + * @private + * @type {ReactComponent} + */ + this._volumeComponent = null; + + // Bind event handlers so they are only bound once per instance. + this._onClick = this._onClick.bind(this); + this._setVolumeComponent = this._setVolumeComponent.bind(this); + } + + _onClick: () => void; + + /** + * Handles clicking/pressing this {@code AudioRouteButton} by showing an + * audio route picker. + * + * @private + * @returns {void} + */ + _onClick() { + if (MPVolumeView) { + const handle = findNodeHandle(this._volumeComponent); + + NativeModules.MPVolumeViewManager.show(handle); + } else if (AudioRoutePickerDialog) { + this.props.dispatch(openDialog(AudioRoutePickerDialog)); + } + } + + _setVolumeComponent: (?Object) => void; + + /** + * Sets the internal reference to the React Component wrapping the + * {@code MPVolumeView} component. + * + * @param {ReactComponent} component - React Component. + * @returns {void} + */ + _setVolumeComponent(component) { + this._volumeComponent = component; + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + const { iconName, iconStyle, style, underlayColor } = this.props; + + return ( + + + { + MPVolumeView + && + } + + ); + } +} + +export default connect()(AudioRouteButton); diff --git a/react/features/toolbox/components/Toolbox.native.js b/react/features/toolbox/components/Toolbox.native.js index 4e371d3e4..9df0f8e4b 100644 --- a/react/features/toolbox/components/Toolbox.native.js +++ b/react/features/toolbox/components/Toolbox.native.js @@ -25,6 +25,8 @@ import { abstractMapDispatchToProps, abstractMapStateToProps } from '../functions'; + +import AudioRouteButton from './AudioRouteButton'; import styles from './styles'; import ToolbarButton from './ToolbarButton'; @@ -310,6 +312,14 @@ class Toolbox extends Component { style = { style } underlayColor = { underlayColor } /> } + { + AudioRouteButton.supported() + && + } );