1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.wear.internal.widget.drawer;
18 
19 import android.graphics.drawable.Drawable;
20 
21 import androidx.annotation.RestrictTo;
22 import androidx.annotation.RestrictTo.Scope;
23 import androidx.wear.widget.drawer.WearableNavigationDrawerView;
24 import androidx.wear.widget.drawer.WearableNavigationDrawerView.WearableNavigationDrawerAdapter;
25 
26 import org.jspecify.annotations.Nullable;
27 
28 /**
29  * Provides a {@link WearableNavigationDrawerPresenter} implementation that is designed for the
30  * single page navigation drawer.
31  *
32  */
33 @RestrictTo(Scope.LIBRARY)
34 public class SinglePagePresenter extends WearableNavigationDrawerPresenter {
35 
36     private static final long DRAWER_CLOSE_DELAY_MS = 500;
37 
38     private final Ui mUi;
39     private final boolean mIsAccessibilityEnabled;
40     private @Nullable WearableNavigationDrawerAdapter mAdapter;
41     private int mCount = 0;
42     private int mSelected = 0;
43 
44     /**
45      * Controls the user interface of a single-page {@link WearableNavigationDrawerView}.
46      */
47     public interface Ui {
48 
49         /**
50          * Associates a {@link WearableNavigationDrawerPresenter} with this {@link Ui}.
51          */
setPresenter(WearableNavigationDrawerPresenter presenter)52         void setPresenter(WearableNavigationDrawerPresenter presenter);
53 
54         /**
55          * Initializes the {@link Ui} with {@code count} items.
56          */
initialize(int count)57         void initialize(int count);
58 
59         /**
60          * Sets the item's {@link Drawable} icon and its {@code contentDescription}.
61          */
setIcon(int index, Drawable drawable, CharSequence contentDescription)62         void setIcon(int index, Drawable drawable, CharSequence contentDescription);
63 
64         /**
65          * Displays {@code itemText} in a {@link android.widget.TextView} used to indicate which
66          * item is selected. When the {@link Ui} doesn't have space, it should show a {@link
67          * android.widget.Toast} if {@code showToastIfNoTextView} is {@code true}.
68          */
setText(CharSequence itemText, boolean showToastIfNoTextView)69         void setText(CharSequence itemText, boolean showToastIfNoTextView);
70 
71         /**
72          * Indicates that the item at {@code index} has been selected.
73          */
selectItem(int index)74         void selectItem(int index);
75 
76         /**
77          * Removes the indication that the item at {@code index} has been selected.
78          */
deselectItem(int index)79         void deselectItem(int index);
80 
81         /**
82          * Closes the drawer after the given delay.
83          */
closeDrawerDelayed(long delayMs)84         void closeDrawerDelayed(long delayMs);
85 
86         /**
87          * Peeks the {@link WearableNavigationDrawerView}.
88          */
peekDrawer()89         void peekDrawer();
90     }
91 
SinglePagePresenter(Ui ui, boolean isAccessibilityEnabled)92     public SinglePagePresenter(Ui ui, boolean isAccessibilityEnabled) {
93         if (ui == null) {
94             throw new IllegalArgumentException("Received null ui.");
95         }
96 
97         mIsAccessibilityEnabled = isAccessibilityEnabled;
98         mUi = ui;
99         mUi.setPresenter(this);
100         onDataSetChanged();
101     }
102 
103     @Override
onDataSetChanged()104     public void onDataSetChanged() {
105         if (mAdapter == null) {
106             return;
107         }
108         int count = mAdapter.getCount();
109         if (mCount != count) {
110             mCount = count;
111             mSelected = Math.min(mSelected, count - 1);
112             mUi.initialize(count);
113         }
114         for (int i = 0; i < count; i++) {
115             mUi.setIcon(i, mAdapter.getItemDrawable(i), mAdapter.getItemText(i));
116         }
117 
118         mUi.setText(mAdapter.getItemText(mSelected), false /* showToastIfNoTextView */);
119         mUi.selectItem(mSelected);
120     }
121 
122     @Override
onNewAdapter(WearableNavigationDrawerAdapter adapter)123     public void onNewAdapter(WearableNavigationDrawerAdapter adapter) {
124         if (adapter == null) {
125             throw new IllegalArgumentException("Received null adapter.");
126         }
127         mAdapter = adapter;
128         mAdapter.setPresenter(this);
129         onDataSetChanged();
130     }
131 
132     @Override
onSelected(int index)133     public void onSelected(int index) {
134         mUi.deselectItem(mSelected);
135         mUi.selectItem(index);
136         mSelected = index;
137         if (mIsAccessibilityEnabled) {
138             // When accessibility gestures are enabled, the user can't access a closed nav drawer,
139             // so peek it instead.
140             mUi.peekDrawer();
141         } else {
142             mUi.closeDrawerDelayed(DRAWER_CLOSE_DELAY_MS);
143         }
144 
145         if (mAdapter != null) {
146             mUi.setText(mAdapter.getItemText(index), true /* showToastIfNoTextView */);
147         }
148         notifyItemSelectedListeners(index);
149     }
150 
151     @Override
onSetCurrentItemRequested(int index, boolean smoothScrollTo)152     public void onSetCurrentItemRequested(int index, boolean smoothScrollTo) {
153         mUi.deselectItem(mSelected);
154         mUi.selectItem(index);
155         mSelected = index;
156         if (mAdapter != null) {
157             mUi.setText(mAdapter.getItemText(index), false /* showToastIfNoTextView */);
158         }
159         notifyItemSelectedListeners(index);
160     }
161 
162     @Override
onDrawerTapped()163     public boolean onDrawerTapped() {
164         // Do nothing. Use onSelected as our tap trigger so that we get which index was tapped on.
165         return false;
166     }
167 }
168