浏览代码

wip: iOS controls

Tienson Qin 1 月之前
父节点
当前提交
e00fae6e89

+ 10 - 0
ios/App/App.xcodeproj/project.pbxproj

@@ -28,6 +28,8 @@
 		D3490CC62E7CE9EA00E796A6 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3490CC52E7CE9EA00E796A6 /* WidgetKit.framework */; };
 		D3490CC82E7CE9EA00E796A6 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3490CC72E7CE9EA00E796A6 /* SwiftUI.framework */; };
 		D3490CD52E7CE9EB00E796A6 /* shortcutsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D3490CC42E7CE9EA00E796A6 /* shortcutsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+		D39D1FDD2E7D6D8A00C903D1 /* LogseqIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D1FDB2E7D6D8A00C903D1 /* LogseqIntents.swift */; };
+		D39D1FDE2E7D6D8A00C903D1 /* LogseqShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39D1FDC2E7D6D8A00C903D1 /* LogseqShortcuts.swift */; };
 		D3D62A0A275C92880003FBDC /* FileContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D62A09275C92880003FBDC /* FileContainer.swift */; };
 		D3D62A0C275C928F0003FBDC /* FileContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = D3D62A0B275C928F0003FBDC /* FileContainer.m */; };
 		FE647FF427BDFEDE00F3206B /* FsWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE647FF327BDFEDE00F3206B /* FsWatcher.swift */; };
@@ -98,6 +100,8 @@
 		D3490CC52E7CE9EA00E796A6 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
 		D3490CC72E7CE9EA00E796A6 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
 		D3490CDA2E7CEA3900E796A6 /* shortcutsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = shortcutsExtension.entitlements; sourceTree = "<group>"; };
+		D39D1FDB2E7D6D8A00C903D1 /* LogseqIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogseqIntents.swift; sourceTree = "<group>"; };
+		D39D1FDC2E7D6D8A00C903D1 /* LogseqShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogseqShortcuts.swift; sourceTree = "<group>"; };
 		D3D62A09275C92880003FBDC /* FileContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileContainer.swift; sourceTree = "<group>"; };
 		D3D62A0B275C928F0003FBDC /* FileContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileContainer.m; sourceTree = "<group>"; };
 		DE5650F4AD4E2242AB9C012D /* Pods-Logseq.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Logseq.debug.xcconfig"; path = "Target Support Files/Pods-Logseq/Pods-Logseq.debug.xcconfig"; sourceTree = "<group>"; };
@@ -183,6 +187,8 @@
 				D32752BC275496A60039291C /* App.entitlements */,
 				50379B222058CBB4000EE86E /* capacitor.config.json */,
 				504EC3071FED79650016851F /* AppDelegate.swift */,
+				D39D1FDB2E7D6D8A00C903D1 /* LogseqIntents.swift */,
+				D39D1FDC2E7D6D8A00C903D1 /* LogseqShortcuts.swift */,
 				504EC30B1FED79650016851F /* Main.storyboard */,
 				504EC30E1FED79650016851F /* Assets.xcassets */,
 				504EC3101FED79650016851F /* LaunchScreen.storyboard */,
@@ -415,6 +421,8 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D39D1FDD2E7D6D8A00C903D1 /* LogseqIntents.swift in Sources */,
+				D39D1FDE2E7D6D8A00C903D1 /* LogseqShortcuts.swift in Sources */,
 				504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
 				5FF8632C283B5BFD0047731B /* Utils.m in Sources */,
 				FE647FF427BDFEDE00F3206B /* FsWatcher.swift in Sources */,
@@ -616,6 +624,7 @@
 				DEVELOPMENT_TEAM = K378MFWK59;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = App/Info.plist;
+				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
 				IPHONEOS_DEPLOYMENT_TARGET = 16.6;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
@@ -648,6 +657,7 @@
 				DEVELOPMENT_TEAM = K378MFWK59;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = App/Info.plist;
+				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
 				IPHONEOS_DEPLOYMENT_TARGET = 16.6;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",

+ 4 - 0
ios/App/App/App.entitlements

@@ -4,6 +4,10 @@
 <dict>
 	<key>aps-environment</key>
 	<string>development</string>
+	<key>com.apple.developer.associated-domains</key>
+	<array>
+		<string>applinks:https://logseq.com</string>
+	</array>
 	<key>com.apple.developer.icloud-container-identifiers</key>
 	<array>
 		<string>iCloud.com.logseq.logseq</string>

+ 3 - 1
ios/App/App/AppDelegate.swift

@@ -59,11 +59,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
     func application(_ application: UIApplication,
                      open url: URL,
                      options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
+        print("🔥 opened with URL:", url)
+
+        // Forward to Capacitor
         return ApplicationDelegateProxy.shared.application(application,
                                                            open: url,
                                                            options: options)
     }
-
     private func openURL(_ urlString: String) {
         if let url = URL(string: urlString) {
             UIApplication.shared.open(url, options: [:], completionHandler: nil)

+ 145 - 143
ios/App/App/Info.plist

@@ -2,149 +2,151 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
- <key>NSAppTransportSecurity</key>
-        <dict>
-                <key>NSAllowsArbitraryLoads</key>
-                <true/>
-        </dict>
-        <key>APFiles</key>
-        <dict>
-                <key>APFileDescriptionKey</key>
-                <string></string>
-                <key>APFileDestinationPath</key>
-                <string></string>
-                <key>APFileName</key>
-                <string></string>
-                <key>APFileSourcePath</key>
-                <string></string>
-        </dict>
-        <key>CFBundleDevelopmentRegion</key>
-        <string>en</string>
-        <key>CFBundleDisplayName</key>
-        <string>Logseq</string>
-        <key>CFBundleExecutable</key>
-        <string>$(EXECUTABLE_NAME)</string>
-        <key>CFBundleIdentifier</key>
-        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-        <key>CFBundleInfoDictionaryVersion</key>
-        <string>6.0</string>
-        <key>CFBundleName</key>
-        <string>$(PRODUCT_NAME)</string>
-        <key>CFBundlePackageType</key>
-        <string>APPL</string>
-        <key>CFBundleShortVersionString</key>
-        <string>$(MARKETING_VERSION)</string>
-        <key>CFBundleURLTypes</key>
-        <array>
-                <dict>
-                        <key>CFBundleTypeRole</key>
-                        <string>Viewer</string>
-                        <key>CFBundleURLName</key>
-                        <string>com.logseq.logseq</string>
-                        <key>CFBundleURLSchemes</key>
-                        <array>
-                                <string>logseq</string>
-                        </array>
-                </dict>
-        </array>
-        <key>CFBundleVersion</key>
-        <string>$(CURRENT_PROJECT_VERSION)</string>
-        <key>LSApplicationCategoryType</key>
-        <string></string>
-        <key>LSRequiresIPhoneOS</key>
+	<key>APFiles</key>
+	<dict>
+		<key>APFileDescriptionKey</key>
+		<string></string>
+		<key>APFileDestinationPath</key>
+		<string></string>
+		<key>APFileName</key>
+		<string></string>
+		<key>APFileSourcePath</key>
+		<string></string>
+	</dict>
+	<key>AppIntentsEnabled</key>
+	<true/>
+        <key>AppControlsEnabled</key>
         <true/>
-        <key>LSSupportsOpeningDocumentsInPlace</key>
-        <true/>
-        <key>UIFileSharingEnabled</key>
-        <true/>
-        <key>NSCameraUsageDescription</key>
-        <string>We will access your camera when you take a photo, and embed it in your note.</string>
-        <key>NSDocumentsFolderUsageDescription</key>
-        <string></string>
-        <key>NSDownloadsFolderUsageDescription</key>
-        <string></string>
-        <key>NSFileProviderDomainUsageDescription</key>
-        <string></string>
-        <key>NSFileProviderPresenceUsageDescription</key>
-        <string></string>
-        <key>NSSpeechRecognitionUsageDescription</key>
-        <string>We need access to speech recognition to convert your voice to text.</string>
-        <key>NSMicrophoneUsageDescription</key>
-        <string>We will access your microphone to record audio notes</string>
-        <key>NSPhotoLibraryAddUsageDescription</key>
-        <string>We will access your album when you save a photo.</string>
-        <key>NSPhotoLibraryUsageDescription</key>
-        <string>We will access your album when you choose a photo, and embed it in your note.</string>
-        <key>NSUbiquitousContainers</key>
-        <dict>
-                <key>iCloud.com.logseq.logseq</key>
-                <dict>
-                        <key>NSUbiquitousContainerIsDocumentScopePublic</key>
-                        <true/>
-                        <key>NSUbiquitousContainerName</key>
-                        <string>Logseq</string>
-                        <key>NSUbiquitousContainerSupportedFolderLevels</key>
-                        <string>ANY</string>
-                </dict>
-        </dict>
-        <key>UIBackgroundModes</key>
-        <array>
-                <string>audio</string>
-        </array>
-        <key>UILaunchStoryboardName</key>
-        <string>LaunchScreen</string>
-        <key>UIMainStoryboardFile</key>
-        <string>Main</string>
-        <key>UIRequiredDeviceCapabilities</key>
-        <array>
-                <string>armv7</string>
-        </array>
-        <key>UISupportedInterfaceOrientations</key>
-        <array>
-                <string>UIInterfaceOrientationPortrait</string>
-                <string>UIInterfaceOrientationLandscapeLeft</string>
-                <string>UIInterfaceOrientationLandscapeRight</string>
-        </array>
-        <key>UISupportedInterfaceOrientations~ipad</key>
-        <array>
-                <string>UIInterfaceOrientationPortrait</string>
-                <string>UIInterfaceOrientationPortraitUpsideDown</string>
-                <string>UIInterfaceOrientationLandscapeLeft</string>
-                <string>UIInterfaceOrientationLandscapeRight</string>
-        </array>
-        <key>UISupportsDocumentBrowser</key>
-        <true/>
-        <key>UIViewControllerBasedStatusBarAppearance</key>
-        <true/>
-        <key>CFBundleGetInfoString</key>
-        <string></string>
-        <key>ITSAppUsesNonExemptEncryption</key>
-        <false/>
-        <key>UIApplicationShortcutItems</key>
-        <array>
-            <dict>
-                <key>UIApplicationShortcutItemType</key>
-                <string>logseq.voice</string>
-                <key>UIApplicationShortcutItemTitle</key>
-                <string>Voice</string>
-                <key>UIApplicationShortcutItemIconSymbolName</key>
-                <string>waveform</string>
-            </dict>
-            <dict>
-                <key>UIApplicationShortcutItemType</key>
-                <string>logseq.quickadd</string>
-                <key>UIApplicationShortcutItemTitle</key>
-                <string>Quick Add</string>
-                <key>UIApplicationShortcutItemIconSymbolName</key>
-                <string>plus</string>
-            </dict>
-        </array>
-        <key>AppIntentsEnabled</key>
-        <true/>
-        <key>NSUserActivityTypes</key>
-        <array>
-            <string>com.logseq.quickadd</string>
-            <string>com.logseq.audio</string>
-        </array>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleDisplayName</key>
+	<string>Logseq</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleGetInfoString</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(MARKETING_VERSION)</string>
+	<key>CFBundleURLTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeRole</key>
+			<string>Viewer</string>
+			<key>CFBundleURLName</key>
+			<string>com.logseq.logseq</string>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>logseq</string>
+			</array>
+		</dict>
+	</array>
+	<key>CFBundleVersion</key>
+	<string>$(CURRENT_PROJECT_VERSION)</string>
+	<key>ITSAppUsesNonExemptEncryption</key>
+	<false/>
+	<key>LSApplicationCategoryType</key>
+	<string></string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>LSSupportsOpeningDocumentsInPlace</key>
+	<true/>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+	</dict>
+	<key>NSCameraUsageDescription</key>
+	<string>We will access your camera when you take a photo, and embed it in your note.</string>
+	<key>NSDocumentsFolderUsageDescription</key>
+	<string></string>
+	<key>NSDownloadsFolderUsageDescription</key>
+	<string></string>
+	<key>NSFileProviderDomainUsageDescription</key>
+	<string></string>
+	<key>NSFileProviderPresenceUsageDescription</key>
+	<string></string>
+	<key>NSMicrophoneUsageDescription</key>
+	<string>We will access your microphone to record audio notes</string>
+	<key>NSPhotoLibraryAddUsageDescription</key>
+	<string>We will access your album when you save a photo.</string>
+	<key>NSPhotoLibraryUsageDescription</key>
+	<string>We will access your album when you choose a photo, and embed it in your note.</string>
+	<key>NSSpeechRecognitionUsageDescription</key>
+	<string>We need access to speech recognition to convert your voice to text.</string>
+	<key>NSUbiquitousContainers</key>
+	<dict>
+		<key>iCloud.com.logseq.logseq</key>
+		<dict>
+			<key>NSUbiquitousContainerIsDocumentScopePublic</key>
+			<true/>
+			<key>NSUbiquitousContainerName</key>
+			<string>Logseq</string>
+			<key>NSUbiquitousContainerSupportedFolderLevels</key>
+			<string>ANY</string>
+		</dict>
+	</dict>
+	<key>NSUserActivityTypes</key>
+	<array>
+		<string>com.logseq.quickadd</string>
+		<string>com.logseq.audio</string>
+	</array>
+	<key>UIApplicationShortcutItems</key>
+	<array>
+		<dict>
+			<key>UIApplicationShortcutItemIconSymbolName</key>
+			<string>waveform</string>
+			<key>UIApplicationShortcutItemTitle</key>
+			<string>Voice</string>
+			<key>UIApplicationShortcutItemType</key>
+			<string>logseq.voice</string>
+		</dict>
+		<dict>
+			<key>UIApplicationShortcutItemIconSymbolName</key>
+			<string>plus</string>
+			<key>UIApplicationShortcutItemTitle</key>
+			<string>Quick Add</string>
+			<key>UIApplicationShortcutItemType</key>
+			<string>logseq.quickadd</string>
+		</dict>
+	</array>
+	<key>UIBackgroundModes</key>
+	<array>
+		<string>audio</string>
+	</array>
+	<key>UIFileSharingEnabled</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+	</array>
+	<key>UISupportsDocumentBrowser</key>
+	<true/>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<true/>
+        
+        
 </dict>
 </plist>

+ 20 - 19
ios/App/App/LogseqShortcuts.swift

@@ -2,25 +2,26 @@ import AppIntents
 
 struct LogseqShortcuts: AppShortcutsProvider {
     static var appShortcuts: [AppShortcut] {
-        [
-          AppShortcut(
-            intent: QuickAddIntent(),
-            phrases: [
-              "Quick add in \(.applicationName)",
-              "Add note in \(.applicationName)"
-            ],
-            shortTitle: "Quick Add",
-            systemImageName: "plus.circle"
-          ),
-          AppShortcut(
-            intent: RecordAudioIntent(),
-            phrases: [
-              "Record audio in \(.applicationName)",
-              "Start recording in \(.applicationName)"
-            ],
-            shortTitle: "Record Audio",
-            systemImageName: "mic"
-          )
+        return [
+            AppShortcut(
+                intent: QuickAddIntent(),
+                phrases: [
+                    "Quick add in \(.applicationName)",
+                    "Add note in \(.applicationName)"
+                ],
+                shortTitle: "Quick Add",
+                systemImageName: "plus.circle"
+            ),
+            
+            AppShortcut(
+                intent: RecordAudioIntent(),
+                phrases: [
+                    "Record audio in \(.applicationName)",
+                    "Start recording in \(.applicationName)"
+                ],
+                shortTitle: "Record Audio",
+                systemImageName: "waveform"
+            )
         ]
     }
 }

+ 3 - 1
ios/App/shortcuts/shortcutsBundle.swift

@@ -9,9 +9,11 @@ import WidgetKit
 import SwiftUI
 
 @main
+@available(iOSApplicationExtension 18.0, *)
 struct shortcutsBundle: WidgetBundle {
     var body: some Widget {
         Shortcuts()
-        shortcutsControl()
+        QuickAddButton()
+        RecordAudioButton()
     }
 }

+ 35 - 27
ios/App/shortcuts/shortcutsControl.swift

@@ -9,46 +9,54 @@ import AppIntents
 import SwiftUI
 import WidgetKit
 
-struct shortcutsControl: ControlWidget {
+@available(iOS 18.0, *)
+struct QuickAddButton: ControlWidget {
     var body: some ControlWidgetConfiguration {
         StaticControlConfiguration(
-            kind: "com.logseq.logseq.shortcuts",
-            provider: Provider()
-        ) { value in
-            ControlWidgetToggle(
-                "Start Timer",
-                isOn: value,
-                action: StartTimerIntent()
-            ) { isRunning in
-                Label(isRunning ? "On" : "Off", systemImage: "timer")
+          kind: "com.logseq.logseq.quickAddButton"
+        ) {
+            ControlWidgetButton(action: QuickAddIntent()) {
+                Label("Quick Add", systemImage: "plus.circle")
             }
         }
-        .displayName("Timer")
-        .description("A an example control that runs a timer.")
+          .displayName("Quick Add")
+          .description("Quick note.")
     }
 }
 
-extension shortcutsControl {
-    struct Provider: ControlValueProvider {
-        var previewValue: Bool {
-            false
-        }
-
-        func currentValue() async throws -> Bool {
-            let isRunning = true // Check if the timer is running
-            return isRunning
+@available(iOS 18.0, *)
+struct RecordAudioButton: ControlWidget {
+    var body: some ControlWidgetConfiguration {
+        StaticControlConfiguration(
+          kind: "com.logseq.logseq.recordAudioButton"
+        ) {
+            ControlWidgetButton(action: RecordAudioIntent()) {   // ✅ fixed
+                Label("Record Audio", systemImage: "waveform")
+            }
         }
+          .displayName("Record Audio")
+          .description("Record Audio.")
     }
 }
 
-struct StartTimerIntent: SetValueIntent {
-    static let title: LocalizedStringResource = "Start a timer"
+@available(iOS 18.0, *)
+struct QuickAddIntent: AppIntent {
+    static var title: LocalizedStringResource = "Quick Add"
+    static var description = IntentDescription("Open Logseq Quick Add")
+
+    // TODO: use https://logseq.com/mobile/go/quick-add
+    func perform() async throws -> some IntentResult {
+        .result()
+    }
+}
 
-    @Parameter(title: "Timer is running")
-    var value: Bool
+@available(iOS 18.0, *)
+struct RecordAudioIntent: AppIntent {
+    static var title: LocalizedStringResource = "Record Audio"
+    static var description = IntentDescription("Open Logseq Record Audio")
 
+    // TODO: https://logseq.com/mobile/go/record-audio
     func perform() async throws -> some IntentResult {
-        // Start / stop the timer based on `value`.
-        return .result()
+        .result()
     }
 }