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