import Defaults import Foundation final class RecentsModel: ObservableObject { static var shared = RecentsModel() @Default(.recentlyOpened) var items @Default(.saveRecents) var saveRecents func clear() { items = [] } func clearQueries() { items.removeAll { $0.type == .query } } func add(_ item: RecentItem) { if !saveRecents { clear() if item.type == .query { return } } if let index = items.firstIndex(where: { $0.id == item.id }) { items.remove(at: index) } items.append(item) } func close(_ item: RecentItem) { if let index = items.firstIndex(where: { $0.id == item.id }) { items.remove(at: index) } } func addQuery(_ query: String) { if !query.isEmpty { if NavigationModel.shared.tabSelection != .search { NavigationModel.shared.tabSelection = .search } add(.init(from: query)) } } var presentedChannel: Channel? { if let recent = items.last(where: { $0.type == .channel }) { return recent.channel } return nil } var presentedPlaylist: ChannelPlaylist? { if let recent = items.last(where: { $0.type == .playlist }) { return recent.playlist } return nil } var presentedItem: RecentItem? { guard let recent = items.last else { return nil } return recent } static func symbolSystemImage(_ name: String) -> String { let firstLetter = name.first?.lowercased() let regex = #"^[a-z0-9]$"# let symbolName = firstLetter?.range(of: regex, options: .regularExpression) != nil ? firstLetter! : "questionmark" return "\(symbolName).circle" } } struct RecentItem: Defaults.Serializable, Identifiable { static var bridge = RecentItemBridge() enum ItemType: String { case channel, playlist, query } var type: ItemType var id: String var title: String var tag: String { "recent\(type.rawValue.capitalized)\(id)" } var query: SearchQuery? { guard type == .query else { return nil } return SearchQuery(query: title) } var channel: Channel? { guard type == .channel else { return nil } return Channel(app: .invidious, id: id, name: title) } var playlist: ChannelPlaylist? { guard type == .playlist else { return nil } return ChannelPlaylist(id: id, title: title) } init(type: ItemType, identifier: String, title: String) { self.type = type id = identifier self.title = title } init(from channel: Channel) { type = .channel id = channel.id title = channel.name } init(from query: String) { type = .query id = query title = query } init(from playlist: ChannelPlaylist) { type = .playlist id = playlist.id title = playlist.title } } struct RecentItemBridge: Defaults.Bridge { typealias Value = RecentItem typealias Serializable = [String: String] func serialize(_ value: Value?) -> Serializable? { guard let value else { return nil } return [ "type": value.type.rawValue, "identifier": value.id, "title": value.title ] } func deserialize(_ object: Serializable?) -> Value? { guard let object, let type = object["type"], let identifier = object["identifier"], let title = object["title"] else { return nil } return RecentItem( type: .init(rawValue: type)!, identifier: identifier, title: title ) } }