jsdw_ios/QuickLocation/Section/AppRestrict/AppCatalogStore.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
)
}
}
}