diff --git a/Riot/Modules/Room/Views/RemoveJitsiWidget/ArrowsAnimationView.swift b/Riot/Modules/Room/Views/RemoveJitsiWidget/ArrowsAnimationView.swift new file mode 100644 index 000000000..1c4c0c9d2 --- /dev/null +++ b/Riot/Modules/Room/Views/RemoveJitsiWidget/ArrowsAnimationView.swift @@ -0,0 +1,114 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class ArrowsAnimationView: UIView { + + private enum Constants { + static let numberOfArrows: Int = 3 + static let arrowSize: CGSize = CGSize(width: 14, height: 14) + static let gradientAnimationKey: String = "gradient" + static let gradientRatios: [CGFloat] = [1.0, 0.3, 0.2] + } + + private var gradientLayer: CAGradientLayer! + private lazy var gradientAnimation: CABasicAnimation = { + let animation = CABasicAnimation(keyPath: "locations") + animation.fromValue = [0.0, 0.0, 0.25] + animation.toValue = [0.75, 1.0, 1.0] + animation.repeatCount = .infinity + animation.duration = 1 + return animation + }() + private var theme: Theme = ThemeService.shared().theme + private var arrowImageViews: [UIImageView] = [] + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setup() + } + + private func setup() { + let arrowImage = Asset.Images.disclosureIcon.image + for i in 0.. Bool { + switch (lhs, rhs) { + case (.notStarted, .notStarted): + return true + case (let .sliding(percentage1), let .sliding(percentage2)): + return percentage1 == percentage2 + case (.completed, .completed): + return true + default: + return false + } + } + } + + @IBOutlet private weak var slidingViewLeadingConstraint: NSLayoutConstraint! + @IBOutlet private weak var slidingView: UIView! + @IBOutlet private weak var slidingViewLabel: UILabel! { + didSet { + slidingViewLabel.text = VectorL10n.roomSlideToEndGroupCall + } + } + @IBOutlet private weak var arrowsView: ArrowsAnimationView! + @IBOutlet private weak var hangupView: UIView! + @IBOutlet private weak var hangupImage: UIImageView! + @IBOutlet private weak var topSeparatorView: UIView! + @IBOutlet private weak var bottomSeparatorView: UIView! + + private var state: State = .notStarted + private var theme: Theme = ThemeService.shared().theme + + // MARK - Private + + private func configure(withState state: State) { + switch state { + case .notStarted: + arrowsView.isAnimating = false + hangupView.backgroundColor = .clear + hangupImage.tintColor = theme.noticeColor + slidingViewLeadingConstraint.constant = 0 + case .sliding(let percentage): + arrowsView.isAnimating = true + if percentage < Constants.activationThreshold { + hangupView.backgroundColor = .clear + hangupImage.tintColor = theme.noticeColor + } else { + hangupView.backgroundColor = theme.noticeColor + hangupImage.tintColor = theme.backgroundColor + } + slidingViewLeadingConstraint.constant = percentage * slidingView.frame.width + case .completed: + arrowsView.isAnimating = false + hangupView.backgroundColor = theme.noticeColor + hangupImage.tintColor = theme.backgroundColor + } + } + + private func updateState(to newState: State) { + guard newState != state else { + return + } + configure(withState: newState) + state = newState + + if state == .completed { + delegate?.removeJitsiWidgetViewDidCompleteSliding(self) + } + } + + // MARK: - Touch Handling + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + + if state == .notStarted { + updateState(to: .sliding(percentage: 0)) + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) + + switch state { + case .sliding: + if let touch = touches.first { + let touchLocation = touch.location(in: self) + if frame.contains(touchLocation) { + let percentage = touchLocation.x / frame.width + updateState(to: .sliding(percentage: percentage)) + } + } + default: + break + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + + switch state { + case .sliding(let percentage): + if percentage < Constants.activationThreshold { + updateState(to: .notStarted) + } else { + updateState(to: .completed) + } + default: + break + } + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + + switch state { + case .sliding(let percentage): + if percentage < Constants.activationThreshold { + updateState(to: .notStarted) + } else { + updateState(to: .completed) + } + default: + break + } + } + + // MARK: - API + + weak var delegate: RemoveJitsiWidgetViewDelegate? + + static func instantiate() -> RemoveJitsiWidgetView { + let view = RemoveJitsiWidgetView.loadFromNib() + view.update(theme: ThemeService.shared().theme) + return view + } + + func reset() { + updateState(to: .notStarted) + } + +} + +// MARK: - NibLoadable + +extension RemoveJitsiWidgetView: NibLoadable { } + +// MARK: - Themable + +extension RemoveJitsiWidgetView: Themable { + + func update(theme: Theme) { + self.theme = theme + + self.backgroundColor = theme.headerBackgroundColor + + slidingViewLabel.textColor = theme.textPrimaryColor + arrowsView.update(theme: theme) + topSeparatorView.backgroundColor = theme.lineBreakColor + bottomSeparatorView.backgroundColor = theme.lineBreakColor + + configure(withState: state) + } + +} diff --git a/Riot/Modules/Room/Views/RemoveJitsiWidget/RemoveJitsiWidgetView.xib b/Riot/Modules/Room/Views/RemoveJitsiWidget/RemoveJitsiWidgetView.xib new file mode 100644 index 000000000..6d2d62b0b --- /dev/null +++ b/Riot/Modules/Room/Views/RemoveJitsiWidget/RemoveJitsiWidgetView.xib @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/RemoveJitsiWidget/RemoveJitsiWidgetViewDelegate.swift b/Riot/Modules/Room/Views/RemoveJitsiWidget/RemoveJitsiWidgetViewDelegate.swift new file mode 100644 index 000000000..f48af79a1 --- /dev/null +++ b/Riot/Modules/Room/Views/RemoveJitsiWidget/RemoveJitsiWidgetViewDelegate.swift @@ -0,0 +1,25 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@objc +protocol RemoveJitsiWidgetViewDelegate: class { + + /// Tells the delegate that the user complete sliding on the view + /// - Parameter view: The view instance + func removeJitsiWidgetViewDidCompleteSliding(_ view: RemoveJitsiWidgetView) +}