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:
christophhagen
2020-06-18 22:55:51 +02:00
parent 7287607a60
commit 8892d04f62
22 changed files with 1484 additions and 930 deletions

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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)])
}
}
}
}

View File

@ -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.
}
*/
}

View File

@ -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()
}

View File

@ -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
}
}
}