Commit 78d70d21 authored by Jose Blaya's avatar Jose Blaya
Browse files

Drag and drop tiles

parent 6bea24d3
This diff is collapsed.
......@@ -55,7 +55,6 @@ class DashboardViewController: AutolayoutViewController {
private var tileModeStatus: TileStatus = .normal {
didSet {
collectionView.reloadData()
self.updateTileLayout()
}
}
......@@ -140,15 +139,7 @@ class DashboardViewController: AutolayoutViewController {
viewContent.isHidden = false
viewRows.isHidden = false
// XXX: scale menu item manually
// UIButton *buttonMenu = [UIButton buttonWithType:UIButtonTypeCustom];
// const CGFloat itemRatio = 22.0 / 15.0;
// const CGFloat itemWidth = 17.0;
// buttonMenu.frame = CGRectMake(0, 0, itemWidth, itemWidth / itemRatio);
// [buttonMenu setImage:[UIImage imageNamed:@"item-menu"] forState:UIControlStateNormal];
// [buttonMenu addTarget:self action:@selector(openMenu:) forControlEvents:UIControlEventTouchUpInside];
// self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:buttonMenu];
collectionView.reloadData()
updateCurrentStatus()
}
......@@ -396,6 +387,8 @@ class DashboardViewController: AutolayoutViewController {
Theme.current.applyLightNavigationBar(navigationController!.navigationBar)
Theme.current.applySolidLightBackground(collectionView)
collectionView.collectionViewLayout.invalidateLayout()
collectionView.reloadData()
}
......@@ -496,7 +489,7 @@ extension DashboardViewController: UICollectionViewDelegate, UICollectionViewDat
let tileIndex = tileModeStatus == .normal ?
Client.providers.tileProvider.visibleTiles[indexPath.row].rawValue :
AvailableTiles.allTiles()[indexPath.row].rawValue
Client.providers.tileProvider.orderedTiles[indexPath.row].rawValue
let identifier = Cells.objectIdentifyBy(index: tileIndex).identifier
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier,
......@@ -504,7 +497,6 @@ extension DashboardViewController: UICollectionViewDelegate, UICollectionViewDat
if let cell = cell as? EditableTileCell {
cell.setupCellForStatus(self.tileModeStatus)
}
Theme.current.applySolidLightBackground(cell)
return cell
}
......@@ -515,20 +507,23 @@ extension DashboardViewController: UICollectionViewDelegate, UICollectionViewDat
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tileModeStatus == .normal ?
Client.providers.tileProvider.visibleTiles.count :
AvailableTiles.allTiles().count
Client.providers.tileProvider.orderedTiles.count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let detailedCell = cell as? DetailedTileCell,
detailedCell.hasDetailView(),
let segueIdentifier = detailedCell.segueIdentifier() {
performSegue(withIdentifier: segueIdentifier, sender: nil)
if tileModeStatus == .normal {
let cell = collectionView.cellForItem(at: indexPath)
if let detailedCell = cell as? DetailedTileCell,
detailedCell.hasDetailView(),
let segueIdentifier = detailedCell.segueIdentifier() {
performSegue(withIdentifier: segueIdentifier, sender: nil)
}
}
}
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? DetailedTileCell,
if tileModeStatus == .normal,
let cell = collectionView.cellForItem(at: indexPath) as? DetailedTileCell,
cell.hasDetailView() {
UIView.animate(withDuration: 0.1, animations: {
cell.highlightCell()
......@@ -537,11 +532,24 @@ extension DashboardViewController: UICollectionViewDelegate, UICollectionViewDat
}
func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? DetailedTileCell,
if tileModeStatus == .normal,
let cell = collectionView.cellForItem(at: indexPath) as? DetailedTileCell,
cell.hasDetailView() {
UIView.animate(withDuration: 0.1, animations: {
cell.unhighlightCell()
})
}
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return self.tileModeStatus == .edit
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
var orderedTiles = Client.providers.tileProvider.orderedTiles
let tile = orderedTiles.remove(at: sourceIndexPath.row)
orderedTiles.insert(tile, at: destinationIndexPath.row)
Client.providers.tileProvider.orderedTiles = orderedTiles
collectionView.reloadData()
}
}
......@@ -2,7 +2,7 @@
"images" : [
{
"idiom" : "universal",
"filename" : "eyeInactive.pdf"
"filename" : "eyeActive-2.pdf"
}
],
"info" : {
......
......@@ -2,7 +2,7 @@
"images" : [
{
"idiom" : "universal",
"filename" : "eyeActive.pdf"
"filename" : "eyeActive-3.pdf"
}
],
"info" : {
......
{
"images" : [
{
"idiom" : "universal",
"filename" : "eyeInactive-2.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "eyeInactive-3.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
......@@ -106,8 +106,10 @@ enum Asset {
enum Global {
static let dragDropIndicatorDark = ImageAsset(name: "drag-drop-indicator-dark")
static let dragDropIndicatorLight = ImageAsset(name: "drag-drop-indicator-light")
static let eyeActive = ImageAsset(name: "eye-active")
static let eyeInactive = ImageAsset(name: "eye-inactive")
static let eyeActiveDark = ImageAsset(name: "eye-active-dark")
static let eyeActiveLight = ImageAsset(name: "eye-active-light")
static let eyeInactiveDark = ImageAsset(name: "eye-inactive-dark")
static let eyeInactiveLight = ImageAsset(name: "eye-inactive-light")
static let favoriteGreen = ImageAsset(name: "favorite-green")
static let favoriteSelected = ImageAsset(name: "favorite-selected")
static let favoriteUnselectedDark = ImageAsset(name: "favorite-unselected-dark")
......@@ -503,8 +505,10 @@ enum Asset {
Piax.Dashboard.vpnButton,
Piax.Global.dragDropIndicatorDark,
Piax.Global.dragDropIndicatorLight,
Piax.Global.eyeActive,
Piax.Global.eyeInactive,
Piax.Global.eyeActiveDark,
Piax.Global.eyeActiveLight,
Piax.Global.eyeInactiveDark,
Piax.Global.eyeInactiveLight,
Piax.Global.favoriteGreen,
Piax.Global.favoriteSelected,
Piax.Global.favoriteUnselectedDark,
......
......@@ -177,4 +177,16 @@ extension Theme {
Asset.Piax.Global.dragDropIndicatorLight.image
}
public func activeEyeImage() -> UIImage {
return palette.appearance == .dark ?
Asset.Piax.Global.eyeActiveDark.image :
Asset.Piax.Global.eyeActiveLight.image
}
public func inactiveEyeImage() -> UIImage {
return palette.appearance == .dark ?
Asset.Piax.Global.eyeInactiveDark.image :
Asset.Piax.Global.eyeInactiveLight.image
}
}
......@@ -19,8 +19,10 @@ class IPTileCollectionViewCell: UICollectionViewCell, TileableCell {
@IBOutlet private weak var accessoryButtonLeft: UIButton!
@IBOutlet weak var tileLeftConstraint: NSLayoutConstraint!
@IBOutlet weak var tileRightConstraint: NSLayoutConstraint!
func setupCellForStatus(_ status: TileStatus) {
Theme.current.applySolidLightBackground(self)
Theme.current.applySolidLightBackground(self.contentView)
self.accessoryImageRight.image = Theme.current.dragDropImage()
tile.status = status
UIView.animate(withDuration: AppConfiguration.Animations.duration, animations: {
......@@ -39,11 +41,11 @@ class IPTileCollectionViewCell: UICollectionViewCell, TileableCell {
private func setupVisibilityButton() {
if Client.providers.tileProvider.visibleTiles.contains(tileType) {
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeActive.image, for: .normal)
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeInactive.image, for: .highlighted)
accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal)
accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted)
} else {
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeInactive.image, for: .normal)
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeActive.image, for: .highlighted)
accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal)
accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted)
}
}
......
......@@ -21,6 +21,8 @@ class QuickConnectTileCollectionViewCell: UICollectionViewCell, TileableCell {
@IBOutlet weak var tileRightConstraint: NSLayoutConstraint!
func setupCellForStatus(_ status: TileStatus) {
Theme.current.applySolidLightBackground(self)
Theme.current.applySolidLightBackground(self.contentView)
self.accessoryImageRight.image = Theme.current.dragDropImage()
tile.status = status
UIView.animate(withDuration: AppConfiguration.Animations.duration, animations: {
......@@ -39,11 +41,11 @@ class QuickConnectTileCollectionViewCell: UICollectionViewCell, TileableCell {
private func setupVisibilityButton() {
if Client.providers.tileProvider.visibleTiles.contains(tileType) {
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeActive.image, for: .normal)
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeInactive.image, for: .highlighted)
accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal)
accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted)
} else {
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeInactive.image, for: .normal)
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeActive.image, for: .highlighted)
accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal)
accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted)
}
}
......
......@@ -29,6 +29,8 @@ class RegionTileCollectionViewCell: UICollectionViewCell, TileableCell {
}
func setupCellForStatus(_ status: TileStatus) {
Theme.current.applySolidLightBackground(self)
Theme.current.applySolidLightBackground(self.contentView)
tile.status = status
UIView.animate(withDuration: AppConfiguration.Animations.duration, animations: {
switch status {
......@@ -48,20 +50,22 @@ class RegionTileCollectionViewCell: UICollectionViewCell, TileableCell {
func highlightCell() {
Theme.current.applyLightBackground(tile)
Theme.current.applyLightBackground(self)
Theme.current.applyLightBackground(self.contentView)
}
func unhighlightCell() {
Theme.current.applySolidLightBackground(tile)
Theme.current.applySolidLightBackground(self)
Theme.current.applySolidLightBackground(self.contentView)
}
private func setupVisibilityButton() {
if Client.providers.tileProvider.visibleTiles.contains(tileType) {
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeActive.image, for: .normal)
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeInactive.image, for: .highlighted)
accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal)
accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted)
} else {
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeInactive.image, for: .normal)
accessoryButtonLeft.setImage(Asset.Piax.Global.eyeActive.image, for: .highlighted)
accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal)
accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted)
}
}
......
......@@ -12,14 +12,34 @@ import PIALibrary
private let separatorDecorationViewTop = "separator-top"
private let separatorDecorationViewBottom = "separator-bottom"
private struct DragDropSettings {
static let minimumPressDuration: TimeInterval = 0.2
static let shadowOpacity: Float = 0.8
static let shadowRadius: CGFloat = 10
static let springDamping: CGFloat = 0.4
static let viewAlpha: CGFloat = 0.95
static let viewTransform: CGAffineTransform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}
final class TileFlowLayout: UICollectionViewFlowLayout {
private var longPress: UILongPressGestureRecognizer!
private var originalIndexPath: IndexPath?
private var draggingIndexPath: IndexPath?
private var draggingView: UIView?
private var dragOffset = CGPoint.zero
override func awakeFromNib() {
super.awakeFromNib()
register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationViewTop)
register(SeparatorView.self, forDecorationViewOfKind: separatorDecorationViewBottom)
}
override func prepare() {
super.prepare()
self.installGestureRecognizer()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect) ?? []
let lineWidth = self.minimumLineSpacing
......@@ -66,6 +86,112 @@ final class TileFlowLayout: UICollectionViewFlowLayout {
}
}
private func installGestureRecognizer() {
if longPress == nil {
longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(longPress:)))
longPress.minimumPressDuration = DragDropSettings.minimumPressDuration
collectionView?.addGestureRecognizer(longPress)
}
}
@objc private func handleLongPress(longPress: UILongPressGestureRecognizer) {
let location = longPress.location(in: collectionView!)
switch longPress.state {
case .began: startDragAtLocation(location)
case .changed: updateDragAtLocation(location)
case .ended: endDragAtLocation(location)
default:
break
}
}
private func startDragAtLocation(_ location: CGPoint) {
guard let cv = collectionView else { return }
guard let indexPath = cv.indexPathForItem(at: location) else { return }
guard let dataSource = cv.dataSource else { return }
guard dataSource.collectionView!(cv, canMoveItemAt: indexPath) == true else { return }
guard let cell = cv.cellForItem(at: indexPath) else { return }
originalIndexPath = indexPath
draggingIndexPath = indexPath
draggingView = cell.snapshotView(afterScreenUpdates: true)
draggingView!.frame = cell.frame
cv.addSubview(draggingView!)
dragOffset = CGPoint(x: draggingView!.center.x - location.x,
y: draggingView!.center.y - location.y)
draggingView?.layer.shadowPath = UIBezierPath(rect: draggingView!.bounds).cgPath
draggingView?.layer.shadowColor = UIColor.black.cgColor
draggingView?.layer.shadowOpacity = DragDropSettings.shadowOpacity
draggingView?.layer.shadowRadius = DragDropSettings.shadowRadius
invalidateLayout()
UIView.animate(withDuration: AppConfiguration.Animations.duration,
delay: 0,
usingSpringWithDamping: DragDropSettings.springDamping,
initialSpringVelocity: 0,
options: [],
animations: {
self.draggingView?.alpha = DragDropSettings.viewAlpha
self.draggingView?.transform = DragDropSettings.viewTransform
}, completion: nil)
}
private func updateDragAtLocation(_ location: CGPoint) {
guard let view = draggingView else { return }
guard let cv = collectionView else { return }
view.center = CGPoint(x: location.x + dragOffset.x,
y: location.y + dragOffset.y)
if let newIndexPath = cv.indexPathForItem(at: location) {
cv.moveItem(at: draggingIndexPath!, to: newIndexPath)
draggingIndexPath = newIndexPath
}
}
private func endDragAtLocation(_ location: CGPoint) {
guard let dragView = draggingView else { return }
guard let indexPath = draggingIndexPath else { return }
guard let cv = collectionView else { return }
guard let datasource = cv.dataSource else { return }
let targetCenter = datasource.collectionView(cv, cellForItemAt: indexPath).center
let shadowFade = CABasicAnimation(keyPath: "shadowOpacity")
shadowFade.fromValue = 0.8
shadowFade.toValue = 0
shadowFade.duration = AppConfiguration.Animations.duration
dragView.layer.add(shadowFade, forKey: "shadowFade")
UIView.animate(withDuration: AppConfiguration.Animations.duration,
delay: 0,
usingSpringWithDamping: DragDropSettings.springDamping,
initialSpringVelocity: 0,
options: [],
animations: {
dragView.center = targetCenter
dragView.transform = .identity
}) { (completed) in
if let original = self.originalIndexPath {
if indexPath.compare(original) != ComparisonResult.orderedSame {
datasource.collectionView!(cv, moveItemAt: original, to: indexPath)
}
dragView.removeFromSuperview()
self.draggingIndexPath = nil
self.draggingView = nil
self.invalidateLayout()
}
}
}
}
private final class SeparatorView: UICollectionReusableView {
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment