Explorar el Código

Add Settings Share App menu and More Apps App Store link.

Wire Share App to the system share picker anchored beside the row icon, and open the developer catalog when More Apps is tapped, with App Store IDs configurable via build settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 hace 3 semanas
padre
commit
b53e4ef8e1

+ 4 - 0
App for Indeed.xcodeproj/project.pbxproj

@@ -255,6 +255,8 @@
255 255
 				ENABLE_APP_SANDBOX = YES;
256 256
 				ENABLE_USER_SELECTED_FILES = readonly;
257 257
 				GENERATE_INFOPLIST_FILE = YES;
258
+				INFOPLIST_KEY_AppStoreAppID = "$(APP_STORE_APP_ID)";
259
+				INFOPLIST_KEY_AppStoreDeveloperID = "$(APP_STORE_DEVELOPER_ID)";
258 260
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
259 261
 				INFOPLIST_KEY_NSMainStoryboardFile = Main;
260 262
 				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
@@ -289,6 +291,8 @@
289 291
 				ENABLE_APP_SANDBOX = YES;
290 292
 				ENABLE_USER_SELECTED_FILES = readonly;
291 293
 				GENERATE_INFOPLIST_FILE = YES;
294
+				INFOPLIST_KEY_AppStoreAppID = "$(APP_STORE_APP_ID)";
295
+				INFOPLIST_KEY_AppStoreDeveloperID = "$(APP_STORE_DEVELOPER_ID)";
292 296
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
293 297
 				INFOPLIST_KEY_NSMainStoryboardFile = Main;
294 298
 				INFOPLIST_KEY_NSPrincipalClass = NSApplication;

+ 74 - 0
App for Indeed/AppMarketingLinks.swift

@@ -0,0 +1,74 @@
1
+//
2
+//  AppMarketingLinks.swift
3
+//  App for Indeed
4
+//
5
+
6
+import Foundation
7
+
8
+/// Mac App Store URLs for Settings → Share App / More Apps.
9
+/// Override via build settings: `INFOPLIST_KEY_AppStoreAppID` and `INFOPLIST_KEY_AppStoreDeveloperID`.
10
+enum AppMarketingLinks {
11
+    /// Numeric App Store app ID from App Store Connect (e.g. `1234567890`).
12
+    static var macAppStoreURL: URL? {
13
+        guard let appID = resolvedAppStoreAppID else { return nil }
14
+        return URL(string: "https://apps.apple.com/app/id\(appID)")
15
+    }
16
+
17
+    /// Developer catalog on the App Store (other apps by the same publisher).
18
+    static var developerAppsURL: URL? {
19
+        guard let developerID = resolvedAppStoreDeveloperID else { return nil }
20
+        return URL(string: "https://apps.apple.com/developer/-/id\(developerID)")
21
+    }
22
+
23
+    static var appDisplayName: String {
24
+        if let display = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String,
25
+           !display.isEmpty {
26
+            return display
27
+        }
28
+        if let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String,
29
+           !name.isEmpty {
30
+            return name
31
+        }
32
+        return "App for Indeed"
33
+    }
34
+
35
+    static var shareText: String {
36
+        let name = appDisplayName
37
+        if let url = macAppStoreURL {
38
+            return "Check out \(name) on the Mac App Store:\n\(url.absoluteString)"
39
+        }
40
+        return "Check out \(name) on the Mac App Store."
41
+    }
42
+
43
+    static var shareEmailSubject: String {
44
+        "Check out \(appDisplayName)"
45
+    }
46
+
47
+    /// Payload for `NSSharingServicePicker` (Mail, Messages, AirDrop, Notes, Copy Link, etc.).
48
+    static var shareItems: [Any] {
49
+        var items: [Any] = [shareText as NSString]
50
+        if let url = macAppStoreURL {
51
+            items.append(url)
52
+        }
53
+        return items
54
+    }
55
+
56
+    static var isConfigured: Bool {
57
+        macAppStoreURL != nil
58
+    }
59
+
60
+    private static var resolvedAppStoreAppID: String? {
61
+        normalizedID(from: Bundle.main.object(forInfoDictionaryKey: "AppStoreAppID"))
62
+    }
63
+
64
+    private static var resolvedAppStoreDeveloperID: String? {
65
+        normalizedID(from: Bundle.main.object(forInfoDictionaryKey: "AppStoreDeveloperID"))
66
+    }
67
+
68
+    private static func normalizedID(from value: Any?) -> String? {
69
+        guard let raw = value as? String else { return nil }
70
+        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
71
+        guard !trimmed.isEmpty else { return nil }
72
+        return trimmed
73
+    }
74
+}

+ 72 - 3
App for Indeed/Views/DashboardView.swift

@@ -19,7 +19,7 @@ private enum PremiumSheetLayout {
19 19
     static let overscanExtraTop: CGFloat = 0.5
20 20
 }
21 21
 
22
-final class DashboardView: NSView, NSTextFieldDelegate {
22
+final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDelegate, NSSharingServiceDelegate {
23 23
     /// Indeed.com-inspired neutrals and brand blue (white surfaces, `#2557a7` accent, `#2d2d2d` / `#767676` text, `#d4d2d0` borders).
24 24
     private enum Theme {
25 25
         static let brandBlue = NSColor(srgbRed: 37 / 255, green: 87 / 255, blue: 167 / 255, alpha: 1)
@@ -161,6 +161,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
161 161
     private weak var sidebarUpgradeDescription: NSTextField?
162 162
     private weak var sidebarUpgradeButton: HoverableButton?
163 163
     private var subscriptionObserver: NSObjectProtocol?
164
+    /// Retains the system share picker until the user picks a destination or dismisses the menu.
165
+    private var appSharePicker: NSSharingServicePicker?
164 166
 
165 167
     /// Upper bound sent to the model per request (was fixed at 8 in the prompt). Clamped when calling the API.
166 168
     private static let jobsPerSearchDefault = 15
@@ -1496,8 +1498,8 @@ final class DashboardView: NSView, NSTextFieldDelegate {
1496 1498
         contentStack.translatesAutoresizingMaskIntoConstraints = false
1497 1499
 
1498 1500
         let settingsSection = makeSettingsSection(rows: [
1499
-            makeSettingsRow(title: "Share App", systemImage: "square.and.arrow.up", accessory: nil),
1500
-            makeSettingsRow(title: "More Apps", systemImage: "square.grid.2x2", accessory: nil)
1501
+            makeSettingsRow(title: "Share App", systemImage: "square.and.arrow.up", accessory: nil, tapAction: #selector(didTapShareApp)),
1502
+            makeSettingsRow(title: "More Apps", systemImage: "square.grid.2x2", accessory: nil, tapAction: #selector(didTapMoreApps))
1501 1503
         ])
1502 1504
 
1503 1505
         let aboutTitle = NSTextField(labelWithString: "About")
@@ -1796,6 +1798,73 @@ final class DashboardView: NSView, NSTextFieldDelegate {
1796 1798
         focusSearchField(seed: "Find jobs that require skill: ")
1797 1799
     }
1798 1800
 
1801
+    @objc private func didTapShareApp(_ sender: NSButton) {
1802
+        presentAppShareMenu(anchoredTo: sender)
1803
+    }
1804
+
1805
+    /// Shows the macOS share menu (Mail, Messages, AirDrop, Copy Link, etc.) with the app link.
1806
+    private func presentAppShareMenu(anchoredTo sender: NSButton) {
1807
+        guard let row = sender.superview else { return }
1808
+        let items = AppMarketingLinks.shareItems
1809
+        guard !items.isEmpty else { return }
1810
+
1811
+        let picker = NSSharingServicePicker(items: items)
1812
+        picker.delegate = self
1813
+        appSharePicker = picker
1814
+
1815
+        // Match `makeSettingsRow` layout: 16pt leading inset + 38pt icon tile — anchor the
1816
+        // popover beside the share icon, not the horizontal center of the full-width row.
1817
+        let iconTileInset: CGFloat = 16
1818
+        let iconTileSize: CGFloat = 38
1819
+        let anchorRect = NSRect(
1820
+            x: iconTileInset,
1821
+            y: row.bounds.minY + 6,
1822
+            width: iconTileSize,
1823
+            height: max(row.bounds.height - 12, 1)
1824
+        )
1825
+        picker.show(relativeTo: anchorRect, of: row, preferredEdge: .minY)
1826
+    }
1827
+
1828
+    func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, delegateFor sharingService: NSSharingService) -> Any? {
1829
+        self
1830
+    }
1831
+
1832
+    func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, didChoose sharingService: NSSharingService?) {
1833
+        appSharePicker = nil
1834
+    }
1835
+
1836
+    func sharingService(_ sharingService: NSSharingService, willShareItems items: [Any]) -> [Any] {
1837
+        if sharingService == NSSharingService(named: .composeEmail) {
1838
+            sharingService.subject = AppMarketingLinks.shareEmailSubject
1839
+        }
1840
+        return items
1841
+    }
1842
+
1843
+    @objc private func didTapMoreApps() {
1844
+        guard let url = AppMarketingLinks.developerAppsURL else {
1845
+            presentAppMarketingConfigurationAlert(feature: "More Apps")
1846
+            return
1847
+        }
1848
+        NSWorkspace.shared.open(url)
1849
+    }
1850
+
1851
+    private func presentAppMarketingConfigurationAlert(feature: String) {
1852
+        let alert = NSAlert()
1853
+        alert.messageText = "\(feature) isn’t available yet"
1854
+        alert.informativeText = """
1855
+        Add your Mac App Store IDs in the target’s build settings:
1856
+        • AppStoreAppID — numeric app ID from App Store Connect
1857
+        • AppStoreDeveloperID — numeric developer ID (for your other apps page)
1858
+        """
1859
+        alert.alertStyle = .informational
1860
+        alert.addButton(withTitle: "OK")
1861
+        if let window {
1862
+            alert.beginSheetModal(for: window)
1863
+        } else {
1864
+            alert.runModal()
1865
+        }
1866
+    }
1867
+
1799 1868
     @objc private func didTapSupport() {
1800 1869
         presentSettingsPlaceholderAlert(for: "Support")
1801 1870
     }