Compare commits

...

17 Commits

Author SHA1 Message Date
Alex Bumbu ba1b17a15c merge fixes 2022-05-05 16:30:09 +03:00
Tobias Marschall 8600d51ef1 remove old comment 2022-05-05 15:41:20 +03:00
Tobias Marschall 1793fb2884 clip to bounds to avoid ui glitch when entering or exiting pip mode 2022-05-05 15:41:20 +03:00
Tobias Marschall 3f274bac82 fix ios pip resizing and positioning 2022-05-05 15:41:20 +03:00
Saúl Ibarra Corretgé 1fcb8c8e44 fix(rn,config) fix loading config due to broken import 2022-05-03 17:18:58 +03:00
Gabriel Borlea 36d838cc13 Fixes for highlights mobile (#11209) 2022-04-05 14:39:27 +03:00
Saúl Ibarra Corretgé dbabdf3351 chore(rn,versions) bump app versions 2022-03-29 15:25:27 +02:00
Saúl Ibarra Corretgé 3bcde982ac chore(rn,versions) bump SDK versions 2022-03-29 15:24:08 +02:00
Saúl Ibarra Corretgé beb301e691 fix(android,back-button) rework back button handling on Android
We used to have a registry which registered a single handlerwith RN.
THis was registered really early in the app.

When React Navigation was introduced we ddidn't realize it interacts
with the back button. In a stack nagigator it will navigate to the
previous screen. This meant our back button handling was broken.

This commit removes our previous registry and uses the RN back button
handler directly in the 2 components that use it: the conference and
bottom sheets.

Since these handlers are registered after navigation, our handlers are
going to run first so we cna implement the behavior we need, namely to
dismiss an open botom sheet or set the conference in PiP mode.
2022-03-29 15:21:40 +02:00
Calin Chitu d0836ff651 feat(participants/native) - fix joining breakout room 2022-03-29 15:19:45 +02:00
Saúl Ibarra Corretgé c028511aaf fix(rn,lobby) fix lobby not showing up on subsequent tries
We need to make sure to hide it explicitly so the Redux state is in sync
with reality.
2022-03-29 15:19:44 +02:00
Saúl Ibarra Corretgé 664552bc05 chore(rn,versions) bump SDK versions 2022-03-28 11:22:36 +02:00
Saúl Ibarra Corretgé ac35eea08e feat(ios) enable Dropbox recording 2022-03-25 17:24:06 +01:00
Saúl Ibarra Corretgé 172683d645 fix(rn,recording) fix recording dialog state not updating 2022-03-25 17:23:30 +01:00
Calinteodor 858e83b09e fix(rn,recording) fix start button not being enabled 2022-03-25 11:53:30 +02:00
Calin Chitu e2750ee58e feat(participants/native) - updated container styles 2022-03-25 11:51:01 +02:00
Calin Chitu fe132581d4 fix(mobile/navigation) - fixed bottom color glitch 2022-03-23 19:09:52 +02:00
31 changed files with 321 additions and 20231 deletions

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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];

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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))
} }
} }

View File

@ -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)
}
}

19965
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -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';

View File

@ -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;

View File

@ -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);
} }

View File

@ -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));
});
}; };
} }

View File

@ -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;

View File

@ -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();

View File

@ -1,3 +0,0 @@
// @flow
export { default as BackButtonRegistry } from './BackButtonRegistry';

View File

@ -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);
});

View File

@ -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'

View File

@ -71,8 +71,8 @@ export default {
}, },
transparentButton: { transparentButton: {
...baseButton, backgroundColor: 'transparent',
backgroundColor: 'transparent' marginTop: BaseTheme.spacing[3]
}, },
leaveButtonLabel: { leaveButtonLabel: {

View File

@ -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 ]);

View File

@ -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

View File

@ -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 } />
{ {

View File

@ -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: {

View File

@ -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

View File

@ -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()}.
* *

View File

@ -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%'
} }
}; };

View File

@ -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'

View File

@ -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,