Lots of updates
- Add unlock - Update Sorting menu - Prepare to load multiple tile images - New logging - Calculate thumbnails and colors before schowing grid
This commit is contained in:
@ -17,7 +17,7 @@ class CapCell: UITableViewCell {
|
||||
|
||||
@IBOutlet private weak var nameLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var countLabel: UILabel!
|
||||
@IBOutlet private weak var countLabel: UILabel!
|
||||
|
||||
var id: Int = 0
|
||||
|
||||
@ -25,24 +25,15 @@ class CapCell: UITableViewCell {
|
||||
capImage.image = image ?? UIImage(named: "launch")
|
||||
}
|
||||
|
||||
func set(cap: Cap, match: Float?) {
|
||||
id = cap.id
|
||||
if let image = cap.image {
|
||||
set(image: image)
|
||||
|
||||
} else {
|
||||
capImage.image = UIImage(named: "launch")
|
||||
cap.downloadMainImage() { image in
|
||||
self.set(image: image)
|
||||
}
|
||||
}
|
||||
|
||||
//capImage.borderColor = AppDelegate.tintColor
|
||||
|
||||
matchLabel.text = cap.matchDescription(match: match)
|
||||
nameLabel.text = cap.name
|
||||
countLabel.text = cap.subtitle
|
||||
func set(name: String) {
|
||||
self.nameLabel.text = name
|
||||
}
|
||||
|
||||
|
||||
func set(matchLabel: String) {
|
||||
self.matchLabel.text = matchLabel
|
||||
}
|
||||
|
||||
func set(countLabel: String) {
|
||||
self.countLabel.text = countLabel
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,7 @@ import UIKit
|
||||
|
||||
class GridViewController: UIViewController {
|
||||
|
||||
/// The number of caps horizontally.
|
||||
private let columns = 40
|
||||
|
||||
/// The number of hroizontal pixels for each cap.
|
||||
/// The number of horizontal pixels for each cap.
|
||||
static let len: CGFloat = 60
|
||||
|
||||
private lazy var rowHeight = GridViewController.len * 0.866
|
||||
@ -27,12 +24,20 @@ class GridViewController: UIViewController {
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
|
||||
/// A dictionary of the caps for the tiles
|
||||
private var tiles = [Cap]()
|
||||
private var tiles = [Int]()
|
||||
|
||||
/// The name of the tile image
|
||||
private var name: String = "default"
|
||||
|
||||
/// The number of caps horizontally.
|
||||
private var columns = 40
|
||||
|
||||
/// A dictionary for the colors of the caps
|
||||
private var colors = [Int : UIColor]()
|
||||
|
||||
/// The currently displaxed image views indexed by their tile ids
|
||||
private var installedTiles = [Int : RoundedImageView]()
|
||||
|
||||
private var changedTiles = Set<Int>()
|
||||
|
||||
private var selectedTile: Int? = nil
|
||||
|
||||
private weak var selectionView: RoundedButton!
|
||||
@ -47,34 +52,51 @@ class GridViewController: UIViewController {
|
||||
|
||||
private var isShowingColors = false
|
||||
|
||||
private var capCount = 0
|
||||
|
||||
@IBAction func toggleAverageColor(_ sender: Any) {
|
||||
isShowingColors = !isShowingColors
|
||||
for (tile, view) in installedTiles {
|
||||
if isShowingColors {
|
||||
view.image = nil
|
||||
view.backgroundColor = tileColor(tile: tile)
|
||||
} else {
|
||||
if let image = tiles[tile].thumbnail {
|
||||
let id = tiles[tile]
|
||||
if let image = app.storage.thumbnail(for: id) {
|
||||
view.image = image
|
||||
continue
|
||||
}
|
||||
tiles[tile].downloadMainImage() { image in
|
||||
view.image = image
|
||||
}
|
||||
self.downloadImage(cap: id, tile: tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func load(tileImage: Database.TileImage) {
|
||||
let totalCount = app.database.capCount
|
||||
let firstNewId = tileImage.caps.count + 1
|
||||
if totalCount >= firstNewId {
|
||||
self.tiles = tileImage.caps + (firstNewId...totalCount)
|
||||
} else {
|
||||
self.tiles = tileImage.caps
|
||||
}
|
||||
self.columns = tileImage.width
|
||||
self.name = tileImage.name
|
||||
}
|
||||
|
||||
private func saveTileImage() {
|
||||
let tileImage = Database.TileImage(name: name, width: columns, caps: tiles)
|
||||
guard app.database.save(tileImage: tileImage) else {
|
||||
log("Failed to save tile image")
|
||||
return
|
||||
}
|
||||
log("Tile image saved")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
app.database.add(listener: self)
|
||||
capCount = app.database.capCount
|
||||
tiles = app.database.caps.sorted { $0.tile < $1.tile }
|
||||
colors = app.database.colors
|
||||
|
||||
let width = CGFloat(columns) * GridViewController.len + GridViewController.len / 2
|
||||
let height = (CGFloat(capCount) / CGFloat(columns)).rounded(.up) * rowHeight + margin
|
||||
let height = (CGFloat(tiles.count) / CGFloat(columns)).rounded(.up) * rowHeight + margin
|
||||
canvasSize = CGSize(width: width, height: height)
|
||||
myView = UIView(frame: CGRect(origin: .zero, size: canvasSize))
|
||||
|
||||
@ -103,20 +125,14 @@ class GridViewController: UIViewController {
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
saveChangedTiles()
|
||||
saveTileImage()
|
||||
}
|
||||
|
||||
// MARK: Tiles
|
||||
|
||||
private func tileColor(tile: Int) -> UIColor {
|
||||
return tiles[tile].color
|
||||
}
|
||||
|
||||
private func saveChangedTiles() {
|
||||
for tile in changedTiles {
|
||||
let cap = tiles[tile]
|
||||
app.database.update(tile: tile, for: cap.id)
|
||||
}
|
||||
private func tileColor(tile: Int) -> UIColor? {
|
||||
let id = tiles[tile]
|
||||
return colors[id]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,8 +142,6 @@ class GridViewController: UIViewController {
|
||||
let temp = tiles[rhs]
|
||||
tiles[rhs] = tiles[lhs]
|
||||
tiles[lhs] = temp
|
||||
changedTiles.insert(lhs)
|
||||
changedTiles.insert(rhs)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -193,22 +207,53 @@ class GridViewController: UIViewController {
|
||||
private func makeTile(_ tile: Int) {
|
||||
let view = RoundedImageView(frame: frame(for: tile))
|
||||
myView.addSubview(view)
|
||||
view.backgroundColor = tileColor(tile: tile)
|
||||
defer {
|
||||
installedTiles[tile] = view
|
||||
}
|
||||
// Only set image if images are shown
|
||||
guard !isShowingColors else {
|
||||
view.backgroundColor = tileColor(tile: tile)
|
||||
return
|
||||
|
||||
}
|
||||
if let image = tiles[tile].thumbnail {
|
||||
if let image = app.storage.thumbnail(for: tiles[tile]) {
|
||||
view.image = image
|
||||
return
|
||||
}
|
||||
|
||||
tiles[tile].downloadMainImage() { image in
|
||||
view.image = image
|
||||
downloadImage(tile: tile)
|
||||
}
|
||||
|
||||
private func downloadImage(tile: Int) {
|
||||
let id = tiles[tile]
|
||||
downloadImage(cap: id, tile: tile)
|
||||
}
|
||||
|
||||
private func downloadImage(cap id: Int, tile: Int) {
|
||||
app.database.downloadMainImage(for: id) { success in
|
||||
guard success else {
|
||||
return
|
||||
}
|
||||
guard let view = self.installedTiles[tile] else {
|
||||
self.log("No installed tile for downloaded image \(id)")
|
||||
return
|
||||
}
|
||||
guard let image = app.storage.thumbnail(for: id) else {
|
||||
self.log("Failed to load image for cap \(id) after successful download")
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
guard self.isShowingColors else {
|
||||
view.image = image
|
||||
return
|
||||
}
|
||||
guard let color = image.averageColor else {
|
||||
self.log("Failed to get average color from image for cap \(id)")
|
||||
return
|
||||
}
|
||||
view.backgroundColor = color
|
||||
self.colors[id] = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,7 +296,7 @@ class GridViewController: UIViewController {
|
||||
}
|
||||
|
||||
private func showTiles(in rect: CGRect) {
|
||||
for tile in 0..<capCount {
|
||||
for tile in 0..<tiles.count {
|
||||
refresh(tile: tile, inVisibleRect: rect)
|
||||
}
|
||||
}
|
||||
@ -313,30 +358,3 @@ private extension Int {
|
||||
}
|
||||
|
||||
extension GridViewController: Logger { }
|
||||
|
||||
extension GridViewController: DatabaseDelegate {
|
||||
|
||||
func database(didChangeCap id: Int) {
|
||||
guard let view = installedTiles[id] else {
|
||||
return
|
||||
}
|
||||
guard let cap = app.database.cap(for: id) else {
|
||||
return
|
||||
}
|
||||
tiles[cap.tile] = cap
|
||||
view.backgroundColor = cap.color
|
||||
// Only set image if images are shown
|
||||
if !isShowingColors {
|
||||
view.image = cap.image
|
||||
}
|
||||
}
|
||||
|
||||
func database(didAddCap cap: Cap) {
|
||||
tiles.append(cap)
|
||||
refresh(tile: cap.tile, inVisibleRect: visibleRect)
|
||||
}
|
||||
|
||||
func databaseRequiresFullRefresh() {
|
||||
updateTiles()
|
||||
}
|
||||
}
|
||||
|
@ -96,25 +96,32 @@ class ImageSelector: UIViewController {
|
||||
private func downloadImages() {
|
||||
images = [UIImage?](repeating: nil, count: cap.count)
|
||||
log("\(cap.count) images for cap \(cap.id)")
|
||||
if let image = cap.image {
|
||||
if let image = app.storage.image(for: cap.id) {
|
||||
self.images[0] = image
|
||||
self.collection.reloadItems(at: [IndexPath(row: 0, section: 0)])
|
||||
} else {
|
||||
cap.downloadMainImage { image in
|
||||
app.database.downloadMainImage(for: cap.id) { success in
|
||||
guard success, let image = app.storage.image(for: self.cap.id) else {
|
||||
return
|
||||
}
|
||||
self.images[0] = image
|
||||
self.collection.reloadItems(at: [IndexPath(row: 0, section: 0)])
|
||||
DispatchQueue.main.async {
|
||||
self.collection.reloadItems(at: [IndexPath(row: 0, section: 0)])
|
||||
}
|
||||
}
|
||||
}
|
||||
guard cap.count > 0 else {
|
||||
return
|
||||
}
|
||||
for number in 1..<cap.count {
|
||||
_ = cap.downloadImage(number) { image in
|
||||
guard let image = image else {
|
||||
app.database.downloadImage(for: cap.id, version: number) { success in
|
||||
guard success, let image = app.storage.image(for: self.cap.id, version: number) else {
|
||||
return
|
||||
}
|
||||
self.images[number] = image
|
||||
self.collection.reloadItems(at: [IndexPath(row: number, section: 0)])
|
||||
DispatchQueue.main.async {
|
||||
self.collection.reloadItems(at: [IndexPath(row: number, section: 0)])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
//
|
||||
// LogViewController.swift
|
||||
// CapCollector
|
||||
//
|
||||
// Created by Christoph on 05.04.19.
|
||||
// Copyright © 2019 CH. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class LogViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
textView.text = logFile
|
||||
}
|
||||
|
||||
@IBOutlet weak var textView: UITextView!
|
||||
|
||||
/*
|
||||
// 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.
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
@ -70,7 +70,7 @@ class SearchAndDisplayAccessory: PassthroughView {
|
||||
weak var currentBlurContraint: NSLayoutConstraint?
|
||||
|
||||
weak var delegate: CapAccessoryDelegate?
|
||||
|
||||
|
||||
var currentImage: UIImage? {
|
||||
capImage.image
|
||||
}
|
||||
@ -122,7 +122,7 @@ class SearchAndDisplayAccessory: PassthroughView {
|
||||
|
||||
cameraButton.setImage(UIImage.templateImage(named: "camera_square"), for: .normal)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Search bar
|
||||
|
||||
func dismissAndClearSearchBar() {
|
||||
@ -132,14 +132,22 @@ class SearchAndDisplayAccessory: PassthroughView {
|
||||
|
||||
// MARK: Cap image
|
||||
|
||||
func showImageView(with image: UIImage) {
|
||||
func showImageView(with image: UIImage, isUnlocked: Bool) {
|
||||
capImage.image = image
|
||||
|
||||
saveButton.isHidden = !isUnlocked
|
||||
saveButton.isEnabled = isUnlocked
|
||||
let text = isUnlocked ? "Delete" : "Clear image"
|
||||
deleteButton.setTitle(text, for: .normal)
|
||||
|
||||
showImageView()
|
||||
}
|
||||
|
||||
func discardImage() {
|
||||
dismissAndClearSearchBar()
|
||||
hideImageView()
|
||||
DispatchQueue.main.async {
|
||||
self.dismissAndClearSearchBar()
|
||||
self.hideImageView()
|
||||
}
|
||||
delegate?.capAccessoryDidDiscardImage()
|
||||
}
|
||||
|
||||
|
@ -13,68 +13,101 @@ enum SortCriteria: Int {
|
||||
case name = 1
|
||||
case count = 2
|
||||
case match = 3
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .id:
|
||||
return "Id"
|
||||
case .name:
|
||||
return "Name"
|
||||
case .count:
|
||||
return "Count"
|
||||
case .match:
|
||||
return "Match"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol SortControllerDelegate: class {
|
||||
|
||||
var sortControllerShouldIncludeMatchOption: Bool { get }
|
||||
|
||||
|
||||
func sortController(didSelect sortType: SortCriteria, ascending: Bool)
|
||||
}
|
||||
|
||||
class SortController: UITableViewController {
|
||||
|
||||
var selected: SortCriteria = .count
|
||||
@IBOutlet weak var thirdRowLabel: UILabel!
|
||||
|
||||
var ascending: Bool = true
|
||||
var selected: SortCriteria = .id
|
||||
|
||||
private var includeMatches: Bool {
|
||||
delegate?.sortControllerShouldIncludeMatchOption ?? false
|
||||
}
|
||||
var ascending: Bool = false
|
||||
|
||||
weak var delegate: SortControllerDelegate?
|
||||
|
||||
var options = [SortCriteria]()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let height = includeMatches ? 298 : 258
|
||||
preferredContentSize = CGSize(width: 200, height: height)
|
||||
preferredContentSize = CGSize(width: 200, height: 139 + options.count * 40)
|
||||
}
|
||||
|
||||
private func giveFeedback(_ style: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||
UIImpactFeedbackGenerator(style: style).impactOccurred()
|
||||
}
|
||||
|
||||
private func giveFeedback(_ style: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||
let generator = UIImpactFeedbackGenerator(style: style)
|
||||
generator.impactOccurred()
|
||||
private func sortCriteria(for index: Int) -> SortCriteria {
|
||||
index < options.count ? options[index] : .match
|
||||
}
|
||||
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
section == 0 ? "Sort order" : "Sort by"
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
section == 0 ? 1 : options.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "SortCell")!
|
||||
|
||||
guard indexPath.section != 0 else {
|
||||
cell.accessoryType = ascending ? .checkmark : .none
|
||||
cell.textLabel?.text = "Ascending"
|
||||
return cell
|
||||
}
|
||||
let select = sortCriteria(for: indexPath.row)
|
||||
cell.textLabel?.text = select.text
|
||||
guard select == selected else {
|
||||
cell.accessoryType = .none
|
||||
return cell
|
||||
}
|
||||
cell.accessoryType = .checkmark
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
40
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
guard indexPath.section == 1 else {
|
||||
ascending = !ascending
|
||||
tableView.reloadData()
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
delegate?.sortController(didSelect: selected, ascending: ascending)
|
||||
giveFeedback(.light)
|
||||
return
|
||||
}
|
||||
giveFeedback(.medium)
|
||||
selected = SortCriteria(rawValue: indexPath.row)!
|
||||
selected = sortCriteria(for: indexPath.row)
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
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 includeMatches ? indexPath : nil
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
cell.accessoryType = ascending ? .checkmark : .none
|
||||
default:
|
||||
cell.accessoryType = indexPath.row == selected.rawValue ? .checkmark : .none
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user