@craft-native/ios 0.0.207 → 0.0.209

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/ios",
3
- "version": "0.0.207",
3
+ "version": "0.0.209",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/ios#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -621,6 +621,7 @@ final class CraftChrome: ObservableObject {
621
621
  }
622
622
 
623
623
  @Published var tabs: [Tab] = []
624
+ @Published var tabAccessory: Tab?
624
625
  @Published var selected: String?
625
626
  @Published var tabBarVisible = false
626
627
  @Published var splashVisible = true
@@ -688,6 +689,16 @@ final class CraftChrome: ObservableObject {
688
689
  tabs[index].badge = (item["badge"] as? String).flatMap { $0.isEmpty ? nil : String($0.prefix(8)) }
689
690
  }
690
691
  }
692
+ /// A destination beside the tab pill, outside the tab stacks and swipe order.
693
+ func updateTabAccessory(_ item: [String: Any]?) {
694
+ guard let item, let path = item["path"] as? String,
695
+ path.hasPrefix("/"), !path.hasPrefix("//"),
696
+ let title = item["title"] as? String else { tabAccessory = nil; return }
697
+ tabAccessory = Tab(id: path, title: title, symbol: item["symbol"] as? String ?? "message",
698
+ badge: (item["badge"] as? String).flatMap { $0.isEmpty ? nil : String($0.prefix(8)) },
699
+ accessibilityLabel: item["accessibilityLabel"] as? String)
700
+ }
701
+
691
702
  func setHiddenByScreen(_ hidden: Bool) {
692
703
  guard hiddenByScreen != hidden else { return }
693
704
  hiddenByScreen = hidden
@@ -913,7 +924,31 @@ struct CraftTabBarView: View {
913
924
 
914
925
  var body: some View {
915
926
  if chrome.tabBarVisible && !chrome.tabs.isEmpty {
916
- bar
927
+ HStack(spacing: 8) {
928
+ bar
929
+ if let accessory = chrome.tabAccessory {
930
+ Button { CraftHybrid.shared.open(accessory.id) } label: {
931
+ Image(systemName: accessory.symbol)
932
+ .font(.system(size: iconSize))
933
+ .frame(width: 62, height: 62)
934
+ .background(.ultraThinMaterial, in: Circle())
935
+ .overlay(alignment: .topTrailing) {
936
+ if let badge = accessory.badge {
937
+ Text(badge)
938
+ .font(.system(size: badgeSize, weight: .bold))
939
+ .foregroundStyle(.white)
940
+ .padding(.horizontal, 4)
941
+ .frame(minWidth: 18, minHeight: 18)
942
+ .background(Capsule().fill(Color.red))
943
+ }
944
+ }
945
+ }
946
+ .buttonStyle(.plain)
947
+ .accessibilityLabel(accessory.accessibilityLabel ?? accessory.title)
948
+ .accessibilityValue(accessory.badge ?? "")
949
+ .accessibilityIdentifier("craft-tab-accessory")
950
+ }
951
+ }
917
952
  .padding(.horizontal, 16)
918
953
  .padding(.bottom, 4)
919
954
  .background(GeometryReader { proxy in
@@ -3959,11 +3959,16 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
3959
3959
  // Each screen says whether it wants the tab bar, so one that hid it
3960
3960
  // never leaves it hidden for the next.
3961
3961
  CraftChrome.shared.setHiddenByScreen(tabBarHidden)
3962
+ CraftChrome.shared.updateTabAccessory(tabAccessory)
3962
3963
  refreshTraitDefaults()
3963
3964
  }
3964
3965
 
3965
3966
  override func viewWillDisappear(_ animated: Bool) {
3966
3967
  super.viewWillDisappear(animated)
3968
+ // The accessory belongs to the visible native screen. Clear the
3969
+ // shared shell state before a hybrid/web route appears, otherwise a
3970
+ // destination from the outgoing screen remains tappable on top of it.
3971
+ CraftChrome.shared.updateTabAccessory(nil)
3967
3972
  // Popped (or its stack dismissed) while the bar was away: it comes
3968
3973
  // back for whatever shows next, a web page included.
3969
3974
  if tabBarHidden, isMovingFromParent || isBeingDismissed || navigationController?.isBeingDismissed == true {
@@ -4529,6 +4534,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
4529
4534
  private var statusBarHiddenOverride: Bool?
4530
4535
  /// The app's tab bar away while this screen shows (`tabBarHidden`).
4531
4536
  private var tabBarHidden = false
4537
+ private var tabAccessory: [String: Any]?
4532
4538
  /// Whether a back-swipe may leave this screen at all, the edge's
4533
4539
  /// included (`swipeBack: false`): a screen that steps through its own
4534
4540
  /// pages with a Back of its own (a first-run tour's questions) would
@@ -4575,6 +4581,12 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
4575
4581
  }
4576
4582
  // The app script has no screen, so no bar and no tab bar to change.
4577
4583
  guard !isAppScript else { return }
4584
+ if options.keys.contains("tabAccessory") {
4585
+ tabAccessory = options["tabAccessory"] as? [String: Any]
4586
+ if navigationController?.topViewController === self, view.window != nil {
4587
+ CraftChrome.shared.updateTabAccessory(tabAccessory)
4588
+ }
4589
+ }
4578
4590
  if options.keys.contains("title") {
4579
4591
  navigationItem.title = options["title"] as? String
4580
4592
  titleFromOptions = true
@@ -6939,8 +6951,15 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
6939
6951
  case "900": weight = .black
6940
6952
  default: weight = .regular
6941
6953
  }
6942
- var font = (style["fontFamily"] as? String).flatMap { UIFont(name: $0, size: size) }
6943
- ?? .systemFont(ofSize: size, weight: weight)
6954
+ // Android's TextView uses SP, so its native text scales with the
6955
+ // user's content-size setting. Apply the same Dynamic Type multiplier
6956
+ // here before assigning the font to labels, buttons, and inputs.
6957
+ let scaledSize = UIFontMetrics(forTextStyle: .body).scaledValue(
6958
+ for: size,
6959
+ compatibleWith: traitCollection
6960
+ )
6961
+ var font = (style["fontFamily"] as? String).flatMap { UIFont(name: $0, size: scaledSize) }
6962
+ ?? .systemFont(ofSize: scaledSize, weight: weight)
6944
6963
  if style["fontStyle"] as? String == "italic",
6945
6964
  let descriptor = font.fontDescriptor.withSymbolicTraits(.traitItalic) {
6946
6965
  font = UIFont(descriptor: descriptor, size: size)