2021-06-29 18:09:42 -07:00
|
|
|
//
|
|
|
|
|
// GeneralSettingsViewController.swift
|
|
|
|
|
// GeneralSettingsViewController
|
|
|
|
|
//
|
|
|
|
|
// Created by James Magahern on 6/25/21.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
|
|
|
|
|
|
struct LabelContentConfiguration : UIContentConfiguration
|
|
|
|
|
{
|
|
|
|
|
var text: String = ""
|
|
|
|
|
var insets: UIEdgeInsets = .zero
|
|
|
|
|
var textAlignment: NSTextAlignment = .natural
|
|
|
|
|
|
|
|
|
|
func makeContentView() -> UIView & UIContentView {
|
|
|
|
|
let label = UILabel(frame: .zero)
|
|
|
|
|
let contentView = GenericContentView<UILabel, LabelContentConfiguration>(configuration: self, view: label) { config, label in
|
|
|
|
|
label.text = config.text
|
|
|
|
|
label.textAlignment = config.textAlignment
|
|
|
|
|
}
|
|
|
|
|
contentView.insets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
|
|
|
|
|
return contentView
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updated(for state: UIConfigurationState) -> Self {
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-05 18:55:19 -07:00
|
|
|
struct TextFieldContentConfiguration : UIContentConfiguration
|
|
|
|
|
{
|
|
|
|
|
var text: String = ""
|
|
|
|
|
var placeholderText: String? = nil
|
2023-04-19 14:30:06 -07:00
|
|
|
var textChanged: ((String) -> Void)? = nil
|
|
|
|
|
var pressedReturn: ((UITextField) -> Void)? = nil
|
|
|
|
|
var keyboardType: UIKeyboardType = .default
|
|
|
|
|
var returnKeyType: UIReturnKeyType = .default
|
2022-08-05 18:55:19 -07:00
|
|
|
|
|
|
|
|
func makeContentView() -> UIView & UIContentView {
|
|
|
|
|
let textField = UITextField(frame: .zero)
|
|
|
|
|
textField.borderStyle = .roundedRect
|
|
|
|
|
textField.autocorrectionType = .no
|
|
|
|
|
textField.autocapitalizationType = .none
|
2023-04-19 14:30:06 -07:00
|
|
|
textField.keyboardType = keyboardType
|
|
|
|
|
textField.returnKeyType = returnKeyType
|
2022-08-05 18:55:19 -07:00
|
|
|
|
|
|
|
|
return GenericContentView<UITextField, TextFieldContentConfiguration>(configuration: self, view: textField) { config, textField in
|
|
|
|
|
textField.text = config.text
|
|
|
|
|
textField.placeholder = config.placeholderText
|
2023-04-19 14:30:06 -07:00
|
|
|
|
|
|
|
|
if let textChanged = config.textChanged {
|
|
|
|
|
textField.addAction(UIAction { _ in textChanged(textField.text ?? "") }, for: .editingChanged)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let pressedReturn = config.pressedReturn {
|
|
|
|
|
class ReturnDelegate : NSObject, UITextFieldDelegate {
|
|
|
|
|
var pressedReturn: ((UITextField) -> Void)
|
|
|
|
|
|
|
|
|
|
public init(pressedReturn: @escaping ((UITextField) -> Void)) {
|
|
|
|
|
self.pressedReturn = pressedReturn
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 14:33:17 -07:00
|
|
|
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
2023-04-19 14:30:06 -07:00
|
|
|
pressedReturn(textField)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let delegate = ReturnDelegate(pressedReturn: pressedReturn)
|
|
|
|
|
objc_setAssociatedObject(textField, "returnDelegate", delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
|
|
|
textField.delegate = delegate
|
|
|
|
|
}
|
2022-08-05 18:55:19 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updated(for state: UIConfigurationState) -> TextFieldContentConfiguration {
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-29 18:09:42 -07:00
|
|
|
struct ButtonContentConfiguration : UIContentConfiguration
|
|
|
|
|
{
|
|
|
|
|
var menu: UIMenu
|
|
|
|
|
|
|
|
|
|
func makeContentView() -> UIView & UIContentView {
|
|
|
|
|
let button = UIButton(primaryAction: nil)
|
|
|
|
|
return GenericContentView<UIButton, ButtonContentConfiguration>(configuration: self, view: button) { config, button in
|
|
|
|
|
button.menu = menu
|
|
|
|
|
button.showsMenuAsPrimaryAction = true
|
|
|
|
|
if #available(macCatalyst 15.0, iOS 15.0, *) {
|
|
|
|
|
var buttonConfiguration = UIButton.Configuration.plain()
|
|
|
|
|
buttonConfiguration.titleAlignment = .trailing
|
|
|
|
|
|
|
|
|
|
button.configuration = buttonConfiguration
|
|
|
|
|
button.changesSelectionAsPrimaryAction = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updated(for state: UIConfigurationState) -> ButtonContentConfiguration {
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class GeneralSettingsViewController: UIViewController
|
|
|
|
|
{
|
|
|
|
|
enum Section: String, CaseIterable {
|
|
|
|
|
case searchEngine = "Search Engine"
|
2022-08-05 18:55:19 -07:00
|
|
|
case syncServer = "Sync Server"
|
2021-06-29 18:09:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typealias Item = String
|
|
|
|
|
|
|
|
|
|
static let SearchProviderPopupItem = "searchProvider.popup"
|
2022-08-05 18:55:19 -07:00
|
|
|
static let SyncServerItem = "syncServer.field"
|
2021-06-29 18:09:42 -07:00
|
|
|
|
|
|
|
|
let dataSource: UICollectionViewDiffableDataSource<Section, Item>
|
|
|
|
|
let collectionView: UICollectionView
|
|
|
|
|
|
|
|
|
|
static func createLayout(forIdiom idiom: UIUserInterfaceIdiom) -> UICollectionViewLayout {
|
2021-11-29 10:56:14 -10:00
|
|
|
#if targetEnvironment(macCatalyst)
|
2021-06-29 18:09:42 -07:00
|
|
|
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
|
|
|
|
|
heightDimension: .fractionalHeight(1.0))
|
|
|
|
|
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
|
|
|
|
|
|
|
|
|
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.60),
|
|
|
|
|
heightDimension: .absolute(44))
|
|
|
|
|
|
|
|
|
|
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
|
|
|
|
|
group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(1.0), top: nil, trailing: nil, bottom: nil)
|
|
|
|
|
|
|
|
|
|
let insets = NSDirectionalEdgeInsets(top: 24.0, leading: 64.0, bottom: 24.0, trailing: 64.0)
|
|
|
|
|
|
|
|
|
|
let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.40),
|
|
|
|
|
heightDimension: .estimated(44.0))
|
|
|
|
|
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
|
|
|
|
|
layoutSize: headerFooterSize,
|
|
|
|
|
elementKind: UICollectionView.elementKindSectionHeader,
|
|
|
|
|
alignment: .topLeading,
|
2022-08-22 17:43:04 -07:00
|
|
|
absoluteOffset: CGPoint(x: insets.leading, y: insets.top + 5.0)
|
2021-06-29 18:09:42 -07:00
|
|
|
)
|
|
|
|
|
sectionHeader.extendsBoundary = false
|
|
|
|
|
sectionHeader.contentInsets = insets
|
|
|
|
|
|
|
|
|
|
let section = NSCollectionLayoutSection(group: group)
|
|
|
|
|
// section.interGroupSpacing = spacing
|
|
|
|
|
section.contentInsets = insets
|
|
|
|
|
section.supplementariesFollowContentInsets = true
|
|
|
|
|
section.boundarySupplementaryItems = [ sectionHeader ]
|
|
|
|
|
|
|
|
|
|
let layout = UICollectionViewCompositionalLayout(section: section)
|
|
|
|
|
return layout
|
2021-11-29 10:56:14 -10:00
|
|
|
#else
|
2021-06-29 18:09:42 -07:00
|
|
|
var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
|
|
|
|
|
listConfiguration.headerMode = .supplementary
|
|
|
|
|
return UICollectionViewCompositionalLayout.list(using: listConfiguration)
|
2021-11-29 10:56:14 -10:00
|
|
|
#endif
|
2021-06-29 18:09:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static func sectionHeaderConfiguration(forIdiom idiom: UIUserInterfaceIdiom, sectionName: String) -> UIContentConfiguration {
|
|
|
|
|
if idiom == .mac {
|
|
|
|
|
return LabelContentConfiguration(
|
|
|
|
|
text: sectionName + ": ",
|
2022-08-05 18:55:19 -07:00
|
|
|
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0, right: 10.0),
|
2021-06-29 18:09:42 -07:00
|
|
|
textAlignment: .right
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
var config = UIListContentConfiguration.plainHeader()
|
|
|
|
|
config.text = sectionName
|
|
|
|
|
return config
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if targetEnvironment(macCatalyst)
|
|
|
|
|
static let staticIdiom = UIUserInterfaceIdiom.mac
|
|
|
|
|
#else
|
|
|
|
|
static let staticIdiom = UIUserInterfaceIdiom.pad
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
|
let actionHandler = { (action: UIAction) in
|
|
|
|
|
let providerString = action.title
|
|
|
|
|
let provider = Settings.SearchProviderSetting(rawValue: providerString)!
|
|
|
|
|
Settings.shared.searchProvider = provider
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let itemCellRegistry = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, identifier in
|
|
|
|
|
if identifier == Self.SearchProviderPopupItem {
|
|
|
|
|
let menu = UIMenu(children: Settings.SearchProviderSetting.allCases.map { provider in
|
|
|
|
|
let action = UIAction(title: provider.rawValue, handler: actionHandler)
|
|
|
|
|
action.state = Settings.shared.searchProvider == provider ? .on : .off
|
|
|
|
|
return action
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
cell.contentConfiguration = ButtonContentConfiguration(menu: menu)
|
2022-08-05 18:55:19 -07:00
|
|
|
} else if identifier == Self.SyncServerItem {
|
|
|
|
|
cell.contentConfiguration = TextFieldContentConfiguration(
|
2022-08-22 15:43:06 -07:00
|
|
|
text: Settings.shared.syncServer ?? "",
|
2022-08-05 18:55:19 -07:00
|
|
|
placeholderText: "https://sync.server.com",
|
|
|
|
|
textChanged: { newString in
|
|
|
|
|
Settings.shared.syncServer = newString
|
2023-04-19 14:30:06 -07:00
|
|
|
},
|
|
|
|
|
pressedReturn: { $0.resignFirstResponder() },
|
|
|
|
|
keyboardType: .URL,
|
|
|
|
|
returnKeyType: .done
|
2022-08-05 18:55:19 -07:00
|
|
|
)
|
2021-06-29 18:09:42 -07:00
|
|
|
}
|
2022-08-05 18:55:19 -07:00
|
|
|
|
|
|
|
|
#if !targetEnvironment(macCatalyst)
|
|
|
|
|
cell.backgroundConfiguration = UIBackgroundConfiguration.listGroupedCell()
|
|
|
|
|
#endif
|
2021-06-29 18:09:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let sectionHeaderRegistry = UICollectionView.SupplementaryRegistration<UICollectionViewCell>(elementKind: UICollectionView.elementKindSectionHeader, handler: {
|
|
|
|
|
(cell, string, indexPath) in
|
|
|
|
|
let sectionName = Section.allCases[indexPath.section].rawValue
|
|
|
|
|
cell.contentConfiguration = Self.sectionHeaderConfiguration(forIdiom: Self.staticIdiom, sectionName: sectionName)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
let layout = Self.createLayout(forIdiom: Self.staticIdiom)
|
|
|
|
|
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
|
|
|
|
collectionView.backgroundColor = .systemGroupedBackground
|
|
|
|
|
|
|
|
|
|
dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
|
|
|
|
|
(collectionView, indexPath, identifier) in
|
|
|
|
|
return collectionView.dequeueConfiguredReusableCell(using: itemCellRegistry, for: indexPath, item: identifier)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
|
|
|
|
|
return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderRegistry, for: indexPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
super.init(nibName: nil, bundle: nil)
|
|
|
|
|
|
2023-04-19 14:30:06 -07:00
|
|
|
collectionView.delegate = self
|
2021-06-29 18:09:42 -07:00
|
|
|
tabBarItem.title = "General"
|
|
|
|
|
tabBarItem.image = UIImage(systemName: "gear")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func loadView() {
|
|
|
|
|
self.view = collectionView
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
|
|
super.viewDidLoad()
|
|
|
|
|
|
|
|
|
|
var snapshot = dataSource.snapshot()
|
|
|
|
|
snapshot.appendSections(Section.allCases)
|
|
|
|
|
// iOS
|
|
|
|
|
// snapshot.appendItems(Settings.SearchProviderSetting.allCases.map { $0.rawValue }, toSection: .searchEngine)
|
|
|
|
|
snapshot.appendItems([ Self.SearchProviderPopupItem ], toSection: .searchEngine)
|
2022-08-05 18:55:19 -07:00
|
|
|
snapshot.appendItems([ Self.SyncServerItem ], toSection: .syncServer)
|
2021-06-29 18:09:42 -07:00
|
|
|
dataSource.apply(snapshot, animatingDifferences: false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2023-04-19 14:30:06 -07:00
|
|
|
|
|
|
|
|
extension GeneralSettingsViewController : UICollectionViewDelegate {
|
|
|
|
|
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|