10ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam/*
20ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Copyright (C) 2017 The Android Open Source Project
30ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam *
40ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Licensed under the Apache License, Version 2.0 (the "License");
50ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * you may not use this file except in compliance with the License.
60ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * You may obtain a copy of the License at
70ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam *
80ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam *      http://www.apache.org/licenses/LICENSE-2.0
90ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam *
100ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Unless required by applicable law or agreed to in writing, software
110ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * distributed under the License is distributed on an "AS IS" BASIS,
120ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * See the License for the specific language governing permissions and
140ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * limitations under the License.
150ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */
160ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
170ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lampackage com.android.setupwizardlib.template;
180ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
190ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.os.Handler;
200ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.os.Looper;
210ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.support.annotation.NonNull;
220ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.support.annotation.Nullable;
230ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.support.annotation.StringRes;
240ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.view.View;
250ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.view.View.OnClickListener;
260ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.widget.Button;
270ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
280ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport com.android.setupwizardlib.TemplateLayout;
290ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport com.android.setupwizardlib.view.NavigationBar;
300ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
310ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam/**
320ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * A mixin to require the a scrollable container (BottomScrollView, RecyclerView or ListView) to
330ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * be scrolled to bottom, making sure that the user sees all content above and below the fold.
340ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */
350ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lampublic class RequireScrollMixin implements Mixin {
360ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
370ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /* static section */
380ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
390ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
400ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * Listener for when the require-scroll state changes. Note that this only requires the user to
410ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to
420ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * bottom is not required again.
430ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
440ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public interface OnRequireScrollStateChangedListener {
450ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
460ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        /**
470ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam         * Called when require-scroll state changed.
480ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam         *
490ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam         * @param scrollNeeded True if the user should be required to scroll to bottom.
500ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam         */
510ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        void onRequireScrollStateChanged(boolean scrollNeeded);
520ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
530ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
540ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
550ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * A delegate to detect scrollability changes and to scroll the page. This provides a layer
560ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * of abstraction for BottomScrollView, RecyclerView and ListView. The delegate should call
570ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * {@link #notifyScrollabilityChange(boolean)} when the view scrollability is changed.
580ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
590ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    interface ScrollHandlingDelegate {
600ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
610ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        /**
620ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam         * Starts listening to scrollability changes at the target scrollable container.
630ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam         */
640ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        void startListening();
650ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
660ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        /**
670ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam         * Scroll the page content down by one page.
680ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam         */
690ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        void pageScrollDown();
700ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
710ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
720ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /* non-static section */
730ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
740ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    @NonNull
750ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    private final TemplateLayout mTemplateLayout;
760ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
770ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    private final Handler mHandler = new Handler(Looper.getMainLooper());
780ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
790ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    private boolean mRequiringScrollToBottom = false;
800ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
810ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    // Whether the user have seen the more button yet.
820ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    private boolean mEverScrolledToBottom = false;
830ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
840ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    private ScrollHandlingDelegate mDelegate;
850ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
860ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    @Nullable
870ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    private OnRequireScrollStateChangedListener mListener;
880ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
890ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
900ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @param templateLayout The template containing this mixin
910ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
920ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public RequireScrollMixin(@NonNull TemplateLayout templateLayout) {
930ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        mTemplateLayout = templateLayout;
940ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
950ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
960ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
970ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * Sets the delegate to handle scrolling. The type of delegate should depend on whether the
980ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * scrolling view is a BottomScrollView, RecyclerView or ListView.
990ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
1000ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public void setScrollHandlingDelegate(@NonNull ScrollHandlingDelegate delegate) {
1010ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        mDelegate = delegate;
1020ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
1030ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
1040ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
1050ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * Listen to require scroll state changes. When scroll is required,
1060ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * {@link OnRequireScrollStateChangedListener#onRequireScrollStateChanged(boolean)} is called
1070ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * with {@code true}, and vice versa.
1080ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
1090ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public void setOnRequireScrollStateChangedListener(
1100ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @Nullable OnRequireScrollStateChangedListener listener) {
1110ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        mListener = listener;
1120ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
1130ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
1140ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
1150ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @return The scroll state listener previously set, or {@code null} if none is registered.
1160ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
1170ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public OnRequireScrollStateChangedListener getOnRequireScrollStateChangedListener() {
1180ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        return mListener;
1190ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
1200ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
1210ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
1220ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * Creates an {@link OnClickListener} which if scrolling is required, will scroll the page down,
1230ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * and if scrolling is not required, delegates to the wrapped {@code listener}. Note that you
1240ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * should call {@link #requireScroll()} as well in order to start requiring scrolling.
1250ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *
1260ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @param listener The listener to be invoked when scrolling is not needed and the user taps on
1270ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *                 the button. If {@code null}, the click listener will be a no-op when scroll
1280ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *                 is not required.
1290ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @return A new {@link OnClickListener} which will scroll the page down or delegate to the
1300ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *         given listener depending on the current require-scroll state.
1310ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
1320ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public OnClickListener createOnClickListener(@Nullable final OnClickListener listener) {
1330ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        return new OnClickListener() {
1340ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @Override
1350ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            public void onClick(View view) {
1360ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                if (mRequiringScrollToBottom) {
1370ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                    mDelegate.pageScrollDown();
1380ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                } else if (listener != null) {
1390ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                    listener.onClick(view);
1400ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                }
1410ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            }
1420ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        };
1430ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
1440ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
1450ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
1460ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * Coordinate with the given navigation bar to require scrolling on the page. The more button
1470ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * will be shown instead of the next button while scrolling is required.
1480ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
1490ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public void requireScrollWithNavigationBar(@NonNull final NavigationBar navigationBar) {
1500ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        setOnRequireScrollStateChangedListener(
1510ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                new OnRequireScrollStateChangedListener() {
1520ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                    @Override
1530ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                    public void onRequireScrollStateChanged(boolean scrollNeeded) {
1540ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                        navigationBar.getMoreButton()
1550ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                                .setVisibility(scrollNeeded ? View.VISIBLE : View.GONE);
1560ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                        navigationBar.getNextButton()
1570ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                                .setVisibility(scrollNeeded ? View.GONE : View.VISIBLE);
1580ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                    }
1590ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                });
1600ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        navigationBar.getMoreButton().setOnClickListener(createOnClickListener(null));
1610ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        requireScroll();
1620ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
1630ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
1640ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
1650ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @see #requireScrollWithButton(Button, CharSequence, OnClickListener)
1660ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
1670ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public void requireScrollWithButton(
1680ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @NonNull Button button,
1690ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @StringRes int moreText,
1700ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @Nullable OnClickListener onClickListener) {
1710ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        requireScrollWithButton(button, button.getContext().getText(moreText), onClickListener);
1720ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
1730ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
1740ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
1750ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * Use the given {@code button} to require scrolling. When scrolling is required, the button
1760ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * label will change to {@code moreText}, and tapping the button will cause the page to scroll
1770ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * down.
1780ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *
1790ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * <p>Note: Calling {@link View#setOnClickListener} on the button after this method will remove
1800ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * its link to the require-scroll mechanism. If you need to do that, obtain the click listener
1810ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * from {@link #createOnClickListener(OnClickListener)}.
1820ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *
1830ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * <p>Note: The normal button label is taken from the button's text at the time of calling this
1840ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * method. Calling {@link android.widget.TextView#setText} after calling this method causes
1850ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * undefined behavior.
1860ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *
1870ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @param button The button to use for require scroll. The button's "normal" label is taken from
1880ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *               the text at the time of calling this method, and the click listener of it will
1890ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *               be replaced.
1900ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @param moreText The button label when scroll is required.
1910ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @param onClickListener The listener for clicks when scrolling is not required.
1920ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
1930ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public void requireScrollWithButton(
1940ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @NonNull final Button button,
1950ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            final CharSequence moreText,
1960ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @Nullable OnClickListener onClickListener) {
1970ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        final CharSequence nextText = button.getText();
1980ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        button.setOnClickListener(createOnClickListener(onClickListener));
1990ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        setOnRequireScrollStateChangedListener(new OnRequireScrollStateChangedListener() {
2000ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @Override
2010ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            public void onRequireScrollStateChanged(boolean scrollNeeded) {
2020ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                button.setText(scrollNeeded ? moreText : nextText);
2030ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            }
2040ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        });
2050ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        requireScroll();
2060ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
2070ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
2080ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
2090ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @return True if scrolling is required. Note that this mixin only requires the user to
2100ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to
2110ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * bottom is not required again.
2120ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
2130ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public boolean isScrollingRequired() {
2140ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        return mRequiringScrollToBottom;
2150ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
2160ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
2170ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
2180ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * Start requiring scrolling on the layout. After calling this method, this mixin will start
2190ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * listening to scroll events from the scrolling container, and call
2200ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * {@link OnRequireScrollStateChangedListener} when the scroll state changes.
2210ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
2220ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    public void requireScroll() {
2230ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        mDelegate.startListening();
2240ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
2250ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
2260ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    /**
2270ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * {@link ScrollHandlingDelegate} should call this method when the scrollability of the
2280ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * scrolling container changed, so this mixin can recompute whether scrolling should be
2290ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * required.
2300ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     *
2310ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     * @param canScrollDown True if the view can scroll down further.
2320ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam     */
2330ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    void notifyScrollabilityChange(boolean canScrollDown) {
2340ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        if (canScrollDown == mRequiringScrollToBottom) {
2350ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            // Already at the desired require-scroll state
2360ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            return;
2370ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        }
2380ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        if (canScrollDown) {
2390ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            if (!mEverScrolledToBottom) {
2400ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                postScrollStateChange(true);
2410ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                mRequiringScrollToBottom = true;
2420ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            }
2430ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        } else {
2440ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            postScrollStateChange(false);
2450ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            mRequiringScrollToBottom = false;
2460ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            mEverScrolledToBottom = true;
2470ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        }
2480ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
2490ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam
2500ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    private void postScrollStateChange(final boolean scrollNeeded) {
2510ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        mHandler.post(new Runnable() {
2520ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            @Override
2530ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            public void run() {
2540ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                if (mListener != null) {
2550ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                    mListener.onRequireScrollStateChanged(scrollNeeded);
2560ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam                }
2570ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam            }
2580ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam        });
2590ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam    }
2600ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam}
261