Compare commits
17 Commits
jitihouse/
...
fix-pip
Author | SHA1 | Date |
---|---|---|
Alex Bumbu | ba1b17a15c | |
Tobias Marschall | 8600d51ef1 | |
Tobias Marschall | 1793fb2884 | |
Tobias Marschall | 3f274bac82 | |
Saúl Ibarra Corretgé | 1fcb8c8e44 | |
Gabriel Borlea | 36d838cc13 | |
Saúl Ibarra Corretgé | dbabdf3351 | |
Saúl Ibarra Corretgé | 3bcde982ac | |
Saúl Ibarra Corretgé | beb301e691 | |
Calin Chitu | d0836ff651 | |
Saúl Ibarra Corretgé | c028511aaf | |
Saúl Ibarra Corretgé | 664552bc05 | |
Saúl Ibarra Corretgé | ac35eea08e | |
Saúl Ibarra Corretgé | 172683d645 | |
Calinteodor | 858e83b09e | |
Calin Chitu | e2750ee58e | |
Calin Chitu | fe132581d4 |
|
@ -26,5 +26,5 @@ android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.bundle.enableUncompressedNativeLibs=false
|
android.bundle.enableUncompressedNativeLibs=false
|
||||||
|
|
||||||
appVersion=22.1.0
|
appVersion=22.1.1
|
||||||
sdkVersion=5.0.0
|
sdkVersion=5.0.2
|
||||||
|
|
|
@ -636,11 +636,11 @@ EXTERNAL SOURCES:
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Amplitude: 834c7332dfb9640a751e21c13efb22a07c0c12d4
|
Amplitude: 834c7332dfb9640a751e21c13efb22a07c0c12d4
|
||||||
amplitude-react-native: 0ed8cab759aafaa94961b82122bf56297da607ad
|
amplitude-react-native: 68ea115c6e5677512abd459dae20d6f924a4e4de
|
||||||
AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7
|
AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7
|
||||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||||
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
||||||
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
|
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
||||||
FBLazyVector: e5569e42a1c79ca00521846c223173a57aca1fe1
|
FBLazyVector: e5569e42a1c79ca00521846c223173a57aca1fe1
|
||||||
FBReactNativeSpec: fe08c1cd7e2e205718d77ad14b34957cce949b58
|
FBReactNativeSpec: fe08c1cd7e2e205718d77ad14b34957cce949b58
|
||||||
Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5
|
Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5
|
||||||
|
@ -651,7 +651,7 @@ SPEC CHECKSUMS:
|
||||||
FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
|
FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
|
||||||
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
|
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
|
||||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||||
glog: 5337263514dd6f09803962437687240c5dc39aa4
|
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||||
GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
|
GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
|
||||||
GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
|
GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
|
||||||
GoogleSignIn: fd381840dbe7c1137aa6dc30849a5c3e070c034a
|
GoogleSignIn: fd381840dbe7c1137aa6dc30849a5c3e070c034a
|
||||||
|
@ -673,18 +673,18 @@ SPEC CHECKSUMS:
|
||||||
React-jsiexecutor: 94ce921e1d8ce7023366873ec371f3441383b396
|
React-jsiexecutor: 94ce921e1d8ce7023366873ec371f3441383b396
|
||||||
React-jsinspector: d0374f7509d407d2264168b6d0fad0b54e300b85
|
React-jsinspector: d0374f7509d407d2264168b6d0fad0b54e300b85
|
||||||
React-logger: 933f80c97c633ee8965d609876848148e3fef438
|
React-logger: 933f80c97c633ee8965d609876848148e3fef438
|
||||||
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
react-native-background-timer: 117fffdc9b0d6f144444bb49029f94275a45fdb5
|
||||||
react-native-get-random-values: 30b3f74ca34e30e2e480de48e4add2706a40ac8f
|
react-native-get-random-values: 30b3f74ca34e30e2e480de48e4add2706a40ac8f
|
||||||
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
|
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||||
react-native-netinfo: 27f287f2d191693f3b9d01a4273137fcf91c3b5d
|
react-native-netinfo: 1792d13894f12e8050c6cd7207192c74025e90d1
|
||||||
react-native-pager-view: 3ee7d4c7697fb3ef788346e834a60cca97ed8540
|
react-native-pager-view: 311c10a4eead1be627cad59062aa059d8108b943
|
||||||
react-native-performance: f4b6604a9d5a8a7407e34a82fab6c641d9a3ec12
|
react-native-performance: 7060cb5011e52ab924a07ff1628eed390bb9293b
|
||||||
react-native-safe-area-context: 584dc04881deb49474363f3be89e4ca0e854c057
|
react-native-safe-area-context: 5cf05f49df9d17261e40e518481f2e334c6cd4b5
|
||||||
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
|
react-native-slider: b0b4c575507df69bc8ceb307eaad0a8f89455a8e
|
||||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||||
react-native-video: a4c2635d0802f983594b7057e1bce8f442f0ad28
|
react-native-video: 77ce22be7abff9e373527ca10106a99dea3ba56c
|
||||||
react-native-webrtc: 1856ac061df94b1bd6037f1f3b56d1b8bc2b50e7
|
react-native-webrtc: 1856ac061df94b1bd6037f1f3b56d1b8bc2b50e7
|
||||||
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
|
react-native-webview: 6ff12d99d519224fade77d1569e31f2762f33f9f
|
||||||
React-perflogger: 93075d8931c32cd1fce8a98c15d2d5ccc4d891bd
|
React-perflogger: 93075d8931c32cd1fce8a98c15d2d5ccc4d891bd
|
||||||
React-RCTActionSheet: 7d3041e6761b4f3044a37079ddcb156575fb6d89
|
React-RCTActionSheet: 7d3041e6761b4f3044a37079ddcb156575fb6d89
|
||||||
React-RCTAnimation: 743e88b55ac62511ae5c2e22803d4f503f2a3a13
|
React-RCTAnimation: 743e88b55ac62511ae5c2e22803d4f503f2a3a13
|
||||||
|
@ -697,21 +697,21 @@ SPEC CHECKSUMS:
|
||||||
React-RCTVibration: e3ffca672dd3772536cb844274094b0e2c31b187
|
React-RCTVibration: e3ffca672dd3772536cb844274094b0e2c31b187
|
||||||
React-runtimeexecutor: dec32ee6f2e2a26e13e58152271535fadff5455a
|
React-runtimeexecutor: dec32ee6f2e2a26e13e58152271535fadff5455a
|
||||||
ReactCommon: 57b69f6383eafcbd7da625bfa6003810332313c4
|
ReactCommon: 57b69f6383eafcbd7da625bfa6003810332313c4
|
||||||
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
|
RNCalendarEvents: 99a07d9a74a62b5b423d8c9df04dc0b8f8b94faf
|
||||||
RNCAsyncStorage: ea6b5c280997b2b32a587793163b1f10e580c4f7
|
RNCAsyncStorage: 4b6538222ce3f01f61011f512226ba3aaa0dc336
|
||||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3
|
||||||
RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd
|
RNCMaskedView: d367b2a8df3992114e31b32b091a0c00dc800827
|
||||||
RNDefaultPreference: 326860d42a681bfd7338c8f6d061cf58745bd860
|
RNDefaultPreference: 62388e0cc3b82995307ecfd2514204980a369693
|
||||||
RNDeviceInfo: 0400a6d0c94186d1120c3cbd97b23abc022187a9
|
RNDeviceInfo: 417b2ef4456c13a9d50ad8c57895eea837b735a8
|
||||||
RNGestureHandler: e5c7cab5f214503dcefd6b2b0cefb050e1f51c4a
|
RNGestureHandler: e5c7cab5f214503dcefd6b2b0cefb050e1f51c4a
|
||||||
RNGoogleSignin: c4381751eefd73c552b923ba347a9bfc6f18771c
|
RNGoogleSignin: aeb29e549e6748da3e5847516fdd1327c85894c5
|
||||||
RNReanimated: 514a11da3a2bcc6c3dfd9de32b38e2b9bf101926
|
RNReanimated: 514a11da3a2bcc6c3dfd9de32b38e2b9bf101926
|
||||||
RNScreens: 522705f2e5c9d27efb17f24aceb2bf8335bc7b8e
|
RNScreens: 522705f2e5c9d27efb17f24aceb2bf8335bc7b8e
|
||||||
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
|
RNSound: 9253013eeaa051eab63d354fd4654f7aa67c82f0
|
||||||
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
|
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
|
||||||
RNWatch: 99637948ec9b5c9ec5a41920642594ad5ba07e80
|
RNWatch: 99637948ec9b5c9ec5a41920642594ad5ba07e80
|
||||||
Yoga: e7dc4e71caba6472ff48ad7d234389b91dadc280
|
Yoga: e7dc4e71caba6472ff48ad7d234389b91dadc280
|
||||||
|
|
||||||
PODFILE CHECKSUM: 7fafb3480e45473da539aa09d06374868b021f90
|
PODFILE CHECKSUM: 7fafb3480e45473da539aa09d06374868b021f90
|
||||||
|
|
||||||
COCOAPODS: 1.11.2
|
COCOAPODS: 1.11.3
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>22.1.0</string>
|
<string>22.1.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
|
|
|
@ -37,13 +37,8 @@
|
||||||
[builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES];
|
[builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES];
|
||||||
[builder setFeatureFlag:@"resolution" withValue:@(360)];
|
[builder setFeatureFlag:@"resolution" withValue:@(360)];
|
||||||
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
|
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
|
||||||
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
|
|
||||||
|
|
||||||
// Apple rejected our app because they claim requiring a
|
|
||||||
// Dropbox account for recording is not acceptable.
|
|
||||||
#if DEBUG
|
|
||||||
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];
|
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];
|
||||||
#endif
|
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
|
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>22.1.0</string>
|
<string>22.1.1</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>22.1.0</string>
|
<string>22.1.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>22.1.0</string>
|
<string>22.1.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CLKComplicationPrincipalClass</key>
|
<key>CLKComplicationPrincipalClass</key>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>FMWK</string>
|
<string>FMWK</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.0.0</string>
|
<string>5.0.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
final class DragGestureController {
|
final class DragGestureController {
|
||||||
|
|
||||||
var insets: UIEdgeInsets = UIEdgeInsets.zero
|
var insets: UIEdgeInsets = UIEdgeInsets.zero
|
||||||
|
var currentPosition: PiPViewCoordinator.Position? = nil
|
||||||
|
|
||||||
private var frameBeforeDragging: CGRect = CGRect.zero
|
private var frameBeforeDragging: CGRect = CGRect.zero
|
||||||
private weak var view: UIView?
|
private weak var view: UIView?
|
||||||
private lazy var panGesture: UIPanGestureRecognizer = {
|
private lazy var panGesture: UIPanGestureRecognizer = {
|
||||||
return UIPanGestureRecognizer(target: self,
|
UIPanGestureRecognizer(target: self,
|
||||||
action: #selector(handlePan(gesture:)))
|
action: #selector(handlePan(gesture:)))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func startDragListener(inView view: UIView) {
|
func startDragListener(inView view: UIView) {
|
||||||
|
@ -40,7 +40,9 @@ final class DragGestureController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handlePan(gesture: UIPanGestureRecognizer) {
|
@objc private func handlePan(gesture: UIPanGestureRecognizer) {
|
||||||
guard let view = self.view else { return }
|
guard let view = view else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let translation = gesture.translation(in: view.superview)
|
let translation = gesture.translation(in: view.superview)
|
||||||
let velocity = gesture.velocity(in: view.superview)
|
let velocity = gesture.velocity(in: view.superview)
|
||||||
|
@ -59,24 +61,29 @@ final class DragGestureController {
|
||||||
let currentPos = view.frame.origin
|
let currentPos = view.frame.origin
|
||||||
let finalPos = calculateFinalPosition()
|
let finalPos = calculateFinalPosition()
|
||||||
|
|
||||||
let distance = CGPoint(x: currentPos.x - finalPos.x,
|
let distance = CGPoint(
|
||||||
y: currentPos.y - finalPos.y)
|
x: currentPos.x - finalPos.x,
|
||||||
|
y: currentPos.y - finalPos.y
|
||||||
|
)
|
||||||
let distanceMagnitude = magnitude(vector: distance)
|
let distanceMagnitude = magnitude(vector: distance)
|
||||||
let velocityMagnitude = magnitude(vector: velocity)
|
let velocityMagnitude = magnitude(vector: velocity)
|
||||||
let animationDuration = 0.5
|
let animationDuration = 0.5
|
||||||
let initialSpringVelocity =
|
let initialSpringVelocity =
|
||||||
velocityMagnitude / distanceMagnitude / CGFloat(animationDuration)
|
velocityMagnitude / distanceMagnitude / CGFloat(animationDuration)
|
||||||
|
|
||||||
frame.origin = CGPoint(x: finalPos.x, y: finalPos.y)
|
frame.origin = CGPoint(x: finalPos.x, y: finalPos.y)
|
||||||
|
|
||||||
UIView.animate(withDuration: animationDuration,
|
UIView.animate(
|
||||||
delay: 0,
|
withDuration: animationDuration,
|
||||||
usingSpringWithDamping: 0.9,
|
delay: 0,
|
||||||
initialSpringVelocity: initialSpringVelocity,
|
usingSpringWithDamping: 0.9,
|
||||||
options: .curveLinear,
|
initialSpringVelocity: initialSpringVelocity,
|
||||||
animations: {
|
options: .curveLinear,
|
||||||
view.frame = frame
|
animations: {
|
||||||
}, completion: nil)
|
view.frame = frame
|
||||||
|
},
|
||||||
|
completion: nil
|
||||||
|
)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -85,9 +92,11 @@ final class DragGestureController {
|
||||||
|
|
||||||
private func calculateFinalPosition() -> CGPoint {
|
private func calculateFinalPosition() -> CGPoint {
|
||||||
guard
|
guard
|
||||||
let view = self.view,
|
let view = view,
|
||||||
let bounds = view.superview?.frame
|
let bounds = view.superview?.frame
|
||||||
else { return CGPoint.zero }
|
else {
|
||||||
|
return CGPoint.zero
|
||||||
|
}
|
||||||
|
|
||||||
let currentSize = view.frame.size
|
let currentSize = view.frame.size
|
||||||
let adjustedBounds = bounds.inset(by: insets)
|
let adjustedBounds = bounds.inset(by: insets)
|
||||||
|
@ -109,19 +118,26 @@ final class DragGestureController {
|
||||||
goUp = location.y < bounds.midY
|
goUp = location.y < bounds.midY
|
||||||
}
|
}
|
||||||
|
|
||||||
let finalPosX: CGFloat =
|
if (goLeft && goUp) {
|
||||||
goLeft
|
currentPosition = .upperLeftCorner
|
||||||
? adjustedBounds.origin.x
|
}
|
||||||
: bounds.size.width - insets.right - currentSize.width
|
|
||||||
let finalPosY: CGFloat =
|
|
||||||
goUp
|
|
||||||
? adjustedBounds.origin.y
|
|
||||||
: bounds.size.height - insets.bottom - currentSize.height
|
|
||||||
|
|
||||||
return CGPoint(x: finalPosX, y: finalPosY)
|
if (!goLeft && goUp) {
|
||||||
|
currentPosition = .upperRightCorner
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!goLeft && !goUp) {
|
||||||
|
currentPosition = .lowerRightCorner
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goLeft && !goUp) {
|
||||||
|
currentPosition = .lowerLeftCorner
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOriginFor(position: currentPosition!, inBounds: adjustedBounds, withSize: currentSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func magnitude(vector: CGPoint) -> CGFloat {
|
private func magnitude(vector: CGPoint) -> CGFloat {
|
||||||
return sqrt(pow(vector.x, 2) + pow(vector.y, 2))
|
sqrt(pow(vector.x, 2) + pow(vector.y, 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,43 +19,42 @@ import UIKit
|
||||||
public typealias AnimationCompletion = (Bool) -> Void
|
public typealias AnimationCompletion = (Bool) -> Void
|
||||||
|
|
||||||
public protocol PiPViewCoordinatorDelegate: class {
|
public protocol PiPViewCoordinatorDelegate: class {
|
||||||
|
|
||||||
func exitPictureInPicture()
|
func exitPictureInPicture()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Coordinates the view state of a specified view to allow
|
/// Coordinates the view state of a specified view to allow
|
||||||
/// to be presented in full screen or in a custom Picture in Picture mode.
|
/// to be presented in full screen or in a custom Picture in Picture mode.
|
||||||
/// This object will also provide the drag and tap interactions of the view
|
/// This object will also provide the drag and tap interactions of the view
|
||||||
/// when is presented in Picure in Picture mode.
|
/// when is presented in Picture in Picture mode.
|
||||||
public class PiPViewCoordinator {
|
public class PiPViewCoordinator {
|
||||||
|
|
||||||
/// Limits the boundaries of view position on screen when minimized
|
|
||||||
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
|
|
||||||
left: 5,
|
|
||||||
bottom: 5,
|
|
||||||
right: 5) {
|
|
||||||
didSet {
|
|
||||||
dragController.insets = dragBoundInsets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Position {
|
public enum Position {
|
||||||
case lowerRightCorner
|
case lowerRightCorner
|
||||||
case upperRightCorner
|
case upperRightCorner
|
||||||
case lowerLeftCorner
|
case lowerLeftCorner
|
||||||
case upperLeftCorner
|
case upperLeftCorner
|
||||||
}
|
}
|
||||||
|
|
||||||
public var initialPositionInSuperview = Position.lowerRightCorner
|
/// Limits the boundaries of view position on screen when minimized
|
||||||
|
public var dragBoundInsets: UIEdgeInsets = UIEdgeInsets(top: 25,
|
||||||
|
left: 5,
|
||||||
|
bottom: 5,
|
||||||
|
right: 5) {
|
||||||
|
didSet {
|
||||||
|
dragController.insets = dragBoundInsets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let initialPositionInSuperView: Position = .lowerRightCorner
|
||||||
|
|
||||||
// Unused. Remove on the next major release.
|
// Unused. Remove on the next major release.
|
||||||
@available(*, deprecated, message: "The PiP window size is now fixed to 150px.")
|
@available(*, deprecated, message: "The PiP window size is now fixed to 150px.")
|
||||||
public var c: CGFloat = 0.0
|
public var c: CGFloat = 0.0
|
||||||
|
|
||||||
public weak var delegate: PiPViewCoordinatorDelegate?
|
public weak var delegate: PiPViewCoordinatorDelegate?
|
||||||
|
|
||||||
private(set) var isInPiP: Bool = false // true if view is in PiP mode
|
private(set) var isInPiP: Bool = false // true if view is in PiP mode
|
||||||
|
|
||||||
private(set) var view: UIView
|
private(set) var view: UIView
|
||||||
private var currentBounds: CGRect = CGRect.zero
|
private var currentBounds: CGRect = CGRect.zero
|
||||||
|
|
||||||
|
@ -66,6 +65,13 @@ public class PiPViewCoordinator {
|
||||||
|
|
||||||
public init(withView view: UIView) {
|
public init(withView view: UIView) {
|
||||||
self.view = view
|
self.view = view
|
||||||
|
// Required because otherwise the view will not rotate correctly.
|
||||||
|
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
|
||||||
|
// Otherwise the enter/exit pip animation looks odd
|
||||||
|
// when pip window is bottom left, top left or top right,
|
||||||
|
// because the jitsi view content does not animate, but jumps to the new size immediately.
|
||||||
|
view.clipsToBounds = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure the view to be always on top of all the contents
|
/// Configure the view to be always on top of all the contents
|
||||||
|
@ -73,8 +79,10 @@ public class PiPViewCoordinator {
|
||||||
/// If a parentView is not provided it will try to use the main window
|
/// If a parentView is not provided it will try to use the main window
|
||||||
public func configureAsStickyView(withParentView parentView: UIView? = nil) {
|
public func configureAsStickyView(withParentView parentView: UIView? = nil) {
|
||||||
guard
|
guard
|
||||||
let parentView = parentView ?? UIApplication.shared.keyWindow
|
let parentView = parentView ?? UIApplication.shared.keyWindow
|
||||||
else { return }
|
else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
parentView.addSubview(view)
|
parentView.addSubview(view)
|
||||||
currentBounds = parentView.bounds
|
currentBounds = parentView.bounds
|
||||||
|
@ -109,14 +117,19 @@ public class PiPViewCoordinator {
|
||||||
/// around screen, and add a button of top of the view to be able to exit mode
|
/// around screen, and add a button of top of the view to be able to exit mode
|
||||||
public func enterPictureInPicture() {
|
public func enterPictureInPicture() {
|
||||||
isInPiP = true
|
isInPiP = true
|
||||||
|
// Resizing is done by hand when in pip.
|
||||||
|
view.autoresizingMask = []
|
||||||
|
|
||||||
animateViewChange()
|
animateViewChange()
|
||||||
dragController.startDragListener(inView: view)
|
dragController.startDragListener(inView: view)
|
||||||
dragController.insets = dragBoundInsets
|
dragController.insets = dragBoundInsets
|
||||||
|
|
||||||
// add single tap gesture recognition for displaying exit PiP UI
|
// add single tap gesture recognition for displaying exit PiP UI
|
||||||
let exitSelector = #selector(toggleExitPiP)
|
let exitSelector = #selector(toggleExitPiP)
|
||||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
|
let tapGestureRecognizer = UITapGestureRecognizer(
|
||||||
action: exitSelector)
|
target: self,
|
||||||
|
action: exitSelector
|
||||||
|
)
|
||||||
self.tapGestureRecognizer = tapGestureRecognizer
|
self.tapGestureRecognizer = tapGestureRecognizer
|
||||||
view.addGestureRecognizer(tapGestureRecognizer)
|
view.addGestureRecognizer(tapGestureRecognizer)
|
||||||
}
|
}
|
||||||
|
@ -125,6 +138,9 @@ public class PiPViewCoordinator {
|
||||||
/// exit pip button, and disable the drag gesture
|
/// exit pip button, and disable the drag gesture
|
||||||
@objc public func exitPictureInPicture() {
|
@objc public func exitPictureInPicture() {
|
||||||
isInPiP = false
|
isInPiP = false
|
||||||
|
// Enable autoresizing again, which got disabled for pip.
|
||||||
|
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
|
||||||
animateViewChange()
|
animateViewChange()
|
||||||
dragController.stopDragListener()
|
dragController.stopDragListener()
|
||||||
|
|
||||||
|
@ -136,7 +152,7 @@ public class PiPViewCoordinator {
|
||||||
let exitSelector = #selector(toggleExitPiP)
|
let exitSelector = #selector(toggleExitPiP)
|
||||||
tapGestureRecognizer?.removeTarget(self, action: exitSelector)
|
tapGestureRecognizer?.removeTarget(self, action: exitSelector)
|
||||||
tapGestureRecognizer = nil
|
tapGestureRecognizer = nil
|
||||||
|
|
||||||
delegate?.exitPictureInPicture()
|
delegate?.exitPictureInPicture()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +160,12 @@ public class PiPViewCoordinator {
|
||||||
/// screen size changes
|
/// screen size changes
|
||||||
public func resetBounds(bounds: CGRect) {
|
public func resetBounds(bounds: CGRect) {
|
||||||
currentBounds = bounds
|
currentBounds = bounds
|
||||||
|
|
||||||
|
// Is required because otherwise the pip window is buggy when rotating the device.
|
||||||
|
// When not in pip then autoresize will do the job.
|
||||||
|
if (isInPiP) {
|
||||||
|
view.frame = changeViewRect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the dragging gesture of the root view
|
/// Stop the dragging gesture of the root view
|
||||||
|
@ -155,8 +177,8 @@ public class PiPViewCoordinator {
|
||||||
open func configureExitPiPButton(target: Any,
|
open func configureExitPiPButton(target: Any,
|
||||||
action: Selector) -> UIButton {
|
action: Selector) -> UIButton {
|
||||||
let buttonImage = UIImage.init(named: "image-resize",
|
let buttonImage = UIImage.init(named: "image-resize",
|
||||||
in: Bundle(for: type(of: self)),
|
in: Bundle(for: type(of: self)),
|
||||||
compatibleWith: nil)
|
compatibleWith: nil)
|
||||||
let button = UIButton(type: .custom)
|
let button = UIButton(type: .custom)
|
||||||
let size: CGSize = CGSize(width: 44, height: 44)
|
let size: CGSize = CGSize(width: 44, height: 44)
|
||||||
button.setImage(buttonImage, for: .normal)
|
button.setImage(buttonImage, for: .normal)
|
||||||
|
@ -169,13 +191,14 @@ public class PiPViewCoordinator {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Interactions
|
// MARK: - Interactions
|
||||||
|
|
||||||
@objc private func toggleExitPiP() {
|
@objc private func toggleExitPiP() {
|
||||||
if exitPiPButton == nil {
|
if exitPiPButton == nil {
|
||||||
// show button
|
// show button
|
||||||
let exitSelector = #selector(exitPictureInPicture)
|
let exitSelector = #selector(exitPictureInPicture)
|
||||||
let button = configureExitPiPButton(target: self,
|
let button = configureExitPiPButton(
|
||||||
action: exitSelector)
|
target: self,
|
||||||
|
action: exitSelector
|
||||||
|
)
|
||||||
view.addSubview(button)
|
view.addSubview(button)
|
||||||
exitPiPButton = button
|
exitPiPButton = button
|
||||||
|
|
||||||
|
@ -186,51 +209,53 @@ public class PiPViewCoordinator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Size calculation
|
func animateViewChange() {
|
||||||
|
|
||||||
private func animateViewChange() {
|
|
||||||
UIView.animate(withDuration: 0.25) {
|
UIView.animate(withDuration: 0.25) {
|
||||||
self.view.frame = self.changeViewRect()
|
self.view.frame = self.changeViewRect()
|
||||||
self.view.setNeedsLayout()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func changeViewRect() -> CGRect {
|
private func changeViewRect() -> CGRect {
|
||||||
let bounds = currentBounds
|
let bounds = currentBounds
|
||||||
|
|
||||||
guard isInPiP else {
|
if !isInPiP {
|
||||||
return bounds
|
return bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize to suggested ratio and position to the bottom right
|
// resize to suggested ratio and position to the bottom right
|
||||||
let adjustedBounds = bounds.inset(by: dragBoundInsets)
|
let adjustedBounds = bounds.inset(by: dragBoundInsets)
|
||||||
let size = CGSize(width: 150, height: 150)
|
let size = CGSize(width: 150, height: 150)
|
||||||
let origin = initialPositionFor(pipSize: size, bounds: adjustedBounds)
|
let origin = getOriginFor(
|
||||||
|
position: dragController.currentPosition ?? initialPositionInSuperView,
|
||||||
|
inBounds: adjustedBounds,
|
||||||
|
withSize: size
|
||||||
|
)
|
||||||
return CGRect(x: origin.x, y: origin.y, width: size.width, height: size.height)
|
return CGRect(x: origin.x, y: origin.y, width: size.width, height: size.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func initialPositionFor(pipSize size: CGSize, bounds: CGRect) -> CGPoint {
|
|
||||||
switch initialPositionInSuperview {
|
|
||||||
case .lowerLeftCorner:
|
|
||||||
return CGPoint(x: bounds.minX, y: bounds.maxY - size.height)
|
|
||||||
case .lowerRightCorner:
|
|
||||||
return CGPoint(x: bounds.maxX - size.width, y: bounds.maxY - size.height)
|
|
||||||
case .upperLeftCorner:
|
|
||||||
return CGPoint(x: bounds.minX, y: bounds.minY)
|
|
||||||
case .upperRightCorner:
|
|
||||||
return CGPoint(x: bounds.maxX - size.width, y: bounds.minY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Animation helpers
|
// MARK: - Animation helpers
|
||||||
|
|
||||||
private func animateTransition(animations: @escaping () -> Void,
|
private func animateTransition(animations: @escaping () -> Void,
|
||||||
completion: AnimationCompletion?) {
|
completion: AnimationCompletion?) {
|
||||||
UIView.animate(withDuration: 0.1,
|
UIView.animate(
|
||||||
delay: 0,
|
withDuration: 0.1,
|
||||||
options: .beginFromCurrentState,
|
delay: 0,
|
||||||
animations: animations,
|
options: .beginFromCurrentState,
|
||||||
completion: completion)
|
animations: animations,
|
||||||
|
completion: completion
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOriginFor(position: PiPViewCoordinator.Position, inBounds bounds: CGRect, withSize size: CGSize) -> CGPoint {
|
||||||
|
switch position {
|
||||||
|
case .lowerLeftCorner:
|
||||||
|
return CGPoint(x: bounds.minX, y: bounds.maxY - size.height)
|
||||||
|
case .lowerRightCorner:
|
||||||
|
return CGPoint(x: bounds.maxX - size.width, y: bounds.maxY - size.height)
|
||||||
|
case .upperLeftCorner:
|
||||||
|
return CGPoint(x: bounds.minX, y: bounds.minY)
|
||||||
|
case .upperRightCorner:
|
||||||
|
return CGPoint(x: bounds.maxX - size.width, y: bounds.minY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,7 @@ import {
|
||||||
storeConfig
|
storeConfig
|
||||||
} from '../base/config';
|
} from '../base/config';
|
||||||
import { connect, disconnect, setLocationURL } from '../base/connection';
|
import { connect, disconnect, setLocationURL } from '../base/connection';
|
||||||
import { loadConfig } from '../base/lib-jitsi-meet';
|
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
|
||||||
import { createDesiredLocalTracks } from '../base/tracks';
|
import { createDesiredLocalTracks } from '../base/tracks';
|
||||||
import {
|
import {
|
||||||
getBackendSafeRoomName,
|
getBackendSafeRoomName,
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import '../authentication/middleware';
|
import '../authentication/middleware';
|
||||||
import '../mobile/audio-mode/middleware';
|
import '../mobile/audio-mode/middleware';
|
||||||
import '../mobile/back-button/middleware';
|
|
||||||
import '../mobile/background/middleware';
|
import '../mobile/background/middleware';
|
||||||
import '../mobile/call-integration/middleware';
|
import '../mobile/call-integration/middleware';
|
||||||
import '../mobile/external-api/middleware';
|
import '../mobile/external-api/middleware';
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
import React, { PureComponent, type Node } from 'react';
|
import React, { PureComponent, type Node } from 'react';
|
||||||
import {
|
import {
|
||||||
Animated,
|
Animated,
|
||||||
|
BackHandler,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
TouchableWithoutFeedback,
|
TouchableWithoutFeedback,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
import { BackButtonRegistry } from '../../../../mobile/back-button';
|
|
||||||
import { type StyleType } from '../../../styles';
|
import { type StyleType } from '../../../styles';
|
||||||
|
|
||||||
import styles from './slidingviewstyles';
|
import styles from './slidingviewstyles';
|
||||||
|
@ -121,7 +121,7 @@ export default class SlidingView extends PureComponent<Props, State> {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
BackButtonRegistry.addListener(this._onHardwareBackPress, true);
|
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||||
|
|
||||||
this._mounted = true;
|
this._mounted = true;
|
||||||
this._setShow(this.props.show);
|
this._setShow(this.props.show);
|
||||||
|
@ -146,7 +146,7 @@ export default class SlidingView extends PureComponent<Props, State> {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
BackButtonRegistry.removeListener(this._onHardwareBackPress);
|
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||||
|
|
||||||
this._mounted = false;
|
this._mounted = false;
|
||||||
}
|
}
|
||||||
|
@ -229,13 +229,9 @@ export default class SlidingView extends PureComponent<Props, State> {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
_onHardwareBackPress() {
|
_onHardwareBackPress() {
|
||||||
const { onHide } = this.props;
|
this._onHide();
|
||||||
|
|
||||||
if (typeof onHide === 'function') {
|
return true;
|
||||||
return onHide();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onHide: () => void;
|
_onHide: () => void;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
||||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
import { appNavigate } from '../../../app/actions';
|
import { appNavigate } from '../../../app/actions';
|
||||||
|
@ -22,7 +22,6 @@ import { CalleeInfoContainer } from '../../../invite';
|
||||||
import { LargeVideo } from '../../../large-video';
|
import { LargeVideo } from '../../../large-video';
|
||||||
import { KnockingParticipantList } from '../../../lobby/components/native';
|
import { KnockingParticipantList } from '../../../lobby/components/native';
|
||||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||||
import { BackButtonRegistry } from '../../../mobile/back-button';
|
|
||||||
import { navigate }
|
import { navigate }
|
||||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||||
import { screen } from '../../../mobile/navigation/routes';
|
import { screen } from '../../../mobile/navigation/routes';
|
||||||
|
@ -166,7 +165,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
BackButtonRegistry.addListener(this._onHardwareBackPress);
|
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,7 +195,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||||
*/
|
*/
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
// Tear handling any hardware button presses for back navigation down.
|
// Tear handling any hardware button presses for back navigation down.
|
||||||
BackButtonRegistry.removeListener(this._onHardwareBackPress);
|
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||||
|
|
||||||
clearTimeout(this._expandedLabelTimeout.current);
|
clearTimeout(this._expandedLabelTimeout.current);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// @flow
|
import { batch } from 'react-redux';
|
||||||
|
|
||||||
import { type Dispatch } from 'redux';
|
|
||||||
|
|
||||||
import { appNavigate } from '../app/actions';
|
import { appNavigate } from '../app/actions';
|
||||||
|
|
||||||
|
import { hideLobbyScreen, setKnockingState } from './actions.any';
|
||||||
|
|
||||||
export * from './actions.any';
|
export * from './actions.any';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,8 +12,11 @@ export * from './actions.any';
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function cancelKnocking() {
|
export function cancelKnocking() {
|
||||||
return async (dispatch: Dispatch<any>) => {
|
return dispatch => {
|
||||||
dispatch(appNavigate(undefined));
|
batch(() => {
|
||||||
|
dispatch(setKnockingState(false));
|
||||||
|
dispatch(hideLobbyScreen());
|
||||||
|
dispatch(appNavigate(undefined));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ export function showLobbyChatButton(
|
||||||
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
|
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
|
||||||
const conference = getCurrentConference(state);
|
const conference = getCurrentConference(state);
|
||||||
|
|
||||||
const lobbyLocalId = conference.myLobbyUserId();
|
const lobbyLocalId = conference?.myLobbyUserId();
|
||||||
|
|
||||||
if (!enableLobbyChat) {
|
if (!enableLobbyChat) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An registry that dispatches hardware back button events for subscribers with a custom logic.
|
|
||||||
*/
|
|
||||||
class BackButtonRegistry {
|
|
||||||
_listeners: Array<Function>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new instance of the registry.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this._listeners = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a listener to the registry.
|
|
||||||
*
|
|
||||||
* NOTE: Due to the different order of component mounts, we allow a component to register
|
|
||||||
* its listener to the top of the list, so then that will be invoked before other, 'non-top'
|
|
||||||
* listeners. For example a 'non-top' listener can be the one that puts the app into PiP mode,
|
|
||||||
* while a 'top' listener is the one that closes a modal in a conference.
|
|
||||||
*
|
|
||||||
* @param {Function} listener - The listener function.
|
|
||||||
* @param {boolean?} top - If true, the listener will be put on the top (eg for modal-like components).
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
addListener(listener: Function, top: boolean = false) {
|
|
||||||
if (top) {
|
|
||||||
this._listeners.splice(0, 0, listener);
|
|
||||||
} else {
|
|
||||||
this._listeners.push(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a listener from the registry.
|
|
||||||
*
|
|
||||||
* @param {Function} listener - The listener to remove.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
removeListener(listener: Function) {
|
|
||||||
this._listeners = this._listeners.filter(f => f !== listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
onHardwareBackPress: () => boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for the back button press event.
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
onHardwareBackPress() {
|
|
||||||
for (const listener of this._listeners) {
|
|
||||||
const result = listener();
|
|
||||||
|
|
||||||
if (result === true) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new BackButtonRegistry();
|
|
|
@ -1,3 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
export { default as BackButtonRegistry } from './BackButtonRegistry';
|
|
|
@ -1,36 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import { BackHandler } from 'react-native';
|
|
||||||
|
|
||||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
|
|
||||||
import { MiddlewareRegistry } from '../../base/redux';
|
|
||||||
|
|
||||||
import BackButtonRegistry from './BackButtonRegistry';
|
|
||||||
|
|
||||||
// Binding function to the proper instance, so then the event emitter won't replace the
|
|
||||||
// underlying instance.
|
|
||||||
BackButtonRegistry.onHardwareBackPress = BackButtonRegistry.onHardwareBackPress.bind(BackButtonRegistry);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware that captures App lifetime actions and subscribes to application
|
|
||||||
* state changes. When the application state changes it will fire the action
|
|
||||||
* required to mute or unmute the local video in case the application goes to
|
|
||||||
* the background or comes back from it.
|
|
||||||
*
|
|
||||||
* @param {Store} store - The redux store.
|
|
||||||
* @returns {Function}
|
|
||||||
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
|
|
||||||
*/
|
|
||||||
MiddlewareRegistry.register(() => next => action => {
|
|
||||||
switch (action.type) {
|
|
||||||
case APP_WILL_MOUNT:
|
|
||||||
BackHandler.addEventListener('hardwareBackPress', BackButtonRegistry.onHardwareBackPress);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case APP_WILL_UNMOUNT:
|
|
||||||
BackHandler.removeEventListener('hardwareBackPress', BackButtonRegistry.onHardwareBackPress);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(action);
|
|
||||||
});
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
import { BoxModel } from '../../../base/styles';
|
import { BoxModel } from '../../../base/styles';
|
||||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||||
|
|
||||||
|
@ -11,13 +9,12 @@ export const TEXT_COLOR = BaseTheme.palette.text01;
|
||||||
*/
|
*/
|
||||||
export const navigationStyles = {
|
export const navigationStyles = {
|
||||||
connectingScreenContainer: {
|
connectingScreenContainer: {
|
||||||
|
backgroundColor: BaseTheme.palette.uiBackground,
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
|
|
||||||
connectingScreenContent: {
|
connectingScreenContent: {
|
||||||
...StyleSheet.absoluteFillObject,
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: BaseTheme.palette.uiBackground,
|
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
|
|
|
@ -71,8 +71,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
transparentButton: {
|
transparentButton: {
|
||||||
...baseButton,
|
backgroundColor: 'transparent',
|
||||||
backgroundColor: 'transparent'
|
marginTop: BaseTheme.spacing[3]
|
||||||
},
|
},
|
||||||
|
|
||||||
leaveButtonLabel: {
|
leaveButtonLabel: {
|
||||||
|
|
|
@ -31,7 +31,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollapsibleList = ({ children, containerStyle, onLongPress, title }: Props) => {
|
const CollapsibleList = ({ children, containerStyle, onLongPress, title }: Props) => {
|
||||||
const [ collapsed, setCollapsed ] = useState(true);
|
const [ collapsed, setCollapsed ] = useState(false);
|
||||||
const _toggleCollapsed = useCallback(() => {
|
const _toggleCollapsed = useCallback(() => {
|
||||||
setCollapsed(!collapsed);
|
setCollapsed(!collapsed);
|
||||||
}, [ collapsed ]);
|
}, [ collapsed ]);
|
||||||
|
|
|
@ -50,11 +50,21 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_sortedRemoteParticipants: Map<string, string>,
|
_sortedRemoteParticipants: Map<string, string>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of breakout rooms that were created.
|
||||||
|
*/
|
||||||
|
breakoutRooms: Array,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The redux dispatch function.
|
* The redux dispatch function.
|
||||||
*/
|
*/
|
||||||
dispatch: Function,
|
dispatch: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of participants waiting in lobby.
|
||||||
|
*/
|
||||||
|
lobbyParticipants: Array,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Participants search string.
|
* Participants search string.
|
||||||
*/
|
*/
|
||||||
|
@ -180,6 +190,8 @@ class MeetingParticipantList extends PureComponent<Props> {
|
||||||
_participantsCount,
|
_participantsCount,
|
||||||
_showInviteButton,
|
_showInviteButton,
|
||||||
_sortedRemoteParticipants,
|
_sortedRemoteParticipants,
|
||||||
|
breakoutRooms,
|
||||||
|
lobbyParticipants,
|
||||||
t
|
t
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const title = _currentRoom?.name
|
const title = _currentRoom?.name
|
||||||
|
@ -192,12 +204,19 @@ class MeetingParticipantList extends PureComponent<Props> {
|
||||||
// Regarding the fact that we have 3 sections, we apply
|
// Regarding the fact that we have 3 sections, we apply
|
||||||
// a certain height percentage for every section in order for all to fit
|
// a certain height percentage for every section in order for all to fit
|
||||||
// inside the participants pane container
|
// inside the participants pane container
|
||||||
|
// If there are only meeting participants available,
|
||||||
|
// we take the full container height
|
||||||
|
const onlyMeetingParticipants
|
||||||
|
= breakoutRooms?.length === 0 && lobbyParticipants.length === 0;
|
||||||
const containerStyle
|
const containerStyle
|
||||||
= _participantsCount > 3 && styles.meetingListContainer;
|
= onlyMeetingParticipants
|
||||||
|
? styles.meetingListFullContainer : styles.meetingListContainer;
|
||||||
|
const finalContainerStyle
|
||||||
|
= _participantsCount > 3 && containerStyle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleList
|
<CollapsibleList
|
||||||
containerStyle = { containerStyle }
|
containerStyle = { finalContainerStyle }
|
||||||
title = { title } >
|
title = { title } >
|
||||||
{
|
{
|
||||||
_showInviteButton
|
_showInviteButton
|
||||||
|
|
|
@ -19,6 +19,10 @@ import {
|
||||||
getCurrentRoomId,
|
getCurrentRoomId,
|
||||||
isInBreakoutRoom
|
isInBreakoutRoom
|
||||||
} from '../../../breakout-rooms/functions';
|
} from '../../../breakout-rooms/functions';
|
||||||
|
import {
|
||||||
|
getKnockingParticipants,
|
||||||
|
getLobbyEnabled
|
||||||
|
} from '../../../lobby/functions';
|
||||||
import MuteEveryoneDialog
|
import MuteEveryoneDialog
|
||||||
from '../../../video-menu/components/native/MuteEveryoneDialog';
|
from '../../../video-menu/components/native/MuteEveryoneDialog';
|
||||||
import {
|
import {
|
||||||
|
@ -63,11 +67,15 @@ const ParticipantsPane = () => {
|
||||||
&& participantsCount > 2 && rooms.length > 1;
|
&& participantsCount > 2 && rooms.length > 1;
|
||||||
const addBreakoutRoom
|
const addBreakoutRoom
|
||||||
= _isBreakoutRoomsSupported && !hideAddRoomButton && isLocalModerator;
|
= _isBreakoutRoomsSupported && !hideAddRoomButton && isLocalModerator;
|
||||||
|
const lobbyEnabled = useSelector(getLobbyEnabled);
|
||||||
|
const lobbyParticipants = useSelector(getKnockingParticipants);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JitsiScreen style = { styles.participantsPaneContainer }>
|
<JitsiScreen style = { styles.participantsPaneContainer }>
|
||||||
<LobbyParticipantList />
|
<LobbyParticipantList />
|
||||||
<MeetingParticipantList
|
<MeetingParticipantList
|
||||||
|
breakoutRooms = { _isBreakoutRoomsSupported && rooms }
|
||||||
|
lobbyParticipants = { lobbyEnabled && lobbyParticipants }
|
||||||
searchString = { searchString }
|
searchString = { searchString }
|
||||||
setSearchString = { setSearchString } />
|
setSearchString = { setSearchString } />
|
||||||
{
|
{
|
||||||
|
|
|
@ -196,7 +196,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
lobbyListContent: {
|
lobbyListContent: {
|
||||||
height: '20%'
|
height: '16%'
|
||||||
},
|
},
|
||||||
|
|
||||||
lobbyListDescription: {
|
lobbyListDescription: {
|
||||||
|
@ -217,7 +217,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
meetingListContainer: {
|
meetingListContainer: {
|
||||||
height: '60%'
|
height: '56%'
|
||||||
|
},
|
||||||
|
|
||||||
|
meetingListFullContainer: {
|
||||||
|
height: '82%'
|
||||||
},
|
},
|
||||||
|
|
||||||
meetingListDescription: {
|
meetingListDescription: {
|
||||||
|
|
|
@ -379,10 +379,7 @@ class StartRecordingDialogContent extends Component<Props> {
|
||||||
<Container>
|
<Container>
|
||||||
<Container
|
<Container
|
||||||
className = 'recording-header recording-header-line'
|
className = 'recording-header recording-header-line'
|
||||||
style = { [
|
style = { styles.headerIntegrations }>
|
||||||
styles.headerIntegrations,
|
|
||||||
_dialogStyles.topBorderContainer
|
|
||||||
] }>
|
|
||||||
<Container
|
<Container
|
||||||
className = 'recording-icon-container'>
|
className = 'recording-icon-container'>
|
||||||
<Image
|
<Image
|
||||||
|
|
|
@ -9,6 +9,7 @@ import HeaderNavigationButton
|
||||||
from '../../../../mobile/navigation/components/HeaderNavigationButton';
|
from '../../../../mobile/navigation/components/HeaderNavigationButton';
|
||||||
import { goBack } from
|
import { goBack } from
|
||||||
'../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
'../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||||
|
import { RECORDING_TYPES } from '../../../constants';
|
||||||
import AbstractStartRecordingDialog, {
|
import AbstractStartRecordingDialog, {
|
||||||
type Props,
|
type Props,
|
||||||
mapStateToProps
|
mapStateToProps
|
||||||
|
@ -43,30 +44,38 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
super.componentDidMount();
|
||||||
_fileRecordingsServiceEnabled,
|
|
||||||
_isDropboxEnabled,
|
|
||||||
navigation,
|
|
||||||
t
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
const { navigation, t } = this.props;
|
||||||
isTokenValid,
|
|
||||||
isValidating
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
// disable start button id recording service is shown only, when
|
|
||||||
// validating dropbox token, if that is not enabled we either always
|
|
||||||
// show the start button or if just dropbox is enabled start is available
|
|
||||||
// when there is token
|
|
||||||
const isStartDisabled
|
|
||||||
= _fileRecordingsServiceEnabled ? isValidating
|
|
||||||
: _isDropboxEnabled ? !isTokenValid : false;
|
|
||||||
|
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<HeaderNavigationButton
|
<HeaderNavigationButton
|
||||||
disabled = { isStartDisabled }
|
disabled = { this.isStartRecordingDisabled() }
|
||||||
|
label = { t('dialog.start') }
|
||||||
|
onPress = { this._onStartPress }
|
||||||
|
twoActions = { true } />
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#componentDidUpdate()}. Invoked
|
||||||
|
* immediately after this component is updated.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
super.componentDidUpdate(prevProps);
|
||||||
|
|
||||||
|
const { navigation, t } = this.props;
|
||||||
|
|
||||||
|
navigation.setOptions({
|
||||||
|
// eslint-disable-next-line react/no-multi-comp
|
||||||
|
headerRight: () => (
|
||||||
|
<HeaderNavigationButton
|
||||||
|
disabled = { this.isStartRecordingDisabled() }
|
||||||
label = { t('dialog.start') }
|
label = { t('dialog.start') }
|
||||||
onPress = { this._onStartPress }
|
onPress = { this._onStartPress }
|
||||||
twoActions = { true } />
|
twoActions = { true } />
|
||||||
|
@ -85,6 +94,30 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
|
||||||
this._onSubmit() && goBack();
|
this._onSubmit() && goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isStartRecordingDisabled: () => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables start recording button.
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isStartRecordingDisabled() {
|
||||||
|
const { isTokenValid, selectedRecordingService } = this.state;
|
||||||
|
|
||||||
|
// Start button is disabled if recording service is only shown;
|
||||||
|
// When validating dropbox token, if that is not enabled, we either always
|
||||||
|
// show the start button or, if just dropbox is enabled, start button
|
||||||
|
// is available when there is token.
|
||||||
|
if (selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) {
|
||||||
|
return false;
|
||||||
|
} else if (selectedRecordingService === RECORDING_TYPES.DROPBOX) {
|
||||||
|
return !isTokenValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#render()}.
|
* Implements React's {@link Component#render()}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -42,7 +42,8 @@ const title = {
|
||||||
const baseHighlightDialogButton = {
|
const baseHighlightDialogButton = {
|
||||||
borderRadius: BaseTheme.shape.borderRadius,
|
borderRadius: BaseTheme.shape.borderRadius,
|
||||||
height: BaseTheme.spacing[7],
|
height: BaseTheme.spacing[7],
|
||||||
flex: 1
|
flex: 1,
|
||||||
|
justifyContent: 'space-around'
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseHighlightDialogLabel = {
|
const baseHighlightDialogLabel = {
|
||||||
|
@ -101,7 +102,7 @@ export default {
|
||||||
},
|
},
|
||||||
highlightDialogButtonsContainer: {
|
highlightDialogButtonsContainer: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row'
|
flexDirection: 'column-reverse'
|
||||||
},
|
},
|
||||||
highlightDialogCancelButton: {
|
highlightDialogCancelButton: {
|
||||||
...baseHighlightDialogButton,
|
...baseHighlightDialogButton,
|
||||||
|
@ -120,8 +121,8 @@ export default {
|
||||||
color: BaseTheme.palette.text01
|
color: BaseTheme.palette.text01
|
||||||
},
|
},
|
||||||
highlightDialogButtonsSpace: {
|
highlightDialogButtonsSpace: {
|
||||||
width: 16,
|
height: 16,
|
||||||
height: '100%'
|
width: '100%'
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { translate } from '../../../../base/i18n';
|
||||||
import { connect } from '../../../../base/redux';
|
import { connect } from '../../../../base/redux';
|
||||||
import { toggleScreenshotCaptureSummary } from '../../../../screenshot-capture';
|
import { toggleScreenshotCaptureSummary } from '../../../../screenshot-capture';
|
||||||
import { isScreenshotCaptureEnabled } from '../../../../screenshot-capture/functions';
|
import { isScreenshotCaptureEnabled } from '../../../../screenshot-capture/functions';
|
||||||
|
import { RECORDING_TYPES } from '../../../constants';
|
||||||
import AbstractStartRecordingDialog, {
|
import AbstractStartRecordingDialog, {
|
||||||
mapStateToProps as abstractMapStateToProps
|
mapStateToProps as abstractMapStateToProps
|
||||||
} from '../AbstractStartRecordingDialog';
|
} from '../AbstractStartRecordingDialog';
|
||||||
|
@ -19,6 +20,30 @@ import StartRecordingDialogContent from '../StartRecordingDialogContent';
|
||||||
* @augments Component
|
* @augments Component
|
||||||
*/
|
*/
|
||||||
class StartRecordingDialog extends AbstractStartRecordingDialog {
|
class StartRecordingDialog extends AbstractStartRecordingDialog {
|
||||||
|
|
||||||
|
isStartRecordingDisabled: () => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables start recording button.
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isStartRecordingDisabled() {
|
||||||
|
const { isTokenValid, selectedRecordingService } = this.state;
|
||||||
|
|
||||||
|
// Start button is disabled if recording service is only shown;
|
||||||
|
// When validating dropbox token, if that is not enabled, we either always
|
||||||
|
// show the start button or, if just dropbox is enabled, start button
|
||||||
|
// is available when there is token.
|
||||||
|
if (selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) {
|
||||||
|
return false;
|
||||||
|
} else if (selectedRecordingService === RECORDING_TYPES.DROPBOX) {
|
||||||
|
return !isTokenValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#render()}.
|
* Implements React's {@link Component#render()}.
|
||||||
*
|
*
|
||||||
|
@ -33,19 +58,14 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
|
||||||
spaceLeft,
|
spaceLeft,
|
||||||
userName
|
userName
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { _fileRecordingsServiceEnabled, _fileRecordingsServiceSharingEnabled, _isDropboxEnabled } = this.props;
|
const {
|
||||||
|
_fileRecordingsServiceEnabled,
|
||||||
// disable ok button id recording service is shown only, when
|
_fileRecordingsServiceSharingEnabled
|
||||||
// validating dropbox token, if that is not enabled we either always
|
} = this.props;
|
||||||
// show the ok button or if just dropbox is enabled ok is available
|
|
||||||
// when there is token
|
|
||||||
const isOkDisabled
|
|
||||||
= _fileRecordingsServiceEnabled ? isValidating
|
|
||||||
: _isDropboxEnabled ? !isTokenValid : false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
okDisabled = { isOkDisabled }
|
okDisabled = { this.isStartRecordingDisabled() }
|
||||||
okKey = 'dialog.startRecording'
|
okKey = 'dialog.startRecording'
|
||||||
onSubmit = { this._onSubmit }
|
onSubmit = { this._onSubmit }
|
||||||
titleKey = 'dialog.startRecording'
|
titleKey = 'dialog.startRecording'
|
||||||
|
|
|
@ -155,7 +155,7 @@ export async function sendMeetingHighlight(state: Object) {
|
||||||
|
|
||||||
const reqBody = {
|
const reqBody = {
|
||||||
meetingFqn: extractFqnFromPath(state),
|
meetingFqn: extractFqnFromPath(state),
|
||||||
sessionId: conference.sessionId,
|
sessionId: conference.getMeetingUniqueId(),
|
||||||
submitted: Date.now(),
|
submitted: Date.now(),
|
||||||
participantId: localParticipant.jwtId,
|
participantId: localParticipant.jwtId,
|
||||||
participantName: localParticipant.name,
|
participantName: localParticipant.name,
|
||||||
|
|
Loading…
Reference in New Issue