Create slider view

This commit is contained in:
ismailgulek
2021-04-23 17:54:03 +03:00
parent 678fba8431
commit 81c80b97f9
4 changed files with 452 additions and 0 deletions
@@ -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..<Constants.numberOfArrows {
let totalSpace = frame.width - CGFloat(Constants.numberOfArrows) * Constants.arrowSize.width
let oneSpace = totalSpace / CGFloat(Constants.numberOfArrows - 1)
let x = CGFloat(i) * (oneSpace + Constants.arrowSize.width)
let y = (frame.height - Constants.arrowSize.height) / 2
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: x, y: y),
size: Constants.arrowSize))
imageView.contentMode = .scaleAspectFit
imageView.tintColor = theme.tabBarUnselectedItemTintColor
imageView.image = arrowImage
addSubview(imageView)
arrowImageViews.append(imageView)
}
gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.locations = [0.25, 0.5, 0.75]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
// this color doesn't have to come from the theme, it's only used as a mask
let color = UIColor.black
let colors = Constants.gradientRatios.map({ color.withAlphaComponent($0) })
gradientLayer.colors = colors.map({ $0.cgColor })
layer.mask = gradientLayer
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
// MARK: - API
var isAnimating: Bool = false {
didSet {
if isAnimating {
if gradientLayer.animation(forKey: Constants.gradientAnimationKey) == nil {
gradientLayer.add(gradientAnimation,
forKey: Constants.gradientAnimationKey)
}
} else {
if gradientLayer.animation(forKey: Constants.gradientAnimationKey) != nil {
gradientLayer.removeAnimation(forKey: Constants.gradientAnimationKey)
}
}
}
}
}
// MARK: - Themable
extension ArrowsAnimationView: Themable {
func update(theme: Theme) {
self.theme = theme
arrowImageViews.forEach({ $0.tintColor = theme.tabBarUnselectedItemTintColor })
}
}
@@ -0,0 +1,194 @@
//
// 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
import Reusable
@objcMembers
class RemoveJitsiWidgetView: UIView {
private enum Constants {
static let activationThreshold: CGFloat = 0.5
}
private enum State: Equatable {
case notStarted
case sliding(percentage: CGFloat)
case completed
static func == (lhs: State, rhs: State) -> 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<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if state == .notStarted {
updateState(to: .sliding(percentage: 0))
}
}
override func touchesMoved(_ touches: Set<UITouch>, 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<UITouch>, 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<UITouch>, 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)
}
}
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="RemoveJitsiWidgetView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="55"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kKF-lQ-ZyI">
<rect key="frame" x="0.0" y="0.0" width="236.5" height="55"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Slide to end the call for everyone " textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dT6-Xr-WEJ">
<rect key="frame" x="24" y="0.0" width="202.5" height="55"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="dT6-Xr-WEJ" secondAttribute="trailing" constant="10" id="EbF-GZ-NzF"/>
<constraint firstItem="dT6-Xr-WEJ" firstAttribute="top" secondItem="kKF-lQ-ZyI" secondAttribute="top" id="Qu0-Ik-PYy"/>
<constraint firstItem="dT6-Xr-WEJ" firstAttribute="leading" secondItem="kKF-lQ-ZyI" secondAttribute="leading" constant="24" id="aJj-xS-5HE"/>
<constraint firstAttribute="bottom" secondItem="dT6-Xr-WEJ" secondAttribute="bottom" id="n5n-Ky-ncI"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bi1-rF-LRz" customClass="ArrowsAnimationView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="236.5" y="0.0" width="50.5" height="55"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="50" id="sKo-iu-uGJ"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PIa-uP-1J7">
<rect key="frame" x="295" y="0.0" width="80" height="55"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="voice_call_hangup_icon" translatesAutoresizingMaskIntoConstraints="NO" id="bWv-EC-L99">
<rect key="frame" x="28" y="15.5" width="24" height="24"/>
<color key="tintColor" systemColor="systemRedColor"/>
</imageView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="bWv-EC-L99" firstAttribute="centerY" secondItem="PIa-uP-1J7" secondAttribute="centerY" id="2Vl-CE-QUT"/>
<constraint firstItem="bWv-EC-L99" firstAttribute="centerX" secondItem="PIa-uP-1J7" secondAttribute="centerX" id="9vE-r4-Nje"/>
<constraint firstAttribute="width" constant="80" id="NW6-rp-ycN"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ntn-XH-RxI">
<rect key="frame" x="0.0" y="0.0" width="375" height="1"/>
<color key="backgroundColor" systemColor="systemGrayColor"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="yX4-ag-LWD"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bLk-fK-yz0">
<rect key="frame" x="0.0" y="54" width="375" height="1"/>
<color key="backgroundColor" systemColor="systemGrayColor"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="LsE-CW-ztj"/>
</constraints>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="PIa-uP-1J7" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="41n-Yu-G3I"/>
<constraint firstItem="kKF-lQ-ZyI" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="Ciw-a7-Pct"/>
<constraint firstAttribute="trailing" secondItem="Ntn-XH-RxI" secondAttribute="trailing" id="E9k-gw-h02"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="bLk-fK-yz0" secondAttribute="trailing" id="ERx-So-ce7"/>
<constraint firstAttribute="bottom" secondItem="bLk-fK-yz0" secondAttribute="bottom" id="I42-n7-cBJ"/>
<constraint firstItem="bi1-rF-LRz" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="Iyl-NN-rCe"/>
<constraint firstItem="bLk-fK-yz0" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="Oj8-Jf-cWi"/>
<constraint firstItem="PIa-uP-1J7" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="XJo-0J-fte"/>
<constraint firstItem="kKF-lQ-ZyI" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="aUb-po-3Dw"/>
<constraint firstItem="PIa-uP-1J7" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="fn9-gi-1Fo"/>
<constraint firstItem="bi1-rF-LRz" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="gbK-bh-J3T"/>
<constraint firstItem="bi1-rF-LRz" firstAttribute="leading" secondItem="kKF-lQ-ZyI" secondAttribute="trailing" priority="750" id="hWc-xf-WTt"/>
<constraint firstItem="kKF-lQ-ZyI" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="k9f-Tc-VQw"/>
<constraint firstItem="Ntn-XH-RxI" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="lzw-UG-Zeh"/>
<constraint firstItem="PIa-uP-1J7" firstAttribute="leading" secondItem="bi1-rF-LRz" secondAttribute="trailing" constant="8" id="sSR-kg-guw"/>
<constraint firstItem="Ntn-XH-RxI" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="vJc-3a-yWr"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="arrowsView" destination="bi1-rF-LRz" id="9NK-yt-cXa"/>
<outlet property="bottomSeparatorView" destination="bLk-fK-yz0" id="6Fn-W3-VTa"/>
<outlet property="hangupImage" destination="bWv-EC-L99" id="UAz-cI-2ai"/>
<outlet property="hangupView" destination="PIa-uP-1J7" id="Qd0-It-bFg"/>
<outlet property="slidingView" destination="kKF-lQ-ZyI" id="bLF-Aa-1Oy"/>
<outlet property="slidingViewLabel" destination="dT6-Xr-WEJ" id="OSR-IL-92L"/>
<outlet property="slidingViewLeadingConstraint" destination="aUb-po-3Dw" id="R7j-74-W4J"/>
<outlet property="topSeparatorView" destination="Ntn-XH-RxI" id="nRw-nh-427"/>
</connections>
<point key="canvasLocation" x="110.86956521739131" y="-142.96875"/>
</view>
</objects>
<resources>
<image name="voice_call_hangup_icon" width="24" height="24"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemGrayColor">
<color red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemRedColor">
<color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
@@ -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)
}