// // LanguagePicker.swift // ComposeUI // // Created by Shadowfacts on 5/4/23. // import SwiftUI @available(iOS 16.0, *) struct LanguagePicker: View { @Binding var draftLanguage: String? @Binding var hasChangedSelection: Bool @State private var isShowingSheet = false private var codeFromDraft: Locale.LanguageCode? { draftLanguage.map(Locale.LanguageCode.init(_:)) } private var codeFromActiveInputMode: Locale.LanguageCode? { UITextInputMode.activeInputModes.first.flatMap(Self.codeFromInputMode(_:)) } static func codeFromInputMode(_ mode: UITextInputMode) -> Locale.LanguageCode? { guard let bcp47Lang = mode.primaryLanguage, !bcp47Lang.isEmpty else { return nil } var maybeIso639Code = bcp47Lang[.. { Binding { return codeFromDraft ?? codeFromActiveInputMode ?? codeFromPreferredLanguages ?? .english } set: { newValue in draftLanguage = newValue.identifier } } var body: some View { Button { isShowingSheet = true } label: { Text((languageCode.wrappedValue.identifier(.alpha2) ?? languageCode.wrappedValue.identifier).uppercased()) } .accessibilityLabel("Post Language") .padding(5) .hoverEffect() .sheet(isPresented: $isShowingSheet) { NavigationStack { LanguagePickerList(languageCode: languageCode, hasChangedSelection: $hasChangedSelection, isPresented: $isShowingSheet) } .presentationDetents([.large, .medium]) } } } @available(iOS 16.0, *) private struct LanguagePickerList: View { @Binding var languageCode: Locale.LanguageCode @Binding var hasChangedSelection: Bool @Binding var isPresented: Bool @Environment(\.composeUIConfig.groupedBackgroundColor) private var groupedBackgroundColor @Environment(\.composeUIConfig.groupedCellBackgroundColor) private var groupedCellBackgroundColor @State private var recentLangs: [Lang] = [] @State private var langs: [Lang] = [] @State private var filteredLangs: [Lang]? @State private var query = "" private var defaults: UserDefaults { UserDefaults(suiteName: "group.space.vaccor.Tusker") ?? .standard } private var recentIdentifiers: [String] { get { defaults.object(forKey: "LanguagePickerRecents") as? [String] ?? [] } nonmutating set { defaults.set(newValue, forKey: "LanguagePickerRecents") } } var body: some View { List { Section { ForEach(recentLangs) { lang in button(for: lang) } .listRowBackground(groupedCellBackgroundColor) } header: { Text("Recently Used") } Section { ForEach(filteredLangs ?? langs) { lang in button(for: lang) } .listRowBackground(groupedCellBackgroundColor) } header: { Text("All Languages") } } .listStyle(.insetGrouped) .scrollContentBackground(.hidden) .background(groupedBackgroundColor.edgesIgnoringSafeArea(.all)) .searchable(text: $query) #if !os(visionOS) .scrollDismissesKeyboard(.interactively) #endif .navigationTitle("Post Language") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { isPresented = false } } } .onAppear { // make sure recents always contains the currently selected lang let recents = addRecentLang(languageCode) recentLangs = recents .filter { $0 != "mul" && $0 != "und" } .map { Lang(code: .init($0)) } .sorted { $0.name < $1.name } langs = Locale.LanguageCode.isoLanguageCodes .filter { $0.identifier != "mul" && $0.identifier != "und" } .map { Lang(code: $0) } .sorted { $0.name < $1.name } } #if os(visionOS) .onChange(of: query, initial: true) { filteredLangsChanged(query: query) } #else .onChange(of: query) { newValue in filteredLangsChanged(query: newValue) } #endif } private func filteredLangsChanged(query: String) { if query.isEmpty { filteredLangs = nil } else { filteredLangs = langs.filter { $0.name.localizedCaseInsensitiveContains(query) || $0.code.identifier.localizedCaseInsensitiveContains(query) } } } @discardableResult private func addRecentLang(_ code: Locale.LanguageCode) -> [String] { var recents = recentIdentifiers if !recents.contains(languageCode.identifier) { recents.insert(languageCode.identifier, at: 0) if recents.count > 5 { recents = Array(recents[..<5]) } recentIdentifiers = recents } return recents } private func button(for lang: Lang) -> some View { Button { languageCode = lang.code hasChangedSelection = true isPresented = false addRecentLang(lang.code) } label: { HStack { Text(lang.name) Spacer() if lang.code == languageCode { Image(systemName: "checkmark") } } } } struct Lang: Identifiable { let code: Locale.LanguageCode let name: String var id: String { code.identifier } init(code: Locale.LanguageCode) { self.code = code self.name = Locale.current.localizedString(forLanguageCode: code.identifier) ?? code.identifier } } }