craft-native 0.0.189 → 0.0.192
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/dist/android/templates/CraftNativeServices.kt.template +4 -1
- package/dist/android/templates/CraftNativeShell.kt.template +44 -3
- package/dist/android/templates/MainActivityNative.kt.template +43 -11
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/templates/CraftApp.swift +47 -6
- package/dist/ios/templates/CraftNativeScreen.swift +3 -0
- package/dist/ios/templates/CraftNativeServices.swift +32 -3
- package/package.json +1 -1
|
@@ -286,7 +286,10 @@ internal class CraftNativeServices(
|
|
|
286
286
|
else -> Intent(Intent.ACTION_SEND).apply { type = "text/plain" }
|
|
287
287
|
}
|
|
288
288
|
if (text.isNotBlank()) intent.putExtra(Intent.EXTRA_TEXT, text)
|
|
289
|
-
options.optString("title").takeIf { it.isNotBlank() }?.let {
|
|
289
|
+
options.optString("title").takeIf { it.isNotBlank() }?.let {
|
|
290
|
+
intent.putExtra(Intent.EXTRA_SUBJECT, it)
|
|
291
|
+
intent.putExtra(Intent.EXTRA_TITLE, it)
|
|
292
|
+
}
|
|
290
293
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
291
294
|
host.startActivity(Intent.createChooser(intent, options.optString("title").ifBlank { null }))
|
|
292
295
|
// Android does not say whether something was shared; the sheet opened.
|
|
@@ -576,6 +576,9 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
576
576
|
private val buttons = LinearLayout(context)
|
|
577
577
|
private val tabBar = LinearLayout(context)
|
|
578
578
|
private val tabViews = mutableListOf<Pair<TextView, TextView>>()
|
|
579
|
+
private val tabIconText = mutableMapOf<String, String>()
|
|
580
|
+
private val tabImageURLs = mutableMapOf<String, String>()
|
|
581
|
+
private val tabImages = mutableMapOf<String, android.graphics.drawable.Drawable>()
|
|
579
582
|
private val density = resources.displayMetrics.density
|
|
580
583
|
private var selectedTab = 0
|
|
581
584
|
private var swipeTracking = false
|
|
@@ -703,6 +706,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
703
706
|
|
|
704
707
|
/** Draws `options` (a screen's `setOptions`) for a screen `depth` deep in its stack. */
|
|
705
708
|
fun apply(options: JSONObject, depth: Int, fallbackTitle: String?) {
|
|
709
|
+
options.optJSONObject("tabItem")?.let(::updateTab)
|
|
706
710
|
val hidden = options.optBoolean("hidden", false)
|
|
707
711
|
val large = options.optBoolean("largeTitle", false) && depth == 0
|
|
708
712
|
val title = options.optString("title").takeIf { options.has("title") } ?: fallbackTitle.orEmpty()
|
|
@@ -728,6 +732,42 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
728
732
|
refreshColors()
|
|
729
733
|
}
|
|
730
734
|
|
|
735
|
+
private fun updateTab(item: JSONObject) {
|
|
736
|
+
val id = item.optString("id")
|
|
737
|
+
val index = tabs.indexOfFirst { it.id == id }
|
|
738
|
+
if (index < 0) return
|
|
739
|
+
val cell = tabBar.getChildAt(index)
|
|
740
|
+
if (item.has("accessibilityLabel")) cell.contentDescription = item.optString("accessibilityLabel").takeUnless { item.isNull("accessibilityLabel") || it.isBlank() } ?: tabs[index].title
|
|
741
|
+
if (item.has("iconText")) {
|
|
742
|
+
tabIconText.remove(id)
|
|
743
|
+
item.optString("iconText").takeUnless { item.isNull("iconText") || it.isBlank() }?.let { tabIconText[id] = it.take(8) }
|
|
744
|
+
}
|
|
745
|
+
if (item.has("image")) {
|
|
746
|
+
val address = item.optString("image").takeIf { !item.isNull("image") && it.startsWith("https://") }
|
|
747
|
+
if (address != tabImageURLs[id]) {
|
|
748
|
+
tabImages.remove(id)
|
|
749
|
+
tabImageURLs.remove(id)
|
|
750
|
+
if (address != null) {
|
|
751
|
+
tabImageURLs[id] = address
|
|
752
|
+
Thread {
|
|
753
|
+
val image = runCatching {
|
|
754
|
+
val connection = java.net.URL(address).openConnection().apply { connectTimeout = 5000; readTimeout = 5000 }
|
|
755
|
+
val bitmap = connection.getInputStream().use { android.graphics.BitmapFactory.decodeStream(it) } ?: return@runCatching null
|
|
756
|
+
androidx.core.graphics.drawable.RoundedBitmapDrawableFactory.create(resources, android.graphics.Bitmap.createScaledBitmap(bitmap, dp(27), dp(27), true)).apply { isCircular = true }
|
|
757
|
+
}.getOrNull()
|
|
758
|
+
post {
|
|
759
|
+
if (tabImageURLs[id] == address && image != null) {
|
|
760
|
+
tabImages[id] = image
|
|
761
|
+
refreshColors()
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}.start()
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
refreshColors()
|
|
769
|
+
}
|
|
770
|
+
|
|
731
771
|
/** Light or dark changed: the bars follow the screen's background. */
|
|
732
772
|
fun refreshColors() {
|
|
733
773
|
val background = ContextCompat.getColor(context, R.color.background)
|
|
@@ -749,11 +789,12 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
749
789
|
)).apply { setLayerInset(1, 0, maxOf(1, (density / 2).toInt()), 0, 0) }
|
|
750
790
|
tabViews.forEachIndexed { index, (icon, label) ->
|
|
751
791
|
val color = if (index == selectedTab) accent else muted
|
|
752
|
-
|
|
792
|
+
val number = tabIconText[tabs[index].id]
|
|
793
|
+
icon.setTextColor(if (number != null) Color.parseColor("#FF2D55") else color)
|
|
753
794
|
label.setTextColor(color)
|
|
754
|
-
val vector = CraftNativeIcons.drawable(context, null, tabs[index].symbol, color)
|
|
795
|
+
val vector = tabImages[tabs[index].id] ?: if (number == null) CraftNativeIcons.drawable(context, null, tabs[index].symbol, color) else null
|
|
755
796
|
icon.foreground = vector
|
|
756
|
-
if (vector != null)
|
|
797
|
+
icon.text = if (vector != null) "" else number ?: CraftNativeSymbols.glyph(tabs[index].symbol)
|
|
757
798
|
}
|
|
758
799
|
for (index in 0 until buttons.childCount) (buttons.getChildAt(index) as? TextView)?.setTextColor(accent)
|
|
759
800
|
}
|
|
@@ -2653,6 +2653,37 @@ private class CraftNativeDatePickerView(context: Context) : FrameLayout(context)
|
|
|
2653
2653
|
}
|
|
2654
2654
|
}
|
|
2655
2655
|
|
|
2656
|
+
/** A field's pending native edits and the props acknowledged by the screen. */
|
|
2657
|
+
internal class CraftNativeInputEcho {
|
|
2658
|
+
private val pending = ArrayDeque<String>()
|
|
2659
|
+
private var rendered: String? = null
|
|
2660
|
+
private var initialized = false
|
|
2661
|
+
|
|
2662
|
+
fun record(value: String) {
|
|
2663
|
+
pending.addLast(value)
|
|
2664
|
+
while (pending.size > 64) pending.removeFirst()
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
fun accept(value: String?, nativeValue: String): Boolean {
|
|
2668
|
+
if (value == null) {
|
|
2669
|
+
initialized = false
|
|
2670
|
+
rendered = null
|
|
2671
|
+
pending.clear()
|
|
2672
|
+
return false
|
|
2673
|
+
}
|
|
2674
|
+
// A retained field can render the same prop more than once, after
|
|
2675
|
+
// its echo was consumed. That repeated prop must not overwrite a
|
|
2676
|
+
// keystroke that has not made the native/JS round trip yet.
|
|
2677
|
+
val changed = !initialized || rendered != value
|
|
2678
|
+
initialized = true
|
|
2679
|
+
rendered = value
|
|
2680
|
+
if (!changed) return false
|
|
2681
|
+
val index = pending.indexOf(value)
|
|
2682
|
+
if (index >= 0) repeat(index + 1) { pending.removeFirst() }
|
|
2683
|
+
return nativeValue != value && index < 0
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2656
2687
|
/**
|
|
2657
2688
|
* A native screen's `<Video>`: the platform's VideoView.
|
|
2658
2689
|
*
|
|
@@ -3261,7 +3292,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
3261
3292
|
* the screen answer with a value the field has already moved past;
|
|
3262
3293
|
* that echo is not written back over what was typed since.
|
|
3263
3294
|
*/
|
|
3264
|
-
val inputEchoes = mutableMapOf<EditText,
|
|
3295
|
+
val inputEchoes = mutableMapOf<EditText, CraftNativeInputEcho>()
|
|
3265
3296
|
val controls = mutableMapOf<String, View>()
|
|
3266
3297
|
// Protocol ids identify mutation targets; authored keys identify the
|
|
3267
3298
|
// retained view. Keep both mappings so a targeted update can recover
|
|
@@ -4601,10 +4632,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
4601
4632
|
val autofillHint = autofillHintFor(props)
|
|
4602
4633
|
if (autofillHint == null) setAutofillHints(*emptyArray()) else setAutofillHints(autofillHint)
|
|
4603
4634
|
val controlledValue = props.opt("value")?.takeUnless { it == JSONObject.NULL }?.toString()
|
|
4604
|
-
val echoes = screen.inputEchoes
|
|
4605
|
-
|
|
4606
|
-
if (echoIndex >= 0) repeat(echoIndex + 1) { echoes!!.removeFirst() }
|
|
4607
|
-
if (controlledValue != null && text.toString() != controlledValue && echoIndex < 0) {
|
|
4635
|
+
val echoes = screen.inputEchoes.getOrPut(this) { CraftNativeInputEcho() }
|
|
4636
|
+
if (echoes.accept(controlledValue, text.toString()) && controlledValue != null) {
|
|
4608
4637
|
screen.suppressedTextChanges.add(this)
|
|
4609
4638
|
setTextPreservingSelection(this, controlledValue)
|
|
4610
4639
|
}
|
|
@@ -4662,9 +4691,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
4662
4691
|
(screen.inputIdentities[this@apply] ?: identity).let { screen.drafts[it] = updated }
|
|
4663
4692
|
if (screen.suppressedTextChanges.remove(this@apply)) return
|
|
4664
4693
|
val current = screen.handlers[this@apply] ?: return
|
|
4665
|
-
|
|
4666
|
-
echoes.addLast(updated)
|
|
4667
|
-
while (echoes.size > 64) echoes.removeFirst()
|
|
4694
|
+
screen.inputEchoes.getOrPut(this@apply) { CraftNativeInputEcho() }.record(updated)
|
|
4668
4695
|
send(screen, "EVENT", JSONObject()
|
|
4669
4696
|
.put("handlerName", current)
|
|
4670
4697
|
.put("nativeEvent", JSONObject().put("text", updated)))
|
|
@@ -5084,7 +5111,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
5084
5111
|
val pressOutHandler = craftNativeEventName(events, "onPressOut").takeIf { it.isNotBlank() }
|
|
5085
5112
|
if (pressInHandler != null) screen.pressInHandlers[control] = pressInHandler else screen.pressInHandlers.remove(control)
|
|
5086
5113
|
if (pressOutHandler != null) screen.pressOutHandlers[control] = pressOutHandler else screen.pressOutHandlers.remove(control)
|
|
5087
|
-
if (type != "Button" && type != "Link" && type != "TextInput" && type != "Picker" && type != "DatePicker" && type != "Modal" && type != "Switch" && type != "Slider" && type != "ActivityIndicator" && type != "KeyboardAvoidingView" && type != "StatusBar" && type != "RefreshControl" && type != "Map") {
|
|
5114
|
+
if (type != "Button" && type != "Link" && type != "Checkbox" && type != "TextInput" && type != "Picker" && type != "DatePicker" && type != "Modal" && type != "Switch" && type != "Slider" && type != "ActivityIndicator" && type != "KeyboardAvoidingView" && type != "StatusBar" && type != "RefreshControl" && type != "Map") {
|
|
5088
5115
|
val handler = craftNativeEventName(events, "onPress", "onClick")
|
|
5089
5116
|
val disabled = props.optBoolean("disabled", false)
|
|
5090
5117
|
val target = listOf(props.opt("href"), props.opt("to"))
|
|
@@ -5430,6 +5457,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
5430
5457
|
view.elevation = dp(craftNativeNonNegativeDouble(style, "elevation", 0.0)).toFloat()
|
|
5431
5458
|
view.z = craftNativeFiniteFloat(style, "zIndex", 0f)
|
|
5432
5459
|
val background = style.has("backgroundColor") && !style.isNull("backgroundColor")
|
|
5460
|
+
// VideoView's Surface is behind this window. An opaque View
|
|
5461
|
+
// background paints over the decoded picture (only rounded corners
|
|
5462
|
+
// showed it). Keep the outline/borders, with a transparent fill.
|
|
5463
|
+
val backgroundColor = if (view is CraftNativeVideoView) Color.TRANSPARENT
|
|
5464
|
+
else color(style.optString("backgroundColor"), Color.TRANSPARENT)
|
|
5433
5465
|
val border = craftNativeNonNegativeDouble(style, "borderWidth", 0.0)
|
|
5434
5466
|
val radius = craftNativeNonNegativeDouble(style, "borderRadius", 0.0)
|
|
5435
5467
|
val sideBorderKeys = listOf("borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth")
|
|
@@ -5452,14 +5484,14 @@ class MainActivity : AppCompatActivity() {
|
|
|
5452
5484
|
if (background || border > 0 || radius > 0 || hasSideBorders) {
|
|
5453
5485
|
if (hasSideBorders) {
|
|
5454
5486
|
view.background = CraftNativeSideBorderDrawable(
|
|
5455
|
-
|
|
5487
|
+
backgroundColor,
|
|
5456
5488
|
cornerRadii,
|
|
5457
5489
|
sideWidths,
|
|
5458
5490
|
sideColors,
|
|
5459
5491
|
)
|
|
5460
5492
|
} else {
|
|
5461
5493
|
view.background = GradientDrawable().apply {
|
|
5462
|
-
setColor(
|
|
5494
|
+
setColor(backgroundColor)
|
|
5463
5495
|
if (hasCornerRadii) setCornerRadii(cornerRadii) else cornerRadius = dp(radius).toFloat()
|
|
5464
5496
|
if (border > 0) {
|
|
5465
5497
|
val borderColor = color(style.optString("borderColor"), Color.TRANSPARENT)
|
package/dist/cli.js
CHANGED
|
@@ -3424,7 +3424,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
3424
3424
|
`);
|
|
3425
3425
|
}
|
|
3426
3426
|
// package.json
|
|
3427
|
-
var version = "0.0.
|
|
3427
|
+
var version = "0.0.192";
|
|
3428
3428
|
|
|
3429
3429
|
// bin/cli.ts
|
|
3430
3430
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -614,7 +614,10 @@ final class CraftChrome: ObservableObject {
|
|
|
614
614
|
let id: String
|
|
615
615
|
let title: String
|
|
616
616
|
let symbol: String
|
|
617
|
-
|
|
617
|
+
var badge: String?
|
|
618
|
+
var iconText: String? = nil
|
|
619
|
+
var accessibilityLabel: String? = nil
|
|
620
|
+
var image: String? = nil
|
|
618
621
|
}
|
|
619
622
|
|
|
620
623
|
@Published var tabs: [Tab] = []
|
|
@@ -668,6 +671,23 @@ final class CraftChrome: ObservableObject {
|
|
|
668
671
|
/// the bar away while it shows (`tabBarHidden`, CraftNativeScreen.swift);
|
|
669
672
|
/// the next screen shown says again. The page's own hideTabBar is apart.
|
|
670
673
|
private(set) var hiddenByScreen = false
|
|
674
|
+
|
|
675
|
+
/// Change one tab's icon without replacing tabs, navigation stacks or selection.
|
|
676
|
+
func updateTab(_ item: [String: Any]) {
|
|
677
|
+
guard let id = item["id"] as? String, let index = tabs.firstIndex(where: { $0.id == id }) else { return }
|
|
678
|
+
if item.keys.contains("iconText") {
|
|
679
|
+
tabs[index].iconText = (item["iconText"] as? String).flatMap { $0.isEmpty ? nil : String($0.prefix(8)) }
|
|
680
|
+
}
|
|
681
|
+
if item.keys.contains("accessibilityLabel") {
|
|
682
|
+
tabs[index].accessibilityLabel = (item["accessibilityLabel"] as? String).map { String($0.prefix(160)) }
|
|
683
|
+
}
|
|
684
|
+
if item.keys.contains("image") {
|
|
685
|
+
tabs[index].image = (item["image"] as? String).flatMap { URL(string: $0)?.scheme == "https" ? $0 : nil }
|
|
686
|
+
}
|
|
687
|
+
if item.keys.contains("badge") {
|
|
688
|
+
tabs[index].badge = (item["badge"] as? String).flatMap { $0.isEmpty ? nil : String($0.prefix(8)) }
|
|
689
|
+
}
|
|
690
|
+
}
|
|
671
691
|
func setHiddenByScreen(_ hidden: Bool) {
|
|
672
692
|
guard hiddenByScreen != hidden else { return }
|
|
673
693
|
hiddenByScreen = hidden
|
|
@@ -941,9 +961,28 @@ struct CraftTabBarView: View {
|
|
|
941
961
|
Button { chrome.tap(tab) } label: {
|
|
942
962
|
VStack(spacing: 3) {
|
|
943
963
|
ZStack(alignment: .topTrailing) {
|
|
944
|
-
|
|
945
|
-
|
|
964
|
+
if let image = tab.image, let url = URL(string: image) {
|
|
965
|
+
AsyncImage(url: url) { image in
|
|
966
|
+
image.resizable().scaledToFill()
|
|
967
|
+
} placeholder: {
|
|
968
|
+
Image(systemName: symbol(tab.symbol, selected: isSelected))
|
|
969
|
+
}
|
|
970
|
+
.frame(width: iconSize + 4, height: iconSize + 4)
|
|
971
|
+
.clipShape(Circle())
|
|
946
972
|
.frame(height: iconSize + 6)
|
|
973
|
+
} else if let text = tab.iconText {
|
|
974
|
+
Text(text)
|
|
975
|
+
.font(.system(size: iconSize, weight: .semibold, design: .rounded))
|
|
976
|
+
.monospacedDigit()
|
|
977
|
+
.foregroundStyle(Color.red)
|
|
978
|
+
.lineLimit(1)
|
|
979
|
+
.minimumScaleFactor(0.7)
|
|
980
|
+
.frame(height: iconSize + 6)
|
|
981
|
+
} else {
|
|
982
|
+
Image(systemName: symbol(tab.symbol, selected: isSelected))
|
|
983
|
+
.font(.system(size: iconSize, weight: isSelected ? .semibold : .regular))
|
|
984
|
+
.frame(height: iconSize + 6)
|
|
985
|
+
}
|
|
947
986
|
if let badge = tab.badge {
|
|
948
987
|
Text(badge)
|
|
949
988
|
.font(.system(size: badgeSize, weight: .bold))
|
|
@@ -975,7 +1014,7 @@ struct CraftTabBarView: View {
|
|
|
975
1014
|
Image(systemName: symbol(tab.symbol, selected: isSelected))
|
|
976
1015
|
Text(tab.title)
|
|
977
1016
|
}
|
|
978
|
-
.accessibilityLabel(tab.badge.map { "\(tab.title), \($0)" } ?? tab.title)
|
|
1017
|
+
.accessibilityLabel(tab.accessibilityLabel ?? tab.badge.map { "\(tab.title), \($0)" } ?? tab.title)
|
|
979
1018
|
.accessibilityAddTraits(isSelected ? [.isSelected, .isButton] : .isButton)
|
|
980
1019
|
}
|
|
981
1020
|
}
|
|
@@ -5709,9 +5748,11 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
5709
5748
|
return
|
|
5710
5749
|
}
|
|
5711
5750
|
var items: [Any] = []
|
|
5712
|
-
|
|
5751
|
+
let title = options["title"] as? String ?? ""
|
|
5713
5752
|
if let text = options["text"] as? String, !text.isEmpty { items.append(text) }
|
|
5714
|
-
if let urlString = options["url"] as? String, let url = URL(string: urlString) {
|
|
5753
|
+
if let urlString = options["url"] as? String, let url = URL(string: urlString) {
|
|
5754
|
+
items.append(CraftShareLink(url: url, title: title))
|
|
5755
|
+
} else if !title.isEmpty { items.append(title) }
|
|
5715
5756
|
if let files = options["files"] as? [String] {
|
|
5716
5757
|
for file in files {
|
|
5717
5758
|
if let url = URL(string: file), url.isFileURL { items.append(url) }
|
|
@@ -4355,6 +4355,9 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
4355
4355
|
/// `craft.navigation.setOptions`: this screen's bar. Every key is
|
|
4356
4356
|
/// optional and only the keys given change.
|
|
4357
4357
|
private func applyNavigationOptions(_ options: [String: Any]) {
|
|
4358
|
+
if let tab = options["tabItem"] as? [String: Any] {
|
|
4359
|
+
CraftChrome.shared.updateTab(tab)
|
|
4360
|
+
}
|
|
4358
4361
|
// The app script has no screen, so no bar and no tab bar to change.
|
|
4359
4362
|
guard !isAppScript else { return }
|
|
4360
4363
|
if options.keys.contains("title") {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import AVFoundation
|
|
2
2
|
import Foundation
|
|
3
|
+
import LinkPresentation
|
|
3
4
|
import UIKit
|
|
4
5
|
import UserNotifications
|
|
5
6
|
|
|
@@ -687,6 +688,32 @@ enum CraftNativePush {
|
|
|
687
688
|
|
|
688
689
|
// MARK: - Share sheet
|
|
689
690
|
|
|
691
|
+
/// A URL stays a URL in Messages, AirDrop and Copy. Its title belongs in the
|
|
692
|
+
/// system preview and mail subject, rather than becoming a second text item.
|
|
693
|
+
final class CraftShareLink: NSObject, UIActivityItemSource {
|
|
694
|
+
let url: URL
|
|
695
|
+
let title: String
|
|
696
|
+
|
|
697
|
+
init(url: URL, title: String) {
|
|
698
|
+
self.url = url
|
|
699
|
+
self.title = title
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { url }
|
|
703
|
+
|
|
704
|
+
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { url }
|
|
705
|
+
|
|
706
|
+
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { title }
|
|
707
|
+
|
|
708
|
+
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
|
|
709
|
+
let metadata = LPLinkMetadata()
|
|
710
|
+
metadata.originalURL = url
|
|
711
|
+
metadata.url = url
|
|
712
|
+
if !title.isEmpty { metadata.title = title }
|
|
713
|
+
return metadata
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
690
717
|
/// `craft.share` on a native screen: the system share sheet with the page's
|
|
691
718
|
/// options (`title`, `text`, `url`, `files`), plus `image`, a picture by URL
|
|
692
719
|
/// that is shared as the picture rather than as its address. Answers true
|
|
@@ -694,9 +721,11 @@ enum CraftNativePush {
|
|
|
694
721
|
enum CraftNativeShare {
|
|
695
722
|
static func share(_ options: [String: Any], done: @escaping (Result<Any, CraftNativeActionError>) -> Void) {
|
|
696
723
|
var items: [Any] = []
|
|
697
|
-
|
|
724
|
+
let title = options["title"] as? String ?? ""
|
|
698
725
|
if let text = options["text"] as? String, !text.isEmpty { items.append(text) }
|
|
699
|
-
if let address = options["url"] as? String, let url = URL(string: address) {
|
|
726
|
+
if let address = options["url"] as? String, let url = URL(string: address) {
|
|
727
|
+
items.append(CraftShareLink(url: url, title: title))
|
|
728
|
+
} else if !title.isEmpty { items.append(title) }
|
|
700
729
|
// A path or file URL, or a file craft.files made (`{ path, name }`).
|
|
701
730
|
let files = (options["files"] as? [Any] ?? []).compactMap { item -> String? in
|
|
702
731
|
item as? String ?? (item as? [String: Any])?["path"] as? String
|
|
@@ -715,7 +744,7 @@ enum CraftNativeShare {
|
|
|
715
744
|
if let error { done(.failure(CraftNativeActionError(code: "SHARE_ERROR", message: error.localizedDescription))) }
|
|
716
745
|
else { done(.success(completed)) }
|
|
717
746
|
}
|
|
718
|
-
if !CraftPresenter.present(sheet, from: nil, anchor:
|
|
747
|
+
if !CraftPresenter.present(sheet, from: nil, anchor: CraftPresenter.rect(options["anchor"])) {
|
|
719
748
|
done(.failure(CraftNativeActionError(code: "SHARE_ERROR", message: "Unable to present the share sheet")))
|
|
720
749
|
}
|
|
721
750
|
}
|