Browse Source

update library

marinofaggiana 5 years ago
parent
commit
8a6d55ef05

+ 0 - 1
Cartfile

@@ -19,5 +19,4 @@ github "huri000/SwiftEntryKit" "1.2.3"
 github "scenee/FloatingPanel"
 github "ivanbruel/MarkdownKit"
 
-github "https://github.com/marinofaggiana/FastScroll" "master"
 github "https://github.com/marinofaggiana/AFNetworking" "master"

+ 16 - 3
Nextcloud.xcodeproj/project.pbxproj

@@ -261,7 +261,6 @@
 		F750374D1DBFA91A008FB480 /* ALView+PureLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = F75037441DBFA91A008FB480 /* ALView+PureLayout.m */; };
 		F750374F1DBFA91A008FB480 /* NSArray+PureLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = F75037461DBFA91A008FB480 /* NSArray+PureLayout.m */; };
 		F75037511DBFA91A008FB480 /* NSLayoutConstraint+PureLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = F75037481DBFA91A008FB480 /* NSLayoutConstraint+PureLayout.m */; };
-		F75153242226920200323DDC /* FastScroll.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F75153232226920200323DDC /* FastScroll.framework */; };
 		F754EEC921772B6100BB1CDF /* DropdownItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754EEC421772B6100BB1CDF /* DropdownItem.swift */; };
 		F754EECA21772B6100BB1CDF /* DropUpMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754EEC521772B6100BB1CDF /* DropUpMenu.swift */; };
 		F754EECB21772B6100BB1CDF /* DropdownMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754EEC721772B6100BB1CDF /* DropdownMenu.swift */; };
@@ -474,6 +473,8 @@
 		F7B174C822FAC0A8000B7579 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B174C722FAC0A8000B7579 /* AppDelegate.m */; };
 		F7B2DEF01F976854007CF4D2 /* NYMnemonic.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B2DEEF1F976785007CF4D2 /* NYMnemonic.m */; };
 		F7B2DEF11F976859007CF4D2 /* english.txt in Resources */ = {isa = PBXBuildFile; fileRef = F7B2DEED1F976785007CF4D2 /* english.txt */; };
+		F7B3FF37243A0F5D00BD9150 /* FastScrollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B3FF35243A0F5D00BD9150 /* FastScrollTableView.swift */; };
+		F7B3FF38243A0F5D00BD9150 /* FastScrollCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B3FF36243A0F5D00BD9150 /* FastScrollCollectionView.swift */; };
 		F7B4F1CB1F44356F00B53B42 /* NCUchardet.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B4F1C81F44356F00B53B42 /* NCUchardet.m */; };
 		F7B6ACD622FC2BD4008AB646 /* NCXMLCommentsParser.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B6ACD422FC2BD3008AB646 /* NCXMLCommentsParser.m */; };
 		F7B6ACD722FC2BD4008AB646 /* NCXMLCommentsParser.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B6ACD422FC2BD3008AB646 /* NCXMLCommentsParser.m */; };
@@ -1164,6 +1165,8 @@
 		F7B2DEED1F976785007CF4D2 /* english.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = english.txt; sourceTree = "<group>"; };
 		F7B2DEEE1F976785007CF4D2 /* NYMnemonic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NYMnemonic.h; sourceTree = "<group>"; };
 		F7B2DEEF1F976785007CF4D2 /* NYMnemonic.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NYMnemonic.m; sourceTree = "<group>"; };
+		F7B3FF35243A0F5D00BD9150 /* FastScrollTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FastScrollTableView.swift; sourceTree = "<group>"; };
+		F7B3FF36243A0F5D00BD9150 /* FastScrollCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FastScrollCollectionView.swift; sourceTree = "<group>"; };
 		F7B4F1C71F44356F00B53B42 /* NCUchardet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NCUchardet.h; sourceTree = "<group>"; };
 		F7B4F1C81F44356F00B53B42 /* NCUchardet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NCUchardet.m; sourceTree = "<group>"; };
 		F7B6ACD422FC2BD3008AB646 /* NCXMLCommentsParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NCXMLCommentsParser.m; sourceTree = "<group>"; };
@@ -1348,7 +1351,6 @@
 				F7C40C102199BA5D0004137E /* Realm.framework in Frameworks */,
 				F774264122EB3F7300B23912 /* DropDown.framework in Frameworks */,
 				F7D4B69B2295666E000C2C86 /* GoogleMobileVision.framework in Frameworks */,
-				F75153242226920200323DDC /* FastScroll.framework in Frameworks */,
 				F7C40C122199BA620004137E /* RealmSwift.framework in Frameworks */,
 				F78AA20621F783E900D0F205 /* SwiftRichString.framework in Frameworks */,
 				F7DBD82C23E46A4700ECB7C6 /* MarkdownKit.framework in Frameworks */,
@@ -2243,6 +2245,15 @@
 			path = languages;
 			sourceTree = "<group>";
 		};
+		F7B3FF34243A0F5D00BD9150 /* FastScroll */ = {
+			isa = PBXGroup;
+			children = (
+				F7B3FF35243A0F5D00BD9150 /* FastScrollTableView.swift */,
+				F7B3FF36243A0F5D00BD9150 /* FastScrollCollectionView.swift */,
+			);
+			path = FastScroll;
+			sourceTree = "<group>";
+		};
 		F7B4F1C51F44356F00B53B42 /* NCUchardet */ = {
 			isa = PBXGroup;
 			children = (
@@ -2498,6 +2509,7 @@
 			children = (
 				F73CC0571E813DFF006E3047 /* BKPasscodeView */,
 				F754EEC321772B6100BB1CDF /* DropdownMenu */,
+				F7B3FF34243A0F5D00BD9150 /* FastScroll */,
 				F7B4F1C51F44356F00B53B42 /* NCUchardet */,
 				F760F75621F21F61006B1A73 /* PhotoEditor */,
 				F762CA9F1EACB66200B38484 /* XLForm */,
@@ -3013,7 +3025,6 @@
 				"$(SRCROOT)/Carthage/Build/iOS/CocoaLumberjackSwift.framework",
 				"$(SRCROOT)/Carthage/Build/iOS/WeScan.framework",
 				"$(SRCROOT)/Carthage/Build/iOS/SwiftRichString.framework",
-				"$(SRCROOT)/Carthage/Build/iOS/FastScroll.framework",
 				"$(SRCROOT)/Carthage/Build/iOS/QRCodeReader.framework",
 				"$(SRCROOT)/Carthage/Build/iOS/AFNetworking.framework",
 				"$(SRCROOT)/Carthage/Build/iOS/ZIPFoundation.framework",
@@ -3295,6 +3306,7 @@
 				F762CB111EACB66200B38484 /* NSString+XLFormAdditions.m in Sources */,
 				F762CB081EACB66200B38484 /* XLFormOptionsViewController.m in Sources */,
 				F704B5E92430C0B800632F5F /* NCCreateFormUploadConflictCell.swift in Sources */,
+				F7B3FF38243A0F5D00BD9150 /* FastScrollCollectionView.swift in Sources */,
 				F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */,
 				F73CC0721E813DFF006E3047 /* BKPasscodeLockScreenManager.m in Sources */,
 				F760F78B21F21F61006B1A73 /* UIImage+Crop.swift in Sources */,
@@ -3431,6 +3443,7 @@
 				F726EEEC1FED1C820030B9C8 /* NCEndToEndInitialize.swift in Sources */,
 				F79A65C62191D95E00FF6DCC /* NCSelect.swift in Sources */,
 				F7E0E1DC22327885006B0911 /* NCAudioRecorderViewController.swift in Sources */,
+				F7B3FF37243A0F5D00BD9150 /* FastScrollTableView.swift in Sources */,
 				F70CAE3A1F8CF31A008125FD /* NCEndToEndEncryption.m in Sources */,
 				3781B9B423DB2BC9006B4B1D /* CCFavorites+Menu.swift in Sources */,
 				F710D1F924057C9D00A6033D /* NCDetailViewController.swift in Sources */,

+ 403 - 0
iOSClient/Library/FastScroll/FastScrollCollectionView.swift

@@ -0,0 +1,403 @@
+//
+//  FastScrollCollectionView.swift
+//  FastScroll
+//
+//  Created by Arsene Huot on 15/06/2018.
+//  Copyright © 2018 Frichti. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+@objc public protocol FastScrollCollectionViewDelegate {
+    @objc func hideHandle()
+}
+
+open class FastScrollCollectionView: UICollectionView {
+    public enum BubbleFocus {
+        case first
+        case last
+        case dynamic
+    }
+    
+    // Bubble to display your information during scroll
+    public var deactivateBubble: Bool = false
+    public var bubble: UITextView?
+    public var bubbleFont: UIFont =  UIFont.systemFont(ofSize: 12.0)
+    public var bubbleTextSize: CGFloat = 12.0
+    public var bubbleTextColor: UIColor = UIColor.white
+    public var bubbleRadius: CGFloat = 20.0
+    public var bubblePadding: CGFloat = 12.0
+    public var bubbleMarginRight: CGFloat = 30.0
+    public var bubbleColor: UIColor = UIColor.darkGray
+    public var bubbleShadowColor: UIColor = UIColor.darkGray
+    public var bubbleShadowOpacity: Float = 0.7
+    public var bubbleShadowRadius: CGFloat = 3.0
+    public var bubbleShadowOffset: CGSize = CGSize(width: 0.0, height: 5.0)
+    public var bubbleFocus: BubbleFocus = .first
+    
+    // Handler to scroll
+    public var handle: UIView?
+    public var handleImage: UIImage?
+    public var handleWidth: CGFloat = 30.0
+    public var handleHeight: CGFloat = 30.0
+    public var handleRadius: CGFloat = 15.0
+    public var handleMarginRight: CGFloat = 6.0
+    public var handleShadowColor: UIColor = UIColor.darkGray
+    public var handleShadowOpacity: Float = 0.7
+    public var handleShadowOffset: CGSize = CGSize(width: 0.0, height: 5.0)
+    public var handleShadowRadius: CGFloat = 3.0
+    public var handleColor: UIColor = UIColor.darkGray
+    public var handleTimeToDisappear: CGFloat = 1.5
+    public var handleDisappearAnimationDuration: CGFloat = 0.2
+    fileprivate var handleTouched: Bool = false
+    
+    // Gesture center on handler
+    public var gestureHandleView: UIView?
+    public var gestureWidth: CGFloat = 50.0
+    public var gestureHeight: CGFloat = 50.0
+    
+    // Scrollbar
+    public var scrollbar: UIView?
+    public var scrollbarWidth: CGFloat = 2.0
+    public var scrollbarColor: UIColor = UIColor(red: 220.0 / 255.0, green: 220.0 / 255.0, blue: 220.0 / 255.0, alpha: 1.0)
+    public var scrollbarRadius: CGFloat = 1.0
+    public var scrollbarMarginTop: CGFloat = 40.0
+    public var scrollbarMarginBottom: CGFloat = 20.0
+    public var scrollbarMarginRight: CGFloat = 20.0
+    
+    // Timer to dismiss handle
+    fileprivate var handleTimer: Timer?
+    
+    // Action callback
+    public var bubbleNameForIndexPath: (IndexPath) -> String = { _ in return ""}
+    
+    // Delegate
+    public var fastScrollDelegate: FastScrollCollectionViewDelegate?
+    
+    // MARK: LifeCycle
+    
+    override open func draw(_ rect: CGRect) {
+        super.draw(rect)
+        setup()
+        setupCollectionView()
+    }
+    
+    // MARK: Setups
+    
+    fileprivate func setupCollectionView() {
+        showsVerticalScrollIndicator = false
+    }
+    
+    public func setup() {
+        cleanViews()
+        
+        setupScrollbar()
+        setupHandle()
+        setupBubble()
+    }
+    
+    public func cleanViews() {
+        guard let bubble = bubble, let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView else {
+            return
+        }
+        
+        bubble.removeFromSuperview()
+        handle.removeFromSuperview()
+        scrollbar.removeFromSuperview()
+        gestureHandleView.removeFromSuperview()
+        
+        self.bubble = nil
+        self.handle = nil
+        self.scrollbar = nil
+        self.gestureHandleView = nil
+    }
+    
+    fileprivate func setupHandle() {
+        if handle == nil {
+            handle = UIView(frame: CGRect(x: self.frame.width - handleWidth - handleMarginRight, y: scrollbarMarginTop, width: handleWidth, height: handleHeight))
+            self.superview?.addSubview(handle!)
+            
+            gestureHandleView  = UIView(frame: CGRect(x: 0.0, y: 0.0, width: gestureWidth, height: gestureHeight))
+            gestureHandleView!.center = handle!.center
+            
+            self.superview?.addSubview(handle!)
+            self.superview?.addSubview(gestureHandleView!)
+        }
+        
+        //config layer
+        handle!.backgroundColor = handleColor
+        handle!.layer.cornerRadius = handleRadius
+        handle!.layer.shadowColor = handleShadowColor.cgColor
+        handle!.layer.shadowOffset = handleShadowOffset
+        handle!.layer.shadowRadius = handleShadowRadius
+        handle!.layer.shadowOpacity = handleShadowOpacity
+        
+        //set imageView
+        if let handleImage = handleImage {
+            let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: handleWidth, height: handleHeight))
+            imageView.image = handleImage
+            handle!.addSubview(imageView)
+        }
+        
+        //set gesture
+        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
+        gestureHandleView!.addGestureRecognizer(panGesture)
+        
+        //hide
+        handle!.alpha = 0.0
+        handle!.isHidden = true
+        gestureHandleView!.isHidden = true
+        
+        //position
+        positionHandle(scrollbarMarginTop)
+    }
+    
+    fileprivate func setupBubble() {
+        if bubble == nil {
+            bubble = UITextView()
+            self.superview?.addSubview(bubble!)
+        }
+        
+        bubble!.font = bubbleFont
+        bubble!.font = UIFont(name: bubbleFont.fontName, size: bubbleTextSize)
+        bubble!.text = "Test"
+        bubble!.textColor = bubbleTextColor
+        bubble!.textAlignment = NSTextAlignment.center
+        bubble!.textContainerInset = UIEdgeInsets(top: bubblePadding, left: bubblePadding, bottom: bubblePadding, right: bubblePadding)
+        bubble!.contentMode = UIView.ContentMode.scaleAspectFit
+        bubble!.sizeToFit()
+        
+        bubble!.backgroundColor = bubbleColor
+        bubble!.layer.cornerRadius = bubbleRadius
+        bubble!.layer.shadowColor = bubbleShadowColor.cgColor
+        bubble!.layer.shadowOffset = bubbleShadowOffset
+        bubble!.layer.shadowRadius = bubbleRadius
+        bubble!.layer.shadowOpacity = bubbleShadowOpacity
+        bubble!.layer.shadowRadius = bubbleShadowRadius
+        bubble!.layer.masksToBounds = false
+        
+        bubble!.isHidden = true
+        
+        updateBubblePosition()
+    }
+    
+    fileprivate func setupScrollbar() {
+        guard let superview = self.superview else {
+            return
+        }
+        
+        if scrollbar == nil {
+            scrollbar = UIView(frame: CGRect(x: self.frame.width - scrollbarWidth - scrollbarMarginRight, y: scrollbarMarginTop, width: scrollbarWidth, height: superview.bounds.height - scrollbarMarginBottom - scrollbarMarginTop))
+            self.superview?.addSubview(scrollbar!)
+        }
+        
+        scrollbar!.backgroundColor = scrollbarColor
+        scrollbar!.layer.cornerRadius = scrollbarRadius
+        scrollbar!.alpha = 0.0
+        scrollbar!.isHidden = true
+    }
+    
+    // MARK: Helpers
+    @objc func hideHandle() {
+        guard let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView else {
+            return
+        }
+        
+        if gestureHandleView.isHidden == false {
+            self.fastScrollDelegate?.hideHandle()
+        }
+        
+        gestureHandleView.isHidden = true
+        
+        UIView.animate(withDuration: TimeInterval(handleDisappearAnimationDuration), animations: {
+            handle.alpha = 0.0
+            scrollbar.alpha = 0.0
+        }, completion: { finished in
+            if finished {
+                handle.isHidden = true
+                scrollbar.isHidden = true
+            }
+        })
+    }
+    
+    fileprivate func updateBubblePosition() {
+        guard let scrollbar = scrollbar, let bubble = bubble, let handle = handle else {
+            return
+        }
+        
+        bubble.frame.origin.x = scrollbar.frame.origin.x - bubble.frame.size.width - bubbleMarginRight
+        bubble.center.y = handle.center.y
+    }
+    
+    fileprivate func positionHandle(_ y: CGFloat) {
+        guard let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView else {
+            return
+        }
+        
+        handle.frame.origin.y = y >= scrollbarMarginTop ?
+            (y > scrollbarMarginTop + scrollbar.frame.height - handle.frame.height) ? scrollbarMarginTop + scrollbar.frame.height - handle.frame.height : y
+            :
+        scrollbarMarginTop
+        
+        gestureHandleView.center = handle.center
+    }
+    
+    fileprivate func scrollCollectionFromHandle() {
+        guard let handle = handle, let scrollbar = scrollbar else {
+            return
+        }
+        
+        let collectionContentHeight = self.contentSize.height - self.bounds.height
+        let scrollBarHeight = scrollbar.frame.height
+        
+        let scrollY = (handle.frame.origin.y - scrollbarMarginTop) * (collectionContentHeight / (scrollBarHeight - handle.frame.size.height))
+        
+        self.setContentOffset(CGPoint(x: 0.0, y: scrollY), animated: false)
+    }
+    
+    @objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
+        guard let superview = superview, let bubble = bubble, let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView  else {
+            return
+        }
+        
+        // get translation
+        let translation = panGesture.translation(in: superview)
+        panGesture.setTranslation(CGPoint.zero, in: superview)
+        
+        // manage start stop pan
+        if panGesture.state == UIGestureRecognizer.State.began {
+            bubble.isHidden = deactivateBubble ? true : false
+            handleTouched = true
+            
+            //invalid hide timer
+            if let handleTimer = handleTimer {
+                handleTimer.invalidate()
+            }
+            
+            handle.alpha = 1.0
+            scrollbar.alpha = 1.0
+            handle.isHidden = false
+            scrollbar.isHidden = false
+            gestureHandleView.isHidden = false
+        }
+        
+        if panGesture.state == UIGestureRecognizer.State.ended {
+            bubble.isHidden = true
+            handleTouched = false
+            if contentOffset.y < 0 {
+                self.setContentOffset(CGPoint(x: 0.0, y: 0), animated: false)
+            }
+            self.handleTimer = Timer.scheduledTimer(timeInterval: TimeInterval(handleTimeToDisappear), target: self, selector: #selector(hideHandle), userInfo: nil, repeats: false)
+        }
+        
+        if panGesture.state == UIGestureRecognizer.State.changed {
+            //invalid hide timer
+            if let handleTimer = handleTimer {
+                handleTimer.invalidate()
+            }
+            
+            handle.alpha = 1.0
+            scrollbar.alpha = 1.0
+            handle.isHidden = false
+            scrollbar.isHidden = false
+            gestureHandleView.isHidden = false
+        }
+        
+        // views positions
+        positionHandle(handle.frame.origin.y + translation.y)
+        updateBubblePosition()
+        scrollCollectionFromHandle()
+        
+        // manage bubble info
+        manageBubbleInfo()
+    }
+    
+    fileprivate func manageBubbleInfo() {
+        guard let bubble = bubble else {
+            return
+        }
+        
+        let visibleCells = self.visibleCells
+        
+        var currentCellIndex: Int
+        
+        switch bubbleFocus {
+        case .first:
+            currentCellIndex = 0
+            
+        case .last:
+            currentCellIndex = visibleCells.count - 1
+            
+        case .dynamic:
+            //Calcul scroll percentage
+            let scrollY =  contentOffset.y
+            let collectionContentHeight = self.contentSize.height > self.bounds.height ? self.contentSize.height - self.bounds.height : self.bounds.height
+            let scrollPercentage = scrollY / collectionContentHeight
+            currentCellIndex = Int(floor(CGFloat(visibleCells.count) * scrollPercentage))
+            if currentCellIndex < 0 {
+                currentCellIndex = 0
+            }
+        }
+        
+        if currentCellIndex < visibleCells.count {
+            if let indexPath = indexPath(for: visibleCells[currentCellIndex]) {
+                bubble.text = bubbleNameForIndexPath(indexPath)
+                let newSize = bubble.sizeThatFits(CGSize(width: self.bounds.width - (self.bounds.width - (bubble.frame.origin.x + bubble.frame.size.width)), height: bubble.frame.size.height))
+                let oldSize = bubble.frame.size
+                bubble.frame = CGRect(x: bubble.frame.origin.x + (oldSize.width - newSize.width), y: bubble.frame.origin.y, width: newSize.width, height: newSize.height)
+            }
+        }
+    }
+}
+
+// MARK: Scroll Management
+
+extension FastScrollCollectionView {
+    public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+        guard let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView else {
+            return
+        }
+        
+        handle.alpha = 1.0
+        scrollbar.alpha = 1.0
+        handle.isHidden = false
+        scrollbar.isHidden = false
+        gestureHandleView.isHidden = false
+    }
+    
+    public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
+        if !decelerate {
+            self.handleTimer = Timer.scheduledTimer(timeInterval: TimeInterval(handleTimeToDisappear), target: self, selector: #selector(hideHandle), userInfo: nil, repeats: false)
+        }
+    }
+    
+    public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
+        self.handleTimer = Timer.scheduledTimer(timeInterval: TimeInterval(handleTimeToDisappear), target: self, selector: #selector(hideHandle), userInfo: nil, repeats: false)
+    }
+    
+    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        guard let handle = handle, let scrollbar = scrollbar else {
+            return
+        }
+        
+        //invalid timer
+        if let handleTimer = handleTimer {
+            handleTimer.invalidate()
+        }
+        
+        //scroll position
+        let scrollY =  scrollView.contentOffset.y
+        let collectionContentHeight = self.contentSize.height > self.bounds.height ? self.contentSize.height - self.bounds.height : self.bounds.height
+        let scrollBarHeight = scrollbar.frame.height
+        
+        
+        let handlePosition = (scrollY / collectionContentHeight) * (scrollBarHeight - handle.frame.size.height) + scrollbarMarginTop
+        if (handleTouched == false) {
+            positionHandle(handlePosition)
+        }
+        
+        updateBubblePosition()
+    }
+}
+

+ 387 - 0
iOSClient/Library/FastScroll/FastScrollTableView.swift

@@ -0,0 +1,387 @@
+//
+//  FastScrollTableView.swift
+//  FastScroll
+//
+//  Created by Arsene Huot on 15/06/2018.
+//  Copyright © 2018 Frichti. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+open class FastScrollTableView: UITableView {
+    public enum BubbleFocus {
+        case first
+        case last
+        case dynamic
+    }
+    
+    // Bubble to display your information during scroll
+    public var deactivateBubble: Bool = false
+    public var bubble: UITextView?
+    public var bubbleFont: UIFont =  UIFont.systemFont(ofSize: 12.0)
+    public var bubbleTextSize: CGFloat = 12.0
+    public var bubbleTextColor: UIColor = UIColor.white
+    public var bubbleRadius: CGFloat = 20.0
+    public var bubblePadding: CGFloat = 12.0
+    public var bubbleMarginRight: CGFloat = 30.0
+    public var bubbleColor: UIColor = UIColor.darkGray
+    public var bubbleShadowColor: UIColor = UIColor.darkGray
+    public var bubbleShadowOpacity: Float = 0.7
+    public var bubbleShadowRadius: CGFloat = 3.0
+    public var bubbleShadowOffset: CGSize = CGSize(width: 0.0, height: 5.0)
+    public var bubbleFocus: BubbleFocus = .first
+    
+    // Handler to scroll
+    public var handle: UIView?
+    public var handleImage: UIImage?
+    public var handleWidth: CGFloat = 30.0
+    public var handleHeight: CGFloat = 30.0
+    public var handleRadius: CGFloat = 15.0
+    public var handleMarginRight: CGFloat = 6.0
+    public var handleShadowColor: UIColor = UIColor.darkGray
+    public var handleShadowOpacity: Float = 0.7
+    public var handleShadowOffset: CGSize = CGSize(width: 0.0, height: 5.0)
+    public var handleShadowRadius: CGFloat = 3.0
+    public var handleColor: UIColor = UIColor.darkGray
+    public var handleTimeToDisappear: CGFloat = 1.5
+    public var handleDisappearAnimationDuration: CGFloat = 0.2
+    fileprivate var handleTouched: Bool = false
+    
+    // Gesture center on handler
+    public var gestureHandleView: UIView?
+    public var gestureWidth: CGFloat = 50.0
+    public var gestureHeight: CGFloat = 50.0
+    
+    // Scrollbar
+    public var scrollbar: UIView?
+    public var scrollbarWidth: CGFloat = 2.0
+    public var scrollbarColor: UIColor = UIColor(red: 220.0 / 255.0, green: 220.0 / 255.0, blue: 220.0 / 255.0, alpha: 1.0)
+    public var scrollbarRadius: CGFloat = 1.0
+    public var scrollbarMarginTop: CGFloat = 40.0
+    public var scrollbarMarginBottom: CGFloat = 20.0
+    public var scrollbarMarginRight: CGFloat = 20.0
+    
+    // Timer to dismiss handle
+    fileprivate var handleTimer: Timer?
+    
+    // Action callback
+    public var bubbleNameForIndexPath: (IndexPath) -> String = { _ in return ""}
+    
+    // MARK: LifeCycle
+    
+    override open func draw(_ rect: CGRect) {
+        super.draw(rect)
+        setup()
+        setupCollectionView()
+    }
+    
+    // MARK: Setups
+    
+    fileprivate func setupCollectionView() {
+        showsVerticalScrollIndicator = false
+    }
+    
+    public func setup() {
+        cleanViews()
+        
+        setupScrollbar()
+        setupHandle()
+        setupBubble()
+    }
+    
+    public func cleanViews() {
+        guard let bubble = bubble, let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView else {
+            return
+        }
+        
+        bubble.removeFromSuperview()
+        handle.removeFromSuperview()
+        scrollbar.removeFromSuperview()
+        gestureHandleView.removeFromSuperview()
+        
+        self.bubble = nil
+        self.handle = nil
+        self.scrollbar = nil
+        self.gestureHandleView = nil
+    }
+    
+    fileprivate func setupHandle() {
+        if handle == nil {
+            handle = UIView(frame: CGRect(x: self.frame.width - handleWidth - handleMarginRight, y: scrollbarMarginTop, width: handleWidth, height: handleHeight))
+            self.superview?.addSubview(handle!)
+            
+            gestureHandleView  = UIView(frame: CGRect(x: 0.0, y: 0.0, width: gestureWidth, height: gestureHeight))
+            gestureHandleView!.center = handle!.center
+            
+            self.superview?.addSubview(handle!)
+            self.superview?.addSubview(gestureHandleView!)
+        }
+        
+        //config layer
+        handle!.backgroundColor = handleColor
+        handle!.layer.cornerRadius = handleRadius
+        handle!.layer.shadowColor = handleShadowColor.cgColor
+        handle!.layer.shadowOffset = handleShadowOffset
+        handle!.layer.shadowRadius = handleShadowRadius
+        handle!.layer.shadowOpacity = handleShadowOpacity
+        
+        //set imageView
+        if let handleImage = handleImage {
+            let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: handleWidth, height: handleHeight))
+            imageView.image = handleImage
+            handle!.addSubview(imageView)
+        }
+        
+        //set gesture
+        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
+        gestureHandleView!.addGestureRecognizer(panGesture)
+        
+        //hide
+        handle!.alpha = 0.0
+        handle!.isHidden = true
+        gestureHandleView!.isHidden = true
+        
+        //position
+        positionHandle(scrollbarMarginTop)
+    }
+    
+    fileprivate func setupBubble() {
+        if bubble == nil {
+            bubble = UITextView()
+            self.superview?.addSubview(bubble!)
+        }
+        
+        bubble!.font = bubbleFont
+        bubble!.font = UIFont(name: bubbleFont.fontName, size: bubbleTextSize)
+        bubble!.text = "Test"
+        bubble!.textColor = bubbleTextColor
+        bubble!.textAlignment = NSTextAlignment.center
+        bubble!.textContainerInset = UIEdgeInsets(top: bubblePadding, left: bubblePadding, bottom: bubblePadding, right: bubblePadding)
+        bubble!.contentMode = UIView.ContentMode.scaleAspectFit
+        bubble!.sizeToFit()
+        
+        bubble!.backgroundColor = bubbleColor
+        bubble!.layer.cornerRadius = bubbleRadius
+        bubble!.layer.shadowColor = bubbleShadowColor.cgColor
+        bubble!.layer.shadowOffset = bubbleShadowOffset
+        bubble!.layer.shadowRadius = bubbleRadius
+        bubble!.layer.shadowOpacity = bubbleShadowOpacity
+        bubble!.layer.shadowRadius = bubbleShadowRadius
+        bubble!.layer.masksToBounds = false
+        
+        bubble!.isHidden = true
+        
+        updateBubblePosition()
+    }
+    
+    fileprivate func setupScrollbar() {
+        guard let superview = self.superview else {
+            return
+        }
+        
+        if scrollbar == nil {
+            scrollbar = UIView(frame: CGRect(x: self.frame.width - scrollbarWidth - scrollbarMarginRight, y: scrollbarMarginTop, width: scrollbarWidth, height: superview.bounds.height - scrollbarMarginBottom - scrollbarMarginTop))
+            self.superview?.addSubview(scrollbar!)
+        }
+        
+        scrollbar!.backgroundColor = scrollbarColor
+        scrollbar!.layer.cornerRadius = scrollbarRadius
+        scrollbar!.alpha = 0.0
+        scrollbar!.isHidden = true
+    }
+    
+    // MARK: Helpers
+    @objc func hideHandle() {
+        guard let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView else {
+            return
+        }
+        
+        gestureHandleView.isHidden = true
+        
+        UIView.animate(withDuration: TimeInterval(handleDisappearAnimationDuration), animations: {
+            handle.alpha = 0.0
+            scrollbar.alpha = 0.0
+        }, completion: { finished in
+            if finished {
+                handle.isHidden = true
+                scrollbar.isHidden = true
+            }
+        })
+    }
+    
+    fileprivate func updateBubblePosition() {
+        guard let scrollbar = scrollbar, let bubble = bubble, let handle = handle else {
+            return
+        }
+        
+        bubble.frame.origin.x = scrollbar.frame.origin.x - bubble.frame.size.width - bubbleMarginRight
+        bubble.center.y = handle.center.y
+    }
+    
+    fileprivate func positionHandle(_ y: CGFloat) {
+        guard let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView else {
+            return
+        }
+        
+        handle.frame.origin.y = y >= scrollbarMarginTop ?
+            (y > scrollbarMarginTop + scrollbar.frame.height - handle.frame.height) ? scrollbarMarginTop + scrollbar.frame.height - handle.frame.height : y
+            :
+        scrollbarMarginTop
+        
+        gestureHandleView.center = handle.center
+    }
+    
+    fileprivate func scrollCollectionFromHandle() {
+        guard let handle = handle, let scrollbar = scrollbar else {
+            return
+        }
+        
+        let collectionContentHeight = self.contentSize.height - self.bounds.height
+        let scrollBarHeight = scrollbar.frame.height
+        
+        let scrollY = (handle.frame.origin.y - scrollbarMarginTop) * (collectionContentHeight / (scrollBarHeight - handle.frame.size.height))
+        
+        self.setContentOffset(CGPoint(x: 0.0, y: scrollY), animated: false)
+    }
+    
+    @objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
+        guard let superview = superview, let bubble = bubble, let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView  else {
+            return
+        }
+        
+        // get translation
+        let translation = panGesture.translation(in: superview)
+        panGesture.setTranslation(CGPoint.zero, in: superview)
+        
+        // manage start stop pan
+        if panGesture.state == UIGestureRecognizer.State.began {
+            bubble.isHidden = deactivateBubble ? true : false
+            handleTouched = true
+            
+            //invalid hide timer
+            if let handleTimer = handleTimer {
+                handleTimer.invalidate()
+            }
+            
+            handle.alpha = 1.0
+            scrollbar.alpha = 1.0
+            handle.isHidden = false
+            scrollbar.isHidden = false
+            gestureHandleView.isHidden = false
+        }
+        
+        if panGesture.state == UIGestureRecognizer.State.ended {
+            bubble.isHidden = true
+            handleTouched = false
+            self.handleTimer = Timer.scheduledTimer(timeInterval: TimeInterval(handleTimeToDisappear), target: self, selector: #selector(hideHandle), userInfo: nil, repeats: false)
+        }
+        
+        if panGesture.state == UIGestureRecognizer.State.changed {
+            //invalid hide timer
+            if let handleTimer = handleTimer {
+                handleTimer.invalidate()
+            }
+            
+            handle.alpha = 1.0
+            scrollbar.alpha = 1.0
+            handle.isHidden = false
+            scrollbar.isHidden = false
+            gestureHandleView.isHidden = false
+        }
+        
+        // views positions
+        positionHandle(handle.frame.origin.y + translation.y)
+        updateBubblePosition()
+        scrollCollectionFromHandle()
+        
+        // manage bubble info
+        manageBubbleInfo()
+    }
+    
+    fileprivate func manageBubbleInfo() {
+        guard let bubble = bubble else {
+            return
+        }
+        
+        let visibleCells = self.visibleCells
+        
+        var currentCellIndex: Int
+        
+        switch bubbleFocus {
+        case .first:
+            currentCellIndex = 0
+            
+        case .last:
+            currentCellIndex = visibleCells.count - 1
+            
+        case .dynamic:
+            //Calcul scroll percentage
+            let scrollY =  contentOffset.y
+            let collectionContentHeight = self.contentSize.height > self.bounds.height ? self.contentSize.height - self.bounds.height : self.bounds.height
+            let scrollPercentage = scrollY / collectionContentHeight
+            currentCellIndex = Int(floor(CGFloat(visibleCells.count) * scrollPercentage))
+        }
+        
+        if currentCellIndex < visibleCells.count {
+            if let indexPath = indexPath(for: visibleCells[currentCellIndex]) {
+                bubble.text = bubbleNameForIndexPath(indexPath)
+                let newSize = bubble.sizeThatFits(CGSize(width: self.bounds.width - (self.bounds.width - (bubble.frame.origin.x + bubble.frame.size.width)), height: bubble.frame.size.height))
+                let oldSize = bubble.frame.size
+                bubble.frame = CGRect(x: bubble.frame.origin.x + (oldSize.width - newSize.width), y: bubble.frame.origin.y, width: newSize.width, height: newSize.height)
+            }
+        }
+    }
+}
+
+// MARK: Scroll Management
+
+extension FastScrollTableView {
+    public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+        guard let handle = handle, let scrollbar = scrollbar, let gestureHandleView = gestureHandleView else {
+            return
+        }
+        
+        handle.alpha = 1.0
+        scrollbar.alpha = 1.0
+        handle.isHidden = false
+        scrollbar.isHidden = false
+        gestureHandleView.isHidden = false
+    }
+    
+    public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
+        if !decelerate {
+            self.handleTimer = Timer.scheduledTimer(timeInterval: TimeInterval(handleTimeToDisappear), target: self, selector: #selector(hideHandle), userInfo: nil, repeats: false)
+        }
+    }
+    
+    public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
+        self.handleTimer = Timer.scheduledTimer(timeInterval: TimeInterval(handleTimeToDisappear), target: self, selector: #selector(hideHandle), userInfo: nil, repeats: false)
+    }
+    
+    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        guard let handle = handle, let scrollbar = scrollbar else {
+            return
+        }
+        
+        //invalid timer
+        if let handleTimer = handleTimer {
+            handleTimer.invalidate()
+        }
+        
+        //scroll position
+        let scrollY =  scrollView.contentOffset.y
+        let collectionContentHeight = self.contentSize.height > self.bounds.height ? self.contentSize.height - self.bounds.height : self.bounds.height
+        let scrollBarHeight = scrollbar.frame.height
+        
+        
+        let handlePosition = (scrollY / collectionContentHeight) * (scrollBarHeight - handle.frame.size.height) + scrollbarMarginTop
+        if (handleTouched == false) {
+            positionHandle(handlePosition)
+        }
+        
+        updateBubblePosition()
+    }
+}
+
+

+ 4 - 6
iOSClient/Media/NCMedia.storyboard

@@ -1,11 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EFX-fO-Oip">
-    <device id="retina5_9" orientation="portrait">
-        <adaptation id="fullscreen"/>
-    </device>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EFX-fO-Oip">
+    <device id="retina5_9" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -18,7 +16,7 @@
                         <rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
-                            <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Zaz-Cl-qpZ" customClass="FastScrollCollectionView" customModule="FastScroll">
+                            <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Zaz-Cl-qpZ" customClass="FastScrollCollectionView" customModule="Nextcloud" customModuleProvider="target">
                                 <rect key="frame" x="0.0" y="44" width="375" height="734"/>
                                 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                 <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="fF1-wd-0xN">

+ 7 - 4
iOSClient/Media/NCMedia.swift

@@ -22,7 +22,6 @@
 //
 
 import Foundation
-import FastScroll
 
 class NCMedia: UIViewController, DropdownMenuDelegate, DZNEmptyDataSetSource, DZNEmptyDataSetDelegate, NCSelectDelegate {
     
@@ -123,6 +122,7 @@ class NCMedia: UIViewController, DropdownMenuDelegate, DZNEmptyDataSetSource, DZ
         self.navigationItem.title = NSLocalizedString("_media_", comment: "")
         
         // Fast Scrool
+        collectionView?.setup()
         configFastScroll()
 
         // Reload Data Source
@@ -144,6 +144,7 @@ class NCMedia: UIViewController, DropdownMenuDelegate, DZNEmptyDataSetSource, DZ
             self.collectionView?.reloadDataThenPerform {
                 self.downloadThumbnail()
             }
+            self.collectionView?.setup()
         }
     }
     
@@ -793,9 +794,11 @@ extension NCMedia: FastScrollCollectionViewDelegate {
         
         //scrollbar
         collectionView.scrollbarWidth = 0.0
-        collectionView.scrollbarMarginTop = 43.0
-        collectionView.scrollbarMarginBottom = 5.0
-        collectionView.scrollbarMarginRight = 10.0
+        collectionView.scrollbarMarginTop = 0//43.0
+        collectionView.scrollbarMarginBottom = 0//5.0
+        collectionView.scrollbarMarginRight = 0//10.0
+        
+        
  
         //callback action to display bubble name
         /*