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 import android.os.Handler;
21 import android.os.Looper;
22 import android.view.Gravity;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.widget.TextView;
26 import android.widget.Toast;
27 
28 import androidx.annotation.IdRes;
29 import androidx.annotation.LayoutRes;
30 import androidx.annotation.RestrictTo;
31 import androidx.annotation.RestrictTo.Scope;
32 import androidx.wear.R;
33 import androidx.wear.widget.CircledImageView;
34 import androidx.wear.widget.drawer.WearableNavigationDrawerView;
35 
36 import org.jspecify.annotations.Nullable;
37 
38 /**
39  * Handles view logic for the single page style {@link WearableNavigationDrawerView}.
40  *
41  */
42 @RestrictTo(Scope.LIBRARY)
43 public class SinglePageUi implements SinglePagePresenter.Ui {
44 
45     @IdRes
46     private static final int[] SINGLE_PAGE_BUTTON_IDS =
47             new int[]{
48                     R.id.ws_nav_drawer_icon_0,
49                     R.id.ws_nav_drawer_icon_1,
50                     R.id.ws_nav_drawer_icon_2,
51                     R.id.ws_nav_drawer_icon_3,
52                     R.id.ws_nav_drawer_icon_4,
53                     R.id.ws_nav_drawer_icon_5,
54                     R.id.ws_nav_drawer_icon_6,
55             };
56 
57     @LayoutRes
58     private static final int[] SINGLE_PAGE_LAYOUT_RES =
59             new int[]{
60                     0,
61                     R.layout.ws_single_page_nav_drawer_1_item,
62                     R.layout.ws_single_page_nav_drawer_2_item,
63                     R.layout.ws_single_page_nav_drawer_3_item,
64                     R.layout.ws_single_page_nav_drawer_4_item,
65                     R.layout.ws_single_page_nav_drawer_5_item,
66                     R.layout.ws_single_page_nav_drawer_6_item,
67                     R.layout.ws_single_page_nav_drawer_7_item,
68             };
69 
70     @SuppressWarnings("WeakerAccess") /* synthetic access */
71     final WearableNavigationDrawerView mDrawer;
72     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
73     private final Runnable mCloseDrawerRunnable =
74             new Runnable() {
75                 @Override
76                 public void run() {
77                     mDrawer.getController().closeDrawer();
78                 }
79             };
80     private WearableNavigationDrawerPresenter mPresenter;
81     private CircledImageView[] mSinglePageImageViews;
82     /**
83      * Indicates currently selected item. {@code null} when the layout lacks space to display it.
84      */
85     private @Nullable TextView mTextView;
86 
SinglePageUi(WearableNavigationDrawerView navigationDrawer)87     public SinglePageUi(WearableNavigationDrawerView navigationDrawer) {
88         if (navigationDrawer == null) {
89             throw new IllegalArgumentException("Received null navigationDrawer.");
90         }
91         mDrawer = navigationDrawer;
92     }
93 
94     @Override
setPresenter(WearableNavigationDrawerPresenter presenter)95     public void setPresenter(WearableNavigationDrawerPresenter presenter) {
96         mPresenter = presenter;
97     }
98 
99     @Override
initialize(int count)100     public void initialize(int count) {
101         if (count < 0 || count >= SINGLE_PAGE_LAYOUT_RES.length
102                 || SINGLE_PAGE_LAYOUT_RES[count] == 0) {
103             mDrawer.setDrawerContent(null);
104             return;
105         }
106 
107         @LayoutRes int layoutRes = SINGLE_PAGE_LAYOUT_RES[count];
108         LayoutInflater inflater = LayoutInflater.from(mDrawer.getContext());
109         View content = inflater.inflate(layoutRes, mDrawer, false /* attachToRoot */);
110         final View peek =
111                 inflater.inflate(
112                         R.layout.ws_single_page_nav_drawer_peek_view, mDrawer,
113                         false /* attachToRoot */);
114 
115         mTextView = content.findViewById(R.id.ws_nav_drawer_text);
116         mSinglePageImageViews = new CircledImageView[count];
117         for (int i = 0; i < count; i++) {
118             mSinglePageImageViews[i] = content.findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
119             mSinglePageImageViews[i].setOnClickListener(new OnSelectedClickHandler(i, mPresenter));
120             mSinglePageImageViews[i].setCircleHidden(true);
121         }
122 
123         mDrawer.setDrawerContent(content);
124         mDrawer.setPeekContent(peek);
125     }
126 
127     @Override
setIcon(int index, Drawable drawable, CharSequence contentDescription)128     public void setIcon(int index, Drawable drawable, CharSequence contentDescription) {
129         mSinglePageImageViews[index].setImageDrawable(drawable);
130         mSinglePageImageViews[index].setContentDescription(contentDescription);
131     }
132 
133     @Override
setText(CharSequence itemText, boolean showToastIfNoTextView)134     public void setText(CharSequence itemText, boolean showToastIfNoTextView) {
135         if (mTextView != null) {
136             mTextView.setText(itemText);
137         } else if (showToastIfNoTextView) {
138             Toast toast = Toast.makeText(mDrawer.getContext(), itemText, Toast.LENGTH_SHORT);
139             toast.setGravity(Gravity.CENTER, 0 /* xOffset */, 0 /* yOffset */);
140             toast.show();
141         }
142     }
143 
144     @Override
selectItem(int index)145     public void selectItem(int index) {
146         mSinglePageImageViews[index].setCircleHidden(false);
147     }
148 
149     @Override
deselectItem(int index)150     public void deselectItem(int index) {
151         mSinglePageImageViews[index].setCircleHidden(true);
152     }
153 
154     @Override
closeDrawerDelayed(long delayMs)155     public void closeDrawerDelayed(long delayMs) {
156         mMainThreadHandler.removeCallbacks(mCloseDrawerRunnable);
157         mMainThreadHandler.postDelayed(mCloseDrawerRunnable, delayMs);
158     }
159 
160     @Override
peekDrawer()161     public void peekDrawer() {
162         mDrawer.getController().peekDrawer();
163     }
164 
165     /**
166      * Notifies the {@code presenter} that the item at the given {@code index} has been selected.
167      */
168     private static class OnSelectedClickHandler implements View.OnClickListener {
169 
170         private final int mIndex;
171         private final WearableNavigationDrawerPresenter mPresenter;
172 
OnSelectedClickHandler(int index, WearableNavigationDrawerPresenter presenter)173         OnSelectedClickHandler(int index, WearableNavigationDrawerPresenter presenter) {
174             mIndex = index;
175             mPresenter = presenter;
176         }
177 
178         @Override
onClick(View v)179         public void onClick(View v) {
180             mPresenter.onSelected(mIndex);
181         }
182     }
183 }
184