AbstractAppBarOnDestinationChangedListener.kt

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.navigation.ui

import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
import androidx.navigation.FloatingWindow
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import java.lang.ref.WeakReference

/**
 * The abstract OnDestinationChangedListener for keeping any type of app bar updated.
 * This handles both updating the title and updating the Up Indicator, transitioning between
 * the drawer icon and up arrow as needed.
 */
internal abstract class AbstractAppBarOnDestinationChangedListener(
    private val context: Context,
    private val configuration: AppBarConfiguration
) : NavController.OnDestinationChangedListener {
    private val openableLayoutWeakReference = configuration.openableLayout?.run {
        WeakReference(this)
    }
    private var arrowDrawable: DrawerArrowDrawable? = null
    private var animator: ValueAnimator? = null

    protected abstract fun setTitle(title: CharSequence?)

    protected abstract fun setNavigationIcon(icon: Drawable?, @StringRes contentDescription: Int)

    override fun onDestinationChanged(
        controller: NavController,
        destination: NavDestination,
        arguments: Bundle?
    ) {
        if (destination is FloatingWindow) {
            return
        }
        val openableLayout = openableLayoutWeakReference?.get()
        if (openableLayoutWeakReference != null && openableLayout == null) {
            controller.removeOnDestinationChangedListener(this)
            return
        }

        val label = destination.fillInLabel(context, arguments)
        if (label != null) {
            setTitle(label)
        }

        val isTopLevelDestination = configuration.isTopLevelDestination(destination)
        if (openableLayout == null && isTopLevelDestination) {
            setNavigationIcon(null, 0)
        } else {
            setActionBarUpIndicator(openableLayout != null && isTopLevelDestination)
        }
    }

    @SuppressLint("ObjectAnimatorBinding")
    private fun setActionBarUpIndicator(showAsDrawerIndicator: Boolean) {
        val (arrow, animate) = arrowDrawable?.run {
            this to true
        } ?: DrawerArrowDrawable(context).also { arrowDrawable = it } to false

        setNavigationIcon(
            arrow,
            if (showAsDrawerIndicator) R.string.nav_app_bar_open_drawer_description
            else R.string.nav_app_bar_navigate_up_description
        )

        val endValue = if (showAsDrawerIndicator) 0f else 1f
        if (animate) {
            val startValue = arrow.progress
            animator?.cancel()
            animator = ObjectAnimator.ofFloat(arrow, "progress", startValue, endValue)
            (animator as ObjectAnimator).start()
        } else {
            arrow.progress = endValue
        }
    }
}