Skip to content

Commit ee53c81

Browse files
committed
feat: add ShareExtension for sharing text and handle incoming shared content
1 parent 1c32541 commit ee53c81

12 files changed

Lines changed: 356 additions & 8 deletions

File tree

‎app/ios-kit/src/commonMain/kotlin/com/softartdev/notedelight/IosAppLauncher.kt‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ import co.touchlab.kermit.crashlytics.CrashlyticsLogWriter
1010
import co.touchlab.kermit.platformLogWriter
1111
import com.softartdev.notedelight.di.sharedModules
1212
import com.softartdev.notedelight.di.uiModules
13+
import com.softartdev.notedelight.interactor.ShareInteractor
1314
import com.softartdev.notedelight.util.DEFAULT_APP_LOG_TAG
1415
import com.softartdev.notedelight.util.kermitLogger
1516
import org.koin.core.component.KoinComponent
17+
import org.koin.core.component.inject
1618
import org.koin.core.context.startKoin
1719
import platform.UIKit.UIViewController
1820

1921
@OptIn(ExperimentalKermitApi::class)
2022
class IosAppLauncher : KoinComponent {
23+
private val shareInteractor: ShareInteractor by inject()
2124
val mainViewController: UIViewController by lazy { ComposeUIViewController { App() } }
2225

2326
fun init(debug: Boolean) {
@@ -29,6 +32,12 @@ class IosAppLauncher : KoinComponent {
2932
}
3033
}
3134

35+
fun onSharedText(text: String) {
36+
if (text.isNotBlank()) {
37+
shareInteractor.sharedTextStateFlow.value = text
38+
}
39+
}
40+
3241
private fun kermitLogWriter(debug: Boolean): LogWriter = when {
3342
debug -> platformLogWriter()
3443
else -> {

‎app/iosApp/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The `app:iosApp` module is the native SwiftUI/UIKit shell for NoteDelight. It co
77
```text
88
app/iosApp/
99
├── iosApp/ # Swift application sources
10+
├── ShareExtension/ # iOS Share Extension (com.apple.share-services)
1011
├── Note Delight Unit-Tests/ # Xcode unit tests
1112
├── iosApp.xcodeproj/ # Open and build this project
1213
├── KotlinMultiplatformLinkedPackage/ # Generated Swift package used for linkage
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>$(DEVELOPMENT_LANGUAGE)</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>Note Delight</string>
9+
<key>CFBundleExecutable</key>
10+
<string>$(EXECUTABLE_NAME)</string>
11+
<key>CFBundleIdentifier</key>
12+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13+
<key>CFBundleInfoDictionaryVersion</key>
14+
<string>6.0</string>
15+
<key>CFBundleName</key>
16+
<string>$(PRODUCT_NAME)</string>
17+
<key>CFBundlePackageType</key>
18+
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
19+
<key>CFBundleShortVersionString</key>
20+
<string>$(MARKETING_VERSION)</string>
21+
<key>CFBundleVersion</key>
22+
<string>$(CURRENT_PROJECT_VERSION)</string>
23+
<key>NSExtension</key>
24+
<dict>
25+
<key>NSExtensionAttributes</key>
26+
<dict>
27+
<key>NSExtensionActivationRule</key>
28+
<dict>
29+
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
30+
<integer>1</integer>
31+
<key>NSExtensionActivationSupportsText</key>
32+
<true/>
33+
</dict>
34+
</dict>
35+
<key>NSExtensionPointIdentifier</key>
36+
<string>com.apple.share-services</string>
37+
<key>NSExtensionPrincipalClass</key>
38+
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
39+
</dict>
40+
</dict>
41+
</plist>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.application-groups</key>
6+
<array>
7+
<string>group.com.softartdev.notedelight</string>
8+
</array>
9+
</dict>
10+
</plist>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Social
2+
import UIKit
3+
import UniformTypeIdentifiers
4+
5+
@objc(ShareViewController)
6+
final class ShareViewController: SLComposeServiceViewController {
7+
private static let appGroupId = "group.com.softartdev.notedelight"
8+
private static let sharedTextKey = "shared_text"
9+
10+
override func presentationAnimationDidFinish() {
11+
super.presentationAnimationDidFinish()
12+
guard contentText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
13+
loadTextFileIfNeeded()
14+
}
15+
16+
override func isContentValid() -> Bool {
17+
!contentText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
18+
}
19+
20+
override func didSelectPost() {
21+
let trimmed = contentText.trimmingCharacters(in: .whitespacesAndNewlines)
22+
if !trimmed.isEmpty {
23+
UserDefaults(suiteName: Self.appGroupId)?.set(trimmed, forKey: Self.sharedTextKey)
24+
}
25+
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
26+
}
27+
28+
override func configurationItems() -> [Any]! {
29+
[]
30+
}
31+
32+
private func loadTextFileIfNeeded() {
33+
guard let items = extensionContext?.inputItems as? [NSExtensionItem] else { return }
34+
for item in items {
35+
for provider in item.attachments ?? [] where provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
36+
provider.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { [weak self] data, _ in
37+
guard let url = data as? URL, url.isFileURL else { return }
38+
let accessed = url.startAccessingSecurityScopedResource()
39+
defer {
40+
if accessed {
41+
url.stopAccessingSecurityScopedResource()
42+
}
43+
}
44+
guard let text = try? String(contentsOf: url, encoding: .utf8) else { return }
45+
DispatchQueue.main.async {
46+
self?.textView.text = text
47+
self?.validateContent()
48+
}
49+
}
50+
return
51+
}
52+
}
53+
}
54+
}

‎app/iosApp/fastlane/Fastfile‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ platform :ios do
4242
keychain_password: ENV["LARGE_SECRET_PASSPHRASE"]
4343
)
4444
install_provisioning_profile(path: "fastlane/NoteDelight_Distribution_Profile.mobileprovision")
45+
if File.exist?("fastlane/NoteDelight_ShareExtension_Distribution_Profile.mobileprovision")
46+
install_provisioning_profile(path: "fastlane/NoteDelight_ShareExtension_Distribution_Profile.mobileprovision")
47+
end
4548
end
4649
desc "AppStore build lane with gym"
4750
lane :appstore_build_gym_lane do
@@ -57,7 +60,8 @@ platform :ios do
5760
export_method: 'app-store',
5861
export_options: {
5962
provisioningProfiles: {
60-
"com.softartdev.notedelight" => "NoteDelight_Distribution_Profile"
63+
"com.softartdev.notedelight" => "NoteDelight_Distribution_Profile",
64+
"com.softartdev.notedelight.ShareExtension" => "NoteDelight_ShareExtension_Distribution_Profile"
6165
}
6266
}
6367
)

0 commit comments

Comments
 (0)