Path: blob/a-new-beginning/Folium-iOS/Controllers/LibraryControllers/LibraryController.swift
2 views
//
// LibraryController.swift
// Folium-iOS
//
// Created by Jarrod Norwell on 12/7/2025.
//
import CombinedCores
import FilesManager
import Foundation
import StoreKit
import SwiftUI
import UIKit
import UniformTypeIdentifiers
class LibraryController : UICollectionViewController {
var dataSource: UICollectionViewDiffableDataSource<Core, BaseGame>? = nil
var snapshot: NSDiffableDataSourceSnapshot<Core, BaseGame>? = nil
enum ImportFilesType : Int {
case games,
systemFiles,
cia
}
var importFilesType: ImportFilesType? = nil
var systemFileToImport: File? = nil
var applicationModel: ApplicationModel
init(applicationModel: ApplicationModel) {
self.applicationModel = applicationModel
super.init(collectionViewLayout: applicationModel.layoutManager.library)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
if let navigationController {
navigationController.navigationBar.prefersLargeTitles = true
}
if #available(iOS 17, *) {
navigationItem.largeTitleDisplayMode = .inline
}
navigationItem.style = .browser
if #available(iOS 26, *) {
navigationItem.largeTitle = .localized(for: .library)
navigationItem.title = navigationItem.largeTitle
navigationItem.largeSubtitle = "0 games available"
navigationItem.subtitle = navigationItem.largeSubtitle
} else {
navigationItem.title = .localized(for: .library)
}
let filterAndJumpToBarButtonItems: [UIBarButtonItem] = if #available(iOS 26, *) {
[
UIBarButtonItem(image: UIImage(systemName: "list.bullet.badge.ellipsis"), menu: UIMenu(children: [
UIDeferredMenuElement.uncached { completion in
guard var filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
return
}
let elements: [UIMenuElement] = Core.cores.map { core in
UIAction(title: core.string,
subtitle: filteredCores.contains(core.string) ? .localized(for: .hideCore) : .localized(for: .showCore),
image: .init(systemName: filteredCores.contains(core.string) ? "eye" : "eye.slash")) { _ in
if filteredCores.contains(core.string) {
filteredCores.removeAll(where: { $0 == core.string })
} else {
filteredCores.append(core.string)
}
UserDefaults.standard.set(filteredCores, forKey: "folium.v1.38.filteredCores")
Task {
await self.populateLibrary()
}
}
}
completion(elements)
}
])),
UIBarButtonItem(image: UIImage(systemName: "calendar.day.timeline.left"), menu: UIMenu(children: [
UIDeferredMenuElement.uncached { completion in
guard let filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
return
}
let elements: [UIMenuElement] = Core.cores.map { core in
UIAction(title: core.string, subtitle: core.consoleShort,
attributes: filteredCores.contains(core.string) ? [] : .disabled) { _ in
guard let dataSource = self.dataSource, let section = dataSource.snapshot().indexOfSection(core) else {
return
}
self.collectionView.scrollToItem(at: .init(item: 0, section: section), at: .top, animated: true)
}
}
completion(elements)
}
]))
]
} else {
[
UIBarButtonItem(image: UIImage(systemName: "list.bullet"), menu: UIMenu(children: [
UIMenu(title: .localized(for: .filter), image: UIImage(systemName: "line.3.horizontal.decrease"), children: [
UIDeferredMenuElement.uncached { completion in
guard var filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
return
}
let elements: [UIMenuElement] = Core.cores.map { core in
UIAction(title: core.string,
subtitle: filteredCores.contains(core.string) ? .localized(for: .hideCore) : .localized(for: .showCore),
image: .init(systemName: filteredCores.contains(core.string) ? "eye" : "eye.slash")) { _ in
if filteredCores.contains(core.string) {
filteredCores.removeAll(where: { $0 == core.string })
} else {
filteredCores.append(core.string)
}
UserDefaults.standard.set(filteredCores, forKey: "folium.v1.38.filteredCores")
Task {
await self.populateLibrary()
}
}
}
completion(elements)
}
]),
UIMenu(title: .localized(for: .jumpTo), image: UIImage(systemName: "calendar.day.timeline.left"), children: [
UIDeferredMenuElement.uncached { completion in
guard let filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
return
}
let elements: [UIMenuElement] = Core.cores.map { core in
UIAction(title: core.string, subtitle: core.consoleShort,
attributes: filteredCores.contains(core.string) ? [] : .disabled) { _ in
guard let dataSource = self.dataSource, let section = dataSource.snapshot().indexOfSection(core) else {
return
}
self.collectionView.scrollToItem(at: .init(item: 0, section: section), at: .top, animated: true)
}
}
completion(elements)
}
])
]))
]
}
navigationItem.trailingItemGroups = [
.init(barButtonItems: [
UIBarButtonItem(image: UIImage(systemName: "plus"), menu: UIMenu(children: [
UIAction(title: .localized(for: .games), image: UIImage(systemName: "gamecontroller")) { _ in
self.importFilesType = .games
let documentPickerController: UIDocumentPickerViewController = .init(forOpeningContentTypes: [
.item
], asCopy: true)
if let sheetPresentationController = documentPickerController.sheetPresentationController {
sheetPresentationController.detents = [
.medium(),
.large()
]
}
documentPickerController.allowsMultipleSelection = true
documentPickerController.delegate = self
self.present(documentPickerController, animated: true)
},
UIMenu(title: .localized(for: .systemFiles), image: UIImage(systemName: "brain"), children: [
UIDeferredMenuElement.uncached { completion in
Task {
var files: [File] = []
for core in Core.cores {
files.append(contentsOf: await self.applicationModel.filesManager.missingFiles(for: core.string).files)
}
let cores: [String] = Array(Set(files.map(\.core))).sorted()
var elements: [UIMenu] = []
cores.forEach { core in
if let core = Core(rawValue: core) {
elements.append(UIMenu(title: core.string, subtitle: core.consoleShort, children: files.filter { file in
file.core == core.string
}.map { file in
UIAction(title: file.name, subtitle: file.importance.string) { _ in
self.importFilesType = .systemFiles
self.systemFileToImport = file
let documentPickerController: UIDocumentPickerViewController = .init(forOpeningContentTypes: [
.item
], asCopy: true)
if let sheetPresentationController = documentPickerController.sheetPresentationController {
sheetPresentationController.detents = [
.medium(),
.large()
]
}
documentPickerController.delegate = self
self.present(documentPickerController, animated: true)
}
}))
}
}
completion(elements)
}
}
])
]))
], representativeItem: nil),
.init(barButtonItems: filterAndJumpToBarButtonItems, representativeItem: nil),
.init(barButtonItems: [
UIBarButtonItem(image: UIImage(systemName: "gearshape"), menu: UIMenu(children: [
UIMenu(options: .displayInline, children: [
UIAction(title: "Folium") { _ in
let viewController: UINavigationController = UINavigationController(rootViewController: FoliumSettingsController())
viewController.modalPresentationStyle = .fullScreen
self.present(viewController, animated: true)
}
]),
UIMenu(title: "Cores", children: [
UIMenu(options: .displayInline, preferredElementSize: .small, children: [
UIAction(title: "Cytrus") { _ in
let settingsController: UINavigationController = UINavigationController(rootViewController: CytrusSettingsController(self.applicationModel.cytrus))
settingsController.view.backgroundColor = .clear
settingsController.modalPresentationStyle = .overFullScreen
self.present(settingsController, animated: true)
},
UIAction(title: "Grape") { _ in
let settingsController: UINavigationController = UINavigationController(rootViewController: GrapeSettingsController())
settingsController.view.backgroundColor = .clear
settingsController.modalPresentationStyle = .overFullScreen
self.present(settingsController, animated: true)
}
])/*,
UIMenu(options: .displayInline, preferredElementSize: .small, children: [
UIAction(title: "Mandarine") { _ in
let settingsController: UINavigationController = UINavigationController(rootViewController: MandarineSettingsController(self.applicationModel.mandarine))
settingsController.view.backgroundColor = .clear
settingsController.modalPresentationStyle = .overFullScreen
self.present(settingsController, animated: true)
}
])*/
])
]))
], representativeItem: nil)
]
let headerCellRegistration: UICollectionView.SupplementaryRegistration<UICollectionViewListCell> = .init(elementKind: UICollectionView.elementKindSectionHeader) { supplementaryView, elementKind, indexPath in
var contentConfiguration = UIListContentConfiguration.extraProminentInsetGroupedHeader()
if let dataSource = self.dataSource, let core = dataSource.sectionIdentifier(for: indexPath.section) {
contentConfiguration.text = core.string
contentConfiguration.secondaryText = core.console
var about: UIButton {
let scale: UIImage.SymbolScale = if #available(iOS 26, *) { .medium } else { .large }
let systemName: String = if #available(iOS 26, *) { "info" } else { "info.circle" }
var configuration: UIButton.Configuration = if #available(iOS 26, *) { .glass() } else { .plain() }
configuration.buttonSize = if #available(iOS 26, *) { .medium } else { .large }
configuration.cornerStyle = .capsule
configuration.image = .init(systemName: systemName)?
.applyingSymbolConfiguration(.init(scale: scale))
let button: UIButton = .init(configuration: configuration)
button.menu = core.information.menu
button.showsMenuAsPrimaryAction = true
return button
}
var home: UIButton {
let scale: UIImage.SymbolScale = if #available(iOS 26, *) { .medium } else { .large }
let systemName: String = "house"
var configuration: UIButton.Configuration = if #available(iOS 26, *) { .glass() } else { .plain() }
configuration.buttonSize = if #available(iOS 26, *) { .medium } else { .large }
configuration.cornerStyle = .capsule
configuration.image = .init(systemName: systemName)?
.applyingSymbolConfiguration(.init(scale: scale))
let button: UIButton = .init(configuration: configuration, primaryAction: UIAction { action in
let (_, _, required) = self.applicationModel.filesManager.missingFiles(for: Core.cytrus.string)
if required {
let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
message: .localized(for: .systemFilesMissingSecondaryText),
preferredStyle: .alert)
alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
self.present(alertController, animated: true)
} else {
let controller: CytrusController = .init(self.applicationModel,
nil)
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true)
}
})
return button
}
var install: UIButton {
let scale: UIImage.SymbolScale = if #available(iOS 26, *) { .medium } else { .large }
let systemName: String = "arrow.down"
var configuration: UIButton.Configuration = if #available(iOS 26, *) { .glass() } else { .plain() }
configuration.buttonSize = if #available(iOS 26, *) { .medium } else { .large }
configuration.cornerStyle = .capsule
configuration.image = .init(systemName: systemName)?
.applyingSymbolConfiguration(.init(scale: scale))
let button: UIButton = .init(configuration: configuration, primaryAction: UIAction { action in
self.importFilesType = .cia
self.systemFileToImport = nil
let documentPickerController: UIDocumentPickerViewController = .init(forOpeningContentTypes: [
.item
], asCopy: true)
documentPickerController.allowsMultipleSelection = true
if let sheetPresentationController = documentPickerController.sheetPresentationController {
sheetPresentationController.detents = [
.medium(),
.large()
]
}
documentPickerController.delegate = self
self.present(documentPickerController, animated: true)
})
return button
}
let aboutAccessory: UICellAccessory = .customView(configuration: .init(customView: about, placement: .trailing(), reservedLayoutWidth: .actual))
// let homeAccessory: UICellAccessory = .customView(configuration: .init(customView: home, placement: .trailing(), reservedLayoutWidth: .actual))
let installAccessory: UICellAccessory = .customView(configuration: .init(customView: install, placement: .trailing(), reservedLayoutWidth: .actual))
supplementaryView.accessories = if let dataSource = self.dataSource, let section = dataSource.sectionIdentifier(for: indexPath.section), section == .cytrus {
[installAccessory, aboutAccessory]
} else {
[aboutAccessory]
}
}
supplementaryView.contentConfiguration = contentConfiguration
}
let cherryCellRegistration: UICollectionView.CellRegistration<CherryCell, NewCherryGame> = .init { cell, indexPath, itemIdentifier in
cell.set(cherryGame: itemIdentifier, controller: self)
}
let cytrusCellRegistration: UICollectionView.CellRegistration<CytrusCell, NewCytrusGame> = .init { cell, indexPath, itemIdentifier in
cell.set(cytrusGame: itemIdentifier, controller: self)
}
let grapeCellRegistration: UICollectionView.CellRegistration<GrapeCell, NewGrapeGame> = .init { cell, indexPath, itemIdentifier in
cell.set(grapeGame: itemIdentifier, controller: self)
}
let guavaCellRegistration: UICollectionView.CellRegistration<GuavaCell, NewGuavaGame> = .init { cell, indexPath, itemIdentifier in
cell.set(guavaGame: itemIdentifier, controller: self)
}
let kiwiCellRegistration: UICollectionView.CellRegistration<KiwiCell, NewKiwiGame> = .init { cell, indexPath, itemIdentifier in
cell.set(kiwiGame: itemIdentifier, controller: self)
}
let mandarineCellRegistration: UICollectionView.CellRegistration<MandarineCell, NewMandarineGame> = .init { cell, indexPath, itemIdentifier in
cell.set(mandarineGame: itemIdentifier, controller: self)
}
let mangoCellRegistration: UICollectionView.CellRegistration<MangoCell, NewMangoGame> = .init { cell, indexPath, itemIdentifier in
cell.set(mangoGame: itemIdentifier, controller: self)
}
let plumCellRegistration: UICollectionView.CellRegistration<PlumCell, NewPlumGame> = .init { cell, indexPath, itemIdentifier in
cell.set(plumGame: itemIdentifier, controller: self)
}
let tomatoCellRegistration: UICollectionView.CellRegistration<TomatoCell, NewTomatoGame> = .init { cell, indexPath, itemIdentifier in
cell.set(tomatoGame: itemIdentifier, controller: self)
}
dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
switch itemIdentifier {
case let cherryGame as NewCherryGame:
collectionView.dequeueConfiguredReusableCell(using: cherryCellRegistration, for: indexPath, item: cherryGame)
case let cytrusGame as NewCytrusGame:
collectionView.dequeueConfiguredReusableCell(using: cytrusCellRegistration, for: indexPath, item: cytrusGame)
case let grapeGame as NewGrapeGame:
collectionView.dequeueConfiguredReusableCell(using: grapeCellRegistration, for: indexPath, item: grapeGame)
case let guavaGame as NewGuavaGame:
collectionView.dequeueConfiguredReusableCell(using: guavaCellRegistration, for: indexPath, item: guavaGame)
case let kiwiGame as NewKiwiGame:
collectionView.dequeueConfiguredReusableCell(using: kiwiCellRegistration, for: indexPath, item: kiwiGame)
case let mandarineGame as NewMandarineGame:
collectionView.dequeueConfiguredReusableCell(using: mandarineCellRegistration, for: indexPath, item: mandarineGame)
case let mangoGame as NewMangoGame:
collectionView.dequeueConfiguredReusableCell(using: mangoCellRegistration, for: indexPath, item: mangoGame)
case let plumGame as NewPlumGame:
collectionView.dequeueConfiguredReusableCell(using: plumCellRegistration, for: indexPath, item: plumGame)
case let tomatoGame as NewTomatoGame:
collectionView.dequeueConfiguredReusableCell(using: tomatoCellRegistration, for: indexPath, item: tomatoGame)
default:
nil
}
}
guard let dataSource else {
return
}
dataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
collectionView.dequeueConfiguredReusableSupplementary(using: headerCellRegistration, for: indexPath)
}
snapshot = .init()
Task {
await populateLibrary()
}
let refreshControl: UIRefreshControl = .init()
refreshControl.addTarget(self, action: #selector(pullToRefreshLibrary(_:)), for: .valueChanged)
collectionView.refreshControl = refreshControl
// UserDefaults.standard.removeObject(forKey: "folium.v1.39.whatsNewComplete")
#if targetEnvironment(simulator)
// UserDefaults.standard.removeObject(forKey: "folium.v1.39.whatsNewComplete")
#endif
if !UserDefaults.standard.bool(forKey: "folium.v1.39.whatsNewComplete") {
let whatsNewModel: WhatsNewModel = WhatsNewModel(applicationModel: applicationModel)
Task {
await whatsNewModel.whatsNew(controller: self)
}
}
}
func populateLibrary() async {
guard let filteredCores: [String] = UserDefaults.standard.stringArray(forKey: "folium.v1.38.filteredCores") else {
return
}
guard let dataSource, var snapshot else {
return
}
var (coresWithGames, games, errors): (AnyRangeReplaceableCollection<Core>, [BaseGame], [Core : [AnyError]]) = await applicationModel.gamesManager.games()
if !errors.values.allSatisfy(\.isEmpty) {
let item: UIBarButtonItem = .init(image: .init(systemName: "exclamationmark.octagon"),
primaryAction: .init { _ in
let libraryErrorController: LibraryIssuesController = .init(self.applicationModel, errors)
let navigationController: UINavigationController = .init(rootViewController: libraryErrorController)
navigationController.modalPresentationStyle = .fullScreen
self.present(navigationController, animated: true)
})
item.tintColor = .systemOrange
// navigationItem.trailingItemGroups = [
// .init(barButtonItems: [
// item
// ], representativeItem: nil),
// .init(barButtonItems: navigationItem.rightBarButtonItems?.reversed() ?? [], representativeItem: nil)
// ]
// navigationItem.rightBarButtonItems = nil
}
games.removeAll(where: { !filteredCores.contains($0.getCore()?.string ?? "") })
coresWithGames.removeAll(where: { !filteredCores.contains($0.string) })
if #available(iOS 26, *) {
navigationItem.largeSubtitle = "\(games.count) game\(games.count == 1 ? "" : "s") available"
navigationItem.subtitle = navigationItem.largeSubtitle
}
snapshot.appendSections(coresWithGames.map { $0 })
snapshot.sectionIdentifiers.forEach { core in
let filtered = games.filter { game in
switch game {
case let cherryGame as NewCherryGame:
cherryGame.core == core
case let cytrusGame as NewCytrusGame:
cytrusGame.core == core
case let grapeGame as NewGrapeGame:
grapeGame.core == core
case let guavaGame as NewGuavaGame:
guavaGame.core == core
case let kiwiGame as NewKiwiGame:
kiwiGame.core == core
case let mandarineGame as NewMandarineGame:
mandarineGame.core == core
case let mangoGame as NewMangoGame:
mangoGame.core == core
case let plumCore as NewPlumGame:
plumCore.core == core
case let tomatoGame as NewTomatoGame:
tomatoGame.core == core
default: false
}
}
snapshot.appendItems(filtered.sorted(), toSection: core)
}
await dataSource.apply(snapshot)
}
@objc func pullToRefreshLibrary(_ refreshControl: UIRefreshControl) {
Task {
refreshControl.beginRefreshing()
await populateLibrary()
refreshControl.endRefreshing()
}
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
guard let dataSource, let item = dataSource.itemIdentifier(for: indexPath) else {
return
}
if let cherryGame: NewCherryGame = item.asGame() {
Task {
let (_, _, required) = await applicationModel.filesManager.missingFiles(for: cherryGame.core.string)
if required {
let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
message: .localized(for: .systemFilesMissingSecondaryText),
preferredStyle: .alert)
alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
present(alertController, animated: true)
} else {
let viewController: CherryController = CherryController(cherryGame,
applicationModel.cherry)
viewController.modalPresentationStyle = .fullScreen
present(viewController, animated: true)
}
}
}
if let cytrusGame: NewCytrusGame = item.asGame() {
Task {
let (_, _, required) = await applicationModel.filesManager.missingFiles(for: cytrusGame.core.string)
if required {
let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
message: .localized(for: .systemFilesMissingSecondaryText),
preferredStyle: .alert)
alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
present(alertController, animated: true)
} else {
let controller: CytrusController = CytrusController(applicationModel,
cytrusGame)
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
}
}
}
if let grapeGame: NewGrapeGame = item.asGame() {
Task {
let (_, _, required) = await applicationModel.filesManager.missingFiles(for: grapeGame.core.string)
if required {
let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
message: .localized(for: .systemFilesMissingSecondaryText),
preferredStyle: .alert)
alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
present(alertController, animated: true)
} else {
let controller: UIViewController = GrapeController(grapeGame, await applicationModel.combinedCores.grapeCore)
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
}
}
}
if let guavaGame: NewGuavaGame = item.asGame() {
Task {
let (_, _, required) = await applicationModel.filesManager.missingFiles(for: guavaGame.core.string)
if required {
let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
message: .localized(for: .systemFilesMissingSecondaryText),
preferredStyle: .alert)
alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
present(alertController, animated: true)
} else {
let controller: GuavaController = GuavaController(guavaGame,
await applicationModel.combinedCores.guavaCore)
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
}
}
}
if let kiwiGame: NewKiwiGame = item.asGame() {
Task {
let kiwiDefaultController: KiwiController = KiwiController(kiwiGame,
await applicationModel.combinedCores.kiwiCore)
kiwiDefaultController.modalPresentationStyle = .fullScreen
present(kiwiDefaultController, animated: true)
}
}
if let mandarineGame: NewMandarineGame = item.asGame() {
Task {
let (_, _, required) = await applicationModel.filesManager.missingFiles(for: mandarineGame.core.string)
if required {
let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
message: .localized(for: .systemFilesMissingSecondaryText),
preferredStyle: .alert)
alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
present(alertController, animated: true)
} else {
let controller: MandarineController = MandarineController(mandarineGame, await applicationModel.combinedCores.mandarineCore)
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
}
}
}
if let mangoGame: NewMangoGame = item.asGame() {
let controller: MangoController = .init(mangoGame, applicationModel.mango)
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
}
if let plumGame: NewPlumGame = item.asGame() {
let controller: PlumController = PlumController(plumGame, applicationModel.plum)
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
}
if let tomatoGame: NewTomatoGame = item.asGame() {
Task {
let (_, _, required) = await applicationModel.filesManager.missingFiles(for: tomatoGame.core.string)
if required {
let alertController: UIAlertController = .init(title: .localized(for: .systemFilesMissing),
message: .localized(for: .systemFilesMissingSecondaryText),
preferredStyle: .alert)
alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
present(alertController, animated: true)
} else {
let controller: TomatoController = TomatoController(tomatoGame, applicationModel.tomato)
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
}
}
}
}
}
extension LibraryController : UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let importFilesType else {
return
}
switch importFilesType {
case .cia:
for url in urls {
let installed: Bool = applicationModel.cytrus.installCIA(url, {})
print(installed)
}
case .games:
guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return
}
let task = Task {
for url in urls {
let `extension`: String = url.pathExtension.lowercased()
guard let supportedFormat: SupportedFormat = .init(rawValue: `extension`) else {
return
}
let coresForExtension: [Core] = Core.cores(for: supportedFormat)
if coresForExtension.count == 1, let core = coresForExtension.first {
let romsDirectoryURL: URL = documentDirectory.appending(component: core.string).appending(component: "roms")
let to: URL = if supportedFormat == .`3ds` {
romsDirectoryURL.appending(component: url.deletingPathExtension().appendingPathExtension("cci").lastPathComponent)
} else {
romsDirectoryURL.appending(component: url.lastPathComponent)
}
try FileManager.default.copyItem(at: url, to: to)
} else if coresForExtension.count != 0 {
let alertController: UIAlertController = .init(title: .localized(for: .conflictingExtension),
message: .localized(for: .conflictingExtensionSecondaryText),
preferredStyle: .alert)
coresForExtension.forEach { core in
alertController.addAction(.init(title: core.string,
style: .default,
handler: { _ in
let romsDirectoryURL: URL = documentDirectory.appending(component: core.string).appending(component: "roms")
let to: URL = if supportedFormat == .`3ds` {
romsDirectoryURL.appending(component: url.deletingPathExtension().appendingPathExtension("cci").lastPathComponent)
} else {
romsDirectoryURL.appending(component: url.lastPathComponent)
}
let task = Task {
try FileManager.default.copyItem(at: url, to: to)
}
Task {
switch await task.result {
case .success:
break
case .failure(let error):
print(#function, #line, error.localizedDescription)
}
}
}))
}
alertController.addAction(.init(title: .localized(for: .dismiss),
style: .cancel))
self.present(alertController, animated: true)
}
}
await populateLibrary()
}
Task {
switch await task.result {
case .success:
break
case .failure(let error):
print(#function, #line, error.localizedDescription)
}
}
case .systemFiles:
guard let systemFileToImport, let url = urls.first else {
return
}
let task = Task {
try systemFileToImport.import(from: url)
await applicationModel.filesManager.import(file: systemFileToImport)
}
Task {
switch await task.result {
case .success:
break
case .failure(let error):
print(#function, #line, error.localizedDescription)
}
}
}
self.importFilesType = nil
self.systemFileToImport = nil
}
}