Version 1
This commit is contained in:
46
CapCollector/Presentation/CapCell.swift
Normal file
46
CapCollector/Presentation/CapCell.swift
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// CapCell.swift
|
||||
// CapFinder
|
||||
//
|
||||
// Created by User on 22.04.18.
|
||||
// Copyright © 2018 User. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
class CapCell: UITableViewCell {
|
||||
|
||||
@IBOutlet private weak var capImage: RoundedImageView!
|
||||
|
||||
@IBOutlet private weak var matchLabel: UILabel!
|
||||
|
||||
@IBOutlet private weak var nameLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var countLabel: UILabel!
|
||||
|
||||
var id = 0
|
||||
|
||||
var cap: Cap! {
|
||||
didSet {
|
||||
updateCell()
|
||||
}
|
||||
}
|
||||
|
||||
func updateCell() {
|
||||
capImage.image = cap.image
|
||||
//capImage.borderColor = AppDelegate.tintColor
|
||||
|
||||
matchLabel.text = text(for: cap.match)
|
||||
nameLabel.text = cap.name
|
||||
countLabel.text = "\(cap.id) (\(cap.count) image" + (cap.count > 1 ? "s)" : ")")
|
||||
}
|
||||
|
||||
private func text(for value: Float?) -> String? {
|
||||
guard let nr = value else {
|
||||
return nil
|
||||
}
|
||||
let percent = Int((nr * 100).rounded())
|
||||
return String(format: "%d %%", arguments: [percent])
|
||||
}
|
||||
}
|
273
CapCollector/Presentation/GridViewController.swift
Normal file
273
CapCollector/Presentation/GridViewController.swift
Normal file
@ -0,0 +1,273 @@
|
||||
//
|
||||
// GridViewController.swift
|
||||
// CapCollector
|
||||
//
|
||||
// Created by Christoph on 07.01.19.
|
||||
// Copyright © 2019 CH. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class GridViewController: UIViewController {
|
||||
|
||||
|
||||
|
||||
private let columns = 40
|
||||
|
||||
static let len: CGFloat = 60
|
||||
|
||||
private lazy var rowHeight = GridViewController.len * 0.866
|
||||
|
||||
private lazy var margin = GridViewController.len - rowHeight
|
||||
|
||||
private var myView: UIView!
|
||||
|
||||
private var canvasSize: CGSize = .zero
|
||||
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
|
||||
private var selectedTile: Int? = nil
|
||||
|
||||
private weak var selectionView: RoundedButton!
|
||||
|
||||
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
|
||||
return .portrait
|
||||
}
|
||||
|
||||
override var shouldAutorotate: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let width = CGFloat(columns) * GridViewController.len + GridViewController.len / 2
|
||||
let height = (CGFloat(Cap.totalCapCount) / CGFloat(columns)).rounded(.up) * rowHeight + margin
|
||||
canvasSize = CGSize(width: width, height: height)
|
||||
myView = UIView(frame: CGRect(origin: .zero, size: canvasSize))
|
||||
|
||||
|
||||
scrollView.addSubview(myView)
|
||||
scrollView.contentSize = canvasSize
|
||||
scrollView.delegate = self
|
||||
scrollView.zoomScale = 0.5
|
||||
scrollView.maximumZoomScale = 1
|
||||
setZoomRange()
|
||||
|
||||
/*
|
||||
guard let image = Cap.mosaic else {
|
||||
error("No mosaic")
|
||||
return
|
||||
}
|
||||
imageView.image = image
|
||||
imageHeight.constant = image.size.height
|
||||
imageWidth.constant = image.size.width
|
||||
scrollView.contentSize = image.size
|
||||
let button = RoundedButton(frame: CGRect(origin: .zero, size: CGSize(width: Cap.mosaicCellSize, height: Cap.mosaicCellSize)))
|
||||
imageView.addSubview(button)
|
||||
selectionView = button
|
||||
button.borderColor = AppDelegate.tintColor
|
||||
button.borderWidth = 3
|
||||
button.isHidden = true
|
||||
scrollView.delegate = self
|
||||
scrollView.zoomScale = 1
|
||||
scrollView.maximumZoomScale = 1
|
||||
setZoomRange()
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
scrollView.addGestureRecognizer(tapRecognizer)
|
||||
event("did load")
|
||||
*/
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
updateTiles()
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
myView.addGestureRecognizer(tapRecognizer)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
setZoomRange()
|
||||
updateTiles()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
Cap.save()
|
||||
}
|
||||
|
||||
private func setZoomRange() {
|
||||
let size = scrollView.frame.size
|
||||
let a = size.width / canvasSize.width
|
||||
let b = size.height / canvasSize.height
|
||||
let scale = min(a,b)
|
||||
scrollView.minimumZoomScale = min(a,b)
|
||||
if scrollView.zoomScale < scale {
|
||||
scrollView.setZoomScale(scale, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handleTap(_ sender: UITapGestureRecognizer) {
|
||||
|
||||
let loc = sender.location(in: myView)
|
||||
let y = loc.y
|
||||
let s = y.truncatingRemainder(dividingBy: rowHeight)
|
||||
let row = Int(y / rowHeight)
|
||||
guard s > margin else {
|
||||
return
|
||||
}
|
||||
let column: CGFloat
|
||||
if row.isEven {
|
||||
column = loc.x / GridViewController.len
|
||||
} else {
|
||||
column = (loc.x - GridViewController.len / 2) / GridViewController.len
|
||||
}
|
||||
handleTileTapped(tile: row * columns + Int(column))
|
||||
|
||||
/*
|
||||
event("Tapped")
|
||||
let loc = sender.location(in: imageView)
|
||||
guard let tile = Cap.tile(for: loc) else {
|
||||
event("No tile for location \(loc)")
|
||||
return
|
||||
}
|
||||
handleTileTapped(tile: tile)
|
||||
*/
|
||||
}
|
||||
|
||||
private func handleTileTapped(tile: Int) {
|
||||
if let selected = selectedTile {
|
||||
switchTiles(oldTile: selected, newTile: tile)
|
||||
} else {
|
||||
showSelection(tile: tile)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
private func showSelection(tile: Int) {
|
||||
event("Selecting tile \(tile)")
|
||||
let point = Cap.origin(for: tile)
|
||||
selectionView.frame = CGRect(origin: point, size: selectionView.frame.size)
|
||||
selectionView.isHidden = false
|
||||
selectedTile = tile
|
||||
}
|
||||
|
||||
private func switchTiles(oldTile: Int, newTile: Int) {
|
||||
event("Switching tiles \(oldTile) and \(newTile)")
|
||||
selectionView.isHidden = true
|
||||
selectedTile = nil
|
||||
guard oldTile != newTile else {
|
||||
return
|
||||
}
|
||||
Cap.switchTiles(oldTile, newTile)
|
||||
Cap.switchTilesInMosaic(imageView, tile1: oldTile, tile2: newTile)
|
||||
}
|
||||
*/
|
||||
|
||||
private var installedTiles = [Int : RoundedImageView]()
|
||||
|
||||
private func showSelection(tile: Int) {
|
||||
clearTileSelection()
|
||||
|
||||
if let view = installedTiles[tile] {
|
||||
view.borderWidth = 3
|
||||
view.borderColor = AppDelegate.tintColor
|
||||
selectedTile = tile
|
||||
} else {
|
||||
selectedTile = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func tileIsVisible(tile: Int, in rect: CGRect) -> Bool {
|
||||
return rect.intersects(frame(for: tile))
|
||||
}
|
||||
|
||||
private func makeTile(_ tile: Int) {
|
||||
let view = RoundedImageView(frame: frame(for: tile))
|
||||
myView.addSubview(view)
|
||||
view.image = Cap.tileImage(tile: tile)
|
||||
installedTiles[tile] = view
|
||||
}
|
||||
|
||||
private func frame(for tile: Int) -> CGRect {
|
||||
let row = tile / columns
|
||||
let column = tile - row * columns
|
||||
let x = CGFloat(column) * GridViewController.len + (row.isEven ? 0 : GridViewController.len / 2)
|
||||
let y = CGFloat(row) * rowHeight
|
||||
return CGRect(x: x, y: y, width: GridViewController.len, height: GridViewController.len)
|
||||
}
|
||||
|
||||
private func switchTiles(oldTile: Int, newTile: Int) {
|
||||
if oldTile != newTile {
|
||||
Cap.switchTiles(oldTile, newTile)
|
||||
installedTiles[oldTile]?.image = Cap.tileImage(tile: oldTile)
|
||||
installedTiles[newTile]?.image = Cap.tileImage(tile: newTile)
|
||||
}
|
||||
clearTileSelection()
|
||||
}
|
||||
|
||||
private func clearTileSelection() {
|
||||
guard let tile = selectedTile else {
|
||||
return
|
||||
}
|
||||
installedTiles[tile]?.borderWidth = 0
|
||||
selectedTile = nil
|
||||
}
|
||||
|
||||
private func showTiles(in rect: CGRect) {
|
||||
for i in 0..<Cap.totalCapCount {
|
||||
if tileIsVisible(tile: i, in: rect) {
|
||||
if installedTiles[i] != nil {
|
||||
continue
|
||||
}
|
||||
makeTile(i)
|
||||
} else if let tile = installedTiles[i] {
|
||||
tile.removeFromSuperview()
|
||||
installedTiles[i] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTiles() {
|
||||
guard #available(iOS 12.0, *) else {
|
||||
return
|
||||
}
|
||||
let scale = scrollView.zoomScale
|
||||
let offset = scrollView.contentOffset
|
||||
let size = scrollView.visibleSize
|
||||
|
||||
let scaledOrigin = CGPoint(x: offset.x / scale, y: offset.y / scale)
|
||||
let scaledSize = CGSize(width: size.width / scale, height: size.height / scale)
|
||||
let rect = CGRect(origin: scaledOrigin, size: scaledSize)
|
||||
|
||||
showTiles(in: rect)
|
||||
}
|
||||
}
|
||||
|
||||
extension GridViewController: UIScrollViewDelegate {
|
||||
|
||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
return myView
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
updateTiles()
|
||||
}
|
||||
}
|
||||
|
||||
private extension Int {
|
||||
|
||||
var isEven: Bool {
|
||||
return self % 2 == 0
|
||||
}
|
||||
}
|
||||
|
||||
extension GridViewController: Logger {
|
||||
|
||||
static let logToken: String = "[Grid]"
|
||||
|
||||
}
|
15
CapCollector/Presentation/ImageCell.swift
Normal file
15
CapCollector/Presentation/ImageCell.swift
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// ImageCell.swift
|
||||
// CapFinder
|
||||
//
|
||||
// Created by User on 07.02.18.
|
||||
// Copyright © 2018 User. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ImageCell: UICollectionViewCell {
|
||||
|
||||
@IBOutlet weak var capView: UIImageView!
|
||||
|
||||
}
|
166
CapCollector/Presentation/ImageSelector.swift
Normal file
166
CapCollector/Presentation/ImageSelector.swift
Normal file
@ -0,0 +1,166 @@
|
||||
//
|
||||
// ListViewController.swift
|
||||
// CapFinder
|
||||
//
|
||||
// Created by User on 22.02.18.
|
||||
// Copyright © 2018 User. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyDropbox
|
||||
|
||||
class ImageSelector: UIViewController {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
/// The number of items per row
|
||||
private let itemsPerRow: CGFloat = 3
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
return .portrait
|
||||
}
|
||||
|
||||
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
|
||||
return .portrait
|
||||
}
|
||||
|
||||
override var shouldAutorotate: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - CollectionView
|
||||
|
||||
private var images = [UIImage?]()
|
||||
|
||||
var cap: Cap!
|
||||
|
||||
@IBOutlet weak var collection: UICollectionView!
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
collection.dataSource = self
|
||||
collection.delegate = self
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
downloadImages()
|
||||
}
|
||||
|
||||
// MARK: Image download
|
||||
|
||||
private func downloadImages() {
|
||||
images = [UIImage?](repeating: nil, count: cap.count)
|
||||
event("\(cap.count) images for cap \(cap.id)")
|
||||
for number in 0..<cap.count {
|
||||
cap.downloadImage(number) { image in
|
||||
self.images[number] = image
|
||||
self.collection.reloadItems(at: [IndexPath(row: number, section: 0)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Select
|
||||
|
||||
private func selectedImage(nr: Int) {
|
||||
guard images[nr] != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
let tempId = cap.count
|
||||
let tempFile = "/Images/\(cap.id)/\(cap.id)-\(tempId).jpg"
|
||||
let oldFile = "/Images/\(cap.id)/\(cap.id)-0.jpg"
|
||||
let newFile = "/Images/\(cap.id)/\(cap.id)-\(nr).jpg"
|
||||
guard oldFile != newFile else {
|
||||
return
|
||||
}
|
||||
DropboxController.shared.move(file: oldFile, to: tempFile) { success in
|
||||
guard success else {
|
||||
self.error("Could not move \(oldFile) to \(tempFile)")
|
||||
return
|
||||
}
|
||||
DropboxController.shared.move(file: newFile, to: oldFile) { success in
|
||||
guard success else {
|
||||
self.error("Could not move \(newFile) to \(oldFile)")
|
||||
return
|
||||
}
|
||||
DropboxController.shared.move(file: tempFile, to: newFile) { success in
|
||||
if !success {
|
||||
self.error("Could not move \(tempFile) to \(newFile)")
|
||||
}
|
||||
self.finish(with: nr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func finish(with nr: Int) {
|
||||
let image = images[nr]!
|
||||
guard cap.save(mainImage: image) else {
|
||||
return
|
||||
}
|
||||
event("Successfully switched image")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
|
||||
extension ImageSelector: UICollectionViewDataSource {
|
||||
|
||||
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return images.count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCell(
|
||||
withReuseIdentifier: "Image",
|
||||
for: indexPath) as! ImageCell
|
||||
|
||||
cell.capView.image = images[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
selectedImage(nr: indexPath.row)
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UICollectionViewDelegateFlowLayout
|
||||
|
||||
extension ImageSelector : UICollectionViewDelegateFlowLayout {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView,
|
||||
layout collectionViewLayout: UICollectionViewLayout,
|
||||
sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
let widthPerItem = collectionView.frame.width / itemsPerRow
|
||||
return CGSize(width: widthPerItem, height: widthPerItem)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView,
|
||||
layout collectionViewLayout: UICollectionViewLayout,
|
||||
insetForSectionAt section: Int) -> UIEdgeInsets {
|
||||
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView,
|
||||
layout collectionViewLayout: UICollectionViewLayout,
|
||||
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageSelector: Logger {
|
||||
|
||||
static let logToken = "[ImageSelector]"
|
||||
}
|
||||
|
||||
|
33
CapCollector/Presentation/NavigationController.swift
Normal file
33
CapCollector/Presentation/NavigationController.swift
Normal file
@ -0,0 +1,33 @@
|
||||
//
|
||||
// NavigationController.swift
|
||||
// CapCollector
|
||||
//
|
||||
// Created by Christoph on 08.01.19.
|
||||
// Copyright © 2019 CH. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class NavigationController: UINavigationController {
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
return allowLandscape ? .allButUpsideDown : .portrait
|
||||
}
|
||||
|
||||
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
|
||||
return .portrait
|
||||
}
|
||||
|
||||
override var shouldAutorotate: Bool {
|
||||
return allowLandscape
|
||||
}
|
||||
|
||||
var allowLandscape: Bool = false
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
}
|
239
CapCollector/Presentation/SettingsController.swift
Normal file
239
CapCollector/Presentation/SettingsController.swift
Normal file
@ -0,0 +1,239 @@
|
||||
//
|
||||
// SettingsController.swift
|
||||
// CapCollector
|
||||
//
|
||||
// Created by Christoph on 15.10.18.
|
||||
// Copyright © 2018 CH. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SettingsController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var totalCapsLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var totalImagesLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var recognizedCapsLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var imagesStatsLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var databaseUpdatesLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var dropboxAccountLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var countsLabel: UILabel!
|
||||
|
||||
private var nameFileChanges = false
|
||||
|
||||
private var isUpdatingCounts = false
|
||||
|
||||
private var isUpdatingThumbnails = false
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
return .portrait
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
updateDropboxStatus()
|
||||
updateNameFileStats()
|
||||
updateDatabaseStats()
|
||||
|
||||
(navigationController as! NavigationController).allowLandscape = false
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
setAccessories()
|
||||
}
|
||||
|
||||
private func updateThumbnails() {
|
||||
isUpdatingThumbnails = true
|
||||
for cap in Cap.all.values {
|
||||
cap.makeThumbnail()
|
||||
}
|
||||
isUpdatingThumbnails = false
|
||||
}
|
||||
|
||||
private func updateDatabaseStats() {
|
||||
let totalCaps = Cap.totalCapCount
|
||||
totalCapsLabel.text = "\(totalCaps) caps"
|
||||
totalImagesLabel.text = "\(Cap.imageCount) images"
|
||||
let recognizedCaps = Persistence.recognizedCapCount
|
||||
let newCapCount = totalCaps - recognizedCaps
|
||||
recognizedCapsLabel.text = "\(newCapCount) new caps"
|
||||
let ratio = Float(Cap.imageCount)/Float(Cap.totalCapCount)
|
||||
let (images, count) = Cap.getCapStatistics().enumerated().first(where: { $0.element != 0 })!
|
||||
imagesStatsLabel.text = String(format: "%.2f images per cap, lowest count: %d (%dx)", ratio, images, count)
|
||||
}
|
||||
|
||||
private func updateNameFileStats() {
|
||||
let capCount = Cap.totalCapCount - Persistence.lastUploadedCapCount
|
||||
let imageCount = Cap.imageCount - Persistence.lastUploadedImageCount
|
||||
nameFileChanges = capCount > 0 || imageCount > 0
|
||||
databaseUpdatesLabel.text = "\(capCount) new caps and \(imageCount) new images"
|
||||
}
|
||||
|
||||
private func updateDropboxStatus() {
|
||||
dropboxAccountLabel.text = DropboxController.shared.isEnabled ? "Sign out" : "Sign in"
|
||||
}
|
||||
|
||||
private func setAccessories() {
|
||||
tableView.cellForRow(at: IndexPath(row: 0, section: 2))?.accessoryType = Persistence.squeezenet ? .checkmark : .none
|
||||
tableView.cellForRow(at: IndexPath(row: 1, section: 2))?.accessoryType = Persistence.resnet ? .checkmark : .none
|
||||
tableView.cellForRow(at: IndexPath(row: 2, section: 2))?.accessoryType = Persistence.xcode ? .checkmark : .none
|
||||
}
|
||||
|
||||
private func toggleClassifier(index: Int) {
|
||||
switch index {
|
||||
case 0: Persistence.squeezenet = !Persistence.squeezenet
|
||||
case 1: Persistence.resnet = !Persistence.resnet
|
||||
case 2: Persistence.xcode = !Persistence.xcode
|
||||
default:
|
||||
return
|
||||
}
|
||||
setAccessories()
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
|
||||
switch indexPath.section {
|
||||
case 0: // Mosaic
|
||||
return true
|
||||
case 1: // Database
|
||||
return indexPath.row == 2 && nameFileChanges
|
||||
case 2: // Choose models
|
||||
return true
|
||||
case 3: // Refresh
|
||||
if indexPath.row == 0 {
|
||||
return !isUpdatingCounts
|
||||
} else {
|
||||
return !isUpdatingThumbnails
|
||||
}
|
||||
case 4: // Dropbox account
|
||||
return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
switch indexPath.section {
|
||||
case 0: // Mosaic
|
||||
return indexPath
|
||||
case 1: // Database
|
||||
return (indexPath.row == 2 && nameFileChanges) ? indexPath : nil
|
||||
case 2: // Choose models
|
||||
return indexPath
|
||||
case 3: // Refresh count
|
||||
if indexPath.row == 0 {
|
||||
return isUpdatingCounts ? nil : indexPath
|
||||
} else {
|
||||
return isUpdatingThumbnails ? nil : indexPath
|
||||
}
|
||||
case 4: // Dropbox account
|
||||
return indexPath
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
switch indexPath.section {
|
||||
case 1: // Upload
|
||||
if indexPath.row == 2 && nameFileChanges {
|
||||
uploadNameFile()
|
||||
}
|
||||
case 2: // Choose models
|
||||
toggleClassifier(index: indexPath.row)
|
||||
case 3: // Refresh count
|
||||
if indexPath.row == 0 {
|
||||
updateCounts()
|
||||
} else {
|
||||
updateThumbnails()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func updateCounts() {
|
||||
isUpdatingCounts = true
|
||||
Cap.shouldSave = false
|
||||
// TODO: Don't make all requests at the same time
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let list = Cap.all.keys.sorted()
|
||||
let total = list.count
|
||||
var finished = 0
|
||||
let chunks = list.chunked(into: 10)
|
||||
for chunk in chunks {
|
||||
self.updateCounts(ids: chunk)
|
||||
finished += 10
|
||||
DispatchQueue.main.async {
|
||||
self.countsLabel.text = "\(finished) / \(total) finished"
|
||||
}
|
||||
}
|
||||
self.isUpdatingCounts = false
|
||||
Cap.shouldSave = true
|
||||
DispatchQueue.main.async {
|
||||
self.countsLabel.text = "Refresh image counts"
|
||||
self.updateDatabaseStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateCounts(ids: [Int]) {
|
||||
var count = ids.count
|
||||
let s = DispatchSemaphore(value: 0)
|
||||
for cap in ids {
|
||||
Cap.all[cap]!.updateCount { _ in
|
||||
count -= 1
|
||||
if count == 0 {
|
||||
s.signal()
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = s.wait(timeout: .now() + .seconds(30))
|
||||
event("Finished updating ids \(ids.first!) to \(ids.last!)")
|
||||
}
|
||||
|
||||
private func uploadNameFile() {
|
||||
Cap.saveAndUpload() { _ in
|
||||
self.updateNameFileStats()
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleDropbox() {
|
||||
guard !DropboxController.shared.isEnabled else {
|
||||
DropboxController.shared.signOut()
|
||||
updateDropboxStatus()
|
||||
return
|
||||
}
|
||||
|
||||
DropboxController.shared.setup(in: self)
|
||||
updateDropboxStatus()
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
guard let id = segue.identifier, id == "showMosaic" else {
|
||||
return
|
||||
}
|
||||
(navigationController as! NavigationController).allowLandscape = true
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsController: Logger {
|
||||
|
||||
static let logToken = "[Settings]"
|
||||
}
|
||||
|
||||
private extension Array {
|
||||
func chunked(into size: Int) -> [[Element]] {
|
||||
return stride(from: 0, to: count, by: size).map {
|
||||
Array(self[$0 ..< Swift.min($0 + size, count)])
|
||||
}
|
||||
}
|
||||
}
|
92
CapCollector/Presentation/SortController.swift
Normal file
92
CapCollector/Presentation/SortController.swift
Normal file
@ -0,0 +1,92 @@
|
||||
//
|
||||
// SortController.swift
|
||||
// CapCollector
|
||||
//
|
||||
// Created by Christoph on 12.11.18.
|
||||
// Copyright © 2018 CH. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
enum SortCriteria: Int {
|
||||
case id = 0
|
||||
case name = 1
|
||||
case count = 2
|
||||
case match = 3
|
||||
}
|
||||
|
||||
protocol SortControllerDelegate {
|
||||
|
||||
func sortController(didSelect sortType: SortCriteria, ascending: Bool)
|
||||
}
|
||||
|
||||
class SortController: UITableViewController {
|
||||
|
||||
var selected: SortCriteria = .count
|
||||
|
||||
var ascending: Bool = true
|
||||
|
||||
var delegate: SortControllerDelegate?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let height = Cap.hasMatches ? 310 : 270
|
||||
preferredContentSize = CGSize(width: 200, height: height)
|
||||
}
|
||||
|
||||
private func setCell() {
|
||||
for i in 0..<4 {
|
||||
let index = IndexPath(row: i, section: 1)
|
||||
let cell = tableView.cellForRow(at: index)
|
||||
cell?.accessoryType = i == selected.rawValue ? .checkmark : .none
|
||||
}
|
||||
let index = IndexPath(row: 0, section: 0)
|
||||
let cell = tableView.cellForRow(at: index)
|
||||
cell?.accessoryType = ascending ? .checkmark : .none
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
setCell()
|
||||
}
|
||||
|
||||
private func giveFeedback(_ style: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||
let generator = UIImpactFeedbackGenerator(style: style)
|
||||
generator.impactOccurred()
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
guard indexPath.section == 1 else {
|
||||
ascending = !ascending
|
||||
setCell()
|
||||
delegate?.sortController(didSelect: selected, ascending: ascending)
|
||||
giveFeedback(.light)
|
||||
return
|
||||
}
|
||||
giveFeedback(.medium)
|
||||
selected = SortCriteria(rawValue: indexPath.row)!
|
||||
delegate?.sortController(didSelect: selected, ascending: ascending)
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||
guard indexPath.row == 3 else { return indexPath }
|
||||
return Cap.hasMatches ? indexPath : nil
|
||||
}
|
||||
|
||||
/*
|
||||
// MARK: - Navigation
|
||||
|
||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
// Get the new view controller using segue.destination.
|
||||
// Pass the selected object to the new view controller.
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
Reference in New Issue
Block a user