package yuku.alkitab.base.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.support.v4.content.res.ResourcesCompat;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.View;
import yuku.alkitab.debug.R;
import yuku.alkitab.model.Book;
import yuku.alkitab.model.Version;
import yuku.alkitab.util.Ari;
public class Floater extends View {
public static final String TAG = Floater.class.getSimpleName();
public static final int LONG_PRESS_DELAY_MILLIS = 650;
private int currentBookId;
private int currentChapter_1;
enum State {
idle,
selectBook,
selectChapter,
selectVerse,
}
public interface Listener {
void onSelectComplete(int ari);
}
Paint passivePaint;
Paint activePaint;
Paint currentPaint; // paint for drawing "current" box
Paint activeBoxPaint;
Paint.FontMetrics passiveFontMetrics;
Paint.FontMetrics activeFontMetrics;
Paint separatorPaint;
Book[] books;
int grid_columns;
int grid_rows;
int activeBookIndex = -1;
int longPressBookIndex = -1;
int previousActiveBookIndex;
int activeChapterIndex = -1;
int longPressChapterIndex = -1;
int previousActiveChapterIndex;
int activeVerseIndex = -1;
float density;
State state;
Listener listener;
Runnable checkLongPressBook = () -> {
if (activeBookIndex == longPressBookIndex) {
// we have been at the same bookIndex since a few ms ago!
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
commitBook();
}
};
Runnable checkLongPressChapter = () -> {
if (activeChapterIndex == longPressChapterIndex) {
// we have been at the same chapterIndex since a few ms ago!
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
commitChapter();
}
};
public Floater(final Context context) {
super(context);
init();
}
public Floater(final Context context, final AttributeSet attrs) {
super(context, attrs);
init();
}
public Floater(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
density = getResources().getDisplayMetrics().density;
passivePaint = new Paint();
passivePaint.setAntiAlias(true);
passivePaint.setColor(0xffd0d0d0);
passivePaint.setShadowLayer(2 * density, 0, 0, 0xff000000);
activePaint = new Paint();
activePaint.setAntiAlias(true);
activePaint.setColor(0xffffffff);
activePaint.setTypeface(Typeface.DEFAULT_BOLD);
final int accentColor = ResourcesCompat.getColor(getResources(), R.color.accent, getContext().getTheme());
currentPaint = new Paint();
currentPaint.setColor(accentColor);
currentPaint.setStrokeWidth(1 * density);
currentPaint.setStyle(Paint.Style.STROKE);
activeBoxPaint = new Paint();
activeBoxPaint.setColor(accentColor);
activeBoxPaint.setStyle(Paint.Style.FILL_AND_STROKE);
separatorPaint = new Paint();
separatorPaint.setStyle(Paint.Style.STROKE);
separatorPaint.setColor(0xffd0d0d0);
separatorPaint.setStrokeWidth(1 * density);
passiveFontMetrics = new Paint.FontMetrics();
activeFontMetrics = new Paint.FontMetrics();
}
@Override
protected void onDraw(final Canvas canvas) {
final float w = getWidth() - getPaddingLeft() - getPaddingRight();
final float h = getHeight() - getPaddingTop() - getPaddingBottom();
if (books == null) return;
canvas.drawColor(0xd0000000);
if (state == State.selectBook) {
final int book_count = books.length;
if (book_count <= 33) { // e.g. NT only
grid_columns = 1;
grid_rows = 33;
} else if (book_count <= 66) {
grid_columns = 2;
grid_rows = 33;
} else {
grid_columns = 3;
grid_rows = (int) Math.ceil((float) book_count / grid_columns);
}
initFontSizes(h);
// passive books
for (int i = 0; i < book_count; i++) {
final Book book = books[i];
final int column = i / grid_rows;
final int row = i % grid_rows;
final float left = getPaddingLeft() + column * (w / grid_columns);
final float bottom = getPaddingTop() + (row + 1) * (h / grid_rows);
if (activeBookIndex != i) {
canvas.drawText(book.shortName, left, bottom, passivePaint);
if (book.bookId == currentBookId) {
float textWidth = passivePaint.measureText(book.shortName);
canvas.drawRect(left - 4 * density, bottom + passiveFontMetrics.ascent, left + textWidth + 4 * density, bottom + passiveFontMetrics.descent, currentPaint);
}
}
if (book.bookId == 38) { // end of old testament
canvas.drawLine(left - 4 * density, bottom + passiveFontMetrics.descent, left + (w / grid_columns) - 4 * density, bottom + passiveFontMetrics.descent, separatorPaint);
}
}
// active book
if (activeBookIndex != -1) {
final int column = activeBookIndex / grid_rows;
final int row = activeBookIndex % grid_rows;
final Book book = books[activeBookIndex];
// draw box
final String text = book.shortName;
drawActiveWithBox(canvas, w, h, column, row, text);
}
}
if (state == State.selectChapter) {
final Book book = books[activeBookIndex];
final int chapter_count = book.chapter_count;
if (chapter_count <= 33) {
grid_columns = 1;
grid_rows = 33;
} else if (chapter_count <= 66) {
grid_columns = 2;
grid_rows = 33;
} else {
grid_columns = 4;
grid_rows = (int) Math.ceil((float) chapter_count / grid_columns);
}
initFontSizes(h);
final String prefix = grid_columns > 2? "": (book.shortName + " ");
// passive chapters
for (int i = 0; i < chapter_count; i++) {
if (activeChapterIndex != i) {
final int column = i / grid_rows;
final int row = i % grid_rows;
final float left = getPaddingLeft() + column * (w / grid_columns);
final float bottom = getPaddingTop() + (row + 1) * (h / grid_rows);
final String text = prefix + (i + 1);
canvas.drawText(text, left, bottom, passivePaint);
if (books[activeBookIndex].bookId == currentBookId && i + 1 == currentChapter_1) {
float textWidth = passivePaint.measureText(text);
canvas.drawRect(left - 4 * density, bottom + passiveFontMetrics.ascent, left + textWidth + 4 * density, bottom + passiveFontMetrics.descent, currentPaint);
}
}
}
// active chapter
if (activeChapterIndex != -1) {
final int column = activeChapterIndex / grid_rows;
final int row = activeChapterIndex % grid_rows;
drawActiveWithBox(canvas, w, h, column, row, prefix + (activeChapterIndex + 1));
}
}
if (state == State.selectVerse) {
final Book book = books[activeBookIndex];
final int verse_count = book.verse_counts[activeChapterIndex];
if (verse_count <= 33) {
grid_columns = 1;
grid_rows = 33;
} else if (verse_count <= 66) {
grid_columns = 2;
grid_rows = 33;
} else {
grid_columns = 4;
grid_rows = (int) Math.ceil((float) verse_count / grid_columns);
}
initFontSizes(h);
final String prefix = grid_columns > 2? ((activeChapterIndex + 1) + ":"): (book.shortName + " " + (activeChapterIndex + 1) + ":");
// passive verses
for (int i = 0; i < verse_count; i++) {
if (activeVerseIndex != i) {
final int column = i / grid_rows;
final int row = i % grid_rows;
canvas.drawText(prefix + (i + 1), getPaddingLeft() + column * (w / grid_columns), getPaddingTop() + (row + 1) * (h / grid_rows), passivePaint);
}
}
// active verse
if (activeVerseIndex != -1) {
final int column = activeVerseIndex / grid_rows;
final int row = activeVerseIndex % grid_rows;
drawActiveWithBox(canvas, w, h, column, row, prefix + (activeVerseIndex + 1));
}
}
}
private void initFontSizes(final float h) {
passivePaint.setTextSize(0.9f * h / grid_rows);
activePaint.setTextSize(h / grid_rows);
passivePaint.getFontMetrics(passiveFontMetrics);
activePaint.getFontMetrics(activeFontMetrics);
}
private void drawActiveWithBox(final Canvas canvas, final float w, final float h, final int column, final int row, final String text) {
final float textWidth = activePaint.measureText(text);
final float left = getPaddingLeft() + column * (w / grid_columns);
final float bottom = getPaddingTop() + (row + 1) * (h / grid_rows);
final float bleed = 4 * density;
canvas.drawRect(left - bleed, bottom + activeFontMetrics.ascent - bleed, left + textWidth + bleed, bottom + activeFontMetrics.descent + bleed, activeBoxPaint);
// draw text
canvas.drawText(text, left, bottom, activePaint);
}
public void onDragStart(final Version version) {
this.books = version.getConsecutiveBooks();
this.state = State.selectBook;
this.activeBookIndex = -1;
this.activeChapterIndex = -1;
this.activeVerseIndex = -1;
}
public void onDragMove(final float px, final float py) {
if (books == null) return;
final float x = px - getPaddingLeft();
final float y = py - getPaddingTop();
final float w = getWidth() - getPaddingLeft() - getPaddingRight();
final float h = getHeight() - getPaddingTop() - getPaddingBottom();
final int column = (int) (x / w * grid_columns);
final int row = (int) (y / h * grid_rows);
final int itemIndex = (y < 0 || y > h)? -1: (column * grid_rows + row);
if (state == State.selectBook) {
// check for invalid book index
if (itemIndex < 0 || itemIndex >= books.length) {
activeBookIndex = -1;
} else {
activeBookIndex = itemIndex;
}
if (activeBookIndex != previousActiveBookIndex || activeBookIndex == -1) {
removeCallbacks(checkLongPressBook);
if (activeBookIndex != -1) {
longPressBookIndex = activeBookIndex;
postDelayed(checkLongPressBook, LONG_PRESS_DELAY_MILLIS);
}
}
previousActiveBookIndex = activeBookIndex;
}
if (state == State.selectChapter) {
final Book book = books[activeBookIndex];
final int chapter_count = book.chapter_count;
if (itemIndex < 0 || itemIndex >= chapter_count) {
activeChapterIndex = -1;
} else {
activeChapterIndex = itemIndex;
}
if (activeChapterIndex != previousActiveChapterIndex || activeChapterIndex == -1) {
removeCallbacks(checkLongPressChapter);
if (activeChapterIndex != -1) {
longPressChapterIndex = activeChapterIndex;
postDelayed(checkLongPressChapter, LONG_PRESS_DELAY_MILLIS);
}
}
previousActiveChapterIndex = activeChapterIndex;
}
if (state == State.selectVerse) {
final Book book = books[activeBookIndex];
final int verse_count = book.verse_counts[activeChapterIndex];
if (itemIndex < 0 || itemIndex >= verse_count) {
activeVerseIndex = -1;
} else {
activeVerseIndex = itemIndex;
}
}
invalidate();
}
public void onDragComplete(final float x, final float y) {
complete();
removeCallbacks(checkLongPressBook);
removeCallbacks(checkLongPressChapter);
this.books = null; // prevent holding of memory
this.state = State.idle;
}
void commitBook() {
if (activeBookIndex == -1) {
// do nothing, user hasn't selected a book
return;
}
final Book book = books[activeBookIndex];
if (book.chapter_count == 1) {
// Handle books with single chapter (e.g. Jude and many more):
// Set chapter to 0 (first chapter) and immediately go to verse selection
activeChapterIndex = 0;
state = State.selectVerse;
invalidate();
} else {
state = State.selectChapter;
invalidate();
}
}
void commitChapter() {
if (activeChapterIndex == -1) {
// do nothing, user hasn't selected a chapter
return;
}
state = State.selectVerse;
invalidate();
}
public void show(int bookId, int chapter_1) {
this.currentBookId = bookId;
this.currentChapter_1 = chapter_1;
setVisibility(VISIBLE);
}
public void hide() {
setVisibility(GONE);
}
void complete() {
if (state == State.selectBook) {
if (activeBookIndex != -1) {
final Book book = books[activeBookIndex];
listener.onSelectComplete(Ari.encode(book.bookId, 1, 0));
}
}
if (state == State.selectChapter) {
if (activeBookIndex != -1 && activeChapterIndex != -1) {
final Book book = books[activeBookIndex];
listener.onSelectComplete(Ari.encode(book.bookId, activeChapterIndex + 1, 0));
}
}
if (state == State.selectVerse) {
if (activeBookIndex != -1 && activeChapterIndex != -1 && activeVerseIndex != -1) {
final Book book = books[activeBookIndex];
listener.onSelectComplete(Ari.encode(book.bookId, activeChapterIndex + 1, activeVerseIndex + 1));
}
}
}
public void setListener(final Listener listener) {
this.listener = listener;
}
}