113 lines
3.9 KiB
Swift
113 lines
3.9 KiB
Swift
//
|
|
// AppCatalogStore.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
import Kingfisher
|
|
|
|
struct AppCatalogItem: Codable, Equatable {
|
|
let id: String
|
|
let name: String
|
|
let icon: String
|
|
let keywords: [String]
|
|
let iconURL: String?
|
|
|
|
init(id: String, name: String, icon: String, keywords: [String], iconURL: String? = nil) {
|
|
self.id = id
|
|
self.name = name
|
|
self.icon = icon
|
|
self.keywords = keywords
|
|
self.iconURL = iconURL
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try container.decode(String.self, forKey: .id)
|
|
name = try container.decode(String.self, forKey: .name)
|
|
icon = try container.decodeIfPresent(String.self, forKey: .icon) ?? ""
|
|
keywords = try container.decodeIfPresent([String].self, forKey: .keywords) ?? []
|
|
iconURL = try container.decodeIfPresent(String.self, forKey: .iconURL)
|
|
}
|
|
|
|
var isRemoteIcon: Bool {
|
|
guard let iconURL, !iconURL.isEmpty else { return false }
|
|
return icon.isEmpty
|
|
}
|
|
}
|
|
|
|
enum AppCatalogStore {
|
|
static let sharedItems: [AppCatalogItem] = {
|
|
guard let url = Bundle.main.url(forResource: "app_catalog", withExtension: "json"),
|
|
let data = try? Data(contentsOf: url),
|
|
let items = try? JSONDecoder().decode([AppCatalogItem].self, from: data) else {
|
|
return []
|
|
}
|
|
return items
|
|
}()
|
|
|
|
static func item(id: String) -> AppCatalogItem? {
|
|
sharedItems.first { $0.id == id }
|
|
}
|
|
|
|
static func resolve(link: AppRestrictLinkRecord) -> AppCatalogItem? {
|
|
if let local = item(id: link.catalogId) {
|
|
return local
|
|
}
|
|
guard let name = link.displayName, !name.isEmpty else { return nil }
|
|
return AppCatalogItem(
|
|
id: link.catalogId,
|
|
name: name,
|
|
icon: "",
|
|
keywords: [],
|
|
iconURL: link.iconURL
|
|
)
|
|
}
|
|
|
|
static func search(_ query: String) -> [AppCatalogItem] {
|
|
let q = query.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
guard !q.isEmpty else { return sharedItems }
|
|
return sharedItems.filter { item in
|
|
item.name.lowercased().contains(q)
|
|
|| item.keywords.contains(where: { $0.lowercased().contains(q) })
|
|
}
|
|
}
|
|
|
|
static func image(for item: AppCatalogItem) -> UIImage? {
|
|
guard !item.icon.isEmpty else { return placeholder(for: item.name) }
|
|
return UIImage(named: item.icon) ?? placeholder(for: item.name)
|
|
}
|
|
|
|
static func setIcon(for imageView: UIImageView, item: AppCatalogItem, cornerRadius: CGFloat = 10) {
|
|
imageView.layer.cornerRadius = cornerRadius
|
|
imageView.clipsToBounds = true
|
|
imageView.contentMode = .scaleAspectFill
|
|
if let urlString = item.iconURL, let url = URL(string: urlString) {
|
|
imageView.kf.setImage(with: url, placeholder: placeholder(for: item.name))
|
|
} else {
|
|
imageView.kf.cancelDownloadTask()
|
|
imageView.image = image(for: item)
|
|
}
|
|
}
|
|
|
|
private static func placeholder(for name: String) -> UIImage? {
|
|
let size = CGSize(width: 44, height: 44)
|
|
let renderer = UIGraphicsImageRenderer(size: size)
|
|
return renderer.image { _ in
|
|
UIColor(hexStr: "#16B3FF").setFill()
|
|
UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 10).fill()
|
|
let letter = String(name.prefix(1)) as NSString
|
|
let attrs: [NSAttributedString.Key: Any] = [
|
|
.font: UIFont.systemFont(ofSize: 18, weight: .bold),
|
|
.foregroundColor: UIColor.white
|
|
]
|
|
let textSize = letter.size(withAttributes: attrs)
|
|
letter.draw(
|
|
at: CGPoint(x: (size.width - textSize.width) / 2, y: (size.height - textSize.height) / 2),
|
|
withAttributes: attrs
|
|
)
|
|
}
|
|
}
|
|
}
|