1 /* 2 * Copyright (C) 2015 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 com.android.contacts.editor; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.provider.ContactsContract.CommonDataKinds.Event; 22 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 23 import android.provider.ContactsContract.CommonDataKinds.Nickname; 24 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 25 import android.util.AttributeSet; 26 import android.view.LayoutInflater; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.widget.ImageView; 30 import android.widget.LinearLayout; 31 import android.widget.TextView; 32 33 import com.android.contacts.R; 34 import com.android.contacts.model.RawContactDelta; 35 import com.android.contacts.model.RawContactModifier; 36 import com.android.contacts.model.ValuesDelta; 37 import com.android.contacts.model.account.AccountType; 38 import com.android.contacts.model.dataitem.DataKind; 39 import com.android.contacts.preference.ContactsPreferences; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * Custom view for an entire section of data as segmented by 46 * {@link DataKind} around a {@link Data#MIMETYPE}. This view shows a 47 * section header and a trigger for adding new {@link Data} rows. 48 */ 49 public class KindSectionView extends LinearLayout { 50 51 /** Callbacks for hosts of {@link KindSectionView}s. */ 52 public interface Listener { 53 54 /** Invoked when all fields in a legacy {@link KindSectionView} are removed. */ onEmptyLegacyKindSectionView()55 void onEmptyLegacyKindSectionView(); 56 } 57 58 /** 59 * Marks a name as super primary when it is changed. 60 * 61 * This is for the case when two or more raw contacts with names are joined where neither is 62 * marked as super primary. 63 */ 64 private static final class StructuredNameEditorListener implements Editor.EditorListener { 65 66 private final ValuesDelta mValuesDelta; 67 private final long mRawContactId; 68 private final RawContactEditorView.Listener mEditorViewListener; 69 StructuredNameEditorListener(ValuesDelta valuesDelta, long rawContactId, RawContactEditorView.Listener editorViewListener)70 public StructuredNameEditorListener(ValuesDelta valuesDelta, long rawContactId, 71 RawContactEditorView.Listener editorViewListener) { 72 mValuesDelta = valuesDelta; 73 mRawContactId = rawContactId; 74 mEditorViewListener = editorViewListener; 75 } 76 77 @Override onRequest(int request)78 public void onRequest(int request) { 79 if (request == Editor.EditorListener.FIELD_CHANGED) { 80 mValuesDelta.setSuperPrimary(true); 81 if (mEditorViewListener != null) { 82 mEditorViewListener.onNameFieldChanged(mRawContactId, mValuesDelta); 83 } 84 } else if (request == Editor.EditorListener.FIELD_TURNED_EMPTY) { 85 mValuesDelta.setSuperPrimary(false); 86 } 87 } 88 89 @Override onDeleteRequested(Editor editor)90 public void onDeleteRequested(Editor editor) { 91 editor.clearAllFields(); 92 } 93 } 94 95 /** 96 * Clears fields when deletes are requested (on phonetic and nickename fields); 97 * does not change the number of editors. 98 */ 99 private static final class OtherNameKindEditorListener implements Editor.EditorListener { 100 101 @Override onRequest(int request)102 public void onRequest(int request) { 103 } 104 105 @Override onDeleteRequested(Editor editor)106 public void onDeleteRequested(Editor editor) { 107 editor.clearAllFields(); 108 } 109 } 110 111 /** 112 * Updates empty fields when fields are deleted or turns empty. 113 * Whether a new empty editor is added is controlled by {@link #setShowOneEmptyEditor} and 114 * {@link #setHideWhenEmpty}. 115 */ 116 private class NonNameEditorListener implements Editor.EditorListener { 117 118 @Override onRequest(int request)119 public void onRequest(int request) { 120 // If a field has become empty or non-empty, then check if another row 121 // can be added dynamically. 122 if (request == FIELD_TURNED_EMPTY || request == FIELD_TURNED_NON_EMPTY) { 123 updateEmptyEditors(/* shouldAnimate = */ true); 124 } 125 } 126 127 @Override onDeleteRequested(Editor editor)128 public void onDeleteRequested(Editor editor) { 129 if (mIsLegacyField && mEditors.getChildCount() == 1) { 130 editor.deleteEditor(); 131 mListener.onEmptyLegacyKindSectionView(); 132 return; 133 } 134 if (mShowOneEmptyEditor && mEditors.getChildCount() == 1) { 135 // If there is only 1 editor in the section, then don't allow the user to 136 // delete it. Just clear the fields in the editor. 137 editor.clearAllFields(); 138 } else { 139 editor.deleteEditor(); 140 } 141 } 142 } 143 144 private class EventEditorListener extends NonNameEditorListener { 145 146 @Override onRequest(int request)147 public void onRequest(int request) { 148 super.onRequest(request); 149 } 150 151 @Override onDeleteRequested(Editor editor)152 public void onDeleteRequested(Editor editor) { 153 if (editor instanceof EventFieldEditorView){ 154 final EventFieldEditorView delView = (EventFieldEditorView) editor; 155 if (delView.isBirthdayType() && mEditors.getChildCount() > 1) { 156 final EventFieldEditorView bottomView = (EventFieldEditorView) mEditors 157 .getChildAt(mEditors.getChildCount() - 1); 158 bottomView.restoreBirthday(); 159 } 160 } 161 super.onDeleteRequested(editor); 162 } 163 } 164 165 private KindSectionData mKindSectionData; 166 private ViewIdGenerator mViewIdGenerator; 167 private RawContactEditorView.Listener mEditorViewListener; 168 private Listener mListener; 169 170 private boolean mIsUserProfile; 171 private boolean mShowOneEmptyEditor = false; 172 private boolean mHideIfEmpty = true; 173 private boolean mIsLegacyField = false; 174 175 private LayoutInflater mLayoutInflater; 176 private ViewGroup mEditors; 177 private ImageView mIcon; 178 KindSectionView(Context context)179 public KindSectionView(Context context) { 180 this(context, /* attrs =*/ null); 181 } 182 KindSectionView(Context context, AttributeSet attrs)183 public KindSectionView(Context context, AttributeSet attrs) { 184 super(context, attrs); 185 } 186 187 @Override setEnabled(boolean enabled)188 public void setEnabled(boolean enabled) { 189 super.setEnabled(enabled); 190 if (mEditors != null) { 191 int childCount = mEditors.getChildCount(); 192 for (int i = 0; i < childCount; i++) { 193 mEditors.getChildAt(i).setEnabled(enabled); 194 } 195 } 196 } 197 198 @Override onFinishInflate()199 protected void onFinishInflate() { 200 super.onFinishInflate(); 201 setDrawingCacheEnabled(true); 202 setAlwaysDrawnWithCacheEnabled(true); 203 204 mLayoutInflater = (LayoutInflater) getContext().getSystemService( 205 Context.LAYOUT_INFLATER_SERVICE); 206 207 mEditors = (ViewGroup) findViewById(R.id.kind_editors); 208 mIcon = (ImageView) findViewById(R.id.kind_icon); 209 } 210 setIsUserProfile(boolean isUserProfile)211 public void setIsUserProfile(boolean isUserProfile) { 212 mIsUserProfile = isUserProfile; 213 } 214 215 /** 216 * @param showOneEmptyEditor If true, we will always show one empty editor, otherwise an empty 217 * editor will not be shown until the user enters a value. Note, this does not apply 218 * to name editors since those are always displayed. 219 */ setShowOneEmptyEditor(boolean showOneEmptyEditor)220 public void setShowOneEmptyEditor(boolean showOneEmptyEditor) { 221 mShowOneEmptyEditor = showOneEmptyEditor; 222 } 223 224 /** 225 * @param hideWhenEmpty If true, the entire section will be hidden if all inputs are empty, 226 * otherwise one empty input will always be displayed. Note, this does not apply 227 * to name editors since those are always displayed. 228 */ setHideWhenEmpty(boolean hideWhenEmpty)229 public void setHideWhenEmpty(boolean hideWhenEmpty) { 230 mHideIfEmpty = hideWhenEmpty; 231 } 232 233 /** Binds the given group data to every {@link GroupMembershipView}. */ setGroupMetaData(Cursor cursor)234 public void setGroupMetaData(Cursor cursor) { 235 for (int i = 0; i < mEditors.getChildCount(); i++) { 236 final View view = mEditors.getChildAt(i); 237 if (view instanceof GroupMembershipView) { 238 ((GroupMembershipView) view).setGroupMetaData(cursor); 239 } 240 } 241 } 242 243 /** 244 * When {@code isLegacyField} is true, prevent users from editing the field. 245 */ setLegacyField(boolean isLegacyField)246 void setLegacyField(boolean isLegacyField) { 247 this.mIsLegacyField = isLegacyField; 248 } 249 250 /** 251 * Whether this is a name kind section view and all name fields (structured, phonetic, 252 * and nicknames) are empty. 253 */ isEmptyName()254 public boolean isEmptyName() { 255 if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionData.getMimeType())) { 256 return false; 257 } 258 for (int i = 0; i < mEditors.getChildCount(); i++) { 259 final View view = mEditors.getChildAt(i); 260 if (view instanceof Editor) { 261 final Editor editor = (Editor) view; 262 if (!editor.isEmpty()) { 263 return false; 264 } 265 } 266 } 267 return true; 268 } 269 getNameEditorView()270 public StructuredNameEditorView getNameEditorView() { 271 if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionData.getMimeType()) 272 || mEditors.getChildCount() == 0) { 273 return null; 274 } 275 return (StructuredNameEditorView) mEditors.getChildAt(0); 276 } 277 getPhoneticEditorView()278 public TextFieldsEditorView getPhoneticEditorView() { 279 if (!StructuredName.CONTENT_ITEM_TYPE.equals(mKindSectionData.getMimeType())) { 280 return null; 281 } 282 for (int i = 0; i < mEditors.getChildCount(); i++) { 283 final View view = mEditors.getChildAt(i); 284 if (!(view instanceof StructuredNameEditorView)) { 285 return (TextFieldsEditorView) view; 286 } 287 } 288 return null; 289 } 290 291 /** 292 * Binds views for the given {@link KindSectionData}. 293 * 294 * We create a structured name and phonetic name editor for each {@link DataKind} with a 295 * {@link StructuredName#CONTENT_ITEM_TYPE} mime type. The number and order of editors are 296 * rendered as they are given to {@link #setState}. 297 * 298 * Empty name editors are never added and at least one structured name editor is always 299 * displayed, even if it is empty. 300 */ setState( KindSectionData kindSectionData, ViewIdGenerator viewIdGenerator, RawContactEditorView.Listener editorViewListener, Listener listener)301 public void setState( 302 KindSectionData kindSectionData, 303 ViewIdGenerator viewIdGenerator, 304 RawContactEditorView.Listener editorViewListener, 305 Listener listener) { 306 mKindSectionData = kindSectionData; 307 mViewIdGenerator = viewIdGenerator; 308 mEditorViewListener = editorViewListener; 309 mListener = listener; 310 311 // Set the icon using the DataKind 312 final DataKind dataKind = mKindSectionData.getDataKind(); 313 if (dataKind != null) { 314 mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(), 315 dataKind.mimeType)); 316 if (mIcon.getDrawable() != null) { 317 mIcon.setContentDescription(dataKind.titleRes == -1 || dataKind.titleRes == 0 318 ? "" : getResources().getString(dataKind.titleRes)); 319 } 320 if (mIsLegacyField) { 321 mIcon.setEnabled(false); 322 } 323 } 324 325 rebuildFromState(); 326 327 updateEmptyEditors(/* shouldAnimate = */ false); 328 } 329 rebuildFromState()330 private void rebuildFromState() { 331 mEditors.removeAllViews(); 332 333 final String mimeType = mKindSectionData.getMimeType(); 334 if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) { 335 addNameEditorViews(mKindSectionData.getAccountType(), 336 mKindSectionData.getRawContactDelta()); 337 } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) { 338 addGroupEditorView(mKindSectionData.getRawContactDelta(), 339 mKindSectionData.getDataKind()); 340 } else { 341 final Editor.EditorListener editorListener; 342 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) { 343 editorListener = new OtherNameKindEditorListener(); 344 } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType)) { 345 editorListener = new EventEditorListener(); 346 } else { 347 editorListener = new NonNameEditorListener(); 348 } 349 final List<ValuesDelta> valuesDeltas = mKindSectionData.getVisibleValuesDeltas(); 350 for (int i = 0; i < valuesDeltas.size(); i++ ) { 351 addNonNameEditorView(mKindSectionData.getRawContactDelta(), 352 mKindSectionData.getDataKind(), valuesDeltas.get(i), editorListener); 353 } 354 } 355 } 356 addNameEditorViews(AccountType accountType, RawContactDelta rawContactDelta)357 private void addNameEditorViews(AccountType accountType, RawContactDelta rawContactDelta) { 358 final boolean readOnly = !accountType.areContactsWritable(); 359 final ValuesDelta nameValuesDelta = rawContactDelta 360 .getSuperPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE); 361 362 if (readOnly) { 363 final View nameView = mLayoutInflater.inflate( 364 R.layout.structured_name_readonly_editor_view, mEditors, 365 /* attachToRoot =*/ false); 366 367 // Display name 368 ((TextView) nameView.findViewById(R.id.display_name)) 369 .setText(nameValuesDelta.getDisplayName()); 370 371 // Account type info 372 final LinearLayout accountTypeLayout = (LinearLayout) 373 nameView.findViewById(R.id.account_type); 374 accountTypeLayout.setVisibility(View.VISIBLE); 375 ((ImageView) accountTypeLayout.findViewById(R.id.account_type_icon)) 376 .setImageDrawable(accountType.getDisplayIcon(getContext())); 377 ((TextView) accountTypeLayout.findViewById(R.id.account_type_name)) 378 .setText(accountType.getDisplayLabel(getContext())); 379 380 mEditors.addView(nameView); 381 return; 382 } 383 384 // Structured name 385 final StructuredNameEditorView nameView = (StructuredNameEditorView) mLayoutInflater 386 .inflate(R.layout.structured_name_editor_view, mEditors, /* attachToRoot =*/ false); 387 if (!mIsUserProfile) { 388 // Don't set super primary for the me contact 389 nameView.setEditorListener(new StructuredNameEditorListener( 390 nameValuesDelta, rawContactDelta.getRawContactId(), mEditorViewListener)); 391 } 392 nameView.setDeletable(false); 393 nameView.setValues(accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_NAME), 394 nameValuesDelta, rawContactDelta, /* readOnly =*/ false, mViewIdGenerator); 395 396 // Correct start margin since there is a second icon in the structured name layout 397 nameView.findViewById(R.id.kind_icon).setVisibility(View.GONE); 398 mEditors.addView(nameView); 399 400 // Phonetic name 401 final DataKind phoneticNameKind = accountType 402 .getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME); 403 // The account type doesn't support phonetic name. 404 if (phoneticNameKind == null) return; 405 406 final TextFieldsEditorView phoneticNameView = (TextFieldsEditorView) mLayoutInflater 407 .inflate(R.layout.text_fields_editor_view, mEditors, /* attachToRoot =*/ false); 408 phoneticNameView.setEditorListener(new OtherNameKindEditorListener()); 409 phoneticNameView.setDeletable(false); 410 phoneticNameView.setValues( 411 accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME), 412 nameValuesDelta, rawContactDelta, /* readOnly =*/ false, mViewIdGenerator); 413 414 // Fix the start margin for phonetic name views 415 final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( 416 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); 417 layoutParams.setMargins(0, 0, 0, 0); 418 phoneticNameView.setLayoutParams(layoutParams); 419 mEditors.addView(phoneticNameView); 420 // Display of phonetic name fields is controlled from settings preferences. 421 mHideIfEmpty = new ContactsPreferences(getContext()).shouldHidePhoneticNamesIfEmpty(); 422 } 423 addGroupEditorView(RawContactDelta rawContactDelta, DataKind dataKind)424 private void addGroupEditorView(RawContactDelta rawContactDelta, DataKind dataKind) { 425 final GroupMembershipView view = (GroupMembershipView) mLayoutInflater.inflate( 426 R.layout.item_group_membership, mEditors, /* attachToRoot =*/ false); 427 view.setKind(dataKind); 428 view.setEnabled(isEnabled()); 429 view.setState(rawContactDelta); 430 431 // Correct start margin since there is a second icon in the group layout 432 view.findViewById(R.id.kind_icon).setVisibility(View.GONE); 433 434 mEditors.addView(view); 435 } 436 addNonNameEditorView(RawContactDelta rawContactDelta, DataKind dataKind, ValuesDelta valuesDelta, Editor.EditorListener editorListener)437 private View addNonNameEditorView(RawContactDelta rawContactDelta, DataKind dataKind, 438 ValuesDelta valuesDelta, Editor.EditorListener editorListener) { 439 // Inflate the layout 440 final View view = mLayoutInflater.inflate( 441 EditorUiUtils.getLayoutResourceId(dataKind.mimeType), mEditors, false); 442 view.setEnabled(isEnabled()); 443 if (view instanceof Editor) { 444 final Editor editor = (Editor) view; 445 editor.setLegacyField(mIsLegacyField); 446 editor.setDeletable(true); 447 editor.setEditorListener(editorListener); 448 editor.setValues(dataKind, valuesDelta, rawContactDelta, !dataKind.editable, 449 mViewIdGenerator); 450 } 451 mEditors.addView(view); 452 453 return view; 454 } 455 456 /** 457 * Updates the editors being displayed to the user removing extra empty 458 * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time. 459 * If there is only 1 empty editor and {@link #setHideWhenEmpty} was set to true, 460 * then the entire section is hidden. 461 */ updateEmptyEditors(boolean shouldAnimate)462 public void updateEmptyEditors(boolean shouldAnimate) { 463 final boolean isNameKindSection = StructuredName.CONTENT_ITEM_TYPE.equals( 464 mKindSectionData.getMimeType()); 465 final boolean isGroupKindSection = GroupMembership.CONTENT_ITEM_TYPE.equals( 466 mKindSectionData.getMimeType()); 467 468 if (isNameKindSection) { 469 // The name kind section is always visible 470 setVisibility(VISIBLE); 471 updateEmptyNameEditors(shouldAnimate); 472 } else if (isGroupKindSection) { 473 // Check whether metadata has been bound for all group views 474 for (int i = 0; i < mEditors.getChildCount(); i++) { 475 final View view = mEditors.getChildAt(i); 476 if (view instanceof GroupMembershipView) { 477 final GroupMembershipView groupView = (GroupMembershipView) view; 478 if (!groupView.wasGroupMetaDataBound() || !groupView.accountHasGroups()) { 479 setVisibility(GONE); 480 return; 481 } 482 } 483 } 484 // Check that the user has selected to display all fields 485 if (mHideIfEmpty) { 486 setVisibility(GONE); 487 return; 488 } 489 setVisibility(VISIBLE); 490 491 // We don't check the emptiness of the group views 492 } else { 493 // Determine if the entire kind section should be visible 494 final int editorCount = mEditors.getChildCount(); 495 final List<View> emptyEditors = getEmptyEditors(); 496 if (editorCount == emptyEditors.size() && mHideIfEmpty) { 497 setVisibility(GONE); 498 return; 499 } 500 setVisibility(VISIBLE); 501 502 updateEmptyNonNameEditors(shouldAnimate); 503 } 504 } 505 updateEmptyNameEditors(boolean shouldAnimate)506 private void updateEmptyNameEditors(boolean shouldAnimate) { 507 boolean isEmptyNameEditorVisible = false; 508 509 for (int i = 0; i < mEditors.getChildCount(); i++) { 510 final View view = mEditors.getChildAt(i); 511 if (view instanceof Editor) { 512 final Editor editor = (Editor) view; 513 if (view instanceof StructuredNameEditorView) { 514 // We always show one empty structured name view 515 if (editor.isEmpty()) { 516 if (isEmptyNameEditorVisible) { 517 // If we're already showing an empty editor then hide any other empties 518 if (mHideIfEmpty) { 519 view.setVisibility(View.GONE); 520 } 521 } else { 522 isEmptyNameEditorVisible = true; 523 } 524 } else { 525 showView(view, shouldAnimate); 526 isEmptyNameEditorVisible = true; 527 } 528 } else { 529 // Since we can't add phonetic names and nicknames, just show or hide them 530 if (mHideIfEmpty && editor.isEmpty()) { 531 hideView(view); 532 } else { 533 showView(view, /* shouldAnimate =*/ false); // Animation here causes jank 534 } 535 } 536 } else { 537 // For read only names, only show them if we're not hiding empty views 538 if (mHideIfEmpty) { 539 hideView(view); 540 } else { 541 showView(view, shouldAnimate); 542 } 543 } 544 } 545 } 546 updateEmptyNonNameEditors(boolean shouldAnimate)547 private void updateEmptyNonNameEditors(boolean shouldAnimate) { 548 // Prune excess empty editors 549 final List<View> emptyEditors = getEmptyEditors(); 550 if (emptyEditors.size() > 1) { 551 // If there is more than 1 empty editor, then remove it from the list of editors. 552 int deleted = 0; 553 for (int i = 0; i < emptyEditors.size(); i++) { 554 final View view = emptyEditors.get(i); 555 // If no child {@link View}s are being focused on within this {@link View}, then 556 // remove this empty editor. We can assume that at least one empty editor has 557 // focus. One way to get two empty editors is by deleting characters from a 558 // non-empty editor, in which case this editor has focus. Another way is if 559 // there is more values delta so we must also count number of editors deleted. 560 if (view.findFocus() == null) { 561 deleteView(view, shouldAnimate); 562 deleted++; 563 if (deleted == emptyEditors.size() - 1) break; 564 } 565 } 566 return; 567 } 568 // Determine if we should add a new empty editor 569 final DataKind dataKind = mKindSectionData.getDataKind(); 570 final RawContactDelta rawContactDelta = mKindSectionData.getRawContactDelta(); 571 if (dataKind == null // There is nothing we can do. 572 // We have already reached the maximum number of editors, don't add any more. 573 || !RawContactModifier.canInsert(rawContactDelta, dataKind) 574 // We have already reached the maximum number of empty editors, don't add any more. 575 || emptyEditors.size() == 1) { 576 return; 577 } 578 // Add a new empty editor 579 if (mShowOneEmptyEditor) { 580 final String mimeType = mKindSectionData.getMimeType(); 581 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && mEditors.getChildCount() > 0) { 582 return; 583 } 584 final ValuesDelta values = RawContactModifier.insertChild(rawContactDelta, dataKind); 585 final Editor.EditorListener editorListener = Event.CONTENT_ITEM_TYPE.equals(mimeType) 586 ? new EventEditorListener() : new NonNameEditorListener(); 587 final View view = addNonNameEditorView(rawContactDelta, dataKind, values, 588 editorListener); 589 showView(view, shouldAnimate); 590 } 591 } 592 hideView(View view)593 private void hideView(View view) { 594 view.setVisibility(View.GONE); 595 } 596 deleteView(View view, boolean shouldAnimate)597 private void deleteView(View view, boolean shouldAnimate) { 598 if (shouldAnimate) { 599 final Editor editor = (Editor) view; 600 editor.deleteEditor(); 601 } else { 602 mEditors.removeView(view); 603 } 604 } 605 showView(View view, boolean shouldAnimate)606 private void showView(View view, boolean shouldAnimate) { 607 if (shouldAnimate) { 608 view.setVisibility(View.GONE); 609 EditorAnimator.getInstance().showFieldFooter(view); 610 } else { 611 view.setVisibility(View.VISIBLE); 612 } 613 } 614 getEmptyEditors()615 private List<View> getEmptyEditors() { 616 final List<View> emptyEditors = new ArrayList<>(); 617 for (int i = 0; i < mEditors.getChildCount(); i++) { 618 final View view = mEditors.getChildAt(i); 619 if (view instanceof Editor && ((Editor) view).isEmpty()) { 620 emptyEditors.add(view); 621 } 622 } 623 return emptyEditors; 624 } 625 isEditorEmpty()626 public boolean isEditorEmpty() { 627 return mKindSectionData.getVisibleValuesDeltas().isEmpty(); 628 } 629 } 630