package com.swmansion.rnscreens.gamma.tabs import android.content.res.Configuration import android.graphics.drawable.Drawable import android.view.ViewGroup import androidx.fragment.app.Fragment import com.facebook.react.uimanager.ThemedReactContext import com.swmansion.rnscreens.gamma.common.FragmentProviding import com.swmansion.rnscreens.gamma.helpers.getSystemDrawableResource import com.swmansion.rnscreens.utils.RNSLog import java.lang.ref.WeakReference import kotlin.properties.Delegates /** * React Component view. */ class TabsScreen( val reactContext: ThemedReactContext, ) : ViewGroup(reactContext), FragmentProviding { override fun onLayout( changed: Boolean, l: Int, t: Int, r: Int, b: Int, ) = Unit private var tabsScreenDelegate: WeakReference = WeakReference(null) internal lateinit var eventEmitter: TabsScreenEventEmitter var tabKey: String? = null set(value) { field = if (value?.isBlank() == true) { null } else { value } } var tabTitle: String? by Delegates.observable(null) { _, oldValue, newValue -> updateMenuItemAttributesIfNeeded(oldValue, newValue) } // Badge var badgeValue: String? by Delegates.observable(null) { _, oldValue, newValue -> updateMenuItemAttributesIfNeeded(oldValue, newValue) } var tabBarItemBadgeTextColor: Int? by Delegates.observable(null) { _, oldValue, newValue -> updateMenuItemAttributesIfNeeded(oldValue, newValue) } var tabBarItemBadgeBackgroundColor: Int? by Delegates.observable(null) { _, oldValue, newValue -> updateMenuItemAttributesIfNeeded(oldValue, newValue) } // Accessibility var tabBarItemTestID: String? by Delegates.observable(null) { _, oldValue, newValue -> updateMenuItemAttributesIfNeeded(oldValue, newValue) } var tabBarItemAccessibilityLabel: String? by Delegates.observable(null) { _, oldValue, newValue -> updateMenuItemAttributesIfNeeded(oldValue, newValue) } // Icon var drawableIconResourceName: String? by Delegates.observable(null) { _, oldValue, newValue -> if (newValue != oldValue) { icon = getSystemDrawableResource(reactContext, newValue) } } var icon: Drawable? by Delegates.observable(null) { _, oldValue, newValue -> updateMenuItemAttributesIfNeeded(oldValue, newValue) } var shouldUseRepeatedTabSelectionScrollToTopSpecialEffect: Boolean = true var shouldUseRepeatedTabSelectionPopToRootSpecialEffect: Boolean = true private fun updateMenuItemAttributesIfNeeded( oldValue: T, newValue: T, ) { if (newValue != oldValue) { onMenuItemAttributesChange() } } override fun onAttachedToWindow() { RNSLog.d(TAG, "TabsScreen [$id] attached to window") super.onAttachedToWindow() } var isFocusedTab: Boolean = false set(value) { if (field != value) { field = value onTabFocusChangedFromJS() } } internal fun setTabsScreenDelegate(delegate: TabsScreenDelegate?) { tabsScreenDelegate = WeakReference(delegate) } override fun getAssociatedFragment(): Fragment? = tabsScreenDelegate.get()?.getFragmentForTabsScreen(this) private fun onTabFocusChangedFromJS() { tabsScreenDelegate.get()?.onTabFocusChangedFromJS(this, isFocusedTab) } private fun onMenuItemAttributesChange() { tabsScreenDelegate.get()?.onMenuItemAttributesChange(this) } internal fun onViewManagerAddEventEmitters() { // When this is called from View Manager the view tag is already set check(id != NO_ID) { "[RNScreens] TabsScreen must have its tag set when registering event emitters" } eventEmitter = TabsScreenEventEmitter(reactContext, id) } /** * Notify the view that it's associated fragment got its config updated. * * There are cases where the fragment will receive configuration change, but it's view will not, * e.g. theme update from JS via Appearance.setColorScheme. */ internal fun onFragmentConfigurationChange( fragment: TabsScreenFragment, config: Configuration, ) { tabsScreenDelegate.get()?.onFragmentConfigurationChange(this, config) } companion object { const val TAG = "TabsScreen" } }