/* * Copyright (C) 2011 Alex Kuiper * * This file is part of PageTurner * * PageTurner is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PageTurner is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PageTurner. If not, see <http://www.gnu.org/licenses/>.* */ package net.nightwhistler.pageturner.view.bookview; import android.content.Context; import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.Layout.Alignment; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.AlignmentSpan; import android.text.style.ClickableSpan; import android.text.style.ImageSpan; import android.view.View; import android.widget.TextView; import com.google.inject.Inject; import jedi.option.Option; import net.nightwhistler.pageturner.R; import net.nightwhistler.pageturner.dto.HighLight; import net.nightwhistler.pageturner.epub.PageTurnerSpine; import net.nightwhistler.pageturner.view.HighlightManager; import java.util.List; import static jedi.option.Options.none; import static jedi.option.Options.option; public class ScrollingStrategy implements PageChangeStrategy { @Inject private Context context; @Inject private HighlightManager highlightManager; private BookView bookView; private TextView childView; private int storedPosition = -1; private double storedPercentage = -1; private Spannable text; @Override public void setBookView(BookView bookView) { this.bookView = bookView; this.childView = bookView.getInnerView(); } @Override public Option<CharSequence> getNextPageText() { return none(); } @Override public Option<CharSequence> getPreviousPageText() { return none(); } @Override public int getTopLeftPosition() { if ( childView.getText().length() == 0 ) { return storedPosition; } else { int yPos = bookView.getScrollY(); return findTextOffset(findClosestLineBottom(yPos)); } } public int getProgressPosition() { return getTopLeftPosition(); } @Override public boolean isAtEnd() { int ypos = bookView.getScrollY() + bookView.getHeight(); Layout layout = this.childView.getLayout(); if ( layout == null ) { return false; } int line = layout.getLineForVertical(ypos); return line == layout.getLineCount() -1; } @Override public boolean isAtStart() { return getTopLeftPosition() == 0; } @Override public void loadText(Spanned newText) { SpannableStringBuilder builder = new SpannableStringBuilder(newText); this.text = addEndTag(builder); addHighlights(builder); } @Override public void updateGUI() { addHighlights( this.text); try { childView.setText(this.text); } catch ( IndexOutOfBoundsException i ) { this.childView.setText( this.text.toString() ); } updatePosition(); } private Spannable addEndTag(SpannableStringBuilder builder) { //Don't add the tag to the last section. PageTurnerSpine spine = bookView.getSpine(); if (spine == null || spine.getPosition() >= spine.size() -1 ) { return builder; } int length = builder.length(); builder.append("\uFFFC"); builder.append("\n"); builder.append( context.getString(R.string.end_of_section)); //If not, consider it an internal nav link. ClickableSpan span = new ClickableSpan() { @Override public void onClick(View widget) { pageDown(); } }; Drawable img = context.getResources().getDrawable(R.drawable.gateway); img.setBounds(0, 0, img.getIntrinsicWidth(), img.getIntrinsicHeight() ); builder.setSpan(new ImageSpan(img), length, length+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); builder.setSpan(span, length, builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); builder.setSpan( (AlignmentSpan) () -> Alignment.ALIGN_CENTER , length, builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return builder; } private void addHighlights( Spannable builder ) { List<HighLight> highLights = highlightManager.getHighLights( bookView.getFileName() ); for ( final HighLight highLight: highLights ) { if ( highLight.getIndex() == bookView.getIndex() ) { builder.setSpan(new HighlightSpan(highLight), highLight.getStart(), highLight.getEnd(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } } @Override public void pageDown() { this.scroll( bookView.getHeight() - 2 * bookView.getVerticalMargin()); } @Override public void pageUp() { this.scroll( (bookView.getHeight() - 2* bookView.getVerticalMargin() ) * -1); } @Override public void setPosition(int pos) { this.storedPosition = pos; updatePosition(); } @Override public void clearText() { this.childView.setText(""); this.text = null; } @Override public void setRelativePosition(double position) { this.storedPercentage = position; updatePosition(); } public void updatePosition() { if ( storedPosition == -1 && this.storedPercentage == -1d ) { return; //Hopefully come back later } if ( childView.getText().length() == 0 ) { return; } if ( storedPercentage != -1d ) { this.storedPosition = (int) (this.childView.getText().length() * storedPercentage); this.storedPercentage = -1d; } Layout layout = this.childView.getLayout(); if ( layout != null ) { int pos = Math.max(0, this.storedPosition); int line = layout.getLineForOffset(pos); if ( line > 0 ) { int newPos = layout.getLineBottom(line -1); bookView.scrollTo(0, newPos); } else { bookView.scrollTo(0, 0); } } } @Override public Option<Spanned> getText() { return option(text); } @Override public void reset() { this.storedPosition = -1; } @Override public void clearStoredPosition() { this.storedPosition = -1; } private void scroll( int delta ) { if ( this.bookView == null ) { return; } int currentPos = bookView.getScrollY(); int newPos = currentPos + delta; bookView.scrollTo(0, findClosestLineBottom(newPos)); if ( bookView.getScrollY() == currentPos ) { if ( delta < 0 ) { if (bookView.getSpine() == null || ! bookView.getSpine().navigateBack() ) { return; } } else { if (bookView.getSpine() == null || ! bookView.getSpine().navigateForward() ) { return; } } this.childView.setText(""); if ( delta > 0 ) { bookView.scrollTo(0,0); this.storedPosition = -1; } else { bookView.scrollTo(0, bookView.getHeight()); //We scrolled back up, so we want the very bottom of the text. this.storedPosition = Integer.MAX_VALUE; } bookView.loadText(); } } private int findClosestLineBottom( int ypos ) { Layout layout = this.childView.getLayout(); if ( layout == null ) { return ypos; } int currentLine = layout.getLineForVertical(ypos); if ( currentLine > 0 ) { return layout.getLineBottom(currentLine -1); } else { return 0; } } private int findTextOffset(int ypos) { Layout layout = this.childView.getLayout(); if ( layout == null ) { return 0; } return layout.getLineStart(layout.getLineForVertical(ypos)); } @Override public boolean isScrolling() { return true; } }