|
2 | 2 | // MessageSearchBar.swift |
3 | 3 | // Meshtastic |
4 | 4 | // |
5 | | -// Shared "find in conversation" bar for ChannelMessageList / UserMessageList. |
6 | | -// Live match count + current index, previous/next navigation, and a close button. |
| 5 | +// Shared "find in conversation" results bar for ChannelMessageList / UserMessageList: |
| 6 | +// live match count + current index and previous/next navigation. The search field itself |
| 7 | +// is provided by each list's `.searchable` modifier; this bar sits just beneath it while |
| 8 | +// a query is active. |
7 | 9 | // |
8 | 10 |
|
9 | 11 | import SwiftUI |
10 | 12 |
|
11 | 13 | struct MessageSearchBar: View { |
12 | | - @Binding var query: String |
13 | 14 | /// Total number of matches for the current query. |
14 | 15 | let matchCount: Int |
15 | 16 | /// Zero-based index of the currently-focused match, or -1 when there are none. |
16 | 17 | let currentIndex: Int |
17 | 18 | var onPrevious: () -> Void |
18 | 19 | var onNext: () -> Void |
19 | | - var onClose: () -> Void |
20 | | - |
21 | | - @FocusState private var focused: Bool |
22 | 20 |
|
23 | 21 | var body: some View { |
24 | | - HStack(spacing: 10) { |
25 | | - HStack(spacing: 6) { |
26 | | - Image(systemName: "magnifyingglass") |
27 | | - .foregroundStyle(.secondary) |
28 | | - TextField("Find in conversation", text: $query) |
29 | | - .textFieldStyle(.plain) |
30 | | - .autocorrectionDisabled() |
31 | | - .submitLabel(.search) |
32 | | - .focused($focused) |
33 | | - .onSubmit(onNext) |
34 | | - if !query.isEmpty { |
35 | | - Button { |
36 | | - query = "" |
37 | | - focused = true |
38 | | - } label: { |
39 | | - Image(systemName: "xmark.circle.fill") |
40 | | - .foregroundStyle(.secondary) |
41 | | - } |
42 | | - .buttonStyle(.borderless) |
43 | | - .accessibilityLabel("Clear search") |
44 | | - } |
45 | | - } |
46 | | - .padding(.horizontal, 10) |
47 | | - .padding(.vertical, 7) |
48 | | - .background(Color(.secondarySystemBackground)) |
49 | | - .clipShape(RoundedRectangle(cornerRadius: 10)) |
| 22 | + HStack(spacing: 16) { |
| 23 | + Text(positionText) |
| 24 | + .font(.caption) |
| 25 | + .monospacedDigit() |
| 26 | + .foregroundStyle(.secondary) |
| 27 | + .accessibilityLabel(matchCount == 0 ? Text("No matches") : Text("Match \(currentIndex + 1) of \(matchCount)")) |
50 | 28 |
|
51 | | - if !query.isEmpty { |
52 | | - Text(positionText) |
53 | | - .font(.caption) |
54 | | - .monospacedDigit() |
55 | | - .foregroundStyle(.secondary) |
56 | | - .accessibilityLabel(matchCount == 0 ? Text("No matches") : Text("Match \(currentIndex + 1) of \(matchCount)")) |
| 29 | + Spacer() |
57 | 30 |
|
58 | | - Button(action: onPrevious) { |
59 | | - Image(systemName: "chevron.up") |
60 | | - } |
61 | | - .disabled(matchCount == 0) |
62 | | - .accessibilityLabel("Previous match") |
63 | | - |
64 | | - Button(action: onNext) { |
65 | | - Image(systemName: "chevron.down") |
66 | | - } |
67 | | - .disabled(matchCount == 0) |
68 | | - .accessibilityLabel("Next match") |
| 31 | + Button(action: onPrevious) { |
| 32 | + Image(systemName: "chevron.up") |
69 | 33 | } |
| 34 | + .disabled(matchCount == 0) |
| 35 | + .accessibilityLabel("Previous match") |
70 | 36 |
|
71 | | - Button("Done", action: onClose) |
72 | | - .font(.callout) |
| 37 | + Button(action: onNext) { |
| 38 | + Image(systemName: "chevron.down") |
| 39 | + } |
| 40 | + .disabled(matchCount == 0) |
| 41 | + .accessibilityLabel("Next match") |
73 | 42 | } |
74 | 43 | .buttonStyle(.borderless) |
75 | 44 | .padding(.horizontal) |
76 | 45 | .padding(.vertical, 8) |
77 | 46 | .background(.bar) |
78 | | - .onAppear { focused = true } |
79 | 47 | } |
80 | 48 |
|
81 | 49 | private var positionText: String { |
82 | | - matchCount == 0 ? "0/0" : "\(currentIndex + 1)/\(matchCount)" |
| 50 | + matchCount == 0 ? "No matches" : "\(currentIndex + 1) of \(matchCount)" |
83 | 51 | } |
84 | 52 | } |
0 commit comments