package yuku.alkitab.base.widget; import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.v4.graphics.ColorUtils; import android.text.Layout; import android.text.TextUtils; import android.util.AttributeSet; import android.view.DragEvent; import android.view.accessibility.AccessibilityEvent; import android.widget.Checkable; import android.widget.RelativeLayout; import android.widget.TextView; import yuku.afw.V; import yuku.afw.storage.Preferences; import yuku.alkitab.base.App; import yuku.alkitab.base.IsiActivity; import yuku.alkitab.base.S; import yuku.alkitab.debug.R; import yuku.alkitab.model.ProgressMark; import java.util.Date; public class VerseItem extends RelativeLayout implements Checkable { public static final String PROGRESS_MARK_DRAG_MIME_TYPE = "application/vnd.yuku.alkitab.progress_mark.drag"; private static final float ATTENTION_DURATION = 2000L; private boolean checked; private static Paint checkedPaintSolid; private boolean collapsed; private boolean dragHover; private Drawable dragHoverBg; public VerseTextView lText; public TextView lVerseNumber; public AttributeView attributeView; /** the ari of the verse represented by this view. If this is 0, this is a pericope or something else. */ private int ari; /** * Whether we briefly "color" this verse resulting from navigation from other parts of the app (e.g. verse navigation, search results). * If the value is 0, it means do not color. If nonzero, it is the starting time when the animation starts. */ private long attentionStart; private static Paint attentionPaint; public VerseItem(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); lText = V.get(this, R.id.lText); lVerseNumber = V.get(this, R.id.lVerseNumber); attributeView = V.get(this, R.id.attributeView); } @Override protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (collapsed) { setMeasuredDimension(getMeasuredWidth(), 0); return; } if ( Build.VERSION.SDK_INT >= 21) { // Fix bug on Lollipop where the last line of the text does not calculate line spacing mult/add. // https://code.google.com/p/android/issues/detail?id=77941 final VerseTextView lText = this.lText; if (lText != null) { final Layout layout = lText.getLayout(); if (layout != null) { final int lastLine = layout.getLineCount() - 1; final int spacing = lText.getIncludeFontPadding() ? (layout.getLineBottom(lastLine) - layout.getLineTop(lastLine)) : (layout.getLineDescent(lastLine) - layout.getLineAscent(lastLine)); final int extra = (int) (spacing * (layout.getSpacingMultiplier() - 1) + layout.getSpacingAdd() + 0.5f); setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + extra); } } } } @Override protected void onDraw(Canvas canvas) { final int w = getWidth(); final int h = getHeight(); if (checked) { Paint solid = checkedPaintSolid; if (solid == null) { checkedPaintSolid = solid = new Paint(); final int colorRgb = Preferences.getInt(R.string.pref_selectedVerseBgColor_key, R.integer.pref_selectedVerseBgColor_default); final int color = ColorUtils.setAlphaComponent(colorRgb, 0xa0); solid.setColor(color); solid.setStyle(Paint.Style.FILL); } canvas.drawRect(0, 0, w, h, solid); } if (dragHover) { if (dragHoverBg == null) { //noinspection deprecation dragHoverBg = getResources().getDrawable(R.drawable.item_verse_bg_draghovered); } //noinspection ConstantConditions dragHoverBg.setBounds(0, 0, w, h); dragHoverBg.draw(canvas); } if (attentionStart != 0) { final long now = System.currentTimeMillis(); final long elapsed = now - attentionStart; if (elapsed >= ATTENTION_DURATION) { attentionStart = 0; } else { final float opacity = 0.4f * (1.f - ((float) elapsed / ATTENTION_DURATION)); Paint p = attentionPaint; if (p == null) { attentionPaint = p = new Paint(); final int colorRgb = Preferences.getInt(R.string.pref_selectedVerseBgColor_key, R.integer.pref_selectedVerseBgColor_default); p.setColor(colorRgb); p.setStyle(Paint.Style.FILL); } p.setColor(ColorUtils.setAlphaComponent(p.getColor(), (int) (opacity * 255))); canvas.drawRect(0, 0, w, h, p); invalidate(); // animate } } super.onDraw(canvas); } void callAttention(final long attentionStartTime) { if (this.attentionStart != attentionStartTime) { this.attentionStart = attentionStartTime; if (attentionStartTime != 0) { setWillNotDraw(false); } invalidate(); } } public static void invalidateSelectedVersePaints() { checkedPaintSolid = null; attentionPaint = null; } @Override public boolean isChecked() { return checked; } @Override public void setChecked(boolean checked) { if (this.checked != checked) { this.checked = checked; if (checked) { setWillNotDraw(false); } invalidate(); } } public void setDragHover(final boolean dragHover) { if (this.dragHover != dragHover) { this.dragHover = dragHover; if (dragHover) { setWillNotDraw(false); } invalidate(); } } @Override public void toggle() { setChecked(!checked); } public void setCollapsed(final boolean collapsed) { if (this.collapsed == collapsed) return; this.collapsed = collapsed; requestLayout(); } public void setAri(final int ari) { this.ari = ari; } @Override public boolean onDragEvent(final DragEvent event) { // Dropping only works on verse not pericope or something else if (ari == 0) return false; switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: // Determines if this View can accept the dragged data if (event.getClipDescription().hasMimeType(PROGRESS_MARK_DRAG_MIME_TYPE)) { return true; } break; case DragEvent.ACTION_DRAG_ENTERED: // Indicate this will receive the drag data. setDragHover(true); invalidate(); return true; case DragEvent.ACTION_DRAG_EXITED: case DragEvent.ACTION_DRAG_ENDED: // Indicate this will no more receive the drag data. setDragHover(false); invalidate(); return true; case DragEvent.ACTION_DROP: App.trackEvent("pin_drop"); final ClipData.Item item = event.getClipData().getItemAt(0); final int preset_id = Integer.parseInt(item.getText().toString()); final ProgressMark progressMark = S.getDb().getProgressMarkByPresetId(preset_id); progressMark.ari = this.ari; progressMark.modifyTime = new Date(); S.getDb().insertOrUpdateProgressMark(progressMark); App.getLbm().sendBroadcast(new Intent(IsiActivity.ACTION_ATTRIBUTE_MAP_CHANGED)); return true; } return false; } /** * This is called AND used when explore-by-touch is off. * Force TallkBack to use the content description and not explore the children of this view. */ @Override public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) { event.getText().add(getContentDescription()); return true; } /** * Make sure TalkBack reads the verse correctly by setting content description. */ @Override public CharSequence getContentDescription() { final StringBuilder res = new StringBuilder(); if (lVerseNumber.length() > 0) { res.append(lVerseNumber.getText()).append(' '); } res.append(lText.getText()); final int bookmark_count = attributeView.getBookmarkCount(); if (bookmark_count == 1) { res.append(' ').append(getContext().getString(R.string.desc_verse_attribute_one_bookmark)); } else if (bookmark_count > 1) { res.append(' ').append(getContext().getString(R.string.desc_verse_attribute_multiple_bookmarks, bookmark_count)); } final int note_count = attributeView.getNoteCount(); if (note_count == 1) { res.append(' ').append(getContext().getString(R.string.desc_verse_attribute_one_note)); } else if (note_count > 1) { res.append(' ').append(getContext().getString(R.string.desc_verse_attribute_multiple_notes, note_count)); } final int progress_mark_bits = attributeView.getProgressMarkBits(); for (int preset_id = 0; preset_id < AttributeView.PROGRESS_MARK_TOTAL_COUNT; preset_id++) { if ((progress_mark_bits & 1 << AttributeView.PROGRESS_MARK_BITS_START + preset_id) != 0) { final ProgressMark progressMark = S.getDb().getProgressMarkByPresetId(preset_id); final String caption; if (TextUtils.isEmpty(progressMark.caption)) { caption = getContext().getString(AttributeView.getDefaultProgressMarkStringResource(preset_id)); } else { caption = progressMark.caption; } res.append(' ').append(getContext().getString(R.string.desc_verse_attribute_progress_mark, caption)); } } return res; } }