@hasna/recordings 0.3.13 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -37
- package/bun.lock +37 -38
- package/dist/cli/index.js +1884 -615
- package/dist/cli/macos-shortcut.d.ts +2 -2
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +6 -0
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/http/client.d.ts +183 -22
- package/dist/http/client.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2858 -2288
- package/dist/lib/audio-artifact-storage.d.ts +98 -0
- package/dist/lib/audio-artifact-storage.d.ts.map +1 -0
- package/dist/lib/capture-probe.d.ts +3 -3
- package/dist/lib/capture-probe.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/local-opt-in.d.ts +132 -0
- package/dist/lib/local-opt-in.d.ts.map +1 -0
- package/dist/lib/macos-bundle.d.ts +1 -1
- package/dist/lib/persistence-probe.d.ts +56 -17
- package/dist/lib/persistence-probe.d.ts.map +1 -1
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1689 -456
- package/dist/sdk/index.d.ts +13 -5
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +904 -1
- package/dist/sdk/resolve.d.ts +53 -0
- package/dist/sdk/resolve.d.ts.map +1 -0
- package/dist/sdk/v1.generated.d.ts +6 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/client-ip.d.ts +39 -0
- package/dist/server/client-ip.d.ts.map +1 -0
- package/dist/server/cloud-config.d.ts +12 -0
- package/dist/server/cloud-config.d.ts.map +1 -1
- package/dist/server/cloud-readiness.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +20 -2
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +566 -284
- package/dist/server/migrate-command.d.ts +1 -1
- package/dist/server/migrate-command.d.ts.map +1 -1
- package/dist/server/openapi.d.ts +21 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1334 -871
- package/dist/store.d.ts +12 -3
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -8
- package/packaging/macos/build_release_pkg.sh +3 -3
- package/packaging/macos/managed_bootstrap.sh +4 -4
- package/packaging/macos/scripts/postinstall +2 -2
- package/packaging/macos/scripts/preinstall +2 -2
- package/scripts/build_companion_cli.sh +7 -1
- package/scripts/install_macos_app.sh +27 -24
- package/scripts/macos_artifact.ts +20 -16
- package/scripts/migrate.ts +4 -20
- package/scripts/smoke_macos_app.sh +21 -21
- package/src/native/Recordings/App/ContentView.swift +53 -68
- package/src/native/Recordings/App/MenuBarStatusView.swift +171 -61
- package/src/native/Recordings/App/RecordWorkspaceView.swift +47 -449
- package/src/native/Recordings/App/RecorderSettingsView.swift +75 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +3 -4
- package/src/native/Recordings/App/RecordingsApp.swift +185 -9
- package/src/native/Recordings/App/RecordingsListView.swift +60 -112
- package/src/native/Recordings/App/RecordingsStore.swift +124 -109
- package/src/native/Recordings/App/Theme.swift +73 -61
- package/src/native/Recordings/Package.swift +1 -0
- package/src/native/Recordings/RecordingsLib/CaptureMonitor.swift +65 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +3 -3
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +75 -21
- package/src/native/Recordings/RecordingsLib/Recording.swift +14 -2
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +316 -34
- package/src/native/Recordings/RecordingsLib/RecordingGlassBackground.swift +34 -0
- package/src/native/Recordings/RecordingsLib/RecordingProvider.swift +102 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +17 -10
- package/src/native/Recordings/RecordingsLib/ServiceAPIConfiguration.swift +107 -0
- package/src/native/Recordings/RecordingsLib/ServiceConnectionView.swift +76 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -149
- package/src/native/Recordings/RecordingsLib/SettingsWindowController.swift +48 -0
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +13 -4
- package/src/native/Recordings/RecordingsTests/OpenAIAPIKeyStoreTests.swift +43 -38
- package/src/native/Recordings/RecordingsTests/RecorderSurfaceTests.swift +60 -0
- package/src/native/Recordings/RecordingsTests/RecordingDateTests.swift +26 -0
- package/src/native/Recordings/RecordingsTests/RecordingProviderTests.swift +195 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +1 -1
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +30 -0
- package/src/native/Recordings/RecordingsTests/ServiceAPIConfigurationTests.swift +75 -0
- package/src/native/Recordings/RecordingsTests/SettingsWindowControllerTests.swift +28 -0
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +1 -1
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +2 -2
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +1 -1
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +1 -1
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +1 -1
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +2 -2
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +1 -1
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +2 -2
- package/src/native/Recordings/build.sh +6 -6
- package/src/native/Recordings/App/SidebarView.swift +0 -212
|
@@ -253,7 +253,7 @@ struct DurableInstallJournal {
|
|
|
253
253
|
let leaf = URL(fileURLWithPath: transactionDirectory).lastPathComponent
|
|
254
254
|
guard leaf.hasPrefix("transaction-") else { throw InstallJournalError.invalidJournal }
|
|
255
255
|
let transactionID = String(leaf.dropFirst("transaction-".count))
|
|
256
|
-
let expectedCandidate = transactionDirectory + "/candidate/
|
|
256
|
+
let expectedCandidate = transactionDirectory + "/candidate/Hasna Recordings.app"
|
|
257
257
|
let activationPhases: Set<String> = [
|
|
258
258
|
"prepared", "launch-barrier-pending", "launch-barrier-held",
|
|
259
259
|
"swap-pending", "swapped", "previous-retaining",
|
|
@@ -201,8 +201,8 @@ struct ActivationRecoveryPolicyTests {
|
|
|
201
201
|
"Contents/Helpers/recordings": 493,
|
|
202
202
|
"Contents/Helpers/recordings-update-client": 493
|
|
203
203
|
},
|
|
204
|
-
"candidate_application_path": "/Library/Application Support/Hasna/Recordings/Updates/transaction-00000000-0000-0000-0000-000000000001/candidate/
|
|
205
|
-
"application_path": "/Applications/
|
|
204
|
+
"candidate_application_path": "/Library/Application Support/Hasna/Recordings/Updates/transaction-00000000-0000-0000-0000-000000000001/candidate/Hasna Recordings.app",
|
|
205
|
+
"application_path": "/Applications/Hasna Recordings.app"
|
|
206
206
|
}
|
|
207
207
|
"""#.utf8)
|
|
208
208
|
let journal = try JSONDecoder().decode(BrokerInstallJournal.self, from: legacyJSON)
|
|
@@ -7,7 +7,7 @@ public enum RecordingsUpdateConstants {
|
|
|
7
7
|
public static let rootMaintenanceSupported = false
|
|
8
8
|
public static let keyRotationSupported = false
|
|
9
9
|
public static let machServiceName = "com.hasna.recordings.updater"
|
|
10
|
-
public static let applicationPath = "/Applications/
|
|
10
|
+
public static let applicationPath = "/Applications/Hasna Recordings.app"
|
|
11
11
|
public static let brokerExecutablePath = "/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
12
12
|
public static let stateRoot = "/Library/Application Support/Hasna/Recordings/Updates"
|
|
13
13
|
public static let trustRoot = "/Library/Application Support/Hasna/Recordings/Trust"
|
|
@@ -729,7 +729,7 @@ int recordings_copy_canonical_application_tree(
|
|
|
729
729
|
if (entry == NULL) break;
|
|
730
730
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
731
731
|
visible_entries += 1;
|
|
732
|
-
if (strcmp(entry->d_name, "
|
|
732
|
+
if (strcmp(entry->d_name, "Hasna Recordings.app") != 0) only_application = false;
|
|
733
733
|
}
|
|
734
734
|
int status = errno;
|
|
735
735
|
closedir(directory);
|
|
@@ -739,7 +739,7 @@ int recordings_copy_canonical_application_tree(
|
|
|
739
739
|
status = copy_directory_at(
|
|
740
740
|
verifier_output_directory_descriptor,
|
|
741
741
|
root_candidate_directory_descriptor,
|
|
742
|
-
"
|
|
742
|
+
"Hasna Recordings.app",
|
|
743
743
|
verifier_user_id,
|
|
744
744
|
&budget
|
|
745
745
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Build, sign, notarize, and finalize a
|
|
2
|
+
# Build, sign, notarize, and finalize a Hasna Recordings.app artifact.
|
|
3
3
|
# Usage: ./build.sh [debug|local] | ./build.sh release <initial-bootstrap|app-update>
|
|
4
4
|
|
|
5
5
|
set -euo pipefail
|
|
@@ -120,11 +120,11 @@ if [ "$VARIANT" = "bar" ]; then
|
|
|
120
120
|
fi
|
|
121
121
|
# Fleet naming rule (knowledge k_msxd5rz3_jfvl3i): every Hasna macOS app is
|
|
122
122
|
# Hasna<Name>.app with 'Hasna' at the beginning. The bundle is named
|
|
123
|
-
#
|
|
123
|
+
# Hasna Recordings.app for BOTH variants (full and bar); the 'bar' is a variant, never
|
|
124
124
|
# a separate app name. Bundle identifier stays com.hasna.recordings so TCC keeps
|
|
125
125
|
# keying on bundle id + signing identity, and the executable inside the bundle stays
|
|
126
126
|
# "Recordings".
|
|
127
|
-
APP_BUNDLE_NAME="
|
|
127
|
+
APP_BUNDLE_NAME="Hasna Recordings.app"
|
|
128
128
|
readonly APP_BUNDLE_NAME
|
|
129
129
|
APP_BASENAME="${APP_BUNDLE_NAME%.app}"
|
|
130
130
|
PLIST_BUDDY="$(select_executable "/usr/libexec/PlistBuddy" "${RECORDINGS_TEST_PLIST_BUDDY_EXECUTABLE:-${PLIST_BUDDY:-}}")"
|
|
@@ -1256,7 +1256,7 @@ fi
|
|
|
1256
1256
|
|
|
1257
1257
|
generate_and_verify_native_fs_guard
|
|
1258
1258
|
|
|
1259
|
-
echo "Building
|
|
1259
|
+
echo "Building Hasna Recordings.app ($MODE)..."
|
|
1260
1260
|
if [ "$MODE" = "release" ]; then
|
|
1261
1261
|
OUTPUT_BUILD_DIR="$BUILD_ROOT/release-output"
|
|
1262
1262
|
else
|
|
@@ -1992,7 +1992,7 @@ if [ "$MODE" = "local" ]; then
|
|
|
1992
1992
|
fi
|
|
1993
1993
|
|
|
1994
1994
|
verify_signed_code "$HELPERS/recordings" "Companion CLI"
|
|
1995
|
-
verify_signed_code "$APP_DIR" "
|
|
1995
|
+
verify_signed_code "$APP_DIR" "Hasna Recordings.app"
|
|
1996
1996
|
ARTIFACT_BASENAME="${APP_BASENAME}-${VERSION}-macos-${RELEASE_SUBTYPE}"
|
|
1997
1997
|
NOTARY_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-notarization.zip"
|
|
1998
1998
|
FINAL_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.zip"
|
|
@@ -2044,7 +2044,7 @@ run_xcrun stapler validate "$APP_DIR"
|
|
|
2044
2044
|
run_release_sensitive_tool "$SPCTL_EXECUTABLE" --assess --type execute --verbose=2 "$APP_DIR"
|
|
2045
2045
|
run_release_sensitive_tool "$SYSPOLICY_CHECK_EXECUTABLE" distribution "$APP_DIR"
|
|
2046
2046
|
verify_signed_code "$HELPERS/recordings" "Companion CLI"
|
|
2047
|
-
verify_signed_code "$APP_DIR" "
|
|
2047
|
+
verify_signed_code "$APP_DIR" "Hasna Recordings.app"
|
|
2048
2048
|
run_codesign --verify --deep --strict --verbose=2 "$APP_DIR"
|
|
2049
2049
|
|
|
2050
2050
|
run_release_sensitive_tool "$DITTO_EXECUTABLE" -c -k --sequesterRsrc --keepParent "$APP_DIR" "$FINAL_ARCHIVE"
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
import RecordingsLib
|
|
3
|
-
|
|
4
|
-
/// Narrow violet sidebar: Record · Library · Projects · Modes · Machines. White text/tint
|
|
5
|
-
/// over the gradient; selected rows get a translucent white highlight (Liquid Glass refracts
|
|
6
|
-
/// the violet beneath).
|
|
7
|
-
struct SidebarView: View {
|
|
8
|
-
@ObservedObject var store: RecordingsStore
|
|
9
|
-
@Environment(\.colorScheme) private var colorScheme
|
|
10
|
-
|
|
11
|
-
@State private var addingProject = false
|
|
12
|
-
@State private var newProjectName = ""
|
|
13
|
-
|
|
14
|
-
var body: some View {
|
|
15
|
-
ScrollView {
|
|
16
|
-
VStack(alignment: .leading, spacing: 18) {
|
|
17
|
-
workspaceSection
|
|
18
|
-
librarySection
|
|
19
|
-
projectsSection
|
|
20
|
-
if hasAnyMode { modesSection }
|
|
21
|
-
if !store.machines.isEmpty { machinesSection }
|
|
22
|
-
Spacer(minLength: 8)
|
|
23
|
-
}
|
|
24
|
-
.padding(14)
|
|
25
|
-
.padding(.top, 24)
|
|
26
|
-
}
|
|
27
|
-
.scrollContentBackground(.hidden)
|
|
28
|
-
.foregroundStyle(.white)
|
|
29
|
-
.tint(.white)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// MARK: - Workspace
|
|
33
|
-
|
|
34
|
-
private var workspaceSection: some View {
|
|
35
|
-
section(title: "Workspace") {
|
|
36
|
-
Button {
|
|
37
|
-
withAnimation(.spring(response: 0.3, dampingFraction: 0.85)) { store.pane = .record }
|
|
38
|
-
} label: {
|
|
39
|
-
rowLabel(icon: "mic.fill", label: "Record", count: nil,
|
|
40
|
-
selected: store.pane == .record, accentDot: store.engine.captureIsActive)
|
|
41
|
-
}
|
|
42
|
-
.buttonStyle(.plain)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// MARK: - Library
|
|
47
|
-
|
|
48
|
-
private var librarySection: some View {
|
|
49
|
-
section(title: "Library") {
|
|
50
|
-
filterRow(.all, icon: "waveform", label: "All Recordings")
|
|
51
|
-
if store.count(for: .noProject) > 0 {
|
|
52
|
-
filterRow(.noProject, icon: "tray", label: "No Project")
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// MARK: - Projects
|
|
58
|
-
|
|
59
|
-
private var projectsSection: some View {
|
|
60
|
-
section(title: "Projects", trailing: {
|
|
61
|
-
Button { addingProject = true; newProjectName = "" } label: {
|
|
62
|
-
Image(systemName: "plus").font(.system(size: 11, weight: .semibold))
|
|
63
|
-
}
|
|
64
|
-
.buttonStyle(.plain)
|
|
65
|
-
.disabled(!store.projectStore.canMutateProjects)
|
|
66
|
-
.help("Add Project")
|
|
67
|
-
.popover(isPresented: $addingProject, arrowEdge: .trailing) {
|
|
68
|
-
VStack(alignment: .leading, spacing: 10) {
|
|
69
|
-
Text("New Project").font(.headline)
|
|
70
|
-
TextField("Project name", text: $newProjectName)
|
|
71
|
-
.textFieldStyle(.roundedBorder).frame(width: 200)
|
|
72
|
-
.onSubmit(commitNewProject)
|
|
73
|
-
HStack {
|
|
74
|
-
Spacer()
|
|
75
|
-
Button("Cancel") { addingProject = false }
|
|
76
|
-
Button("Add") { commitNewProject() }
|
|
77
|
-
.keyboardShortcut(.defaultAction)
|
|
78
|
-
.disabled(newProjectName.trimmingCharacters(in: .whitespaces).isEmpty)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
.padding(14)
|
|
82
|
-
}
|
|
83
|
-
}) {
|
|
84
|
-
if store.projects.isEmpty {
|
|
85
|
-
Text("No projects")
|
|
86
|
-
.font(.caption).foregroundStyle(.white.opacity(0.5)).padding(.leading, 6)
|
|
87
|
-
}
|
|
88
|
-
ForEach(store.projects) { project in
|
|
89
|
-
filterRow(.project(project.id), icon: "folder", label: project.name)
|
|
90
|
-
.disabled(!store.projectStore.canMutateProjects)
|
|
91
|
-
.contextMenu {
|
|
92
|
-
Button("Delete", role: .destructive) {
|
|
93
|
-
do {
|
|
94
|
-
try store.projectStore.removeProject(id: project.id)
|
|
95
|
-
} catch {
|
|
96
|
-
store.operationError = store.projectStore.persistenceError ?? error.localizedDescription
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private func commitNewProject() {
|
|
105
|
-
let name = newProjectName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
106
|
-
guard !name.isEmpty else { return }
|
|
107
|
-
Task {
|
|
108
|
-
do {
|
|
109
|
-
try await store.projectStore.addProject(name: name)
|
|
110
|
-
addingProject = false
|
|
111
|
-
newProjectName = ""
|
|
112
|
-
} catch {
|
|
113
|
-
store.operationError = store.projectStore.persistenceError
|
|
114
|
-
?? (error as? RecordingsCLI.Failure)?.message
|
|
115
|
-
?? error.localizedDescription
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// MARK: - Modes
|
|
121
|
-
|
|
122
|
-
private var hasAnyMode: Bool {
|
|
123
|
-
store.count(for: .mode("enhanced")) > 0 || store.count(for: .mode("raw")) > 0
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private var modesSection: some View {
|
|
127
|
-
section(title: "Modes") {
|
|
128
|
-
if store.count(for: .mode("enhanced")) > 0 {
|
|
129
|
-
filterRow(.mode("enhanced"), icon: "wand.and.stars", label: "Enhanced")
|
|
130
|
-
}
|
|
131
|
-
if store.count(for: .mode("raw")) > 0 {
|
|
132
|
-
filterRow(.mode("raw"), icon: "text.quote", label: "Raw")
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// MARK: - Machines
|
|
138
|
-
|
|
139
|
-
private var machinesSection: some View {
|
|
140
|
-
section(title: "Machines") {
|
|
141
|
-
if store.count(for: .thisMachine) > 0 {
|
|
142
|
-
filterRow(.thisMachine, icon: "desktopcomputer", label: "This Mac")
|
|
143
|
-
}
|
|
144
|
-
ForEach(otherMachines, id: \.self) { machine in
|
|
145
|
-
filterRow(.machine(machine), icon: "macpro.gen3.server", label: machine)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private var otherMachines: [String] {
|
|
151
|
-
store.machines.filter { $0 != store.localMachineID }
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// MARK: - Building blocks
|
|
155
|
-
|
|
156
|
-
@ViewBuilder
|
|
157
|
-
private func section<Trailing: View, Content: View>(
|
|
158
|
-
title: String,
|
|
159
|
-
@ViewBuilder trailing: () -> Trailing = { EmptyView() },
|
|
160
|
-
@ViewBuilder content: () -> Content
|
|
161
|
-
) -> some View {
|
|
162
|
-
VStack(alignment: .leading, spacing: 6) {
|
|
163
|
-
HStack {
|
|
164
|
-
Text(title.uppercased())
|
|
165
|
-
.font(.caption2.weight(.semibold))
|
|
166
|
-
.foregroundStyle(.white.opacity(0.6))
|
|
167
|
-
.padding(.leading, 6)
|
|
168
|
-
Spacer()
|
|
169
|
-
trailing().foregroundStyle(.white.opacity(0.85)).padding(.trailing, 6)
|
|
170
|
-
}
|
|
171
|
-
content()
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private func filterRow(_ target: LibraryFilter, icon: String, label: String) -> some View {
|
|
176
|
-
let selected = store.pane == .library && store.filter == target
|
|
177
|
-
let count = store.count(for: target)
|
|
178
|
-
return Button {
|
|
179
|
-
withAnimation(.spring(response: 0.32, dampingFraction: 0.82)) {
|
|
180
|
-
store.filter = target
|
|
181
|
-
store.pane = .library
|
|
182
|
-
if !store.visibleRecordings.contains(where: { $0.id == store.selection }) {
|
|
183
|
-
store.selection = store.visibleRecordings.first?.id
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
} label: {
|
|
187
|
-
rowLabel(icon: icon, label: label, count: count > 0 ? count : nil, selected: selected, accentDot: false)
|
|
188
|
-
}
|
|
189
|
-
.buttonStyle(.plain)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private func rowLabel(icon: String, label: String, count: Int?, selected: Bool, accentDot: Bool) -> some View {
|
|
193
|
-
HStack(spacing: 9) {
|
|
194
|
-
Image(systemName: icon).font(.system(size: 12, weight: .medium)).frame(width: 16)
|
|
195
|
-
Text(label).font(.system(.subheadline, design: .rounded)).lineLimit(1)
|
|
196
|
-
Spacer(minLength: 4)
|
|
197
|
-
if accentDot {
|
|
198
|
-
Circle().fill(Theme.recordRed).frame(width: 7, height: 7)
|
|
199
|
-
} else if let count {
|
|
200
|
-
Text("\(count)").font(.caption2.monospacedDigit()).foregroundStyle(.white.opacity(0.55))
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
.foregroundStyle(.white.opacity(selected ? 1 : 0.85))
|
|
204
|
-
.padding(.horizontal, 9).padding(.vertical, 7)
|
|
205
|
-
.contentShape(Rectangle())
|
|
206
|
-
.background {
|
|
207
|
-
if selected {
|
|
208
|
-
RoundedRectangle(cornerRadius: Theme.cornerSmall, style: .continuous).fill(.white.opacity(0.22))
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|