feat: added search
b4278c60
4 file(s) · +46 −4
| 89 | 89 | /// The home page URL that the browser navigates to on launch and when pressing Home |
|
| 90 | 90 | @AppStorage("homePage") var homePage: String = "gemini://geminiprotocol.net/" |
|
| 91 | 91 | ||
| 92 | + | /// The search engine URL used for search queries (query appended after ?) |
|
| 93 | + | @AppStorage("searchEngine") var searchEngine: String = "gemini://kennedy.gemi.dev/search" |
|
| 94 | + | ||
| 92 | 95 | /// Computed property for current background color based on system appearance |
|
| 93 | 96 | var backgroundColor: Color { |
|
| 94 | 97 | switch appearanceMode { |
| 92 | 92 | } |
|
| 93 | 93 | ||
| 94 | 94 | private var urlTextField: some View { |
|
| 95 | - | TextField("Enter Gemini URL", text: $urlText) |
|
| 95 | + | TextField("gemini:// or search", text: $urlText) |
|
| 96 | 96 | .focused(isURLFocused) |
|
| 97 | 97 | .autocapitalization(.none) |
|
| 98 | 98 | .disableAutocorrection(true) |
|
| 99 | - | .keyboardType(.URL) |
|
| 99 | + | .keyboardType(.webSearch) |
|
| 100 | 100 | .submitLabel(.go) |
|
| 101 | 101 | .onSubmit { |
|
| 102 | 102 | isURLFocused.wrappedValue = false |
| 219 | 219 | historyIndex < history.count - 1 |
|
| 220 | 220 | } |
|
| 221 | 221 | ||
| 222 | + | private func normalizeURL(_ input: String) -> String { |
|
| 223 | + | let trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines) |
|
| 224 | + | guard !trimmed.isEmpty else { return trimmed } |
|
| 225 | + | ||
| 226 | + | // If it already has a scheme, return as-is |
|
| 227 | + | let lowercased = trimmed.lowercased() |
|
| 228 | + | if lowercased.hasPrefix("gemini://") || |
|
| 229 | + | lowercased.hasPrefix("http://") || |
|
| 230 | + | lowercased.hasPrefix("https://") || |
|
| 231 | + | lowercased.hasPrefix("mailto:") { |
|
| 232 | + | return trimmed |
|
| 233 | + | } |
|
| 234 | + | ||
| 235 | + | // Check if it looks like a domain (contains a dot, no spaces) |
|
| 236 | + | if trimmed.contains(".") && !trimmed.contains(" ") { |
|
| 237 | + | return "gemini://" + trimmed |
|
| 238 | + | } |
|
| 239 | + | ||
| 240 | + | // Otherwise treat as a search query |
|
| 241 | + | let encoded = trimmed.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? trimmed |
|
| 242 | + | return themeSettings.searchEngine + "?" + encoded |
|
| 243 | + | } |
|
| 244 | + | ||
| 222 | 245 | private func navigateTo(_ url: String) { |
|
| 246 | + | let normalizedURL = normalizeURL(url) |
|
| 247 | + | ||
| 223 | 248 | // Check if this is an external URL (http, https, mailto) |
|
| 224 | - | if let urlObj = URL(string: url) { |
|
| 249 | + | if let urlObj = URL(string: normalizedURL) { |
|
| 225 | 250 | let scheme = urlObj.scheme?.lowercased() ?? "" |
|
| 226 | 251 | if scheme == "http" || scheme == "https" || scheme == "mailto" { |
|
| 227 | 252 | UIApplication.shared.open(urlObj) |
|
| 233 | 258 | history = Array(history.prefix(historyIndex + 1)) |
|
| 234 | 259 | } |
|
| 235 | 260 | ||
| 236 | - | urlText = url |
|
| 261 | + | urlText = normalizedURL |
|
| 237 | 262 | fetchContent(addToHistory: true) |
|
| 238 | 263 | } |
|
| 239 | 264 | ||
| 10 | 10 | @Environment(\.dismiss) private var dismiss |
|
| 11 | 11 | ||
| 12 | 12 | @State private var homePageText: String = "" |
|
| 13 | + | @State private var searchEngineText: String = "" |
|
| 13 | 14 | @State private var selectedAppearanceMode: AppearanceMode = .automatic |
|
| 14 | 15 | @State private var selectedAccentColor: Color = .blue |
|
| 15 | 16 | @State private var selectedLightBackgroundColor: Color = .white |
|
| 30 | 31 | Text("Home Page") |
|
| 31 | 32 | } footer: { |
|
| 32 | 33 | Text("The page that loads when you open the app or tap the Home button.") |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | Section { |
|
| 37 | + | TextField("Search Engine URL", text: $searchEngineText) |
|
| 38 | + | .autocapitalization(.none) |
|
| 39 | + | .disableAutocorrection(true) |
|
| 40 | + | .keyboardType(.URL) |
|
| 41 | + | } header: { |
|
| 42 | + | Text("Search Engine") |
|
| 43 | + | } footer: { |
|
| 44 | + | Text("The search engine used when typing a search query. Your query will be appended after a \"?\".") |
|
| 33 | 45 | } |
|
| 34 | 46 | ||
| 35 | 47 | Section { |
|
| 80 | 92 | ToolbarItem(placement: .confirmationAction) { |
|
| 81 | 93 | Button("Save") { |
|
| 82 | 94 | themeSettings.homePage = homePageText |
|
| 95 | + | themeSettings.searchEngine = searchEngineText |
|
| 83 | 96 | themeSettings.setAppearanceMode(selectedAppearanceMode) |
|
| 84 | 97 | themeSettings.setAllAccentColors(selectedAccentColor) |
|
| 85 | 98 | themeSettings.setLightBackgroundColor(selectedLightBackgroundColor) |
|
| 93 | 106 | } |
|
| 94 | 107 | .onAppear { |
|
| 95 | 108 | homePageText = themeSettings.homePage |
|
| 109 | + | searchEngineText = themeSettings.searchEngine |
|
| 96 | 110 | selectedAppearanceMode = themeSettings.appearanceMode |
|
| 97 | 111 | selectedAccentColor = themeSettings.accentColor |
|
| 98 | 112 | selectedLightBackgroundColor = themeSettings.lightBackgroundColor |
|