1 /* 2 * Copyright (C) 2019 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 package com.android.car.ui.paintbooth.toolbar; 17 18 import android.graphics.drawable.Drawable; 19 import android.os.Bundle; 20 import android.text.Editable; 21 import android.text.InputType; 22 import android.text.TextUtils; 23 import android.text.TextWatcher; 24 import android.util.Pair; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.Button; 29 import android.widget.Toast; 30 31 import androidx.annotation.NonNull; 32 import androidx.appcompat.app.AppCompatActivity; 33 import androidx.recyclerview.widget.RecyclerView; 34 35 import com.android.car.ui.AlertDialogBuilder; 36 import com.android.car.ui.FocusArea; 37 import com.android.car.ui.baselayout.Insets; 38 import com.android.car.ui.baselayout.InsetsChangedListener; 39 import com.android.car.ui.core.CarUi; 40 import com.android.car.ui.paintbooth.R; 41 import com.android.car.ui.recyclerview.CarUiRecyclerView; 42 import com.android.car.ui.toolbar.MenuItem; 43 import com.android.car.ui.toolbar.NavButtonMode; 44 import com.android.car.ui.toolbar.SearchMode; 45 import com.android.car.ui.toolbar.TabLayout; 46 import com.android.car.ui.toolbar.Toolbar; 47 import com.android.car.ui.toolbar.Toolbar.State; 48 import com.android.car.ui.toolbar.ToolbarController; 49 50 import java.util.ArrayList; 51 import java.util.HashMap; 52 import java.util.List; 53 import java.util.Map; 54 55 public class ToolbarActivity extends AppCompatActivity implements InsetsChangedListener { 56 57 private final List<MenuItem> mMenuItems = new ArrayList<>(); 58 private final List<Pair<CharSequence, View.OnClickListener>> mButtons = new ArrayList<>(); 59 60 @Override onCreate(Bundle savedInstanceState)61 protected void onCreate(Bundle savedInstanceState) { 62 super.onCreate(savedInstanceState); 63 setContentView(R.layout.car_ui_recycler_view_activity); 64 65 ToolbarController toolbar = CarUi.requireToolbar(this); 66 toolbar.setTitle(getTitle()); 67 toolbar.setNavButtonMode(NavButtonMode.BACK); 68 toolbar.setLogo(R.drawable.ic_launcher); 69 boolean[] isSearching = new boolean[] { false }; 70 toolbar.registerBackListener( 71 () -> { 72 if (toolbar.getState() == Toolbar.State.SEARCH 73 || toolbar.getState() == Toolbar.State.EDIT) { 74 toolbar.setState(Toolbar.State.SUBPAGE); 75 return true; 76 } else if (isSearching[0]) { 77 toolbar.setSearchMode(SearchMode.DISABLED); 78 isSearching[0] = false; 79 return true; 80 } 81 return false; 82 }); 83 84 mMenuItems.add(MenuItem.builder(this) 85 .setToSearch() 86 .setOnClickListener(i -> { 87 isSearching[0] = true; 88 toolbar.setSearchMode(SearchMode.SEARCH); 89 }) 90 .build()); 91 92 toolbar.setMenuItems(mMenuItems); 93 94 mButtons.add(Pair.create("Toggle progress bar", v -> { 95 toolbar.getProgressBar().setVisible(!toolbar.getProgressBar().isVisible()); 96 })); 97 98 mButtons.add(Pair.create("Change title", v -> 99 toolbar.setTitle(toolbar.getTitle() + " X"))); 100 101 mButtons.add(Pair.create("Add/Change subtitle", v -> { 102 CharSequence subtitle = toolbar.getSubtitle(); 103 if (TextUtils.isEmpty(subtitle)) { 104 toolbar.setSubtitle("Subtitle"); 105 } else { 106 toolbar.setSubtitle(subtitle + " X"); 107 } 108 })); 109 110 mButtons.add(Pair.create(getString(R.string.toolbar_cycle_nav_button), v -> { 111 Toolbar.NavButtonMode mode = toolbar.getNavButtonMode(); 112 if (mode == Toolbar.NavButtonMode.DISABLED) { 113 toolbar.setNavButtonMode(NavButtonMode.BACK); 114 } else if (mode == Toolbar.NavButtonMode.BACK) { 115 toolbar.setNavButtonMode(NavButtonMode.CLOSE); 116 } else if (mode == Toolbar.NavButtonMode.CLOSE) { 117 toolbar.setNavButtonMode(NavButtonMode.DOWN); 118 } else { 119 toolbar.setNavButtonMode(NavButtonMode.DISABLED); 120 } 121 })); 122 123 Mutable<Boolean> hasLogo = new Mutable<>(true); 124 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_logo), v -> { 125 toolbar.setLogo(hasLogo.value ? 0 : R.drawable.ic_launcher); 126 hasLogo.value = !hasLogo.value; 127 })); 128 129 mButtons.add(Pair.create(getString(R.string.toolbar_cycle_state), v -> { 130 if (toolbar.getState() == State.HOME) { 131 toolbar.setState(State.SUBPAGE); 132 } else if (toolbar.getState() == State.SUBPAGE) { 133 toolbar.setState(State.EDIT); 134 } else { 135 toolbar.setState(State.HOME); 136 } 137 })); 138 139 mButtons.add(Pair.create(getString(R.string.toolbar_show_tabs_in_subpage), v -> 140 toolbar.setShowTabsInSubpage(!toolbar.getShowTabsInSubpage()))); 141 142 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_search_hint), v -> { 143 if (toolbar.getSearchHint().toString().contentEquals("Foo")) { 144 toolbar.setSearchHint("Bar"); 145 } else { 146 toolbar.setSearchHint("Foo"); 147 } 148 })); 149 150 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_background), 151 v -> toolbar.setBackgroundShown(!toolbar.getBackgroundShown()))); 152 153 mButtons.add(Pair.create(getString(R.string.toolbar_add_tab), v -> toolbar.addTab( 154 new TabLayout.Tab(getDrawable(R.drawable.ic_launcher), "Foo")))); 155 156 mButtons.add(Pair.create(getString(R.string.toolbar_add_tab_with_custom_text), v -> { 157 SimpleTextWatcher textWatcher = new SimpleTextWatcher(); 158 new AlertDialogBuilder(this) 159 .setEditBox(null, textWatcher, null) 160 .setTitle("Enter the text for the title") 161 .setPositiveButton(getString(R.string.ok), (dialog, which) -> 162 toolbar.addTab(new TabLayout.Tab( 163 getDrawable(R.drawable.ic_launcher), 164 textWatcher.getText()))) 165 .show(); 166 })); 167 168 Mutable<Boolean> showingLauncherIcon = new Mutable<>(false); 169 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_search_icon), v -> { 170 if (showingLauncherIcon.value) { 171 toolbar.setSearchIcon(null); 172 } else { 173 toolbar.setSearchIcon(R.drawable.ic_launcher); 174 } 175 showingLauncherIcon.value = !showingLauncherIcon.value; 176 })); 177 178 mButtons.add(Pair.create(getString(R.string.toolbar_set_xml_resource), v -> { 179 mMenuItems.clear(); 180 toolbar.setMenuItems(R.xml.menuitems); 181 })); 182 183 mButtons.add(Pair.create(getString(R.string.toolbar_add_icon), v -> { 184 mMenuItems.add(MenuItem.builder(this) 185 .setToSettings() 186 .setOnClickListener(i -> Toast.makeText(this, "Clicked", 187 Toast.LENGTH_SHORT).show()) 188 .build()); 189 toolbar.setMenuItems(mMenuItems); 190 })); 191 192 mButtons.add(Pair.create(getString(R.string.toolbar_add_untined_icon), v -> { 193 mMenuItems.add(MenuItem.builder(this) 194 .setIcon(R.drawable.ic_tracklist) 195 .setTinted(false) 196 .setOnClickListener( 197 i -> Toast.makeText(this, "Clicked", 198 Toast.LENGTH_SHORT).show()) 199 .build()); 200 toolbar.setMenuItems(mMenuItems); 201 })); 202 203 mButtons.add(Pair.create(getString(R.string.toolbar_add_switch), v -> { 204 mMenuItems.add(MenuItem.builder(this) 205 .setCheckable() 206 .setOnClickListener( 207 i -> 208 Toast.makeText(this, 209 "Checked? " + i.isChecked(), 210 Toast.LENGTH_SHORT) 211 .show()) 212 .build()); 213 toolbar.setMenuItems(mMenuItems); 214 })); 215 216 mButtons.add(Pair.create(getString(R.string.toolbar_add_text), v -> { 217 mMenuItems.add(MenuItem.builder(this) 218 .setTitle("Baz") 219 .setOnClickListener( 220 i -> Toast.makeText(this, "Clicked", 221 Toast.LENGTH_SHORT).show()) 222 .build()); 223 toolbar.setMenuItems(mMenuItems); 224 })); 225 226 mButtons.add(Pair.create(getString(R.string.toolbar_add_icon_text), v -> { 227 mMenuItems.add(MenuItem.builder(this) 228 .setIcon(R.drawable.ic_tracklist) 229 .setTitle("Bar") 230 .setShowIconAndTitle(true) 231 .setOnClickListener( 232 i -> Toast.makeText(this, "Clicked", 233 Toast.LENGTH_SHORT).show()) 234 .build()); 235 toolbar.setMenuItems(mMenuItems); 236 })); 237 238 mButtons.add(Pair.create(getString(R.string.toolbar_add_bordered_text), v -> { 239 mMenuItems.add(MenuItem.builder(this) 240 .setTitle("Baz") 241 .setPrimary(true) 242 .setOnClickListener( 243 i -> Toast.makeText(this, "Clicked", 244 Toast.LENGTH_SHORT).show()) 245 .build()); 246 toolbar.setMenuItems(mMenuItems); 247 })); 248 249 mButtons.add(Pair.create(getString(R.string.toolbar_add_bordered_icon_text), v -> { 250 mMenuItems.add(MenuItem.builder(this) 251 .setIcon(R.drawable.ic_tracklist) 252 .setTitle("Bar") 253 .setPrimary(true) 254 .setShowIconAndTitle(true) 255 .setOnClickListener( 256 i -> Toast.makeText(this, "Clicked", 257 Toast.LENGTH_SHORT).show()) 258 .build()); 259 toolbar.setMenuItems(mMenuItems); 260 })); 261 262 mButtons.add(Pair.create(getString(R.string.toolbar_add_untinted_icon_and_text), v -> { 263 mMenuItems.add(MenuItem.builder(this) 264 .setIcon(R.drawable.ic_tracklist) 265 .setTitle("Bar") 266 .setShowIconAndTitle(true) 267 .setTinted(false) 268 .setOnClickListener( 269 i -> Toast.makeText(this, "Clicked", 270 Toast.LENGTH_SHORT).show()) 271 .build()); 272 toolbar.setMenuItems(mMenuItems); 273 })); 274 275 mButtons.add(Pair.create(getString(R.string.toolbar_add_activatable), v -> { 276 mMenuItems.add(MenuItem.builder(this) 277 .setIcon(R.drawable.ic_tracklist) 278 .setActivatable() 279 .setOnClickListener( 280 i -> Toast.makeText(this, "Clicked", 281 Toast.LENGTH_SHORT).show()) 282 .build()); 283 toolbar.setMenuItems(mMenuItems); 284 })); 285 286 mButtons.add(Pair.create(getString(R.string.toolbar_add_morphing), v -> { 287 mMenuItems.add(MenuItem.builder(this) 288 .setTitle("Become icon") 289 .setOnClickListener(i -> 290 i.setIcon(i.getIcon() == null ? R.drawable.ic_tracklist : 0)) 291 .build()); 292 toolbar.setMenuItems(mMenuItems); 293 })); 294 295 Mutable<Integer> overflowCounter = new Mutable<>(1); 296 mButtons.add(Pair.create(getString(R.string.toolbar_add_overflow), v -> { 297 mMenuItems.add(MenuItem.builder(this) 298 .setTitle("Foo " + overflowCounter.value) 299 .setOnClickListener( 300 i -> Toast.makeText(this, "Clicked", 301 Toast.LENGTH_SHORT).show()) 302 .setDisplayBehavior(MenuItem.DisplayBehavior.NEVER) 303 .build()); 304 toolbar.setMenuItems(mMenuItems); 305 overflowCounter.value++; 306 })); 307 308 mButtons.add(Pair.create(getString(R.string.toolbar_add_overflow_switch), v -> { 309 mMenuItems.add(MenuItem.builder(this) 310 .setTitle("Foo " + overflowCounter.value) 311 .setOnClickListener( 312 i -> Toast.makeText(this, 313 i.isChecked() ? "Checked" : "Unchecked", 314 Toast.LENGTH_SHORT) 315 .show()) 316 .setDisplayBehavior(MenuItem.DisplayBehavior.NEVER) 317 .setCheckable() 318 .build()); 319 toolbar.setMenuItems(mMenuItems); 320 overflowCounter.value++; 321 })); 322 323 mButtons.add(Pair.create(getString(R.string.toolbar_add_icon_text_overflow), v -> { 324 mMenuItems.add(MenuItem.builder(this) 325 .setIcon(R.drawable.ic_tracklist) 326 .setTitle("Bar") 327 .setShowIconAndTitle(true) 328 .setOnClickListener( 329 i -> Toast.makeText(this, "Clicked", 330 Toast.LENGTH_SHORT).show()) 331 .setDisplayBehavior(MenuItem.DisplayBehavior.NEVER) 332 .build()); 333 toolbar.setMenuItems(mMenuItems); 334 })); 335 336 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_visibility), 337 v -> getMenuItem(item -> item.setVisible(!item.isVisible())))); 338 339 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_enable), 340 v -> getMenuItem(item -> item.setEnabled(!item.isEnabled())))); 341 342 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_perform_click), 343 v -> getMenuItem(MenuItem::performClick))); 344 345 final Drawable altIcon = getDrawable(R.drawable.ic_cut); 346 Map<MenuItem, Drawable> iconBackups = new HashMap<>(); 347 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_icon), v -> 348 getMenuItem(item -> { 349 Drawable currentIcon = item.getIcon(); 350 Drawable newIcon = altIcon; 351 if (iconBackups.containsKey(item)) { 352 newIcon = iconBackups.get(item); 353 } 354 item.setIcon(newIcon); 355 iconBackups.put(item, currentIcon); 356 }))); 357 358 mButtons.add(Pair.create(getString(R.string.toolbar_toggle_show_while_search), v -> 359 toolbar.setShowMenuItemsWhileSearching( 360 !toolbar.getShowMenuItemsWhileSearching()))); 361 362 CarUiRecyclerView prv = requireViewById(R.id.list); 363 prv.setAdapter(mAdapter); 364 } 365 366 @Override onCarUiInsetsChanged(@onNull Insets insets)367 public void onCarUiInsetsChanged(@NonNull Insets insets) { 368 FocusArea focusArea = requireViewById(R.id.focus_area); 369 focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom()); 370 focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom()); 371 requireViewById(R.id.list) 372 .setPadding(0, insets.getTop(), 0, insets.getBottom()); 373 requireViewById(android.R.id.content) 374 .setPadding(insets.getLeft(), 0, insets.getRight(), 0); 375 } 376 xmlMenuItemClicked(MenuItem item)377 public void xmlMenuItemClicked(MenuItem item) { 378 Toast.makeText(this, "Xml item clicked! " + item.getTitle() + ", id: " + item.getId(), 379 Toast.LENGTH_SHORT).show(); 380 } 381 getMenuItem(MenuItem.OnClickListener listener)382 private void getMenuItem(MenuItem.OnClickListener listener) { 383 if (mMenuItems.size() == 1) { 384 listener.onClick(mMenuItems.get(0)); 385 return; 386 } 387 388 SimpleTextWatcher textWatcher = new SimpleTextWatcher(); 389 new AlertDialogBuilder(this) 390 .setEditBox("", textWatcher, null, InputType.TYPE_CLASS_NUMBER) 391 .setTitle("Enter the index of the MenuItem") 392 .setPositiveButton(getString(R.string.ok), (dialog, which) -> { 393 try { 394 MenuItem item = mMenuItems.get( 395 Integer.parseInt(textWatcher.getText())); 396 listener.onClick(item); 397 } catch (NumberFormatException | IndexOutOfBoundsException e) { 398 Toast.makeText(this, "Invalid index \"" + textWatcher.getText() 399 + "\", valid range is 0 to " + (mMenuItems.size() - 1), 400 Toast.LENGTH_LONG).show(); 401 } 402 }).show(); 403 } 404 405 private static class ViewHolder extends RecyclerView.ViewHolder { 406 407 private final Button mButton; 408 ViewHolder(View itemView)409 ViewHolder(View itemView) { 410 super(itemView); 411 mButton = itemView.requireViewById(R.id.button); 412 } 413 bind(CharSequence title, View.OnClickListener listener)414 public void bind(CharSequence title, View.OnClickListener listener) { 415 mButton.setText(title); 416 mButton.setOnClickListener(listener); 417 } 418 } 419 420 private final RecyclerView.Adapter<ViewHolder> mAdapter = 421 new RecyclerView.Adapter<ViewHolder>() { 422 @Override 423 public int getItemCount() { 424 return mButtons.size(); 425 } 426 427 @Override 428 public ViewHolder onCreateViewHolder(ViewGroup parent, int position) { 429 View item = 430 LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, 431 parent, false); 432 return new ViewHolder(item); 433 } 434 435 @Override 436 public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 437 Pair<CharSequence, View.OnClickListener> pair = mButtons.get(position); 438 holder.bind(pair.first, pair.second); 439 } 440 }; 441 442 /** 443 * For changing values from lambdas 444 */ 445 private static final class Mutable<E> { 446 447 public E value; 448 Mutable()449 Mutable() { 450 value = null; 451 } 452 Mutable(E value)453 Mutable(E value) { 454 this.value = value; 455 } 456 } 457 458 /** 459 * Used for getting text from a dialog. 460 */ 461 private static final class SimpleTextWatcher implements TextWatcher { 462 463 private String mValue; 464 465 @Override beforeTextChanged(CharSequence s, int start, int count, int after)466 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 467 } 468 469 @Override onTextChanged(CharSequence s, int start, int before, int count)470 public void onTextChanged(CharSequence s, int start, int before, int count) { 471 } 472 473 @Override afterTextChanged(Editable s)474 public void afterTextChanged(Editable s) { 475 mValue = s.toString(); 476 } 477 getText()478 public String getText() { 479 return mValue; 480 } 481 } 482 } 483