also looking at this
feat: finish native customization and platform seams
#401
21 files
+707
-17
| @@ -86,6 +86,11 @@ internal data class CraftNativeTab( | ||
| 86 | 86 | val selectedSymbol: String? = null, val selectedIconTint: String? = null, val selectedIconPulse: Boolean? = null, |
| 87 | 87 | ) |
| 88 | 88 | |
| 89 | internal data class CraftNativeTabBarAppearance( | |
| 90 | val selectionColor: String? = null, | |
| 91 | val selectionCornerRadius: Float? = null, | |
| 92 | ) | |
| 93 | ||
| 89 | 94 | internal data class CraftTabIconAppearance(val symbol: String, val tint: Int?, val pulse: Boolean) |
| 90 | 95 | |
| 91 | 96 | /** The app's paths (`nativeScreens`), its tabs and its site. */ |
| @@ -122,6 +127,16 @@ internal class CraftNativeRoutes(config: JSONObject) { | ||
| 122 | 127 | } |
| 123 | 128 | } |
| 124 | 129 | |
| 130 | val tabBarAppearance: CraftNativeTabBarAppearance = config.optJSONObject("tabBar") | |
| 131 | ?.optJSONObject("selection") | |
| 132 | ?.let { selection -> | |
| 133 | CraftNativeTabBarAppearance( | |
| 134 | selection.optString("color").takeIf { it.isNotBlank() }, | |
| 135 | (selection.opt("cornerRadius") as? Number)?.toFloat()?.takeIf { it.isFinite() && it >= 0 }, | |
| 136 | ) | |
| 137 | } | |
| 138 | ?: CraftNativeTabBarAppearance() | |
| 139 | ||
| 125 | 140 | /** The app's site (`devServerURL`): its origin answers paths no screen draws. */ |
| 126 | 141 | val site: Uri? = config.optString("devServerURL").takeIf { it.isNotBlank() }?.let(Uri::parse) |
| 127 | 142 | val siteOrigin: String? = site?.let { uri -> |
| @@ -755,7 +770,11 @@ internal object CraftNativePrelude { | ||
| 755 | 770 | * `craft.navigation.setOptions` (title, largeTitle, hidden, rightButtons, |
| 756 | 771 | * tabAccessory, tabBarHidden); the tabs come from the app's `tabs`. |
| 757 | 772 | */ |
| 758 | internal class CraftNativeChrome(context: Context, private val tabs: List<CraftNativeTab>) : LinearLayout(context) { | |
| 773 | internal class CraftNativeChrome( | |
| 774 | context: Context, | |
| 775 | private val tabs: List<CraftNativeTab>, | |
| 776 | private val tabBarAppearance: CraftNativeTabBarAppearance = CraftNativeTabBarAppearance(), | |
| 777 | ) : LinearLayout(context) { | |
| 759 | 778 | val content = FrameLayout(context) |
| 760 | 779 | private val bar = LinearLayout(context) |
| 761 | 780 | private val backButton = ImageView(context) |
| @@ -1099,6 +1118,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN | ||
| 1099 | 1118 | accessorySpec?.let { accessoryIcon.setImageDrawable(CraftNativeIcons.drawable(context, null, it.optString("symbol"), text)) } |
| 1100 | 1119 | tabViews.forEachIndexed { index, (icon, label) -> |
| 1101 | 1120 | tabBar.getChildAt(index).isSelected = index == selectedTab |
| 1121 | tabBar.getChildAt(index).background = tabCellBackground(index, dark) | |
| 1102 | 1122 | val color = if (index == selectedTab) accent else muted |
| 1103 | 1123 | val id = tabs[index].id |
| 1104 | 1124 | val number = tabIconText[id] |
| @@ -1188,6 +1208,17 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN | ||
| 1188 | 1208 | |
| 1189 | 1209 | private fun accentColor(dark: Boolean): Int = if (dark) Color.parseColor("#0A84FF") else Color.parseColor("#007AFF") |
| 1190 | 1210 | |
| 1211 | private fun tabCellBackground(index: Int, dark: Boolean): android.graphics.drawable.Drawable? { | |
| 1212 | if (index != selectedTab || (tabBarAppearance.selectionColor == null && tabBarAppearance.selectionCornerRadius == null)) return rippleBackground() | |
| 1213 | val color = tabBarAppearance.selectionColor?.let { runCatching { Color.parseColor(it) }.getOrNull() } | |
| 1214 | ?: if (dark) Color.argb(42, 255, 255, 255) else Color.argb(23, 0, 0, 0) | |
| 1215 | val radius = tabBarAppearance.selectionCornerRadius?.let { (it * density).toInt().toFloat() } ?: dp(32).toFloat() | |
| 1216 | return GradientDrawable().apply { | |
| 1217 | setColor(color) | |
| 1218 | cornerRadius = radius | |
| 1219 | } | |
| 1220 | } | |
| 1221 | ||
| 1191 | 1222 | private fun rippleBackground(): android.graphics.drawable.Drawable? { |
| 1192 | 1223 | val value = TypedValue() |
| 1193 | 1224 | return if (context.theme.resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, value, true)) { |