craft-native 0.0.226 → 0.0.227
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.
|
@@ -643,6 +643,10 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
643
643
|
private var accessorySpec: JSONObject? = null
|
|
644
644
|
private val tabViews = mutableListOf<Pair<TextView, TextView>>()
|
|
645
645
|
private val tabIconText = mutableMapOf<String, String>()
|
|
646
|
+
private val tabTitles = mutableMapOf<String, String>()
|
|
647
|
+
private val tabAccessibilityLabels = mutableMapOf<String, String>()
|
|
648
|
+
private val tabIconTints = mutableMapOf<String, Int>()
|
|
649
|
+
private val tabIconPulses = mutableSetOf<String>()
|
|
646
650
|
private val tabImageURLs = mutableMapOf<String, String>()
|
|
647
651
|
private val tabImages = mutableMapOf<String, android.graphics.drawable.Drawable>()
|
|
648
652
|
private val navButtonSpecs = mutableMapOf<TextView, JSONObject>()
|
|
@@ -706,6 +710,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
706
710
|
cell.gravity = Gravity.CENTER
|
|
707
711
|
cell.background = rippleBackground()
|
|
708
712
|
cell.contentDescription = tab.title
|
|
713
|
+
cell.tag = "craft-tab-${tab.id}"
|
|
709
714
|
val icon = TextView(context)
|
|
710
715
|
icon.text = CraftNativeSymbols.glyph(tab.symbol)
|
|
711
716
|
icon.textSize = 22f
|
|
@@ -716,6 +721,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
716
721
|
val label = TextView(context)
|
|
717
722
|
label.text = tab.title
|
|
718
723
|
label.textSize = 12f
|
|
724
|
+
label.fontFeatureSettings = "tnum"
|
|
719
725
|
label.gravity = Gravity.CENTER
|
|
720
726
|
label.isSingleLine = true
|
|
721
727
|
cell.addView(icon, LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
|
|
@@ -844,12 +850,25 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
844
850
|
val id = item.optString("id")
|
|
845
851
|
val index = tabs.indexOfFirst { it.id == id }
|
|
846
852
|
if (index < 0) return
|
|
847
|
-
|
|
848
|
-
|
|
853
|
+
if (item.has("accessibilityLabel")) {
|
|
854
|
+
tabAccessibilityLabels.remove(id)
|
|
855
|
+
item.optString("accessibilityLabel").takeUnless { item.isNull("accessibilityLabel") || it.isBlank() }?.let { tabAccessibilityLabels[id] = it.take(160) }
|
|
856
|
+
}
|
|
849
857
|
if (item.has("iconText")) {
|
|
850
858
|
tabIconText.remove(id)
|
|
851
859
|
item.optString("iconText").takeUnless { item.isNull("iconText") || it.isBlank() }?.let { tabIconText[id] = it.take(8) }
|
|
852
860
|
}
|
|
861
|
+
if (item.has("title")) {
|
|
862
|
+
tabTitles.remove(id)
|
|
863
|
+
item.optString("title").takeUnless { item.isNull("title") || it.isBlank() }?.let { tabTitles[id] = it.take(40) }
|
|
864
|
+
}
|
|
865
|
+
if (item.has("iconTint")) {
|
|
866
|
+
tabIconTints.remove(id)
|
|
867
|
+
if (!item.isNull("iconTint")) runCatching { Color.parseColor(item.optString("iconTint")) }.getOrNull()?.let { tabIconTints[id] = it }
|
|
868
|
+
}
|
|
869
|
+
if (item.has("iconPulse")) {
|
|
870
|
+
if (item.optBoolean("iconPulse", false)) tabIconPulses.add(id) else tabIconPulses.remove(id)
|
|
871
|
+
}
|
|
853
872
|
if (item.has("image")) {
|
|
854
873
|
val address = item.optString("image").takeIf { !item.isNull("image") && it.startsWith("https://") }
|
|
855
874
|
if (address != tabImageURLs[id]) {
|
|
@@ -896,16 +915,41 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
|
|
|
896
915
|
tabViews.forEachIndexed { index, (icon, label) ->
|
|
897
916
|
tabBar.getChildAt(index).isSelected = index == selectedTab
|
|
898
917
|
val color = if (index == selectedTab) accent else muted
|
|
899
|
-
val
|
|
900
|
-
|
|
918
|
+
val id = tabs[index].id
|
|
919
|
+
val number = tabIconText[id]
|
|
920
|
+
val iconColor = tabIconTints[id] ?: if (number != null) Color.parseColor("#FF2D55") else color
|
|
921
|
+
icon.setTextColor(iconColor)
|
|
922
|
+
label.text = tabTitles[id] ?: tabs[index].title
|
|
923
|
+
tabBar.getChildAt(index).contentDescription = tabAccessibilityLabels[id] ?: label.text
|
|
901
924
|
label.setTextColor(color)
|
|
902
|
-
val vector = tabImages[
|
|
925
|
+
val vector = tabImages[id] ?: if (number == null) CraftNativeIcons.drawable(context, null, tabs[index].symbol, iconColor) else null
|
|
926
|
+
val pulse = tabIconPulses.contains(id) && number == null && tabImages[id] == null && hasWindowFocus() && android.animation.ValueAnimator.areAnimatorsEnabled()
|
|
927
|
+
if (pulse && icon.animation == null) {
|
|
928
|
+
icon.startAnimation(android.view.animation.AlphaAnimation(1f, 0.55f).apply {
|
|
929
|
+
duration = 700
|
|
930
|
+
repeatCount = android.view.animation.Animation.INFINITE
|
|
931
|
+
repeatMode = android.view.animation.Animation.REVERSE
|
|
932
|
+
})
|
|
933
|
+
} else if (!pulse) {
|
|
934
|
+
icon.clearAnimation()
|
|
935
|
+
icon.alpha = 1f
|
|
936
|
+
}
|
|
903
937
|
icon.foreground = vector
|
|
904
938
|
icon.text = if (vector != null) "" else number ?: CraftNativeSymbols.glyph(tabs[index].symbol)
|
|
905
939
|
}
|
|
906
940
|
navButtonSpecs.forEach { (view, spec) -> applyNavButtonTint(view, spec, dark) }
|
|
907
941
|
}
|
|
908
942
|
|
|
943
|
+
override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
|
|
944
|
+
super.onWindowFocusChanged(hasWindowFocus)
|
|
945
|
+
if (hasWindowFocus) refreshColors() else tabViews.forEach { (icon, _) -> icon.clearAnimation() }
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
override fun onDetachedFromWindow() {
|
|
949
|
+
tabViews.forEach { (icon, _) -> icon.clearAnimation() }
|
|
950
|
+
super.onDetachedFromWindow()
|
|
951
|
+
}
|
|
952
|
+
|
|
909
953
|
private fun navButton(button: JSONObject): View {
|
|
910
954
|
val id = button.optString("id")
|
|
911
955
|
val title = button.optString("title")
|
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.227";
|
|
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
|
@@ -616,6 +616,10 @@ final class CraftChrome: ObservableObject {
|
|
|
616
616
|
let symbol: String
|
|
617
617
|
var badge: String?
|
|
618
618
|
var iconText: String? = nil
|
|
619
|
+
var titleOverride: String? = nil
|
|
620
|
+
var iconTint: String? = nil
|
|
621
|
+
var iconPulse = false
|
|
622
|
+
var displayTitle: String { titleOverride ?? title }
|
|
619
623
|
var accessibilityLabel: String? = nil
|
|
620
624
|
var image: String? = nil
|
|
621
625
|
}
|
|
@@ -673,12 +677,21 @@ final class CraftChrome: ObservableObject {
|
|
|
673
677
|
/// the next screen shown says again. The page's own hideTabBar is apart.
|
|
674
678
|
private(set) var hiddenByScreen = false
|
|
675
679
|
|
|
676
|
-
/// Change one tab's icon without replacing
|
|
680
|
+
/// Change one tab's caption or icon without replacing stacks or selection.
|
|
677
681
|
func updateTab(_ item: [String: Any]) {
|
|
678
682
|
guard let id = item["id"] as? String, let index = tabs.firstIndex(where: { $0.id == id }) else { return }
|
|
679
683
|
if item.keys.contains("iconText") {
|
|
680
684
|
tabs[index].iconText = (item["iconText"] as? String).flatMap { $0.isEmpty ? nil : String($0.prefix(8)) }
|
|
681
685
|
}
|
|
686
|
+
if item.keys.contains("title") {
|
|
687
|
+
tabs[index].titleOverride = (item["title"] as? String).flatMap { $0.isEmpty ? nil : String($0.prefix(40)) }
|
|
688
|
+
}
|
|
689
|
+
if item.keys.contains("iconTint") {
|
|
690
|
+
tabs[index].iconTint = (item["iconTint"] as? String).flatMap { UIColor(hex: $0) == nil ? nil : $0 }
|
|
691
|
+
}
|
|
692
|
+
if item.keys.contains("iconPulse") {
|
|
693
|
+
tabs[index].iconPulse = item["iconPulse"] as? Bool ?? false
|
|
694
|
+
}
|
|
682
695
|
if item.keys.contains("accessibilityLabel") {
|
|
683
696
|
tabs[index].accessibilityLabel = (item["accessibilityLabel"] as? String).map { String($0.prefix(160)) }
|
|
684
697
|
}
|
|
@@ -923,6 +936,7 @@ struct CraftTabBarView: View {
|
|
|
923
936
|
@ObservedObject private var chrome = CraftChrome.shared
|
|
924
937
|
@Namespace private var selection
|
|
925
938
|
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
|
939
|
+
@Environment(\.scenePhase) private var scenePhase
|
|
926
940
|
// Dynamic Type, relative to the styles UIKit's own bar uses. The bar is
|
|
927
941
|
// capped at the largest standard size (below), as UITabBar is; past that
|
|
928
942
|
// a long press shows the large content viewer instead.
|
|
@@ -1030,9 +1044,7 @@ struct CraftTabBarView: View {
|
|
|
1030
1044
|
.minimumScaleFactor(0.7)
|
|
1031
1045
|
.frame(height: iconSize + 6)
|
|
1032
1046
|
} else {
|
|
1033
|
-
|
|
1034
|
-
.font(.system(size: iconSize, weight: isSelected ? .semibold : .regular))
|
|
1035
|
-
.frame(height: iconSize + 6)
|
|
1047
|
+
tabSymbol(tab, selected: isSelected)
|
|
1036
1048
|
}
|
|
1037
1049
|
if let badge = tab.badge {
|
|
1038
1050
|
Text(badge)
|
|
@@ -1044,8 +1056,9 @@ struct CraftTabBarView: View {
|
|
|
1044
1056
|
.offset(x: 10, y: -4)
|
|
1045
1057
|
}
|
|
1046
1058
|
}
|
|
1047
|
-
Text(tab.
|
|
1059
|
+
Text(tab.displayTitle)
|
|
1048
1060
|
.font(.system(size: labelSize, weight: isSelected ? .semibold : .medium))
|
|
1061
|
+
.monospacedDigit()
|
|
1049
1062
|
.lineLimit(1)
|
|
1050
1063
|
.minimumScaleFactor(0.8)
|
|
1051
1064
|
}
|
|
@@ -1063,9 +1076,10 @@ struct CraftTabBarView: View {
|
|
|
1063
1076
|
.buttonStyle(.plain)
|
|
1064
1077
|
.accessibilityShowsLargeContentViewer {
|
|
1065
1078
|
Image(systemName: symbol(tab.symbol, selected: isSelected))
|
|
1066
|
-
Text(tab.
|
|
1079
|
+
Text(tab.displayTitle)
|
|
1067
1080
|
}
|
|
1068
|
-
.
|
|
1081
|
+
.accessibilityIdentifier("craft-tab-\(tab.id)")
|
|
1082
|
+
.accessibilityLabel(tab.accessibilityLabel ?? tab.badge.map { "\(tab.displayTitle), \($0)" } ?? tab.displayTitle)
|
|
1069
1083
|
.accessibilityAddTraits(isSelected ? [.isSelected, .isButton] : .isButton)
|
|
1070
1084
|
}
|
|
1071
1085
|
}
|
|
@@ -1075,6 +1089,20 @@ struct CraftTabBarView: View {
|
|
|
1075
1089
|
chrome.tint.map { Color($0) } ?? Color.primary
|
|
1076
1090
|
}
|
|
1077
1091
|
|
|
1092
|
+
/// Live styling belongs to the icon, leaving selection and the caption clear.
|
|
1093
|
+
@ViewBuilder private func tabSymbol(_ tab: CraftChrome.Tab, selected: Bool) -> some View {
|
|
1094
|
+
let color = tab.iconTint.flatMap { UIColor(hex: $0) }.map { Color($0) } ?? (selected ? selectedColor : Color.primary)
|
|
1095
|
+
let icon = Image(systemName: symbol(tab.symbol, selected: selected))
|
|
1096
|
+
.font(.system(size: iconSize, weight: selected ? .semibold : .regular))
|
|
1097
|
+
.foregroundStyle(color)
|
|
1098
|
+
.frame(height: iconSize + 6)
|
|
1099
|
+
if #available(iOS 17.0, *) {
|
|
1100
|
+
icon.symbolEffect(.pulse, options: .repeating, isActive: tab.iconPulse && !reduceMotion && scenePhase == .active)
|
|
1101
|
+
} else {
|
|
1102
|
+
icon
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1078
1106
|
/// The filled variant for the current tab, when the symbol has one.
|
|
1079
1107
|
private func symbol(_ name: String, selected: Bool) -> String {
|
|
1080
1108
|
guard selected, !name.hasSuffix(".fill"), UIImage(systemName: "\(name).fill") != nil else { return name }
|