WIP Settings search

This commit is contained in:
Kylmakalle 2024-09-24 00:50:48 +03:00
parent 7f7cd9c06b
commit 916740e988
3 changed files with 117 additions and 12 deletions

View File

@ -55,6 +55,7 @@ public final class SGItemListArguments<BoolSetting: Hashable, SliderSetting: Has
let setOneFromManyValue: (OneFromManySetting) -> Void
let openDisclosureLink: (DisclosureLink) -> Void
let action: (ActionType) -> Void
let searchInput: (String) -> Void
public init(
@ -64,7 +65,8 @@ public final class SGItemListArguments<BoolSetting: Hashable, SliderSetting: Has
updateSliderValue: @escaping (SliderSetting, Int32) -> Void = { _,_ in },
setOneFromManyValue: @escaping (OneFromManySetting) -> Void = { _ in },
openDisclosureLink: @escaping (DisclosureLink) -> Void = { _ in},
action: @escaping (ActionType) -> Void = { _ in }
action: @escaping (ActionType) -> Void = { _ in },
searchInput: @escaping (String) -> Void = { _ in }
) {
self.context = context
//
@ -73,6 +75,7 @@ public final class SGItemListArguments<BoolSetting: Hashable, SliderSetting: Has
self.setOneFromManyValue = setOneFromManyValue
self.openDisclosureLink = openDisclosureLink
self.action = action
self.searchInput = searchInput
}
}
@ -85,6 +88,7 @@ public enum SGItemListUIEntry<Section: SGItemListSection, BoolSetting: Hashable,
case disclosure(id: Int, section: Section, link: DisclosureLink, text: String)
case peerColorDisclosurePreview(id: Int, section: Section, name: String, color: UIColor)
case action(id: Int, section: Section, actionType: ActionType, text: String, kind: ItemListActionKind)
case searchInput(id: Int, section: Section, title: NSAttributedString, text: String, placeholder: String)
public var section: ItemListSectionId {
switch self {
@ -108,6 +112,9 @@ public enum SGItemListUIEntry<Section: SGItemListSection, BoolSetting: Hashable,
case let .action(_, sectionId, _, _, _):
return sectionId.rawValue
case let .searchInput(_, sectionId, _, _, _):
return sectionId.rawValue
}
}
@ -129,6 +136,8 @@ public enum SGItemListUIEntry<Section: SGItemListSection, BoolSetting: Hashable,
return stableIdValue
case let .action(stableIdValue, _, _, _, _):
return stableIdValue
case let .searchInput(stableIdValue, _, _, _, _):
return stableIdValue
}
}
@ -160,6 +169,9 @@ public enum SGItemListUIEntry<Section: SGItemListSection, BoolSetting: Hashable,
return id1 == id2 && section1 == section2 && settingName1 == settingName2 && text1 == text2 && value1 == value2 && enabled1 == enabled2
case let (.action(id1, section1, actionType1, text1, kind1), .action(id2, section2, actionType2, text2, kind2)):
return id1 == id2 && section1 == section2 && actionType1 == actionType2 && text1 == text2 && kind1 == kind2
case let (.searchInput(id1, lhsValue1, lhsValue2, lhsValue3, lhsValue4), .searchInput(id2, rhsValue1, rhsValue2, rhsValue3, rhsValue4)):
return id1 == id2 && lhsValue1 == rhsValue1 && lhsValue2 == rhsValue2 && lhsValue3 == rhsValue3 && lhsValue4 == rhsValue4
default:
return false
@ -206,6 +218,80 @@ public enum SGItemListUIEntry<Section: SGItemListSection, BoolSetting: Hashable,
return ItemListActionItem(presentationData: presentationData, title: text, kind: kind, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.action(actionType)
})
case let .searchInput(_, _, title, text, placeholder):
return ItemListSingleLineInputItem(presentationData: presentationData, title: title, text: text, placeholder: placeholder, returnKeyType: .done, spacing: 3.0, clearType: .always, selectAllOnFocus: true, secondaryStyle: true, sectionId: self.section, textUpdated: { input in arguments.searchInput(input) }, action: {}, dismissKeyboardOnEnter: true)
}
}
}
public func filterSGItemListUIEntrires<Section: SGItemListSection & Hashable, BoolSetting: Hashable, SliderSetting: Hashable, OneFromManySetting: Hashable, DisclosureLink: Hashable, ActionType: Hashable>(
entries: [SGItemListUIEntry<Section, BoolSetting, SliderSetting, OneFromManySetting, DisclosureLink, ActionType>],
by searchQuery: String?
) -> [SGItemListUIEntry<Section, BoolSetting, SliderSetting, OneFromManySetting, DisclosureLink, ActionType>] {
guard let query = searchQuery?.lowercased(), !query.isEmpty else {
return entries
}
var sectionsWithMatches: Set<Section> = []
var filteredEntries: [SGItemListUIEntry<Section, BoolSetting, SliderSetting, OneFromManySetting, DisclosureLink, ActionType>] = []
func entryMatches(_ entry: SGItemListUIEntry<Section, BoolSetting, SliderSetting, OneFromManySetting, DisclosureLink, ActionType>, query: String) -> Bool {
switch entry {
case .header(_, _, let text, _):
return text.lowercased().contains(query)
case .toggle(_, _, _, _, let text, _):
return text.lowercased().contains(query)
case .notice(_, _, let text):
return text.lowercased().contains(query)
case .percentageSlider:
return false // Assuming percentage sliders don't have searchable text
case .oneFromManySelector(_, _, _, let text, let value, _):
return text.lowercased().contains(query) || value.lowercased().contains(query)
case .disclosure(_, _, _, let text):
return text.lowercased().contains(query)
case .peerColorDisclosurePreview(_, _, let name, _):
return name.lowercased().contains(query)
case .action(_, _, _, let text, _):
return text.lowercased().contains(query)
case .searchInput:
return true // Never hidding search
}
}
// First pass: identify sections with matches
for entry in entries {
if entryMatches(entry, query: query) {
switch entry {
case .header(_, let section, _, _),
.toggle(_, let section, _, _, _, _),
.notice(_, let section, _),
.percentageSlider(_, let section, _, _),
.oneFromManySelector(_, let section, _, _, _, _),
.disclosure(_, let section, _, _),
.peerColorDisclosurePreview(_, let section, _, _),
.action(_, let section, _, _, _):
sectionsWithMatches.insert(section)
case .searchInput:
continue
}
}
}
// Second pass: keep matching entries and headers of sections with matches
for entry in entries {
switch entry {
case .header(_, let section, _, _):
if sectionsWithMatches.contains(section) {
filteredEntries.append(entry)
}
default:
if entryMatches(entry, query: query) {
filteredEntries.append(entry)
}
}
}
return filteredEntries
}

View File

@ -26,6 +26,7 @@ import UndoUI
private enum SGControllerSection: Int32, SGItemListSection {
case search
case content
case tabs
case folders
@ -118,15 +119,20 @@ private struct PeerNameColorScreenState: Equatable {
var updatedBackgroundEmojiId: Int64?
}
private struct SGSettingsControllerState: Equatable {
var searchQuery: String?
}
private typealias SGControllerEntry = SGItemListUIEntry<SGControllerSection, SGBoolSetting, SGSliderSetting, SGOneFromManySetting, SGDisclosureLink, AnyHashable>
private func SGControllerEntries(presentationData: PresentationData, callListSettings: CallListSettings, experimentalUISettings: ExperimentalUISettings, SGSettings: SGUISettings, appConfiguration: AppConfiguration, nameColors: PeerNameColors /*state: PeerNameColorScreenState,*/) -> [SGControllerEntry] {
private func SGControllerEntries(presentationData: PresentationData, callListSettings: CallListSettings, experimentalUISettings: ExperimentalUISettings, SGSettings: SGUISettings, appConfiguration: AppConfiguration, nameColors: PeerNameColors, state: SGSettingsControllerState) -> [SGControllerEntry] {
let lang = presentationData.strings.baseLanguageCode
var entries: [SGControllerEntry] = []
let id = SGItemListCounter()
entries.append(.searchInput(id: id.count, section: .search, title: NSAttributedString(string: "🔍"), text: state.searchQuery ?? "", placeholder: "Search"))
if appConfiguration.sgWebSettings.global.canEditSettings {
entries.append(.disclosure(id: id.count, section: .content, link: .contentSettings, text: i18n("Settings.ContentSettings", lang)))
} else {
@ -267,7 +273,7 @@ private func SGControllerEntries(presentationData: PresentationData, callListSet
entries.append(.toggle(id: id.count, section: .other, settingName: .hidePhoneInSettings, value: SGSimpleSettings.shared.hidePhoneInSettings, text: i18n("Settings.HidePhoneInSettingsUI", lang), enabled: true))
entries.append(.notice(id: id.count, section: .other, text: i18n("Settings.HidePhoneInSettingsUI.Notice", lang)))
return entries
return filterSGItemListUIEntrires(entries: entries, by: state.searchQuery)
}
public func sgSettingsController(context: AccountContext/*, focusOnItemTag: Int? = nil*/) -> ViewController {
@ -277,11 +283,12 @@ public func sgSettingsController(context: AccountContext/*, focusOnItemTag: Int?
// var getNavigationControllerImpl: (() -> NavigationController?)?
var askForRestart: (() -> Void)?
// let statePromise = ValuePromise(PeerNameColorScreenState(), ignoreRepeated: true)
// let stateValue = Atomic(value: PeerNameColorScreenState())
// let updateState: ((PeerNameColorScreenState) -> PeerNameColorScreenState) -> Void = { f in
// statePromise.set(stateValue.modify { f($0) })
// }
let initialState = SGSettingsControllerState()
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
let updateState: ((SGSettingsControllerState) -> SGSettingsControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
// let sliderPromise = ValuePromise(SGSimpleSettings.shared.accountColorsSaturation, ignoreRepeated: true)
// let sliderStateValue = Atomic(value: SGSimpleSettings.shared.accountColorsSaturation)
@ -574,6 +581,12 @@ public func sgSettingsController(context: AccountContext/*, focusOnItemTag: Int?
strongContext.sharedContext.applicationBindings.openUrl(url)
})
}
}, searchInput: { searchQuery in
updateState { state in
var updatedState = state
updatedState.searchQuery = searchQuery
return updatedState
}
})
let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.callListSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings])
@ -584,18 +597,18 @@ public func sgSettingsController(context: AccountContext/*, focusOnItemTag: Int?
contentSettingsConfiguration.set(.single(nil)
|> then(updatedContentSettingsConfiguration))
let signal = combineLatest(simplePromise.get(), /*sliderPromise.get(),*/ /*statePromise.get(),*/ context.sharedContext.presentationData, sharedData, preferences, contentSettingsConfiguration.get(),
let signal = combineLatest(simplePromise.get(), /*sliderPromise.get(),*/ statePromise.get(), context.sharedContext.presentationData, sharedData, preferences, contentSettingsConfiguration.get(),
context.engine.accountData.observeAvailableColorOptions(scope: .replies),
context.engine.accountData.observeAvailableColorOptions(scope: .profile)
)
|> map { _, /*sliderValue,*/ /*state,*/ presentationData, sharedData, view, contentSettingsConfiguration, availableReplyColors, availableProfileColors -> (ItemListControllerState, (ItemListNodeState, Any)) in
|> map { _, /*sliderValue,*/ state, presentationData, sharedData, view, contentSettingsConfiguration, availableReplyColors, availableProfileColors -> (ItemListControllerState, (ItemListNodeState, Any)) in
let sgUISettings: SGUISettings = view.values[ApplicationSpecificPreferencesKeys.SGUISettings]?.get(SGUISettings.self) ?? SGUISettings.default
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
let callListSettings: CallListSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings]?.get(CallListSettings.self) ?? CallListSettings.defaultSettings
let experimentalUISettings: ExperimentalUISettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
let entries = SGControllerEntries(presentationData: presentationData, callListSettings: callListSettings, experimentalUISettings: experimentalUISettings, SGSettings: sgUISettings, appConfiguration: appConfiguration, nameColors: PeerNameColors.with(availableReplyColors: availableReplyColors, availableProfileColors: availableProfileColors) /*state: state,*/)
let entries = SGControllerEntries(presentationData: presentationData, callListSettings: callListSettings, experimentalUISettings: experimentalUISettings, SGSettings: sgUISettings, appConfiguration: appConfiguration, nameColors: PeerNameColors.with(availableReplyColors: availableReplyColors, availableProfileColors: availableProfileColors), state: state)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Swiftgram"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))

View File

@ -67,9 +67,10 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
let processPaste: ((String) -> String)?
let updatedFocus: ((Bool) -> Void)?
let cleared: (() -> Void)?
let dismissKeyboardOnEnter: Bool // MARK: Swiftgram
public let tag: ItemListItemTag?
public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, label: String? = nil, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, label: String? = nil, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil, dismissKeyboardOnEnter: Bool = false) {
self.context = context
self.presentationData = presentationData
self.title = title
@ -93,6 +94,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
self.updatedFocus = updatedFocus
self.action = action
self.cleared = cleared
self.dismissKeyboardOnEnter = dismissKeyboardOnEnter
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
@ -590,6 +592,10 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.item?.action()
// MARK: Swiftgram
if self.item?.dismissKeyboardOnEnter ?? false && self.textNode.textField.canResignFirstResponder {
self.textNode.textField.resignFirstResponder()
}
return false
}