diff options
Diffstat (limited to 'app/src')
64 files changed, 5595 insertions, 0 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..a3fbf2d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,60 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.fox.ttcomics2" + android:versionCode="58" + android:versionName="1.18" > + + <uses-sdk + android:minSdkVersion="16" + android:targetSdkVersion="21" /> + + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.INTERNET" /> + + <application + android:name="org.fox.ttcomics2.Application" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:hardwareAccelerated="true" + android:largeHeap="true" + android:allowBackup="true"> + <activity + android:theme="@style/AppTheme" + android:name=".MainActivity" + android:label="@string/title_activity_main" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity + android:name=".PreferencesActivity" + android:label="@string/title_activity_preferences"> + </activity> + + <activity + android:name=".ViewComicActivity" + android:label="@string/title_activity_main"> + </activity> + + <activity + android:name=".CommonActivity" + android:label="@string/title_activity_main"> + </activity> + + <activity + android:name=".DirectoryPicker" + android:label="@string/title_activity_main"> + </activity> + + <activity android:name="org.acra.CrashReportDialog" + android:theme="@style/DarkDialogTheme" + android:launchMode="singleInstance" + android:excludeFromRecents="true" + android:finishOnTaskLaunch="true" /> + + </application> + +</manifest>
\ No newline at end of file diff --git a/app/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouch.java b/app/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouch.java new file mode 100644 index 0000000..73392b1 --- /dev/null +++ b/app/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouch.java @@ -0,0 +1,268 @@ +package it.sephiroth.android.library.imagezoom; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.ScaleGestureDetector.OnScaleGestureListener; +import android.view.ViewConfiguration; + +public class ImageViewTouch extends ImageViewTouchBase { + + private static final float SCROLL_DELTA_THRESHOLD = 1.0f; + static final float MIN_ZOOM = 0.9f; + protected ScaleGestureDetector mScaleDetector; + protected GestureDetector mGestureDetector; + protected int mTouchSlop; + protected float mCurrentScaleFactor; + protected float mScaleFactor; + protected int mDoubleTapDirection; + protected OnGestureListener mGestureListener; + protected OnScaleGestureListener mScaleListener; + protected boolean mDoubleTapToZoomEnabled = true; + protected boolean mScaleEnabled = true; + protected boolean mScrollEnabled = true; + + private OnImageViewTouchDoubleTapListener doubleTapListener; + + public interface OnScaleChangedListener { + public void onScaleChanged(float scale); + } + + protected OnScaleChangedListener mScaleChangedListener; + + public ImageViewTouch( Context context, AttributeSet attrs ) { + super( context, attrs ); + } + + @Override + protected void init() { + super.init(); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mGestureListener = getGestureListener(); + mScaleListener = getScaleListener(); + + mScaleDetector = new ScaleGestureDetector( getContext(), mScaleListener ); + mGestureDetector = new GestureDetector( getContext(), mGestureListener, null, true ); + + mCurrentScaleFactor = 1f; + mDoubleTapDirection = 1; + } + + public void setDoubleTapListener( OnImageViewTouchDoubleTapListener doubleTapListener ){ + this.doubleTapListener = doubleTapListener; + } + + public void setDoubleTapToZoomEnabled( boolean value ) { + mDoubleTapToZoomEnabled = value; + } + + public void setScaleEnabled( boolean value ) { + mScaleEnabled = value; + } + + public void setScrollEnabled( boolean value ) { + mScrollEnabled = value; + } + + public boolean getDoubleTapEnabled() { + return mDoubleTapToZoomEnabled; + } + + protected OnGestureListener getGestureListener() { + return new GestureListener(); + } + + protected OnScaleGestureListener getScaleListener() { + return new ScaleListener(); + } + + @Override + protected void onBitmapChanged( Drawable drawable ) { + super.onBitmapChanged( drawable ); + + float v[] = new float[9]; + mSuppMatrix.getValues( v ); + mCurrentScaleFactor = v[Matrix.MSCALE_X]; + } + + @Override + protected void _setImageDrawable( final Drawable drawable, final boolean reset, final Matrix initial_matrix, final float maxZoom ) { + super._setImageDrawable( drawable, reset, initial_matrix, maxZoom ); + mScaleFactor = getMaxZoom() / 3; + } + + @Override + public boolean onTouchEvent( MotionEvent event ) { + mScaleDetector.onTouchEvent( event ); + if ( !mScaleDetector.isInProgress() ) mGestureDetector.onTouchEvent( event ); + int action = event.getAction(); + switch ( action & MotionEvent.ACTION_MASK ) { + case MotionEvent.ACTION_UP: + if ( getScale() < 1f ) { + zoomTo( 1f, 50 ); + } + break; + } + return true; + } + + @Override + protected void onZoom( float scale ) { + super.onZoom( scale ); + if ( !mScaleDetector.isInProgress() ) mCurrentScaleFactor = scale; + + if (mScaleChangedListener != null) { + mScaleChangedListener.onScaleChanged(mCurrentScaleFactor); + } + } + + protected float onDoubleTapPost( float scale, float maxZoom ) { + if ( mDoubleTapDirection == 1 ) { + if (mCurrentScaleFactor - 1.0f < 0.01) { //( scale + ( mScaleFactor * 2 ) ) <= maxZoom + + float scaleFactor = mScaleFactor; + + RectF bitmapRect = getBitmapRect(); + + float w = bitmapRect.right - bitmapRect.left; + float h = bitmapRect.bottom - bitmapRect.top; + + if (w < getWidth()) { + scaleFactor = (float)getWidth() / w - scale; + } else if (h < getHeight()) { + scaleFactor = (float)getHeight() / h - scale; + } + + return scale + scaleFactor; + } else { + mDoubleTapDirection = -1; + return maxZoom; + } + } else { + mDoubleTapDirection = 1; + return 1f; + } + } + + /** + * Determines whether this ImageViewTouch can be scrolled. + * @param direction + * - positive direction value means scroll from right to left, + * negative value means scroll from left to right + * + * @return true if there is some more place to scroll, false - otherwise. + */ + public boolean canScroll(int direction) { + RectF bitmapRect = getBitmapRect(); + updateRect(bitmapRect, mScrollRect); + Rect imageViewRect = new Rect(); + getGlobalVisibleRect(imageViewRect); + + if (bitmapRect.right >= imageViewRect.right) { + if (direction < 0) { + return Math.abs(bitmapRect.right - imageViewRect.right) > SCROLL_DELTA_THRESHOLD; + } + } + + double bitmapScrollRectDelta = Math.abs(bitmapRect.left - mScrollRect.left); + return bitmapScrollRectDelta > SCROLL_DELTA_THRESHOLD; + } + + public class GestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onDoubleTap( MotionEvent e ) { + Log.i( LOG_TAG, "onDoubleTap. double tap enabled? " + mDoubleTapToZoomEnabled); + if (mDoubleTapToZoomEnabled) { + float scale = getScale(); + float targetScale = scale; + targetScale = onDoubleTapPost( scale, getMaxZoom() ); + targetScale = Math.min( getMaxZoom(), Math.max( targetScale, MIN_ZOOM ) ); + mCurrentScaleFactor = targetScale; + zoomTo( targetScale, e.getX(), e.getY(), 200 ); + invalidate(); + } + + if( null != doubleTapListener ){ + doubleTapListener.onDoubleTap(); + } + + return super.onDoubleTap( e ); + } + + @Override + public void onLongPress( MotionEvent e ) { + if ( isLongClickable() ) { + if ( !mScaleDetector.isInProgress() ) { + setPressed( true ); + performLongClick(); + } + } + } + + @Override + public boolean onScroll( MotionEvent e1, MotionEvent e2, float distanceX, float distanceY ) { + if ( !mScrollEnabled ) return false; + + if ( e1 == null || e2 == null ) return false; + if ( e1.getPointerCount() > 1 || e2.getPointerCount() > 1 ) return false; + if ( mScaleDetector.isInProgress() ) return false; + if ( getScale() == 1f ) return false; + scrollBy( -distanceX, -distanceY ); + invalidate(); + return super.onScroll( e1, e2, distanceX, distanceY ); + } + + @Override + public boolean onFling( MotionEvent e1, MotionEvent e2, float velocityX, float velocityY ) { + if ( !mScrollEnabled ) return false; + + if ( e1.getPointerCount() > 1 || e2.getPointerCount() > 1 ) return false; + if ( mScaleDetector.isInProgress() ) return false; + + float diffX = e2.getX() - e1.getX(); + float diffY = e2.getY() - e1.getY(); + + if ( Math.abs( velocityX ) > 800 || Math.abs( velocityY ) > 800 ) { + scrollBy( diffX / 2, diffY / 2, 300 ); + invalidate(); + } + return super.onFling( e1, e2, velocityX, velocityY ); + } + } + + public class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + + @SuppressWarnings("unused") + @Override + public boolean onScale( ScaleGestureDetector detector ) { + float span = detector.getCurrentSpan() - detector.getPreviousSpan(); + float targetScale = mCurrentScaleFactor * detector.getScaleFactor(); + if ( mScaleEnabled ) { + targetScale = Math.min( getMaxZoom(), Math.max( targetScale, MIN_ZOOM ) ); + zoomTo( targetScale, detector.getFocusX(), detector.getFocusY() ); + mCurrentScaleFactor = Math.min( getMaxZoom(), Math.max( targetScale, MIN_ZOOM ) ); + mDoubleTapDirection = 1; + invalidate(); + return true; + } + return false; + } + } + + public interface OnImageViewTouchDoubleTapListener { + void onDoubleTap(); + } + + public void setOnScaleChangedListener(OnScaleChangedListener listener) { + mScaleChangedListener = listener; + } +} diff --git a/app/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java b/app/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java new file mode 100644 index 0000000..8452a21 --- /dev/null +++ b/app/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java @@ -0,0 +1,509 @@ +package it.sephiroth.android.library.imagezoom; + +import it.sephiroth.android.library.imagezoom.easing.Cubic; +import it.sephiroth.android.library.imagezoom.easing.Easing; +import it.sephiroth.android.library.imagezoom.graphics.FastBitmapDrawable; +import it.sephiroth.android.library.imagezoom.utils.IDisposable; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.ImageView; + +/** + * Base View to manage image zoom/scrool/pinch operations + * + * @author alessandro + * + */ +public class ImageViewTouchBase extends ImageView implements IDisposable { + + public interface OnBitmapChangedListener { + + void onBitmapChanged( Drawable drawable ); + }; + + public static final String LOG_TAG = "image"; + + protected Easing mEasing = new Cubic(); + protected Matrix mBaseMatrix = new Matrix(); + protected Matrix mSuppMatrix = new Matrix(); + protected Handler mHandler = new Handler(); + protected Runnable mOnLayoutRunnable = null; + protected float mMaxZoom; + protected final Matrix mDisplayMatrix = new Matrix(); + protected final float[] mMatrixValues = new float[9]; + protected int mThisWidth = -1, mThisHeight = -1; + protected boolean mFitToScreen = false; + protected boolean mFitToWidth = false; + final protected float MAX_ZOOM = 3.0f; + + protected RectF mBitmapRect = new RectF(); + protected RectF mCenterRect = new RectF(); + protected RectF mScrollRect = new RectF(); + + private OnBitmapChangedListener mListener; + + public ImageViewTouchBase( Context context ) { + super( context ); + init(); + } + + public ImageViewTouchBase( Context context, AttributeSet attrs ) { + super( context, attrs ); + init(); + } + + public void setOnBitmapChangedListener( OnBitmapChangedListener listener ) { + mListener = listener; + } + + protected void init() { + setScaleType( ImageView.ScaleType.MATRIX ); + } + + public void clear() { + setImageBitmap( null, true ); + } + + public void setFitToScreen( boolean value ) { + if ( value != mFitToScreen ) { + mFitToScreen = value; + requestLayout(); + } + } + + public void setFitToWidth( boolean value ) { + if ( value != mFitToWidth ) { + mFitToWidth = value; + requestLayout(); + } + } + + @Override + protected void onLayout( boolean changed, int left, int top, int right, int bottom ) { + super.onLayout( changed, left, top, right, bottom ); + mThisWidth = right - left; + mThisHeight = bottom - top; + Runnable r = mOnLayoutRunnable; + if ( r != null ) { + mOnLayoutRunnable = null; + r.run(); + } + if ( getDrawable() != null ) { + if ( mFitToScreen ) + getProperBaseMatrix2( getDrawable(), mBaseMatrix ); + else + getProperBaseMatrix( getDrawable(), mBaseMatrix ); + setImageMatrix( getImageViewMatrix() ); + + if (mFitToWidth) zoomToWidth(); + } + } + + @Override + public void setImageBitmap( Bitmap bm ) { + setImageBitmap( bm, true ); + } + + @Override + public void setImageResource( int resId ) { + setImageDrawable( getContext().getResources().getDrawable( resId ) ); + } + + /** + * Set the new image to display and reset the internal matrix. + * + * @param bitmap + * - the {@link Bitmap} to display + * @param reset + * - if true the image bounds will be recreated, otherwise the old {@link Matrix} is used to display the new bitmap + * @see #setImageBitmap(Bitmap) + */ + public void setImageBitmap( final Bitmap bitmap, final boolean reset ) { + setImageBitmap( bitmap, reset, null ); + } + + /** + * Similar to {@link #setImageBitmap(Bitmap, boolean)} but an optional view {@link Matrix} can be passed to determine the new + * bitmap view matrix.<br /> + * This method is useful if you need to restore a Bitmap with the same zoom/pan values from a previous state + * + * @param bitmap + * - the {@link Bitmap} to display + * @param reset + * @param matrix + * - the {@link Matrix} to be used to display the new bitmap + * + * @see #setImageBitmap(Bitmap, boolean) + * @see #setImageBitmap(Bitmap) + * @see #getImageViewMatrix() + * @see #getDisplayMatrix() + */ + public void setImageBitmap( final Bitmap bitmap, final boolean reset, Matrix matrix ) { + setImageBitmap( bitmap, reset, matrix, -1 ); + } + + /** + * + * @param bitmap + * @param reset + * @param matrix + * @param maxZoom + * - maximum zoom allowd during zoom gestures + * + * @see #setImageBitmap(Bitmap, boolean, Matrix) + */ + public void setImageBitmap( final Bitmap bitmap, final boolean reset, Matrix matrix, float maxZoom ) { + + Log.i( LOG_TAG, "setImageBitmap: " + bitmap ); + + if ( bitmap != null ) + setImageDrawable( new FastBitmapDrawable( bitmap ), reset, matrix, maxZoom ); + else + setImageDrawable( null, reset, matrix, maxZoom ); + } + + @Override + public void setImageDrawable( Drawable drawable ) { + setImageDrawable( drawable, true, null, -1 ); + } + + public void setImageDrawable( final Drawable drawable, final boolean reset, final Matrix initial_matrix, final float maxZoom ) { + + final int viewWidth = getWidth(); + + if ( viewWidth <= 0 ) { + mOnLayoutRunnable = new Runnable() { + + @Override + public void run() { + setImageDrawable( drawable, reset, initial_matrix, maxZoom ); + } + }; + return; + } + + _setImageDrawable( drawable, reset, initial_matrix, maxZoom ); + } + + protected void _setImageDrawable( final Drawable drawable, final boolean reset, final Matrix initial_matrix, final float maxZoom ) { + + if ( drawable != null ) { + if ( mFitToScreen ) + getProperBaseMatrix2( drawable, mBaseMatrix ); + else + getProperBaseMatrix( drawable, mBaseMatrix ); + super.setImageDrawable( drawable ); + + if (mFitToWidth) zoomToWidth(); + + } else { + mBaseMatrix.reset(); + super.setImageDrawable( null ); + } + + if ( reset ) { + mSuppMatrix.reset(); + if ( initial_matrix != null ) { + mSuppMatrix = new Matrix( initial_matrix ); + } + } + + setImageMatrix( getImageViewMatrix() ); + + if ( maxZoom < 1 ) + mMaxZoom = maxZoom(); + else + mMaxZoom = maxZoom; + + onBitmapChanged( drawable ); + } + + protected void onBitmapChanged( final Drawable bitmap ) { + if ( mListener != null ) { + mListener.onBitmapChanged( bitmap ); + } + } + + protected float maxZoom() { + final Drawable drawable = getDrawable(); + + if ( drawable == null ) { + return 1F; + } + + float fw = (float) drawable.getIntrinsicWidth() / (float) mThisWidth; + float fh = (float) drawable.getIntrinsicHeight() / (float) mThisHeight; + float max = Math.max( fw, fh ) * 4; + return max; + } + + public float getMaxZoom() { + return mMaxZoom; + } + + public Matrix getImageViewMatrix() { + mDisplayMatrix.set( mBaseMatrix ); + mDisplayMatrix.postConcat( mSuppMatrix ); + return mDisplayMatrix; + } + + /** + * Returns the current image display matrix. This matrix can be used in the next call to the + * {@link #setImageBitmap(Bitmap, boolean, Matrix)} to restore the same view state of the previous {@link Bitmap} + * + * @return + */ + public Matrix getDisplayMatrix() { + return new Matrix( mSuppMatrix ); + } + + /** + * Setup the base matrix so that the image is centered and scaled properly. + * + * @param bitmap + * @param matrix + */ + protected void getProperBaseMatrix( Drawable drawable, Matrix matrix ) { + float viewWidth = getWidth(); + float viewHeight = getHeight(); + float w = drawable.getIntrinsicWidth(); + float h = drawable.getIntrinsicHeight(); + matrix.reset(); + + if ( w > viewWidth || h > viewHeight ) { + float widthScale = Math.min( viewWidth / w, 2.0f ); + float heightScale = Math.min( viewHeight / h, 2.0f ); + float scale = Math.min( widthScale, heightScale ); + matrix.postScale( scale, scale ); + float tw = ( viewWidth - w * scale ) / 2.0f; + float th = ( viewHeight - h * scale ) / 2.0f; + matrix.postTranslate( tw, th ); + } else { + float tw = ( viewWidth - w ) / 2.0f; + float th = ( viewHeight - h ) / 2.0f; + matrix.postTranslate( tw, th ); + } + } + + /** + * Setup the base matrix so that the image is centered and scaled properly. + * + * @param bitmap + * @param matrix + */ + protected void getProperBaseMatrix2( Drawable bitmap, Matrix matrix ) { + float viewWidth = getWidth(); + float viewHeight = getHeight(); + float w = bitmap.getIntrinsicWidth(); + float h = bitmap.getIntrinsicHeight(); + matrix.reset(); + float widthScale = Math.min( viewWidth / w, MAX_ZOOM ); + float heightScale = Math.min( viewHeight / h, MAX_ZOOM ); + float scale = Math.min( widthScale, heightScale ); + matrix.postScale( scale, scale ); + matrix.postTranslate( ( viewWidth - w * scale ) / 2.0f, ( viewHeight - h * scale ) / 2.0f ); + } + + protected float getValue( Matrix matrix, int whichValue ) { + matrix.getValues( mMatrixValues ); + return mMatrixValues[whichValue]; + } + + protected RectF getBitmapRect() { + final Drawable drawable = getDrawable(); + + if ( drawable == null ) return null; + Matrix m = getImageViewMatrix(); + mBitmapRect.set( 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight() ); + m.mapRect( mBitmapRect ); + return mBitmapRect; + } + + protected float getScale( Matrix matrix ) { + return getValue( matrix, Matrix.MSCALE_X ); + } + + public float getRotation() { + return 0; + } + + public float getScale() { + return getScale( mSuppMatrix ); + } + + protected void center( boolean horizontal, boolean vertical ) { + // Log.i(LOG_TAG, "center"); + final Drawable drawable = getDrawable(); + + if ( drawable == null ) return; + RectF rect = getCenter( horizontal, vertical ); + if ( rect.left != 0 || rect.top != 0 ) { + postTranslate( rect.left, rect.top ); + } + } + + protected RectF getCenter( boolean horizontal, boolean vertical ) { + final Drawable drawable = getDrawable(); + + if ( drawable == null ) return new RectF( 0, 0, 0, 0 ); + + RectF rect = getBitmapRect(); + float height = rect.height(); + float width = rect.width(); + float deltaX = 0, deltaY = 0; + if ( vertical ) { + int viewHeight = getHeight(); + if ( height < viewHeight ) { + deltaY = ( viewHeight - height ) / 2 - rect.top; + } else if ( rect.top > 0 ) { + deltaY = -rect.top; + } else if ( rect.bottom < viewHeight ) { + deltaY = getHeight() - rect.bottom; + } + } + if ( horizontal ) { + int viewWidth = getWidth(); + if ( width < viewWidth ) { + deltaX = ( viewWidth - width ) / 2 - rect.left; + } else if ( rect.left > 0 ) { + deltaX = -rect.left; + } else if ( rect.right < viewWidth ) { + deltaX = viewWidth - rect.right; + } + } + mCenterRect.set( deltaX, deltaY, 0, 0 ); + return mCenterRect; + } + + protected void postTranslate( float deltaX, float deltaY ) { + mSuppMatrix.postTranslate( deltaX, deltaY ); + setImageMatrix( getImageViewMatrix() ); + } + + protected void postScale( float scale, float centerX, float centerY ) { + mSuppMatrix.postScale( scale, scale, centerX, centerY ); + setImageMatrix( getImageViewMatrix() ); + } + + protected void zoomTo( float scale ) { + float cx = getWidth() / 2F; + float cy = getHeight() / 2F; + zoomTo( scale, cx, cy ); + } + + public void zoomTo( float scale, float durationMs ) { + float cx = getWidth() / 2F; + float cy = getHeight() / 2F; + zoomTo( scale, cx, cy, durationMs ); + } + + protected void zoomTo( float scale, float centerX, float centerY ) { + if ( scale > mMaxZoom ) scale = mMaxZoom; + float oldScale = getScale(); + float deltaScale = scale / oldScale; + postScale( deltaScale, centerX, centerY ); + onZoom( getScale() ); + center( true, true ); + } + + protected void onZoom( float scale ) {} + + public void scrollBy( float x, float y ) { + panBy( x, y ); + } + + protected void panBy( double dx, double dy ) { + RectF rect = getBitmapRect(); + mScrollRect.set( (float) dx, (float) dy, 0, 0 ); + updateRect( rect, mScrollRect ); + postTranslate( mScrollRect.left, mScrollRect.top ); + center( true, true ); + } + + protected void updateRect( RectF bitmapRect, RectF scrollRect ) { + float width = getWidth(); + float height = getHeight(); + + if ( bitmapRect.top >= 0 && bitmapRect.bottom <= height ) scrollRect.top = 0; + if ( bitmapRect.left >= 0 && bitmapRect.right <= width ) scrollRect.left = 0; + if ( bitmapRect.top + scrollRect.top >= 0 && bitmapRect.bottom > height ) scrollRect.top = (int) ( 0 - bitmapRect.top ); + if ( bitmapRect.bottom + scrollRect.top <= ( height - 0 ) && bitmapRect.top < 0 ) + scrollRect.top = (int) ( ( height - 0 ) - bitmapRect.bottom ); + if ( bitmapRect.left + scrollRect.left >= 0 ) scrollRect.left = (int) ( 0 - bitmapRect.left ); + if ( bitmapRect.right + scrollRect.left <= ( width - 0 ) ) scrollRect.left = (int) ( ( width - 0 ) - bitmapRect.right ); + // Log.d( LOG_TAG, "scrollRect(2): " + scrollRect.toString() ); + } + + protected void scrollBy( float distanceX, float distanceY, final double durationMs ) { + final double dx = distanceX; + final double dy = distanceY; + final long startTime = System.currentTimeMillis(); + mHandler.post( new Runnable() { + + double old_x = 0; + double old_y = 0; + + @Override + public void run() { + long now = System.currentTimeMillis(); + double currentMs = Math.min( durationMs, now - startTime ); + double x = mEasing.easeOut( currentMs, 0, dx, durationMs ); + double y = mEasing.easeOut( currentMs, 0, dy, durationMs ); + panBy( ( x - old_x ), ( y - old_y ) ); + old_x = x; + old_y = y; + if ( currentMs < durationMs ) { + mHandler.post( this ); + } else { + RectF centerRect = getCenter( true, true ); + if ( centerRect.left != 0 || centerRect.top != 0 ) scrollBy( centerRect.left, centerRect.top ); + } + } + } ); + } + + protected void zoomTo( float scale, final float centerX, final float centerY, final float durationMs ) { + // Log.i( LOG_TAG, "zoomTo: " + scale + ", " + centerX + ": " + centerY ); + final long startTime = System.currentTimeMillis(); + final float incrementPerMs = ( scale - getScale() ) / durationMs; + final float oldScale = getScale(); + mHandler.post( new Runnable() { + + @Override + public void run() { + long now = System.currentTimeMillis(); + float currentMs = Math.min( durationMs, now - startTime ); + float target = oldScale + ( incrementPerMs * currentMs ); + zoomTo( target, centerX, centerY ); + if ( currentMs < durationMs ) { + mHandler.post( this ); + } else { + // if ( getScale() < 1f ) {} + } + } + } ); + } + + @Override + public void dispose() { + clear(); + } + + protected void zoomToWidth() { + RectF bitmapRect = getBitmapRect(); + + float w = bitmapRect.right - bitmapRect.left; + + if (w < getWidth()) { + float scale = (float)getWidth() / w; + + zoomTo(scale, 0f, 0f); + } + } +} diff --git a/app/src/main/java/it/sephiroth/android/library/imagezoom/easing/Cubic.java b/app/src/main/java/it/sephiroth/android/library/imagezoom/easing/Cubic.java new file mode 100644 index 0000000..6f7e87d --- /dev/null +++ b/app/src/main/java/it/sephiroth/android/library/imagezoom/easing/Cubic.java @@ -0,0 +1,20 @@ +package it.sephiroth.android.library.imagezoom.easing; + +public class Cubic implements Easing { + + @Override + public double easeOut( double time, double start, double end, double duration ) { + return end * ( ( time = time / duration - 1.0 ) * time * time + 1.0 ) + start; + } + + @Override + public double easeIn( double time, double start, double end, double duration ) { + return end * ( time /= duration ) * time * time + start; + } + + @Override + public double easeInOut( double time, double start, double end, double duration ) { + if ( ( time /= duration / 2.0 ) < 1.0 ) return end / 2.0 * time * time * time + start; + return end / 2.0 * ( ( time -= 2.0 ) * time * time + 2.0 ) + start; + } +} diff --git a/app/src/main/java/it/sephiroth/android/library/imagezoom/easing/Easing.java b/app/src/main/java/it/sephiroth/android/library/imagezoom/easing/Easing.java new file mode 100644 index 0000000..202e9d9 --- /dev/null +++ b/app/src/main/java/it/sephiroth/android/library/imagezoom/easing/Easing.java @@ -0,0 +1,10 @@ +package it.sephiroth.android.library.imagezoom.easing; + +public interface Easing { + + double easeOut( double time, double start, double end, double duration ); + + double easeIn( double time, double start, double end, double duration ); + + double easeInOut( double time, double start, double end, double duration ); +} diff --git a/app/src/main/java/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java b/app/src/main/java/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java new file mode 100644 index 0000000..8afc38e --- /dev/null +++ b/app/src/main/java/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java @@ -0,0 +1,84 @@ +package it.sephiroth.android.library.imagezoom.graphics; + +import java.io.InputStream; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; + +/** + * Fast bitmap drawable. Does not support states. it only + * support alpha and colormatrix + * @author alessandro + * + */ +public class FastBitmapDrawable extends Drawable implements IBitmapDrawable { + + protected Bitmap mBitmap; + protected Paint mPaint; + + public FastBitmapDrawable( Bitmap b ) { + mBitmap = b; + mPaint = new Paint(); + mPaint.setDither( true ); + mPaint.setFilterBitmap( true ); + } + + public FastBitmapDrawable( Resources res, InputStream is ){ + this(BitmapFactory.decodeStream(is)); + } + + @Override + public void draw( Canvas canvas ) { + canvas.drawBitmap( mBitmap, 0.0f, 0.0f, mPaint ); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha( int alpha ) { + mPaint.setAlpha( alpha ); + } + + @Override + public void setColorFilter( ColorFilter cf ) { + mPaint.setColorFilter( cf ); + } + + @Override + public int getIntrinsicWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mBitmap.getHeight(); + } + + @Override + public int getMinimumWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getMinimumHeight() { + return mBitmap.getHeight(); + } + + public void setAntiAlias( boolean value ){ + mPaint.setAntiAlias( value ); + invalidateSelf(); + } + + @Override + public Bitmap getBitmap() { + return mBitmap; + } +}
\ No newline at end of file diff --git a/app/src/main/java/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java b/app/src/main/java/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java new file mode 100644 index 0000000..5a2892a --- /dev/null +++ b/app/src/main/java/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java @@ -0,0 +1,14 @@ +package it.sephiroth.android.library.imagezoom.graphics; + +import it.sephiroth.android.library.imagezoom.ImageViewTouchBase; +import android.graphics.Bitmap; + +/** + * Base interface used in the {@link ImageViewTouchBase} view + * @author alessandro + * + */ +public interface IBitmapDrawable { + + Bitmap getBitmap(); +} diff --git a/app/src/main/java/it/sephiroth/android/library/imagezoom/utils/IDisposable.java b/app/src/main/java/it/sephiroth/android/library/imagezoom/utils/IDisposable.java new file mode 100644 index 0000000..da991a7 --- /dev/null +++ b/app/src/main/java/it/sephiroth/android/library/imagezoom/utils/IDisposable.java @@ -0,0 +1,6 @@ +package it.sephiroth.android.library.imagezoom.utils; + +public interface IDisposable { + + void dispose(); +} diff --git a/app/src/main/java/org/fox/ttcomics2/Application.java b/app/src/main/java/org/fox/ttcomics2/Application.java new file mode 100644 index 0000000..8cf7c7a --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/Application.java @@ -0,0 +1,18 @@ +package org.fox.ttcomics2; + +import org.acra.ACRA; +import org.acra.ReportingInteractionMode; +import org.acra.annotation.ReportsCrashes; + +@ReportsCrashes(formKey = "", mode = ReportingInteractionMode.DIALOG, + excludeMatchingSharedPreferencesKeys = {"password"}, + resDialogText = R.string.crash_dialog_text, + formUri = "http://tt-rss.org/acra/submit/") +public class Application extends android.app.Application { + @Override + public final void onCreate() { + super.onCreate(); + ACRA.init(this); + } + +} diff --git a/app/src/main/java/org/fox/ttcomics2/ByteArrayImageDownloader.java b/app/src/main/java/org/fox/ttcomics2/ByteArrayImageDownloader.java new file mode 100644 index 0000000..9b7f920 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/ByteArrayImageDownloader.java @@ -0,0 +1,29 @@ +package org.fox.ttcomics2; + +import android.content.Context; + +import com.nostra13.universalimageloader.core.download.BaseImageDownloader; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ByteArrayImageDownloader extends BaseImageDownloader { + private static final String SCHEME_STREAM = "stream"; + private static final String STREAM_URI_PREFIX = SCHEME_STREAM + "://"; + + public ByteArrayImageDownloader(Context context) { + super(context); + } + + @Override + protected InputStream getStreamFromOtherSource(String imageUri, Object extra) throws IOException { + if (imageUri.startsWith(STREAM_URI_PREFIX)) { + InputStream is = new ByteArrayInputStream((byte[])extra); + + return is; + } else { + return super.getStreamFromOtherSource(imageUri, extra); + } + } +} diff --git a/app/src/main/java/org/fox/ttcomics2/CbzComicArchive.java b/app/src/main/java/org/fox/ttcomics2/CbzComicArchive.java new file mode 100644 index 0000000..e533ff3 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/CbzComicArchive.java @@ -0,0 +1,92 @@ +package org.fox.ttcomics2; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class CbzComicArchive extends ComicArchive { + private final String TAG = this.getClass().getSimpleName(); + + private String m_fileName; + private ZipFile m_zipFile; + private int m_count; + private ArrayList<ZipEntry> m_entries = new ArrayList<ZipEntry>(); + + @Override + public int getCount() { + return m_count; + } + + @Override + public InputStream getItem(int index) throws IOException { + return m_zipFile.getInputStream(m_entries.get(index)); + } + + protected void initialize() throws IOException { + m_zipFile = new ZipFile(m_fileName); + + Enumeration<? extends ZipEntry> e = m_zipFile.entries(); + + while (e.hasMoreElements()) { + ZipEntry ze = e.nextElement(); + if (!ze.isDirectory() && isValidComic(ze.getName())) { + m_entries.add(ze); + m_count++; + } + } + + Collections.sort(m_entries, new NaturalOrderComparator()); + } + + public CbzComicArchive(String fileName) throws IOException { + m_fileName = fileName; + + initialize(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(m_fileName); + } + + public void readFromParcel(Parcel in) { + m_fileName = in.readString(); + } + + public CbzComicArchive(Parcel in) throws IOException { + readFromParcel(in); + + initialize(); + } + + @SuppressWarnings("rawtypes") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public CbzComicArchive createFromParcel(Parcel in) { + try { + return new CbzComicArchive(in); + } catch (IOException e) { + e.printStackTrace(); + + return null; + } + } + + public CbzComicArchive[] newArray(int size) { + return new CbzComicArchive[size]; + } + }; + +} diff --git a/app/src/main/java/org/fox/ttcomics2/ComicArchive.java b/app/src/main/java/org/fox/ttcomics2/ComicArchive.java new file mode 100644 index 0000000..f85263c --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/ComicArchive.java @@ -0,0 +1,29 @@ +package org.fox.ttcomics2; + +import android.os.Parcelable; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public abstract class ComicArchive implements Parcelable { + public abstract int getCount(); + public abstract InputStream getItem(int index) throws IOException; + public boolean isValidComic(String fileName) { + return fileName.toLowerCase().matches(".*\\.(jpe?g|bmp|gif|png)$"); + } + public byte[] getByteArray(int m_page) throws IOException { + InputStream is = getItem(m_page); + + int size = 16384; + byte[] buf = new byte[size]; + int len = 0; + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + buf = new byte[size]; + while ((len = is.read(buf, 0, size)) != -1) + bos.write(buf, 0, len); + + return bos.toByteArray(); + } +} diff --git a/app/src/main/java/org/fox/ttcomics2/ComicFragment.java b/app/src/main/java/org/fox/ttcomics2/ComicFragment.java new file mode 100644 index 0000000..64a88a5 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/ComicFragment.java @@ -0,0 +1,256 @@ +package org.fox.ttcomics2; + + +import android.app.Activity; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v7.app.ActionBar; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; + +import java.io.IOException; + +import it.sephiroth.android.library.imagezoom.ImageViewTouch; + +public class ComicFragment extends Fragment implements GestureDetector.OnDoubleTapListener { + private final String TAG = this.getClass().getSimpleName(); + + private SharedPreferences m_prefs; + private int m_page; + private ViewComicActivity m_activity; + private GestureDetector m_detector; + private boolean m_thumbnail = false; + private ComicArchive m_archive; + + public ComicFragment() { + super(); + } + + public void initialize(ComicArchive archive, int page) { + m_archive = archive; + m_page = page; + } + + public void setPage(int page) { + m_page = page; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.fragment_comic, container, false); + + final ImageViewTouch image = (ImageViewTouch) view.findViewById(R.id.comic_image); + + if (savedInstanceState != null) { + m_page = savedInstanceState.getInt("page"); + m_thumbnail = savedInstanceState.getBoolean("thumbnail"); + m_archive = savedInstanceState.getParcelable("archive"); + } + + image.setFitToScreen(true); + + ImageLoader imageLoader = ImageLoader.getInstance(); + + if (m_prefs.getBoolean("fit_to_width", false)) { + image.setFitToWidth(true); + } + + try { + byte[] buf = m_archive.getByteArray(m_page); + + DisplayImageOptions options = new DisplayImageOptions.Builder() + .showImageOnFail(R.drawable.badimage) + .extraForDownloader(buf) + .build(); + + imageLoader.displayImage("stream://" + m_page, image, options); + + } catch (IOException e) { + image.setImageResource(R.drawable.badimage); + e.printStackTrace(); + } + + image.setOnScaleChangedListener(new ImageViewTouch.OnScaleChangedListener() { + @Override + public void onScaleChanged(float scale) { + // TODO: shared scale change? + } + }); + + image.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + return m_detector.onTouchEvent(event); + } + }); + + //} + + /* TextView page = (TextView) view.findViewById(R.id.comic_page); + + if (page != null) { + page.setText(String.valueOf(m_page+1)); + } */ + + return view; + + } + + private void onLeftSideTapped() { + ImageViewTouch image = (ImageViewTouch) getView().findViewById(R.id.comic_image); + + if (image != null) { + boolean atLeftEdge = !image.canScroll(1); + + if (atLeftEdge) { + m_activity.selectPreviousComic(); + } + } + } + + public boolean canScroll(int direction) { + ImageViewTouch image = (ImageViewTouch) getView().findViewById(R.id.comic_image); + + if (image != null) { + return image.canScroll(direction); + } else { + return false; + } + } + + private void onRightSideTapped() { + ImageViewTouch image = (ImageViewTouch) getView().findViewById(R.id.comic_image); + + if (image != null) { + boolean atRightEdge = !image.canScroll(-1); + + if (atRightEdge) { + m_activity.selectNextComic(); + } + } + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + + //setThumbnail(!isVisibleToUser); disabled + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_prefs = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + m_activity = (ViewComicActivity) activity; + + m_detector = new GestureDetector(m_activity, new GestureDetector.OnGestureListener() { + + @Override + public boolean onSingleTapUp(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void onShowPress(MotionEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean onDown(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + }); + + m_detector.setOnDoubleTapListener(this); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + out.putInt("page", m_page); + out.putBoolean("thumbnail", m_thumbnail); + out.putParcelable("archive", m_archive); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + + int width = getView().getWidth(); + + int x = Math.round(e.getX()); + + if (x <= width/10) { + onLeftSideTapped(); + } else if (x >= width-(width/10)) { + onRightSideTapped(); + } else { + ActionBar bar = m_activity.getSupportActionBar(); + + if (bar.isShowing()) { + bar.hide(); + m_activity.hideSeekBar(true); + + if (m_prefs.getBoolean("use_full_screen", false)) { + m_activity.hideSystemUI(true); + } + + } else { + m_activity.hideSeekBar(false); + + if (m_prefs.getBoolean("use_full_screen", false)) { + m_activity.hideSystemUI(false); + } + + bar.show(); + } + } + + return false; + } + +} diff --git a/app/src/main/java/org/fox/ttcomics2/ComicListFragment.java b/app/src/main/java/org/fox/ttcomics2/ComicListFragment.java new file mode 100644 index 0000000..9b766f7 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/ComicListFragment.java @@ -0,0 +1,461 @@ +package org.fox.ttcomics2; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.SQLException; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SimpleCursorAdapter; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.nostra13.universalimageloader.core.ImageLoader; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ComicListFragment extends Fragment implements OnItemClickListener { + private final String TAG = this.getClass().getSimpleName(); + + private final static int SIZE_DIR = -100; + + // corresponds to tab indexes + private final static int MODE_ALL = 0; + private final static int MODE_UNREAD = 1; + private final static int MODE_UNFINISHED = 2; + private final static int MODE_READ = 3; + + private CommonActivity m_activity; + private SharedPreferences m_prefs; + private ComicsListAdapter m_adapter; + private int m_mode = 0; + private String m_baseDirectory = ""; + private SwipeRefreshLayout m_swipeLayout; + + public ComicListFragment() { + super(); + } + + public void setBaseDirectory(String baseDirectory) { + m_baseDirectory = baseDirectory; + } + + public void setMode(int mode) { + m_mode = mode; + } + + private class ComicsListAdapter extends SimpleCursorAdapter { + public ComicsListAdapter(Context context, int layout, Cursor c, + String[] from, int[] to, int flags) { + super(context, layout, c, from, to, flags); + // TODO Auto-generated constructor stub + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + + Cursor c = (Cursor) getItem(position); + + String filePath = c.getString(c.getColumnIndex("path")); + String fileBaseName = c.getString(c.getColumnIndex("filename")); + String firstChild = c.getString(c.getColumnIndex("firstchild")); + + int lastPos = m_activity.getLastPosition(filePath + "/" + fileBaseName); + int size = m_activity.getSize(filePath + "/" + fileBaseName); + + if (v == null) { + LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + v = vi.inflate(R.layout.comics_grid_row, null); + } + + TextView name = (TextView) v.findViewById(R.id.file_name); + + if (name != null) { + name.setText(fileBaseName); + } + + TextView info = (TextView) v.findViewById(R.id.file_progress_info); + + if (info != null) { + if (size != -1 && size != SIZE_DIR) { + if (lastPos == size - 1) { + info.setText(getString(R.string.file_finished)); + } else if (lastPos > 0) { + info.setText(getString(R.string.file_progress_info, lastPos + 1, size, (int) (lastPos / (float) size * 100f))); + } else { + info.setText(getString(R.string.file_unread, size)); + } + info.setVisibility(View.VISIBLE); + } else if (size == SIZE_DIR) { + info.setText(getString(R.string.list_type_directory)); + info.setVisibility(View.VISIBLE); + } else { + info.setText(getString(R.string.list_type_unknown)); + info.setVisibility(View.VISIBLE); + } + } + + ProgressBar progressBar = (ProgressBar) v.findViewById(R.id.file_progress_bar); + + if (progressBar != null) { + if (size != -1 && size != SIZE_DIR) { + progressBar.setMax(size - 1); + progressBar.setProgress(lastPos); + progressBar.setEnabled(true); + progressBar.setVisibility(View.VISIBLE); + } else { + progressBar.setProgress(0); + progressBar.setMax(0); + progressBar.setEnabled(false); + progressBar.setVisibility(View.VISIBLE); + } + } + + /* ImageView overflow = (ImageView) v.findViewById(R.id.comic_overflow); + + if (overflow != null) { + if (size == SIZE_DIR) { + overflow.setVisibility(View.GONE); + } else { + overflow.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + getActivity().openContextMenu(v); + } + }); + } + } */ + + File thumbnailFile = new File(m_activity.getCacheFileName(firstChild != null ? firstChild : filePath + "/" + fileBaseName)); + + ImageView thumbnail = (ImageView) v.findViewById(R.id.thumbnail); + + if (thumbnail != null && thumbnailFile != null) { + + ImageLoader imageLoader = ImageLoader.getInstance(); + imageLoader.displayImage("file://" + thumbnailFile.getAbsolutePath(), thumbnail); + } + + return v; + } + } + + public int dpToPx(int dp) { + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)); + return px; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.fragment_comics_list, container, false); + + if (savedInstanceState != null) { + m_mode = savedInstanceState.getInt("mode"); + m_baseDirectory = savedInstanceState.getString("baseDir"); + } + + m_swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.comics_swipe_container); + + m_swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + rescan(true); + } + }); + + m_swipeLayout.setColorScheme(android.R.color.holo_green_dark, + android.R.color.holo_red_dark, + android.R.color.holo_blue_dark, + android.R.color.holo_orange_dark); + + m_adapter = new ComicsListAdapter(getActivity(), R.layout.comics_grid_row, createCursor(), + new String[] { "filename" }, new int[] { R.id.file_name }, 0); + + if (view.findViewById(R.id.comics_list) != null) { + ListView list = (ListView) view.findViewById(R.id.comics_list); + list.setAdapter(m_adapter); + list.setEmptyView(view.findViewById(R.id.no_comics)); + list.setOnItemClickListener(this); + + registerForContextMenu(list); + + } else { + GridView grid = (GridView) view.findViewById(R.id.comics_grid); + grid.setAdapter(m_adapter); + grid.setEmptyView(view.findViewById(R.id.no_comics)); + grid.setOnItemClickListener(this); + + registerForContextMenu(grid); + + } + + return view; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + + getActivity().getMenuInflater().inflate(R.menu.comic_archive_context, menu); + + AdapterContextMenuInfo info = (AdapterContextMenuInfo)menuInfo; + + Cursor c = (Cursor) m_adapter.getItem(info.position); + + if (c != null) { + menu.setHeaderTitle(c.getString(c.getColumnIndex("filename"))); + } + + super.onCreateContextMenu(menu, v, menuInfo); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item + .getMenuInfo(); + + Cursor c = (Cursor) m_adapter.getItem(info.position); + String fileName = c.getString(c.getColumnIndex("path")) + "/" + c.getString(c.getColumnIndex("filename")); + + switch (item.getItemId()) { + case R.id.menu_reset_progress: + if (fileName != null) { + m_activity.resetProgress(fileName); + m_adapter.notifyDataSetChanged(); + } + return true; + case R.id.menu_mark_as_read: + + if (fileName != null) { + m_activity.setLastPosition(fileName, m_activity.getSize(fileName)-1); + m_adapter.notifyDataSetChanged(); + } + + return true; + default: + Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId()); + return super.onContextItemSelected(item); + } + } + + private Cursor createCursor() { + String baseDir = m_baseDirectory.length() == 0 ? m_prefs.getString("comics_directory", "") : m_baseDirectory; + + String selection; + String selectionArgs[]; + + switch (m_mode) { + case MODE_READ: + selection = "path = ? AND position = size - 1"; + selectionArgs = new String[] { baseDir }; + break; + case MODE_UNFINISHED: + selection = "path = ? AND position < size AND position > 0 AND position != size - 1"; + selectionArgs = new String[] { baseDir }; + break; + case MODE_UNREAD: + selection = "path = ? AND position = 0 AND size != ?"; + selectionArgs = new String[] { baseDir, String.valueOf(SIZE_DIR) }; + break; + default: + selection = "path = ?"; + selectionArgs = new String[] { baseDir }; + } + + if (!m_prefs.getBoolean("enable_rar", false)) { + selection += " AND (UPPER(filename) NOT LIKE '%.CBR' AND UPPER(filename) NOT LIKE '%.RAR')"; + } + + return m_activity.getReadableDb().query("comics_cache", new String[] { BaseColumns._ID, "filename", "path", + "(SELECT path || '/' || filename FROM comics_cache AS t2 WHERE t2.path = comics_cache.path || '/' || comics_cache.filename AND filename != '' ORDER BY filename LIMIT 1) AS firstchild" }, + selection, selectionArgs, null, null, "size != " + SIZE_DIR + ", filename, size = " + SIZE_DIR + ", filename"); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_activity = (CommonActivity)activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + } + + protected void rescan(final boolean fullRescan) { + AsyncTask<String, Integer, Integer> rescanTask = new AsyncTask<String, Integer, Integer>() { + + @Override + protected void onProgressUpdate(Integer... progress) { + if (isAdded()) { + m_activity.setProgress(Math.round(((float)progress[0] / (float)progress[1]) * 10000)); + } + } + + @Override + protected Integer doInBackground(String... params) { + String comicsDir = params[0]; + + File dir = new File(comicsDir); + + int fileIndex = 0; + + if (dir.isDirectory()) { + File archives[] = dir.listFiles(); + + java.util.Arrays.sort(archives); + + for (File archive : archives) { + String filePath = archive.getAbsolutePath(); + + if (archive.isDirectory()) { + m_activity.setSize(filePath, SIZE_DIR); + + } else if (archive.getName().toLowerCase().matches(".*\\.(cbz|zip|cbr|rar)") && isAdded() && m_activity != null && + m_activity.getWritableDb() != null && m_activity.getWritableDb().isOpen()) { + try { + int size = m_activity.getSize(filePath); + + if (size == -1 || fullRescan) { + + ComicArchive cba = null; + + if (archive.getName().toLowerCase().matches(".*\\.(cbz|zip)")) { + cba = new CbzComicArchive(filePath); + } + + if (cba != null && cba.getCount() > 0) { + // Get cover + + try { + File thumbnailFile = new File(m_activity.getCacheFileName(filePath)); + + if (m_activity.isStorageWritable() && (!thumbnailFile.exists() || fullRescan)) { + InputStream is = cba.getItem(0); + + if (is != null) { + FileOutputStream fos = new FileOutputStream(thumbnailFile); + + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + + fos.close(); + is.close(); + } + } + + } catch (IOException e) { + e.printStackTrace(); + } + + size = cba.getCount(); + + m_activity.setSize(filePath, size); + } + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + ++fileIndex; + + publishProgress(Integer.valueOf(fileIndex), Integer.valueOf(archives.length)); + } + } + + if (isAdded() && m_activity != null) { + m_activity.cleanupCache(false); + m_activity.cleanupSqliteCache(comicsDir); + } + + return fileIndex; //m_files.size(); + } + + @Override + protected void onPostExecute(Integer result) { + if (isAdded() && m_adapter != null) { + m_adapter.changeCursor(createCursor()); + m_adapter.notifyDataSetChanged(); + + if (m_swipeLayout != null) m_swipeLayout.setRefreshing(false); + } + } + }; + + String comicsDir = m_prefs.getString("comics_directory", null); + + if (comicsDir != null && m_activity.isStorageAvailable()) { + if (m_swipeLayout != null) m_swipeLayout.setRefreshing(true); + + rescanTask.execute(m_baseDirectory.length() > 0 ? m_baseDirectory : comicsDir); + } + } + + @Override + public void onResume() { + super.onResume(); + + m_adapter.notifyDataSetChanged(); + + String comicsDir = m_prefs.getString("comics_directory", ""); + + if (m_activity.getCachedItemCount(m_baseDirectory.length() > 0 ? m_baseDirectory : comicsDir) == 0) { + rescan(false); + } else { + m_adapter.notifyDataSetChanged(); + } + } + + public void onItemClick(AdapterView<?> av, View view, int position, long id) { + //Log.d(TAG, "onItemClick position=" + position); + + Cursor c = (Cursor) m_adapter.getItem(position); + String fileName = c.getString(c.getColumnIndex("path")) + "/" + c.getString(c.getColumnIndex("filename")); + + if (fileName != null) { + m_activity.onComicArchiveSelected(fileName); + } + } + + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("mode", m_mode); + out.putString("baseDir", m_baseDirectory); + } + +} diff --git a/app/src/main/java/org/fox/ttcomics2/ComicPager.java b/app/src/main/java/org/fox/ttcomics2/ComicPager.java new file mode 100644 index 0000000..6ad4a5d --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/ComicPager.java @@ -0,0 +1,204 @@ +package org.fox.ttcomics2; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SeekBar; +import android.widget.TextView; + +import java.io.IOException; + +public class ComicPager extends Fragment { + private String m_fileName; + private SharedPreferences m_prefs; + private final String TAG = this.getClass().getSimpleName(); + private ComicArchive m_archive; + private CommonActivity m_activity; + private SeekBar m_seekBar; + private TextView m_currentPage; + private TextView m_totalPages; + + public void hideSeekBar(boolean hide) { + m_seekBar.setVisibility(hide ? View.GONE : View.VISIBLE); + m_currentPage.setVisibility(hide ? View.GONE : View.VISIBLE); + m_totalPages.setVisibility(hide ? View.GONE : View.VISIBLE); + } + + private class PagerAdapter extends FragmentStatePagerAdapter { + public PagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + ComicFragment cf = new ComicFragment(); + cf.initialize(m_archive, position); + + return cf; + } + + @Override + public int getCount() { + return m_archive.getCount(); + } + } + + private PagerAdapter m_adapter; + + public ComicPager() { + super(); + } + + public ComicArchive getArchive() { + return m_archive; + } + + public int getCount() { + return m_adapter.getCount(); + } + + public int getPosition() { + ViewPager pager = (ViewPager) getView().findViewById(R.id.comics_pager); + + if (pager != null) { + return pager.getCurrentItem(); + } + + return 0; + } + + public String getFileName() { + return m_fileName; + } + + public void setCurrentItem(int item) { + ViewPager pager = (ViewPager) getView().findViewById(R.id.comics_pager); + + if (pager != null) { + try { + pager.setCurrentItem(item); + m_currentPage.setText(String.valueOf(item + 1)); + m_totalPages.setText(String.valueOf(m_archive.getCount())); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + } + + } + + public void setFileName(String fileName) { + m_fileName = fileName; + } + + @SuppressLint("NewApi") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + final View view = inflater.inflate(R.layout.fragment_comics_pager, container, false); + + m_adapter = new PagerAdapter(getActivity().getSupportFragmentManager()); + + final ViewPager pager = (ViewPager) view.findViewById(R.id.comics_pager); + + if (savedInstanceState != null) { + m_fileName = savedInstanceState.getString("fileName"); + } + + try { + if (m_fileName.toLowerCase().matches(".*\\.(cbz|zip)")) { + m_archive = new CbzComicArchive(m_fileName); + } + + final int position = m_activity.getLastPosition(m_fileName); + + m_currentPage = (TextView) view.findViewById(R.id.comics_page); + m_totalPages = (TextView) view.findViewById(R.id.comics_total_pages); + + m_currentPage.setText(String.valueOf(position + 1)); + m_totalPages.setText(String.valueOf(m_archive.getCount())); + + m_seekBar = (SeekBar) view.findViewById(R.id.comics_seek_bar); + m_seekBar.setMax(m_archive.getCount()-1); + m_seekBar.setProgress(position); + m_seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + setCurrentItem(progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + pager.setAdapter(m_adapter); + pager.setCurrentItem(position); + + m_activity.onComicSelected(m_fileName, position); + + if (m_prefs.getBoolean("use_full_screen", false)) { + m_activity.hideSystemUI(true); + hideSeekBar(true); + } + + } catch (IOException e) { + m_activity.toast(R.string.error_could_not_open_comic_archive); + e.printStackTrace(); + } + + pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + public void onPageSelected(int position) { + + m_activity.onComicSelected(m_fileName, position); + m_seekBar.setProgress(position); + + if (m_prefs.getBoolean("use_full_screen", false)) { + m_activity.hideSystemUI(true); + } + } + + public void onPageScrolled(int arg0, float arg1, int arg2) { + // TODO Auto-generated method stub + } + + public void onPageScrollStateChanged(int arg0) { + // TODO Auto-generated method stub + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + m_activity = (CommonActivity) activity; + + m_prefs = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putString("fileName", m_fileName); + } + +} diff --git a/app/src/main/java/org/fox/ttcomics2/CommonActivity.java b/app/src/main/java/org/fox/ttcomics2/CommonActivity.java new file mode 100644 index 0000000..2095d87 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/CommonActivity.java @@ -0,0 +1,605 @@ +package org.fox.ttcomics2; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.graphics.BitmapFactory; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.provider.BaseColumns; +import android.support.v7.app.ActionBarActivity; +import android.util.Log; +import android.view.Display; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; + +import java.io.File; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; + +public class CommonActivity extends ActionBarActivity { + private final String TAG = this.getClass().getSimpleName(); + + protected static final String FRAG_COMICS_PAGER = "comic_pager"; + protected static final String FRAG_COMICS_LIST = "comics_list"; + + protected final static int REQUEST_SHARE = 1; + protected static final int REQUEST_VIEWCOMIC = 2; + + protected SharedPreferences m_prefs; + protected SyncClient m_syncClient = new SyncClient(); + + private boolean m_smallScreenMode = true; + + private boolean m_storageAvailable; + private boolean m_storageWritable; + + private SQLiteDatabase m_readableDb; + private SQLiteDatabase m_writableDb; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (!ImageLoader.getInstance().isInited()) { + + DisplayImageOptions options = new DisplayImageOptions.Builder() + .cacheInMemory(true) + .resetViewBeforeLoading(true) + .cacheOnDisk(false) + .displayer(new FadeInBitmapDisplayer(250)) + .build(); + + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this) + .defaultDisplayImageOptions(options) + .imageDownloader(new ByteArrayImageDownloader(this)) + .build(); + + ImageLoader.getInstance().init(config); + } + + m_prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + + initDatabase(); + initSync(); + } + + @Override + public void onResume() { + super.onResume(); + + String state = Environment.getExternalStorageState(); + + if (Environment.MEDIA_MOUNTED.equals(state)) { + m_storageAvailable = true; + m_storageWritable = true; + } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { + m_storageAvailable = true; + m_storageWritable = false; + } else { + m_storageAvailable = false; + m_storageWritable = false; + } + + initSync(); + } + + private void initSync() { + if (m_prefs.getBoolean("use_position_sync", false)) { + String googleAccount = getGoogleAccount(); + + if (googleAccount != null) { + m_syncClient.setOwner(googleAccount); + } else { + if (Build.HARDWARE.contains("goldfish")) { + m_syncClient.setOwner("TEST-ACCOUNT"); + + //toast(R.string.sync_running_in_test_mode); + } else { + m_syncClient.setOwner(null); + toast(R.string.error_sync_no_account); + + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putBoolean("use_position_sync", false); + editor.commit(); + } + } + } else { + m_syncClient.setOwner(null); + } + } + + protected void setSmallScreen(boolean smallScreen) { + Log.d(TAG, "m_smallScreenMode=" + smallScreen); + m_smallScreenMode = smallScreen; + } + + public boolean isSmallScreen() { + return m_smallScreenMode; + } + + + public void onComicArchiveSelected(String fileName) { + // + } + + public Cursor findComicByFileName(String fileName) { + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", null, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + return c; + } else { + c.close(); + + SQLiteStatement stmt = getWritableDb().compileStatement("INSERT INTO comics_cache " + + "(filename, path, position, max_position, size, checksum) VALUES (?, ?, 0, 0, -1, '')"); + stmt.bindString(1, file.getName()); + stmt.bindString(2, file.getParentFile().getAbsolutePath()); + stmt.execute(); + + c = getReadableDb().query("comics_cache", null, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + return c; + } else { + c.close(); + } + } + + return null; + } + + public void setSize(String fileName, int size) { + Cursor c = findComicByFileName(fileName); + + if (c != null) { + c.close(); + + File file = new File(fileName); + + SQLiteStatement stmt = getWritableDb().compileStatement("UPDATE comics_cache SET size = ? WHERE filename = ? AND path = ?"); + stmt.bindLong(1, size); + stmt.bindString(2, file.getName()); + stmt.bindString(3, file.getParentFile().getAbsolutePath()); + stmt.execute(); + stmt.close(); + } + } + + public void setChecksum(String fileName, String checksum) { + Cursor c = findComicByFileName(fileName); + + if (c != null) { + c.close(); + + File file = new File(fileName); + + SQLiteStatement stmt = getWritableDb().compileStatement("UPDATE comics_cache SET checksum = ? WHERE filename = ? AND path = ?"); + stmt.bindString(1, checksum); + stmt.bindString(2, file.getName()); + stmt.bindString(3, file.getParentFile().getAbsolutePath()); + stmt.execute(); + stmt.close(); + } + } + + public void resetProgress(final String fileName) { + + if (m_prefs.getBoolean("use_position_sync", false) && m_syncClient.hasOwner()) { + setLastPosition(fileName, 0); + setLastMaxPosition(fileName, 0); + + AlertDialog.Builder builder = new AlertDialog.Builder(CommonActivity.this); + builder.setMessage(R.string.reset_remove_synced_progress) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + m_syncClient.clearData(sha1(new File(fileName).getName())); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + + } + + } + + public void setLastPosition(String fileName, int position) { + int lastPosition = getLastPosition(fileName); + + Cursor c = findComicByFileName(fileName); + + if (c != null) { + c.close(); + + File file = new File(fileName); + + SQLiteStatement stmt = getWritableDb().compileStatement("UPDATE comics_cache SET position = ?, max_position = ? WHERE filename = ? AND path = ?"); + stmt.bindLong(1, position); + stmt.bindLong(2, Math.max(position, lastPosition)); + stmt.bindString(3, file.getName()); + stmt.bindString(4, file.getParentFile().getAbsolutePath()); + stmt.execute(); + stmt.close(); + } + + } + + public void setLastMaxPosition(String fileName, int position) { + + Cursor c = findComicByFileName(fileName); + + if (c != null) { + c.close(); + + File file = new File(fileName); + + SQLiteStatement stmt = getWritableDb().compileStatement("UPDATE comics_cache SET max_position = ? WHERE filename = ? AND path = ?"); + stmt.bindLong(1, position); + stmt.bindString(2, file.getName()); + stmt.bindString(3, file.getParentFile().getAbsolutePath()); + stmt.execute(); + stmt.close(); + } + } + + public String getChecksum(String fileName) { + String checksum = null; + + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", new String[] { "checksum" }, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + checksum = c.getString(c.getColumnIndex("checksum")); + } + + c.close(); + + return checksum; + + } + + + public int getLastPosition(String fileName) { + int position = 0; + + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", new String[] { "position" }, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + position = c.getInt(c.getColumnIndex("position")); + } + + c.close(); + + return position; + + } + + public int getCachedItemCount(String baseDir) { + Cursor c = getReadableDb().query("comics_cache", new String[] { "COUNT(*)" }, + "path = ?", + new String[] { baseDir }, null, null, null); + + c.moveToFirst(); + int count = c.getInt(0); + c.close(); + + //Log.d(TAG, "getCachedItemCount:" + baseDir + "=" + count); + + return count; + } + + public int getMaxPosition(String fileName) { + int position = 0; + + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", new String[] { "max_position" }, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + position = c.getInt(c.getColumnIndex("max_position")); + } + + c.close(); + + return position; + } + + public int getSize(String fileName) { + int size = -1; + + File file = new File(fileName); + + Cursor c = getReadableDb().query("comics_cache", new String[] { "size" }, + "filename = ? AND path = ?", + new String[] { file.getName(), file.getParentFile().getAbsolutePath() }, null, null, null); + + if (c.moveToFirst()) { + size = c.getInt(c.getColumnIndex("size")); + } + + c.close(); + + //Log.d(TAG, "getSize:" + fileName + "=" + size); + + return size; + } + + public void onComicSelected(String fileName, int position) { + setLastPosition(fileName, position); + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_rescan: + ComicListFragment frag = (ComicListFragment) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_LIST); + + if (frag != null && frag.isAdded()) { + frag.rescan(true); + } + + return true; + case R.id.menu_settings: + Intent intent = new Intent(CommonActivity.this, + PreferencesActivity.class); + startActivityForResult(intent, 0); + return true; + default: + Log.d(TAG, + "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + @SuppressWarnings("deprecation") + public boolean isPortrait() { + Display display = getWindowManager().getDefaultDisplay(); + + int width = display.getWidth(); + int height = display.getHeight(); + + return width < height; + } + + protected static String md5(String s) { + try { + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + StringBuffer hexString = new StringBuffer(); + for (int i=0; i<messageDigest.length; i++) + hexString.append(Integer.toHexString(0xFF & messageDigest[i])); + + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return null; + } + + protected static String sha1(String s) { + if (s != null) { + try { + MessageDigest digest = java.security.MessageDigest.getInstance("SHA1"); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + StringBuffer hexString = new StringBuffer(); + for (int i=0; i<messageDigest.length; i++) + hexString.append(Integer.toHexString(0xFF & messageDigest[i])); + + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + return null; + } + + public String getCacheFileName(String fileName) { + String hash = md5(fileName); + + File file = new File(getExternalCacheDir().getAbsolutePath() + "/" + hash + ".png"); + + return file.getAbsolutePath(); + } + + public String getGoogleAccount() { + AccountManager manager = (AccountManager) getSystemService(ACCOUNT_SERVICE); + Account[] list = manager.getAccounts(); + + for (Account account: list) { + if (account.type.equalsIgnoreCase("com.google")) { + return account.name; + } + } + return null; + } + + public void toast(int msgId) { + try { + Toast toast = Toast.makeText(CommonActivity.this, msgId, Toast.LENGTH_SHORT); + toast.show(); + } catch (RuntimeException e) { + // might happen if UI lags + } + } + + public void toast(String msg) { + try { + Toast toast = Toast.makeText(CommonActivity.this, msg, Toast.LENGTH_SHORT); + toast.show(); + } catch (RuntimeException e) { + // might happen if UI lags + } + } + + public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + if (width > height) { + inSampleSize = Math.round((float)height / (float)reqHeight); + } else { + inSampleSize = Math.round((float)width / (float)reqWidth); + } + } + return inSampleSize; + } + + + public void cleanupCache(boolean deleteAll) { + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + File cachePath = getExternalCacheDir(); + + long now = new Date().getTime(); + + if (cachePath.isDirectory()) { + for (File file : cachePath.listFiles()) { + if (deleteAll || now - file.lastModified() > 1000*60*60*24*7) { + file.delete(); + } + } + } + } + } + + public boolean isStorageAvailable() { + return m_storageAvailable; + } + + public boolean isStorageWritable() { + return m_storageWritable; + } + + private void initDatabase() { + DatabaseHelper dh = new DatabaseHelper(getApplicationContext()); + + m_writableDb = dh.getWritableDatabase(); + m_readableDb = dh.getReadableDatabase(); + } + + public synchronized SQLiteDatabase getReadableDb() { + return m_readableDb; + } + + public synchronized SQLiteDatabase getWritableDb() { + return m_writableDb; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + m_readableDb.close(); + m_writableDb.close(); + } + + public void selectPreviousComic() { + ComicPager frag = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (frag != null && frag.isAdded() && frag.getPosition() > 0) { + frag.setCurrentItem(frag.getPosition() - 1); + } + } + + public void selectNextComic() { + ComicPager frag = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (frag != null && frag.isAdded() && frag.getPosition() < frag.getCount()-1) { + frag.setCurrentItem(frag.getPosition() + 1); + } + } + + public void cleanupSqliteCache(String baseDir) { + Cursor c = getReadableDb().query("comics_cache", null, + null, null, null, null, null); + + if (c.moveToFirst()) { + while (!c.isAfterLast()) { + int id = c.getInt(c.getColumnIndex(BaseColumns._ID)); + String fileName = c.getString(c.getColumnIndex("path")) + "/" + c.getString(c.getColumnIndex("filename")); + + File file = new File(fileName); + + if (!file.exists()) { + SQLiteStatement stmt = getWritableDb().compileStatement("DELETE FROM comics_cache WHERE " + BaseColumns._ID + " = ?"); + stmt.bindLong(1, id); + stmt.execute(); + } + + c.moveToNext(); + } + } + + c.close(); + } + + public void hideSystemUI(boolean hide) { + View decorView = getWindow().getDecorView(); + + if (hide) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE); + + } else { + decorView.setSystemUiVisibility(View.STATUS_BAR_HIDDEN | View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + + getSupportActionBar().hide(); + + } else { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + } + +} diff --git a/app/src/main/java/org/fox/ttcomics2/DatabaseHelper.java b/app/src/main/java/org/fox/ttcomics2/DatabaseHelper.java new file mode 100644 index 0000000..bf66fec --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/DatabaseHelper.java @@ -0,0 +1,68 @@ +package org.fox.ttcomics2; + +import java.io.File; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import android.provider.BaseColumns; + +public class DatabaseHelper extends SQLiteOpenHelper { + + public static final String DATABASE_NAME = "ComicsCache.db"; + public static final int DATABASE_VERSION = 2; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS comics_cache;"); + + db.execSQL("CREATE TABLE IF NOT EXISTS comics_cache (" + + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + "filename TEXT, " + + "path TEXT, " + //v2 + "checksum TEXT, " + //v2 + "size INTEGER, " + + "position INTEGER, " + + "max_position INTEGER" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion == 1 && newVersion == 2) { + + db.execSQL("ALTER TABLE comics_cache ADD COLUMN path TEXT;"); + db.execSQL("ALTER TABLE comics_cache ADD COLUMN checksum TEXT;"); + + Cursor c = db.query("comics_cache", null, + null, null, null, null, null); + + if (c.moveToFirst()) { + while (!c.isAfterLast()) { + int id = c.getInt(c.getColumnIndex(BaseColumns._ID)); + String fileName = c.getString(c.getColumnIndex("filename")); + + File file = new File(fileName); + + SQLiteStatement stmt = db.compileStatement("UPDATE comics_cache SET filename = ?, path = ? WHERE " + BaseColumns._ID + " = ?"); + stmt.bindString(1, file.getName()); + stmt.bindString(2, file.getParentFile().getAbsolutePath()); + stmt.bindLong(3, id); + stmt.execute(); + + c.moveToNext(); + } + } + + c.close(); + } + } + +} diff --git a/app/src/main/java/org/fox/ttcomics2/DirectoryPicker.java b/app/src/main/java/org/fox/ttcomics2/DirectoryPicker.java new file mode 100644 index 0000000..0c75f09 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/DirectoryPicker.java @@ -0,0 +1,169 @@ +package org.fox.ttcomics2; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; + +import android.app.ListActivity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +/** +Copyright (C) 2011 by Brad Greco <[email protected]> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ + +public class DirectoryPicker extends ListActivity { + + public static final String START_DIR = "startDir"; + public static final String ONLY_DIRS = "onlyDirs"; + public static final String SHOW_HIDDEN = "showHidden"; + public static final String CHOSEN_DIRECTORY = "chosenDir"; + public static final int PICK_DIRECTORY = 43522432; + private File dir; + private boolean showHidden = false; + private boolean onlyDirs = true ; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle extras = getIntent().getExtras(); + dir = Environment.getExternalStorageDirectory(); + if (extras != null) { + String preferredStartDir = extras.getString(START_DIR); + showHidden = extras.getBoolean(SHOW_HIDDEN, false); + onlyDirs = extras.getBoolean(ONLY_DIRS, true); + if(preferredStartDir != null) { + File startDir = new File(preferredStartDir); + if(startDir.isDirectory()) { + dir = startDir; + } + } + } + + setContentView(R.layout.chooser_list); + setTitle(dir.getAbsolutePath()); + Button btnChoose = (Button) findViewById(R.id.btnChoose); + String name = dir.getName(); + if(name.length() == 0) + name = "/"; + btnChoose.setText(getString(R.string.picker_choose, name)); + btnChoose.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + returnDir(dir.getAbsolutePath()); + } + }); + + Button btnParent = (Button) findViewById(R.id.btnParent); + btnParent.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(DirectoryPicker.this, DirectoryPicker.class); + intent.putExtra(DirectoryPicker.START_DIR, dir.getParent()); + intent.putExtra(DirectoryPicker.SHOW_HIDDEN, showHidden); + intent.putExtra(DirectoryPicker.ONLY_DIRS, onlyDirs); + startActivityForResult(intent, PICK_DIRECTORY); + } + }); + + if (dir.getParent() == null) { + btnParent.setVisibility(View.GONE); + } + + ListView lv = getListView(); + lv.setTextFilterEnabled(true); + + if(!dir.canRead()) { + Context context = getApplicationContext(); + String msg = getString(R.string.error_could_not_read_folder_contents_); + Toast toast = Toast.makeText(context, msg, Toast.LENGTH_LONG); + toast.show(); + return; + } + + final ArrayList<File> files = filter(dir.listFiles(), onlyDirs, showHidden); + String[] names = names(files); + setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, names)); + + + lv.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if(!files.get(position).isDirectory()) + return; + String path = files.get(position).getAbsolutePath(); + Intent intent = new Intent(DirectoryPicker.this, DirectoryPicker.class); + intent.putExtra(DirectoryPicker.START_DIR, path); + intent.putExtra(DirectoryPicker.SHOW_HIDDEN, showHidden); + intent.putExtra(DirectoryPicker.ONLY_DIRS, onlyDirs); + startActivityForResult(intent, PICK_DIRECTORY); + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if(requestCode == PICK_DIRECTORY && resultCode == RESULT_OK) { + Bundle extras = data.getExtras(); + String path = (String) extras.get(DirectoryPicker.CHOSEN_DIRECTORY); + returnDir(path); + } + } + + private void returnDir(String path) { + Intent result = new Intent(); + result.putExtra(CHOSEN_DIRECTORY, path); + setResult(RESULT_OK, result); + finish(); + } + + public ArrayList<File> filter(File[] file_list, boolean onlyDirs, boolean showHidden) { + ArrayList<File> files = new ArrayList<File>(); + + for(File file: file_list) { + if(onlyDirs && !file.isDirectory()) + continue; + if(!showHidden && file.isHidden()) + continue; + files.add(file); + } + Collections.sort(files); + + return files; + } + + public String[] names(ArrayList<File> files) { + String[] names = new String[files.size()]; + int i = 0; + for(File file: files) { + names[i] = file.getName(); + i++; + } + return names; + } +} + diff --git a/app/src/main/java/org/fox/ttcomics2/MainActivity.java b/app/src/main/java/org/fox/ttcomics2/MainActivity.java new file mode 100644 index 0000000..9dda29b --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/MainActivity.java @@ -0,0 +1,228 @@ +package org.fox.ttcomics2; + + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.ActionBar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import java.io.File; + +import it.neokree.materialtabs.MaterialTab; +import it.neokree.materialtabs.MaterialTabHost; +import it.neokree.materialtabs.MaterialTabListener; + +public class MainActivity extends CommonActivity implements MaterialTabListener { + private final String TAG = this.getClass().getSimpleName(); + private final static int TRIAL_DAYS = 7; + + private int m_selectedTab; + private String m_baseDirectory = ""; + private String m_fileName = ""; + private MaterialTabHost tabHost; + + @SuppressLint("NewApi") + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + setTitle(R.string.app_name); + + tabHost = (MaterialTabHost) this.findViewById(R.id.materialTabHost); + + if (savedInstanceState == null) { + m_selectedTab = getIntent().getIntExtra("selectedTab", 0); + + Log.d(TAG, "selTab=" + m_selectedTab); + + ComicListFragment frag = new ComicListFragment(); + frag.setMode(m_selectedTab); + + if (getIntent().getStringExtra("baseDir") != null) { + m_baseDirectory = getIntent().getStringExtra("baseDir"); + frag.setBaseDirectory(m_baseDirectory); + } + + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.replace(R.id.comics_list, frag, FRAG_COMICS_LIST); + ft.commit(); + + m_selectedTab = -1; + } else { + m_selectedTab = -1; + m_baseDirectory = savedInstanceState.getString("baseDir"); + m_fileName = savedInstanceState.getString("fileName"); + } + + tabHost.addTab(tabHost.newTab() + .setText(getString(R.string.tab_all_comics)) + .setTabListener(this)); + + tabHost.addTab(tabHost.newTab() + .setText(getString(R.string.tab_unread)) + .setTabListener(this)); + + tabHost.addTab(tabHost.newTab() + .setText(getString(R.string.tab_unfinished)) + .setTabListener(this)); + + tabHost.addTab(tabHost.newTab() + .setText(getString(R.string.tab_read)) + .setTabListener(this)); + + if (savedInstanceState != null) { + m_selectedTab = savedInstanceState.getInt("selectedTab"); + } else { + m_selectedTab = getIntent().getIntExtra("selectedTab", 0); + } + + if (m_selectedTab != -1) + tabHost.setSelectedNavigationItem(m_selectedTab); + + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(m_baseDirectory.length() > 0); + + if (m_prefs.getString("comics_directory", null) == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.dialog_need_prefs_message) + .setCancelable(false) + .setPositiveButton(R.string.dialog_need_prefs_preferences, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // launch preferences + + Intent intent = new Intent(MainActivity.this, + PreferencesActivity.class); + startActivityForResult(intent, 0); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + + //((ViewGroup)findViewById(R.id.comics_list)).setLayoutTransition(new LayoutTransition()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.activity_main, menu); + + return true; + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putInt("selectedTab", m_selectedTab); + out.putString("baseDir", m_baseDirectory); + out.putString("fileName", m_fileName); + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + if (m_baseDirectory.length() > 0) { + finish(); + } + return true; + default: + Log.d(TAG, + "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onComicArchiveSelected(String fileName) { + super.onComicArchiveSelected(fileName); + + File file = new File(fileName); + + if (file.isDirectory()) { + Intent intent = new Intent(MainActivity.this, + MainActivity.class); + + intent.putExtra("baseDir", fileName); + intent.putExtra("selectedTab", m_selectedTab); + + startActivityForResult(intent, 0); + + } else if (file.canRead()) { + Intent intent = new Intent(MainActivity.this, + ViewComicActivity.class); + + intent.putExtra("fileName", fileName); + m_fileName = fileName; + + startActivityForResult(intent, REQUEST_VIEWCOMIC); + } else { + toast(getString(R.string.error_cant_open_file, fileName)); + + ComicListFragment frag = (ComicListFragment) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_LIST); + + if (frag != null && frag.isAdded()) { + frag.rescan(true); + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == REQUEST_VIEWCOMIC) { + Log.d(TAG, "finished viewing comic: " + m_fileName); + + if (m_prefs.getBoolean("use_position_sync", false) && m_syncClient.hasOwner()) { + toast(R.string.sync_uploading); + m_syncClient.setPosition(sha1(new File(m_fileName).getName()), getLastPosition(m_fileName)); + } + } + + System.gc(); + + super.onActivityResult(requestCode, resultCode, intent); + } + + @Override + public void onTabSelected(MaterialTab tab) { + + tabHost.setSelectedNavigationItem(tab.getPosition()); + + FragmentTransaction sft = getSupportFragmentManager().beginTransaction(); + + if (m_selectedTab != tab.getPosition() && m_selectedTab != -1) { + + ComicListFragment frag = new ComicListFragment(); + frag.setMode(tab.getPosition()); + + frag.setBaseDirectory(m_baseDirectory); + + sft.replace(R.id.comics_list, frag, FRAG_COMICS_LIST); + } + + m_selectedTab = tab.getPosition(); + + sft.commit(); + } + + @Override + public void onTabReselected(MaterialTab materialTab) { + + } + + @Override + public void onTabUnselected(MaterialTab materialTab) { + + } +} diff --git a/app/src/main/java/org/fox/ttcomics2/NaturalOrderComparator.java b/app/src/main/java/org/fox/ttcomics2/NaturalOrderComparator.java new file mode 100644 index 0000000..d7522f9 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/NaturalOrderComparator.java @@ -0,0 +1,188 @@ +package org.fox.ttcomics2; + +/* + NaturalOrderComparator.java -- Perform 'natural order' comparisons of strings in Java. + Copyright (C) 2003 by Pierre-Luc Paour <[email protected]> + + Based on the C version by Martin Pool, of which this is more or less a straight conversion. + Copyright (C) 2000 by Martin Pool <[email protected]> + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + */ + +import java.util.*; + +public class NaturalOrderComparator implements Comparator +{ + int compareRight(String a, String b) + { + int bias = 0; + int ia = 0; + int ib = 0; + + // The longest run of digits wins. That aside, the greatest + // value wins, but we can't know that it will until we've scanned + // both numbers to know that they have the same magnitude, so we + // remember it in BIAS. + for (;; ia++, ib++) + { + char ca = charAt(a, ia); + char cb = charAt(b, ib); + + if (!Character.isDigit(ca) && !Character.isDigit(cb)) + { + return bias; + } + else if (!Character.isDigit(ca)) + { + return -1; + } + else if (!Character.isDigit(cb)) + { + return +1; + } + else if (ca < cb) + { + if (bias == 0) + { + bias = -1; + } + } + else if (ca > cb) + { + if (bias == 0) + bias = +1; + } + else if (ca == 0 && cb == 0) + { + return bias; + } + } + } + + public int compare(Object o1, Object o2) + { + String a = o1.toString(); + String b = o2.toString(); + + int ia = 0, ib = 0; + int nza = 0, nzb = 0; + char ca, cb; + int result; + + while (true) + { + // only count the number of zeroes leading the last number compared + nza = nzb = 0; + + ca = charAt(a, ia); + cb = charAt(b, ib); + + // skip over leading spaces or zeros + while (Character.isSpaceChar(ca) || ca == '0') + { + if (ca == '0') + { + nza++; + } + else + { + // only count consecutive zeroes + nza = 0; + } + + ca = charAt(a, ++ia); + } + + while (Character.isSpaceChar(cb) || cb == '0') + { + if (cb == '0') + { + nzb++; + } + else + { + // only count consecutive zeroes + nzb = 0; + } + + cb = charAt(b, ++ib); + } + + // process run of digits + if (Character.isDigit(ca) && Character.isDigit(cb)) + { + if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) + { + return result; + } + } + + if (ca == 0 && cb == 0) + { + // The strings compare the same. Perhaps the caller + // will want to call strcmp to break the tie. + return nza - nzb; + } + + if (ca < cb) + { + return -1; + } + else if (ca > cb) + { + return +1; + } + + ++ia; + ++ib; + } + } + + static char charAt(String s, int i) + { + if (i >= s.length()) + { + return 0; + } + else + { + return s.charAt(i); + } + } + + public static void main(String[] args) + { + String[] strings = new String[] { "1-2", "1-02", "1-20", "10-20", "fred", "jane", "pic01", + "pic2", "pic02", "pic02a", "pic3", "pic4", "pic 4 else", "pic 5", "pic05", "pic 5", + "pic 5 something", "pic 6", "pic 7", "pic100", "pic100a", "pic120", "pic121", + "pic02000", "tom", "x2-g8", "x2-y7", "x2-y08", "x8-y8" }; + + List orig = Arrays.asList(strings); + + System.out.println("Original: " + orig); + + List scrambled = Arrays.asList(strings); + Collections.shuffle(scrambled); + + System.out.println("Scrambled: " + scrambled); + + Collections.sort(scrambled, new NaturalOrderComparator()); + + System.out.println("Sorted: " + scrambled); + } +}
\ No newline at end of file diff --git a/app/src/main/java/org/fox/ttcomics2/PreferencesActivity.java b/app/src/main/java/org/fox/ttcomics2/PreferencesActivity.java new file mode 100644 index 0000000..f6db9cc --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/PreferencesActivity.java @@ -0,0 +1,156 @@ +package org.fox.ttcomics2; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.widget.Toast; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class PreferencesActivity extends PreferenceActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + addPreferencesFromResource(R.xml.preferences); + + Preference dirPref = findPreference("comics_directory"); + dirPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(PreferencesActivity.this, DirectoryPicker.class); + + intent.putExtra(DirectoryPicker.START_DIR, prefs.getString("comics_directory", + Environment.getExternalStorageDirectory().getAbsolutePath())); + + startActivityForResult(intent, DirectoryPicker.PICK_DIRECTORY); + return true; + } + }); + + Preference clearPref = findPreference("clear_sync_data"); + clearPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AlertDialog.Builder builder = new AlertDialog.Builder(PreferencesActivity.this); + builder.setMessage(R.string.dialog_clear_data_title) + .setCancelable(false) + .setPositiveButton(R.string.dialog_clear_data, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + + String googleAccount = getGoogleAccount(); + SyncClient m_syncClient = new SyncClient(); + + if (googleAccount != null) { + m_syncClient.setOwner(googleAccount); + } else { + if (Build.HARDWARE.equals("goldfish")) { + m_syncClient.setOwner("TEST-ACCOUNT"); + } else { + m_syncClient.setOwner(null); + + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean("use_position_sync", false); + editor.commit(); + + Toast toast = Toast.makeText(PreferencesActivity.this, R.string.error_sync_no_account, Toast.LENGTH_SHORT); + toast.show(); + } + } + + if (m_syncClient.hasOwner()) { + m_syncClient.clearData(); + } + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + + return false; + } + }); + + String version = "?"; + int versionCode = -1; + String buildTimestamp = "N/A"; + + try { + PackageInfo packageInfo = getPackageManager(). + getPackageInfo(getPackageName(), 0); + + version = packageInfo.versionName; + versionCode = packageInfo.versionCode; + + ApplicationInfo appInfo = getPackageManager(). + getApplicationInfo(getPackageName(), 0); + + ZipFile zf = new ZipFile(appInfo.sourceDir); + ZipEntry ze = zf.getEntry("classes.dex"); + long time = ze.getTime(); + + buildTimestamp = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss", + Locale.getDefault()).format(time); + + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + findPreference("version").setSummary(getString(R.string.prefs_version, version, versionCode)); + findPreference("build_timestamp").setSummary(getString(R.string.prefs_build_timestamp, buildTimestamp)); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if(requestCode == DirectoryPicker.PICK_DIRECTORY && resultCode == RESULT_OK) { + Bundle extras = data.getExtras(); + String path = (String) extras.get(DirectoryPicker.CHOSEN_DIRECTORY); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("comics_directory", path); + editor.commit(); + + } + } + + public String getGoogleAccount() { + AccountManager manager = (AccountManager) getSystemService(ACCOUNT_SERVICE); + Account[] list = manager.getAccounts(); + + for (Account account: list) { + if (account.type.equalsIgnoreCase("com.google")) { + return account.name; + } + } + return null; + } +} diff --git a/app/src/main/java/org/fox/ttcomics2/SyncClient.java b/app/src/main/java/org/fox/ttcomics2/SyncClient.java new file mode 100644 index 0000000..7d4c64a --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/SyncClient.java @@ -0,0 +1,161 @@ +package org.fox.ttcomics2; + +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +import android.os.AsyncTask; +import android.util.Log; + +public class SyncClient { + public interface PositionReceivedListener { + void onPositionReceived(int position); + } + + private class HttpTask extends AsyncTask<String, Integer, Boolean> { + protected String m_response = null; + protected int m_responseCode = -1; + + @Override + protected Boolean doInBackground(String... params) { + + String requestStr = null; + String op = params[0]; + + if (op.equals("set")) { + requestStr = String.format("op=set&owner=%1$s&hash=%2$s&position=%3$s", m_owner, params[1], params[2]); + } else if (op.equals("get")) { + requestStr = String.format("op=get&owner=%1$s&hash=%2$s", m_owner, params[1]); + } else if (op.equals("clear")) { + if (params.length > 1) { + requestStr = String.format("op=clear&owner=%1$s&hash=%2$s", m_owner, params[1]); + } else { + requestStr = String.format("op=clear&owner=%1$s", m_owner); + } + } + + requestStr += "&version=2"; + + Log.d(TAG, requestStr); + + if (requestStr == null) return false; + + try { + byte[] postData = requestStr.getBytes("UTF-8"); + + URL url = new URL(SYNC_ENDPOINT); + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setUseCaches(false); + conn.setRequestMethod("POST"); + + OutputStream out = conn.getOutputStream(); + out.write(postData); + out.close(); + + m_responseCode = conn.getResponseCode(); + + if (m_responseCode == HttpURLConnection.HTTP_OK) { + StringBuffer response = new StringBuffer(); + InputStreamReader in = new InputStreamReader(conn.getInputStream(), "UTF-8"); + + char[] buf = new char[1024]; + int read = 0; + + while ((read = in.read(buf)) >= 0) { + response.append(buf, 0, read); + } + + //Log.d(TAG, "<<< " + response); + + m_response = response.toString(); + + if (response.indexOf("ERROR") == -1) { + return true; + } else { + return false; + } + } else { + Log.d(TAG, "HTTP error, code: " + m_responseCode); + } + + conn.disconnect(); + + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + } + + private final String TAG = this.getClass().getSimpleName(); + private static final String SYNC_ENDPOINT = "http://tt-rss.org/tcrsync/"; + private String m_owner = null; + + public void setOwner(String owner) { + m_owner = CommonActivity.sha1(owner); + } + + public int getPosition(String hash, final PositionReceivedListener listener) { + if (m_owner != null) { + Log.d(TAG, "Requesting sync data..."); + + HttpTask task = new HttpTask() { + @Override + protected void onPostExecute(Boolean result) { + if (result) { + try { + listener.onPositionReceived(Integer.valueOf(m_response)); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + } + }; + + task.execute("get", hash); + + } + return -1; + } + + public void setPosition(String hash, int position) { + if (m_owner != null) { + Log.d(TAG, "Uploading sync data..."); + + HttpTask task = new HttpTask(); + + task.execute("set", hash, String.valueOf(position)); + } + } + + public void clearData() { + if (m_owner != null) { + Log.d(TAG, "Clearing sync data..."); + + HttpTask task = new HttpTask(); + + task.execute("clear"); + } + } + + public void clearData(String hash) { + if (m_owner != null) { + Log.d(TAG, "Clearing sync data: " + hash); + + HttpTask task = new HttpTask(); + + task.execute("clear", hash); + } + } + + public boolean hasOwner() { + return m_owner != null; + } + +} diff --git a/app/src/main/java/org/fox/ttcomics2/ViewComicActivity.java b/app/src/main/java/org/fox/ttcomics2/ViewComicActivity.java new file mode 100644 index 0000000..abbf835 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/ViewComicActivity.java @@ -0,0 +1,321 @@ +package org.fox.ttcomics2; + + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.widget.NumberPicker; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ViewComicActivity extends CommonActivity { + private final String TAG = this.getClass().getSimpleName(); + + private String m_fileName; + private String m_tmpFileName; + + @SuppressLint("NewApi") + @Override + public void onCreate(Bundle savedInstanceState) { + m_prefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + setTheme(m_prefs.getBoolean("use_dark_theme", false) ? R.style.ViewDarkTheme : R.style.ViewLightTheme); + + super.onCreate(savedInstanceState); + + if (m_prefs.getBoolean("prevent_screen_sleep", false)) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + if (m_prefs.getBoolean("use_full_screen", false)) { + hideSystemUI(true); + } + + setContentView(R.layout.activity_view_comic); + + if (savedInstanceState == null) { + m_fileName = getIntent().getStringExtra("fileName"); + + ComicPager cp = new ComicPager(); + cp.setFileName(m_fileName); + + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.replace(R.id.comics_pager_container, cp, FRAG_COMICS_PAGER); + ft.commit(); + } else { + m_fileName = savedInstanceState.getString("fileName"); + m_tmpFileName = savedInstanceState.getString("tmpFileName"); + } + + setOrientationLock(isOrientationLocked(), true); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + setTitle(new File(m_fileName).getName()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.activity_view_comic, menu); + + menu.findItem(R.id.menu_sync_location).setVisible(m_prefs.getBoolean("use_position_sync", false) && m_syncClient.hasOwner()); + + return true; + } + + @Override + public void onSaveInstanceState(Bundle out) { + super.onSaveInstanceState(out); + + out.putString("fileName", m_fileName); + out.putString("tmpFileName", m_tmpFileName); + } + + @Override + public void onComicSelected(String fileName, int position) { + super.onComicSelected(fileName, position); + } + + public void onPause() { + super.onPause(); + + // upload progress + if (m_prefs.getBoolean("use_position_sync", false) && m_syncClient.hasOwner()) { + //toast(R.string.sync_uploading); + m_syncClient.setPosition(sha1(new File(m_fileName).getName()), getLastPosition(m_fileName)); + } + } + + private void shareComic() { + + ComicPager pager = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (pager != null) { + + try { + File tmpFile = File.createTempFile("trcshare" + sha1(m_fileName + " " + pager.getPosition()), ".jpg", getExternalCacheDir()); + + Log.d(TAG, "FILE=" + tmpFile); + + InputStream is = pager.getArchive().getItem(pager.getPosition()); + + FileOutputStream fos = new FileOutputStream(tmpFile); + + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = is.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + + fos.close(); + is.close(); + + Intent shareIntent = new Intent(Intent.ACTION_SEND); + + shareIntent.setType("image/jpeg"); + shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tmpFile)); + + m_tmpFileName = tmpFile.getAbsolutePath(); + + startActivityForResult(Intent.createChooser(shareIntent, getString(R.string.share_comic)), REQUEST_SHARE); + + } catch (IOException e) { + toast(getString(R.string.error_could_not_prepare_file_for_sharing)); + e.printStackTrace(); + } + + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == REQUEST_SHARE) { + File tmpFile = new File(m_tmpFileName); + + if (tmpFile.exists()) { + tmpFile.delete(); + } + + } + super.onActivityResult(requestCode, resultCode, intent); + } + + protected boolean isOrientationLocked() { + return m_prefs.getBoolean("prefs_lock_orientation", false); + } + + private void setOrientationLock(boolean locked, boolean restoreLast) { + if (locked) { + + int currentOrientation = restoreLast ? m_prefs.getInt("last_orientation", getResources().getConfiguration().orientation) : + getResources().getConfiguration().orientation; + + if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + } + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); + } + + if (locked != isOrientationLocked()) { + SharedPreferences.Editor editor = m_prefs.edit(); + editor.putBoolean("prefs_lock_orientation", locked); + editor.putInt("last_orientation", getResources().getConfiguration().orientation); + editor.commit(); + } + } + + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + shareComic(); + return true; + case R.id.menu_toggle_orientation_lock: + setOrientationLock(!isOrientationLocked(), false); + return true; + case R.id.menu_sync_location: + m_syncClient.getPosition(sha1(new File(m_fileName).getName()), new SyncClient.PositionReceivedListener() { + @Override + public void onPositionReceived(final int position) { + final ComicPager pager = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (pager != null && pager.isAdded()) { + int localPosition = pager.getPosition(); + + if (position > localPosition) { + AlertDialog.Builder builder = new AlertDialog.Builder(ViewComicActivity.this); + builder.setMessage(getString(R.string.sync_server_has_further_page, localPosition+1, position+1)) + .setCancelable(true) + .setPositiveButton(R.string.dialog_open_page, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + pager.setCurrentItem(position); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + + } else { + toast(R.string.error_sync_no_data); + } + + } + } + }); + return true; + case R.id.menu_go_location: + Dialog dialog = new Dialog(ViewComicActivity.this); + AlertDialog.Builder builder = new AlertDialog.Builder(ViewComicActivity.this) + .setTitle("Go to...") + .setItems( + new String[] { + getString(R.string.dialog_location_beginning), + getString(R.string.dialog_location_furthest), + getString(R.string.dialog_location_location), + getString(R.string.dialog_location_end) + }, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + + final ComicPager cp = (ComicPager) getSupportFragmentManager().findFragmentByTag(CommonActivity.FRAG_COMICS_PAGER); + + switch (which) { + case 0: + cp.setCurrentItem(0); + break; + case 1: + cp.setCurrentItem(getMaxPosition(m_fileName)); + break; + case 2: + if (true) { + LayoutInflater inflater = getLayoutInflater(); + View contentView = inflater.inflate(R.layout.dialog_location, null); + + final NumberPicker picker = (NumberPicker) contentView.findViewById(R.id.number_picker); + + picker.setMinValue(1); + picker.setMaxValue(getSize(m_fileName)); + picker.setValue(cp.getPosition() + 1); + + Dialog seekDialog = new Dialog(ViewComicActivity.this); + AlertDialog.Builder seekBuilder = new AlertDialog.Builder(ViewComicActivity.this) + .setTitle(R.string.dialog_open_location) + .setView(contentView) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }).setPositiveButton(R.string.dialog_open_location, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + + cp.setCurrentItem(picker.getValue()-1); + + } + }); + + seekDialog = seekBuilder.create(); + seekDialog.show(); + } + + break; + case 3: + cp.setCurrentItem(cp.getCount() - 1); + break; + } + + dialog.cancel(); + } + }); + + dialog = builder.create(); + dialog.show(); + + return true; + case android.R.id.home: + finish(); + return true; + default: + Log.d(TAG, + "onOptionsItemSelected, unhandled id=" + item.getItemId()); + return super.onOptionsItemSelected(item); + } + } + + public void hideSeekBar(boolean hide) { + ComicPager pager = (ComicPager) getSupportFragmentManager().findFragmentByTag(FRAG_COMICS_PAGER); + + if (pager != null) { + pager.hideSeekBar(hide); + } + } + +} diff --git a/app/src/main/java/org/fox/ttcomics2/ViewPager.java b/app/src/main/java/org/fox/ttcomics2/ViewPager.java new file mode 100644 index 0000000..91feea1 --- /dev/null +++ b/app/src/main/java/org/fox/ttcomics2/ViewPager.java @@ -0,0 +1,38 @@ +package org.fox.ttcomics2; + +import it.sephiroth.android.library.imagezoom.ImageViewTouch; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +public class ViewPager extends android.support.v4.view.ViewPager { + public ViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { + if (v instanceof ImageViewTouch) { + ImageViewTouch ivt = (ImageViewTouch) v; + try { + return ivt.canScroll(dx); + } catch (NullPointerException e) { + // bad image, etc + return false; + } + } else { + return super.canScroll(v, checkV, dx, x, y); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return super.onTouchEvent(event); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return super.onInterceptTouchEvent(event); + } +}
\ No newline at end of file diff --git a/app/src/main/res/anim/appear.xml b/app/src/main/res/anim/appear.xml new file mode 100644 index 0000000..9f44552 --- /dev/null +++ b/app/src/main/res/anim/appear.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> + <alpha + android:fromAlpha="0" + android:toAlpha="1" + android:duration="150" + /> +</set> + diff --git a/app/src/main/res/drawable-hdpi/badimage.png b/app/src/main/res/drawable-hdpi/badimage.png Binary files differnew file mode 100644 index 0000000..84facc9 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/badimage.png diff --git a/app/src/main/res/drawable-hdpi/comic_grid_footer.png b/app/src/main/res/drawable-hdpi/comic_grid_footer.png Binary files differnew file mode 100755 index 0000000..c387044 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/comic_grid_footer.png diff --git a/app/src/main/res/drawable-hdpi/ic_action_overflow.png b/app/src/main/res/drawable-hdpi/ic_action_overflow.png Binary files differnew file mode 100644 index 0000000..002fc4b --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_action_overflow.png diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..e5a62ee --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_launcher.png diff --git a/app/src/main/res/drawable-hdpi/ic_refresh_light.png b/app/src/main/res/drawable-hdpi/ic_refresh_light.png Binary files differnew file mode 100644 index 0000000..bb9d855 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_refresh_light.png diff --git a/app/src/main/res/drawable-hdpi/ic_settings.png b/app/src/main/res/drawable-hdpi/ic_settings.png Binary files differnew file mode 100644 index 0000000..3e4580e --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_settings.png diff --git a/app/src/main/res/drawable-hdpi/ic_share_light.png b/app/src/main/res/drawable-hdpi/ic_share_light.png Binary files differnew file mode 100644 index 0000000..c329f58 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_share_light.png diff --git a/app/src/main/res/drawable-xhdpi/ic_action_good.png b/app/src/main/res/drawable-xhdpi/ic_action_good.png Binary files differnew file mode 100644 index 0000000..49b85ca --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_action_good.png diff --git a/app/src/main/res/drawable-xhdpi/ic_action_overflow.png b/app/src/main/res/drawable-xhdpi/ic_action_overflow.png Binary files differnew file mode 100644 index 0000000..cfe1287 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_action_overflow.png diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..6904f59 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_launcher.png diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh_light.png b/app/src/main/res/drawable-xhdpi/ic_refresh_light.png Binary files differnew file mode 100644 index 0000000..a7fdc0d --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_refresh_light.png diff --git a/app/src/main/res/drawable-xhdpi/ic_search_light.png b/app/src/main/res/drawable-xhdpi/ic_search_light.png Binary files differnew file mode 100644 index 0000000..3549f84 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_search_light.png diff --git a/app/src/main/res/drawable-xhdpi/ic_settings.png b/app/src/main/res/drawable-xhdpi/ic_settings.png Binary files differnew file mode 100644 index 0000000..09b0148 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_settings.png diff --git a/app/src/main/res/drawable-xhdpi/ic_share_light.png b/app/src/main/res/drawable-xhdpi/ic_share_light.png Binary files differnew file mode 100644 index 0000000..15549b0 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_share_light.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..d5f05d2 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_launcher.png diff --git a/app/src/main/res/drawable/s_badimage.svg b/app/src/main/res/drawable/s_badimage.svg new file mode 100644 index 0000000..010d5d1 --- /dev/null +++ b/app/src/main/res/drawable/s_badimage.svg @@ -0,0 +1,519 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + inkscape:export-ydpi="696" + inkscape:export-xdpi="696" + inkscape:export-filename="C:\Users\fox\Documents\Projects\Tiny-Comics-Reader\org.fox.ttcomics\src\main\res\drawable-hdpi\badimage.png" + sodipodi:docname="s_badimage.svg" + inkscape:version="0.91 r13725" + version="1.1" + id="svg2" + height="96" + width="96"> + <defs + id="defs4"> + <linearGradient + id="linearGradient3799"> + <stop + style="stop-color:#ff0000;stop-opacity:1" + offset="0" + id="stop3801" /> + <stop + style="stop-color:#ffdd00;stop-opacity:1" + offset="1" + id="stop3803" /> + </linearGradient> + <filter + id="filter3007" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-.25" + y="-.25"> + <feGaussianBlur + id="feGaussianBlur3009" + in="SourceAlpha" + stdDeviation="2" + result="blur" /> + <feColorMatrix + id="feColorMatrix3011" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.75 0 " /> + <feOffset + id="feOffset3013" + in="bluralpha" + dx="1" + dy="1" + result="offsetBlur" /> + <feMerge + id="feMerge3015"> + <feMergeNode + id="feMergeNode3017" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode3019" + in="SourceGraphic" /> + </feMerge> + </filter> + <filter + id="filter4167" + inkscape:label="Colorize" + inkscape:menu="Color" + inkscape:menu-tooltip="Blend image or object with a flood color and set lightness and contrast" + x="0" + y="0" + width="1" + height="1" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix4169" + values="1" + in="SourceGraphic" + type="saturate" + result="result2" /> + <feFlood + id="feFlood4171" + flood-color="rgb(254,102,0)" + flood-opacity="1" + result="result1" /> + <feBlend + id="feBlend4173" + in2="result2" + mode="multiply" + in="result1" + result="result3" /> + <feComposite + id="feComposite4175" + in2="SourceGraphic" + operator="in" + k2="1" + result="result4" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4404"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4406" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4408" /> + <feGaussianBlur + in="composite1" + stdDeviation="0.5" + result="blur" + id="feGaussianBlur4410" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4412" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4414" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4416"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4418" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4420" /> + <feGaussianBlur + in="composite1" + stdDeviation="0.5" + result="blur" + id="feGaussianBlur4422" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4424" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="fbSourceGraphic" + id="feComposite4426" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4428" /> + <feFlood + id="feFlood4430" + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + in="fbSourceGraphic" /> + <feComposite + id="feComposite4432" + in2="fbSourceGraphic" + in="flood" + operator="in" + result="composite1" /> + <feGaussianBlur + id="feGaussianBlur4434" + in="composite1" + stdDeviation="0.5" + result="blur" /> + <feOffset + id="feOffset4436" + dx="1" + dy="1" + result="offset" /> + <feComposite + id="feComposite4438" + in2="offset" + in="fbSourceGraphic" + operator="over" + result="composite2" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4440"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4442" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4444" /> + <feGaussianBlur + in="composite1" + stdDeviation="0.5" + result="blur" + id="feGaussianBlur4446" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4448" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4450" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4857"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4859" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4861" /> + <feGaussianBlur + in="composite1" + stdDeviation="3" + result="blur" + id="feGaussianBlur4863" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4865" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4867" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4869"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4871" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4873" /> + <feGaussianBlur + in="composite1" + stdDeviation="3" + result="blur" + id="feGaussianBlur4875" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4877" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4879" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4881"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4883" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4885" /> + <feGaussianBlur + in="composite1" + stdDeviation="3" + result="blur" + id="feGaussianBlur4887" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4889" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4891" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter5007"> + <feFlood + flood-opacity="0.321569" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood5009" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite5011" /> + <feGaussianBlur + in="composite1" + stdDeviation="3" + result="blur" + id="feGaussianBlur5013" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset5015" /> + <feComposite + in="offset" + in2="offset" + operator="atop" + result="composite2" + id="feComposite5017" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3799" + id="linearGradient6959" + x1="-72.715965" + y1="990.30542" + x2="-71.995934" + y2="1013.8391" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3799" + id="linearGradient49795" + x1="-72.715965" + y1="990.30542" + x2="-71.995934" + y2="1013.8391" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + inkscape:snap-global="false" + inkscape:snap-nodes="false" + inkscape:window-maximized="1" + inkscape:window-y="-8" + inkscape:window-x="-8" + inkscape:window-height="1137" + inkscape:window-width="1920" + inkscape:guide-bbox="true" + showguides="true" + showgrid="false" + inkscape:current-layer="svg2" + inkscape:document-units="px" + inkscape:cy="37.454658" + inkscape:cx="-18.732881" + inkscape:zoom="3.959798" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base"> + <sodipodi:guide + orientation="1,0" + position="7.5761441,54.043161" + id="guide2993" /> + <sodipodi:guide + orientation="0,1" + position="54.548238,88.13581" + id="guide2995" /> + <sodipodi:guide + orientation="1,0" + position="88.388348,62.881996" + id="guide2997" /> + <sodipodi:guide + orientation="0,1" + position="59.599,8.0812204" + id="guide2999" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-956.36218)"> + <path + inkscape:transform-center-y="-0.78624016" + inkscape:transform-center-x="-0.38354235" + d="m 79.59389,1025.628 -14.797549,-0.5906 -1.802635,14.6992 -12.129199,-8.497 -9.463466,11.3912 -5.609914,-13.7056 -14.119712,4.4665 2.690479,-14.5629 -14.29305,-3.8762 10.136663,-10.7965 -9.928445,-10.98825 14.364529,-3.6023 -2.411629,-14.61165 14.031759,4.73561 5.870861,-13.59592 9.244004,11.56998 12.289396,-8.26359 1.521342,14.73098 14.806134,-0.30763 -6.684334,13.21499 12.622029,7.74598 -12.767782,7.5034 z" + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="false" + sodipodi:arg2="0.86635568" + sodipodi:arg1="0.58075635" + sodipodi:r2="27.064737" + sodipodi:r1="38.66391" + sodipodi:cy="1004.4148" + sodipodi:cx="47.269009" + sodipodi:sides="11" + id="path49973" + style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="star" + transform="matrix(0.85439132,-0.05510671,0.05510671,0.85439132,-48.457117,148.91105)" /> + <path + sodipodi:type="star" + style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path49971" + sodipodi:sides="11" + sodipodi:cx="47.269009" + sodipodi:cy="1004.4148" + sodipodi:r1="38.66391" + sodipodi:r2="27.064737" + sodipodi:arg1="0.58075635" + sodipodi:arg2="0.86635568" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 79.59389,1025.628 -14.797549,-0.5906 -1.802635,14.6992 -12.129199,-8.497 -9.463466,11.3912 -5.609914,-13.7056 -14.119712,4.4665 2.690479,-14.5629 -14.29305,-3.8762 10.136663,-10.7965 -9.928445,-10.98825 14.364529,-3.6023 -2.411629,-14.61165 14.031759,4.73561 5.870861,-13.59592 9.244004,11.56998 12.289396,-8.26359 1.521342,14.73098 14.806134,-0.30763 -6.684334,13.21499 12.622029,7.74598 -12.767782,7.5034 z" + inkscape:transform-center-x="0.14286093" + inkscape:transform-center-y="-0.18702017" + transform="matrix(0.84435138,-0.1417463,0.1417463,0.84435138,-135.0647,163.12814)" /> + <path + transform="matrix(0.67017835,-0.11250683,0.11250683,0.67017835,-97.447266,336.85142)" + inkscape:transform-center-y="-0.14843901" + inkscape:transform-center-x="0.1133964" + d="m 79.59389,1025.628 -14.797549,-0.5906 -1.802635,14.6992 -12.129199,-8.497 -9.463466,11.3912 -5.609914,-13.7056 -14.119712,4.4665 2.690479,-14.5629 -14.29305,-3.8762 10.136663,-10.7965 -9.928445,-10.98825 14.364529,-3.6023 -2.411629,-14.61165 14.031759,4.73561 5.870861,-13.59592 9.244004,11.56998 12.289396,-8.26359 1.521342,14.73098 14.806134,-0.30763 -6.684334,13.21499 12.622029,7.74598 -12.767782,7.5034 z" + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="false" + sodipodi:arg2="0.86635568" + sodipodi:arg1="0.58075635" + sodipodi:r2="27.064737" + sodipodi:r1="38.66391" + sodipodi:cy="1004.4148" + sodipodi:cx="47.269009" + sodipodi:sides="11" + id="path50018" + style="opacity:1;fill:#ffea92;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="star" /> + <g + id="g49985" + transform="translate(31.067894,-5.7610247)"> + <text + transform="matrix(0.99123282,-0.13212683,0.13212683,0.99123282,0,0)" + sodipodi:linespacing="127%" + id="text3801-2" + y="1016.485" + x="-126.51662" + style="font-style:normal;font-weight:normal;font-size:36.53679657px;line-height:126.99999809%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:28px;line-height:126.99999809%;font-family:'BD Cartoon Shout';-inkscape-font-specification:'BD Cartoon Shout';fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + y="1016.485" + x="-126.51662" + id="tspan3803-4" + sodipodi:role="line">?</tspan></text> + <text + transform="matrix(0.99123282,-0.13212683,0.13212683,0.99123282,0,0)" + sodipodi:linespacing="127%" + id="text3801" + y="1014.6411" + x="-128.74716" + style="font-style:normal;font-weight:normal;font-size:36.53679657px;line-height:126.99999809%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:1;fill:url(#linearGradient6959);fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:28px;line-height:126.99999809%;font-family:'BD Cartoon Shout';-inkscape-font-specification:'BD Cartoon Shout';fill:url(#linearGradient6959);fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + y="1014.6411" + x="-128.74716" + id="tspan3803" + sodipodi:role="line">?</tspan></text> + </g> + </g> +</svg> diff --git a/app/src/main/res/drawable/s_launcher.svg b/app/src/main/res/drawable/s_launcher.svg new file mode 100644 index 0000000..4b90414 --- /dev/null +++ b/app/src/main/res/drawable/s_launcher.svg @@ -0,0 +1,523 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="96" + height="96" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="s_launcher.svg" + inkscape:export-filename="C:\Users\fox\Documents\Projects\Tiny-Comics-Reader\org.fox.ttcomics\src\main\res\drawable-xxhdpi\ic_launcher.png" + inkscape:export-xdpi="135" + inkscape:export-ydpi="135"> + <defs + id="defs4"> + <linearGradient + id="linearGradient3799"> + <stop + style="stop-color:#ff0000;stop-opacity:1" + offset="0" + id="stop3801" /> + <stop + style="stop-color:#ffdd00;stop-opacity:1" + offset="1" + id="stop3803" /> + </linearGradient> + <filter + id="filter3007" + inkscape:label="Drop shadow" + width="1.5" + height="1.5" + x="-.25" + y="-.25"> + <feGaussianBlur + id="feGaussianBlur3009" + in="SourceAlpha" + stdDeviation="2" + result="blur" /> + <feColorMatrix + id="feColorMatrix3011" + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.75 0 " /> + <feOffset + id="feOffset3013" + in="bluralpha" + dx="1" + dy="1" + result="offsetBlur" /> + <feMerge + id="feMerge3015"> + <feMergeNode + id="feMergeNode3017" + in="offsetBlur" /> + <feMergeNode + id="feMergeNode3019" + in="SourceGraphic" /> + </feMerge> + </filter> + <filter + id="filter4167" + inkscape:label="Colorize" + inkscape:menu="Color" + inkscape:menu-tooltip="Blend image or object with a flood color and set lightness and contrast" + x="0" + y="0" + width="1" + height="1" + color-interpolation-filters="sRGB"> + <feColorMatrix + id="feColorMatrix4169" + values="1" + in="SourceGraphic" + type="saturate" + result="result2" /> + <feFlood + id="feFlood4171" + flood-color="rgb(254,102,0)" + flood-opacity="1" + result="result1" /> + <feBlend + id="feBlend4173" + in2="result2" + mode="multiply" + in="result1" + result="result3" /> + <feComposite + id="feComposite4175" + in2="SourceGraphic" + operator="in" + k2="1" + result="result4" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4404"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4406" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4408" /> + <feGaussianBlur + in="composite1" + stdDeviation="0.5" + result="blur" + id="feGaussianBlur4410" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4412" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4414" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4416"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4418" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4420" /> + <feGaussianBlur + in="composite1" + stdDeviation="0.5" + result="blur" + id="feGaussianBlur4422" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4424" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="fbSourceGraphic" + id="feComposite4426" /> + <feColorMatrix + result="fbSourceGraphicAlpha" + in="fbSourceGraphic" + values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0" + id="feColorMatrix4428" /> + <feFlood + id="feFlood4430" + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + in="fbSourceGraphic" /> + <feComposite + id="feComposite4432" + in2="fbSourceGraphic" + in="flood" + operator="in" + result="composite1" /> + <feGaussianBlur + id="feGaussianBlur4434" + in="composite1" + stdDeviation="0.5" + result="blur" /> + <feOffset + id="feOffset4436" + dx="1" + dy="1" + result="offset" /> + <feComposite + id="feComposite4438" + in2="offset" + in="fbSourceGraphic" + operator="over" + result="composite2" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4440"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4442" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4444" /> + <feGaussianBlur + in="composite1" + stdDeviation="0.5" + result="blur" + id="feGaussianBlur4446" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4448" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4450" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4857"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4859" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4861" /> + <feGaussianBlur + in="composite1" + stdDeviation="3" + result="blur" + id="feGaussianBlur4863" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4865" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4867" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4869"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4871" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4873" /> + <feGaussianBlur + in="composite1" + stdDeviation="3" + result="blur" + id="feGaussianBlur4875" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4877" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4879" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter4881"> + <feFlood + flood-opacity="0.498039" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood4883" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite4885" /> + <feGaussianBlur + in="composite1" + stdDeviation="3" + result="blur" + id="feGaussianBlur4887" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset4889" /> + <feComposite + in="SourceGraphic" + in2="offset" + operator="over" + result="composite2" + id="feComposite4891" /> + </filter> + <filter + style="color-interpolation-filters:sRGB;" + inkscape:label="Drop Shadow" + id="filter5007"> + <feFlood + flood-opacity="0.321569" + flood-color="rgb(0,0,0)" + result="flood" + id="feFlood5009" /> + <feComposite + in="flood" + in2="SourceGraphic" + operator="in" + result="composite1" + id="feComposite5011" /> + <feGaussianBlur + in="composite1" + stdDeviation="3" + result="blur" + id="feGaussianBlur5013" /> + <feOffset + dx="1" + dy="1" + result="offset" + id="feOffset5015" /> + <feComposite + in="offset" + in2="offset" + operator="atop" + result="composite2" + id="feComposite5017" /> + </filter> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3799" + id="linearGradient6959" + x1="-72.715965" + y1="990.30542" + x2="-71.995934" + y2="1013.8391" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3799" + id="linearGradient49795" + x1="-72.715965" + y1="990.30542" + x2="-71.995934" + y2="1013.8391" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="3.959798" + inkscape:cx="-18.732881" + inkscape:cy="37.454658" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="1920" + inkscape:window-height="1137" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:snap-nodes="false" + inkscape:snap-global="false"> + <sodipodi:guide + orientation="1,0" + position="7.5761441,54.043161" + id="guide2993" /> + <sodipodi:guide + orientation="0,1" + position="54.548238,88.13581" + id="guide2995" /> + <sodipodi:guide + orientation="1,0" + position="88.388348,62.881996" + id="guide2997" /> + <sodipodi:guide + orientation="0,1" + position="59.599,8.0812204" + id="guide2999" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-956.36218)"> + <g + id="g4210" + transform="matrix(1.0817445,0,0,1.0817445,-3.8552003,-82.146781)"> + <path + transform="matrix(0.88223715,-0.05690272,0.05690272,0.88223715,-51.64252,121.01245)" + sodipodi:type="star" + style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path49973" + sodipodi:sides="11" + sodipodi:cx="47.269009" + sodipodi:cy="1004.4148" + sodipodi:r1="38.66391" + sodipodi:r2="27.064737" + sodipodi:arg1="0.58075635" + sodipodi:arg2="0.86635568" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 79.59389,1025.628 -14.797549,-0.5906 -1.802635,14.6992 -12.129199,-8.497 -9.463466,11.3912 -5.609914,-13.7056 -14.119712,4.4665 2.690479,-14.5629 -14.29305,-3.8762 10.136663,-10.7965 -9.928445,-10.98825 14.364529,-3.6023 -2.411629,-14.61165 14.031759,4.73561 5.870861,-13.59592 9.244004,11.56998 12.289396,-8.26359 1.521342,14.73098 14.806134,-0.30763 -6.684334,13.21499 12.622029,7.74598 -12.767782,7.5034 z" + inkscape:transform-center-x="-0.39604598" + inkscape:transform-center-y="-0.81186163" /> + <path + transform="matrix(0.87186999,-0.14636601,0.14636601,0.87186999,-141.07277,135.69289)" + inkscape:transform-center-y="-0.19312114" + inkscape:transform-center-x="0.14752387" + d="m 79.59389,1025.628 -14.797549,-0.5906 -1.802635,14.6992 -12.129199,-8.497 -9.463466,11.3912 -5.609914,-13.7056 -14.119712,4.4665 2.690479,-14.5629 -14.29305,-3.8762 10.136663,-10.7965 -9.928445,-10.98825 14.364529,-3.6023 -2.411629,-14.61165 14.031759,4.73561 5.870861,-13.59592 9.244004,11.56998 12.289396,-8.26359 1.521342,14.73098 14.806134,-0.30763 -6.684334,13.21499 12.622029,7.74598 -12.767782,7.5034 z" + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="false" + sodipodi:arg2="0.86635568" + sodipodi:arg1="0.58075635" + sodipodi:r2="27.064737" + sodipodi:r1="38.66391" + sodipodi:cy="1004.4148" + sodipodi:cx="47.269009" + sodipodi:sides="11" + id="path49971" + style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + sodipodi:type="star" /> + <path + sodipodi:type="star" + style="opacity:1;fill:#ffea92;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path50018" + sodipodi:sides="11" + sodipodi:cx="47.269009" + sodipodi:cy="1004.4148" + sodipodi:r1="38.66391" + sodipodi:r2="27.064737" + sodipodi:arg1="0.58075635" + sodipodi:arg2="0.86635568" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 79.59389,1025.628 -14.797549,-0.5906 -1.802635,14.6992 -12.129199,-8.497 -9.463466,11.3912 -5.609914,-13.7056 -14.119712,4.4665 2.690479,-14.5629 -14.29305,-3.8762 10.136663,-10.7965 -9.928445,-10.98825 14.364529,-3.6023 -2.411629,-14.61165 14.031759,4.73561 5.870861,-13.59592 9.244004,11.56998 12.289396,-8.26359 1.521342,14.73098 14.806134,-0.30763 -6.684334,13.21499 12.622029,7.74598 -12.767782,7.5034 z" + inkscape:transform-center-x="0.1170925" + inkscape:transform-center-y="-0.15327484" + transform="matrix(0.69202042,-0.11617359,0.11617359,0.69202042,-102.22933,315.07806)" /> + </g> + <g + id="g49985" + transform="matrix(1.0325914,0,0,1.0325914,-1.6061161,-32.751826)"> + <text + transform="matrix(0.99123282,-0.13212683,0.13212683,0.99123282,0,0)" + sodipodi:linespacing="127%" + id="text3801-2" + y="1016.485" + x="-126.51662" + style="font-style:normal;font-weight:normal;font-size:36.53679657px;line-height:126.99999809%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:28px;line-height:126.99999809%;font-family:'BD Cartoon Shout';-inkscape-font-specification:'BD Cartoon Shout';fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + y="1016.485" + x="-126.51662" + id="tspan3803-4" + sodipodi:role="line">POW!</tspan></text> + <text + transform="matrix(0.99123282,-0.13212683,0.13212683,0.99123282,0,0)" + sodipodi:linespacing="127%" + id="text3801" + y="1014.6411" + x="-128.74716" + style="font-style:normal;font-weight:normal;font-size:36.53679657px;line-height:126.99999809%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:1;fill:url(#linearGradient6959);fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:28px;line-height:126.99999809%;font-family:'BD Cartoon Shout';-inkscape-font-specification:'BD Cartoon Shout';fill:url(#linearGradient6959);fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + y="1014.6411" + x="-128.74716" + id="tspan3803" + sodipodi:role="line">POW!</tspan></text> + </g> + </g> +</svg> diff --git a/app/src/main/res/layout-sw600dp/activity_main.xml b/app/src/main/res/layout-sw600dp/activity_main.xml new file mode 100755 index 0000000..68170c5 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/activity_main.xml @@ -0,0 +1,31 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/FrameLayout1" + android:fitsSystemWindows="true" + android:orientation="vertical" + android:layout_width="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_height="match_parent"> + + <it.neokree.materialtabs.MaterialTabHost + android:id="@+id/materialTabHost" + android:layout_width="match_parent" + android:layout_height="48dp" + app:textColor="#FFFFFF" + app:primaryColor="#FF6F00" + app:accentColor="#F8CE00" /> + + <FrameLayout + android:id="@+id/comics_list" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + </FrameLayout> + + <TextView + android:id="@+id/tablet_layout_hack" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:text="" /> + +</LinearLayout> diff --git a/app/src/main/res/layout-sw600dp/activity_view_comic.xml b/app/src/main/res/layout-sw600dp/activity_view_comic.xml new file mode 100644 index 0000000..01f320b --- /dev/null +++ b/app/src/main/res/layout-sw600dp/activity_view_comic.xml @@ -0,0 +1,20 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/FrameLayout2" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <FrameLayout + android:id="@+id/comics_pager_container" + android:layout_width="match_parent" + android:layout_height="match_parent" > + </FrameLayout> + + + <TextView + android:id="@+id/tablet_layout_hack" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:text="" /> + +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100755 index 0000000..0a6ecd0 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,24 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/FrameLayout1" + android:fitsSystemWindows="true" + android:orientation="vertical" + android:layout_width="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_height="match_parent"> + + <it.neokree.materialtabs.MaterialTabHost + android:id="@+id/materialTabHost" + android:layout_width="match_parent" + android:layout_height="48dp" + app:textColor="#FFFFFF" + app:primaryColor="#FF6F00" + app:accentColor="#F8CE00" /> + + <FrameLayout + android:id="@+id/comics_list" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + </FrameLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_view_comic.xml b/app/src/main/res/layout/activity_view_comic.xml new file mode 100644 index 0000000..8016730 --- /dev/null +++ b/app/src/main/res/layout/activity_view_comic.xml @@ -0,0 +1,13 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/FrameLayout2" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <FrameLayout + android:id="@+id/comics_pager_container" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + </FrameLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/chooser_list.xml b/app/src/main/res/layout/chooser_list.xml new file mode 100644 index 0000000..4200bc9 --- /dev/null +++ b/app/src/main/res/layout/chooser_list.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <ListView + android:id="@android:id/list" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1" /> + + <LinearLayout + android:id="@+id/linearLayout2" + android:layout_width="fill_parent" + android:layout_height="wrap_content" > + + <Button + android:id="@+id/btnParent" + android:layout_width="fill_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="@string/open_parent" /> + + <Button + android:id="@+id/btnChoose" + android:layout_weight="1" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/choose" /> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/comics_grid_row.xml b/app/src/main/res/layout/comics_grid_row.xml new file mode 100755 index 0000000..10f1ed4 --- /dev/null +++ b/app/src/main/res/layout/comics_grid_row.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.v7.widget.CardView + xmlns:card_view="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/comics_list_row" + android:layout_width="160dp" + android:layout_height="210dp" + android:gravity="center_vertical" + card_view:cardUseCompatPadding="true" + card_view:cardElevation="2dp" + tools:ignore="HardcodedText" > + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_horizontal" + android:layout_weight="0" + android:padding="0dp" > + + <ImageView + android:layout_width="match_parent" + android:layout_height="200dp" + android:id="@+id/thumbnail" + android:scaleType="centerCrop" + android:src="@drawable/badimage" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom|center_horizontal" + android:gravity="center_horizontal|bottom" + android:background="@drawable/comic_grid_footer" + android:padding="4dp" + android:orientation="vertical" > + + <TextView + android:id="@+id/file_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:ellipsize="middle" + android:singleLine="true" + android:text="Comic_1980.cbz" + android:textColor="@android:color/primary_text_dark" + android:textSize="11sp" /> + + <ProgressBar + android:id="@+id/file_progress_bar" + style="?android:attr/progressBarStyleHorizontal" + android:layout_width="96dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <TextView + android:id="@+id/file_progress_info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@android:color/primary_text_dark" + android:layout_weight="0" + android:gravity="right" + android:text="12 of 325" + android:textSize="8sp" /> + </LinearLayout> + + </FrameLayout> + </android.support.v7.widget.CardView>
\ No newline at end of file diff --git a/app/src/main/res/layout/dialog_location.xml b/app/src/main/res/layout/dialog_location.xml new file mode 100644 index 0000000..4b946d3 --- /dev/null +++ b/app/src/main/res/layout/dialog_location.xml @@ -0,0 +1,14 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/linearLayout1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + + + <NumberPicker + android:id="@+id/number_picker" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_comic.xml b/app/src/main/res/layout/fragment_comic.xml new file mode 100644 index 0000000..59103d4 --- /dev/null +++ b/app/src/main/res/layout/fragment_comic.xml @@ -0,0 +1,20 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/FrameLayout3" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <it.sephiroth.android.library.imagezoom.ImageViewTouch + android:id="@+id/comic_image" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <!-- <TextView + android:id="@+id/comic_page" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:layout_margin="4dp" + android:alpha="0.25" + android:text="25" /> --> + +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_comics_list.xml b/app/src/main/res/layout/fragment_comics_list.xml new file mode 100644 index 0000000..5037747 --- /dev/null +++ b/app/src/main/res/layout/fragment_comics_list.xml @@ -0,0 +1,34 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/FrameLayout4" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + + <android.support.v4.widget.SwipeRefreshLayout + android:id="@+id/comics_swipe_container" + android:layout_width="match_parent" + android:layout_height="match_parent" > + <GridView + android:id="@+id/comics_grid" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:columnWidth="160dp" + android:numColumns="auto_fit" > + </GridView> + </android.support.v4.widget.SwipeRefreshLayout> + + <LinearLayout + android:id="@+id/no_comics" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:orientation="vertical" > + + <TextView + android:id="@+id/textView1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/error_no_comic_archives_found_" /> + </LinearLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_comics_pager.xml b/app/src/main/res/layout/fragment_comics_pager.xml new file mode 100755 index 0000000..fa5ebef --- /dev/null +++ b/app/src/main/res/layout/fragment_comics_pager.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +android:id="@+id/comics_pager_container" +android:layout_width="fill_parent" +android:layout_height="fill_parent" > + + <org.fox.ttcomics2.ViewPager + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:id="@+id/comics_pager" + android:layout_above="@+id/comics_seek_bar" /> + + <SeekBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/comics_seek_bar" + android:layout_alignParentBottom="true" + android:layout_toRightOf="@+id/comics_page" + android:layout_toLeftOf="@+id/comics_total_pages" + android:layout_toStartOf="@+id/comics_total_pages" /> + + <TextView + android:layout_width="60dp" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="1123" + android:id="@+id/comics_total_pages" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:singleLine="true" + android:layout_alignTop="@+id/comics_seek_bar" + android:gravity="center" /> + + <TextView + android:layout_width="60dp" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="1" + android:id="@+id/comics_page" + android:singleLine="true" + android:gravity="center" + android:layout_below="@+id/comics_pager" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_alignParentBottom="true" /> + + +</RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml new file mode 100644 index 0000000..f0958d6 --- /dev/null +++ b/app/src/main/res/layout/list_item.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:padding="10dp" + android:textSize="16sp" > +</TextView>
\ No newline at end of file diff --git a/app/src/main/res/menu/activity_main.xml b/app/src/main/res/menu/activity_main.xml new file mode 100644 index 0000000..a7ca037 --- /dev/null +++ b/app/src/main/res/menu/activity_main.xml @@ -0,0 +1,15 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item android:id="@+id/menu_rescan" + android:title="@string/menu_rescan" + android:icon="@drawable/ic_refresh_light" + app:showAsAction="ifRoom" /> + + <item android:id="@+id/menu_settings" + android:title="@string/menu_settings" + android:icon="@drawable/ic_settings" + app:showAsAction="ifRoom" + android:orderInCategory="100" + /> +</menu> diff --git a/app/src/main/res/menu/activity_view_comic.xml b/app/src/main/res/menu/activity_view_comic.xml new file mode 100644 index 0000000..1e5fc29 --- /dev/null +++ b/app/src/main/res/menu/activity_view_comic.xml @@ -0,0 +1,27 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:ugh="http://schemas.android.com/apk/res-auto"> + <item android:id="@+id/menu_settings" + android:title="@string/menu_settings" + android:icon="@drawable/ic_settings" + android:fitsSystemWindows="true" + android:orderInCategory="100" + ugh:showAsAction="never" /> + + <item android:id="@+id/menu_share" + android:title="@string/menu_share" + android:icon="@drawable/ic_share_light" + ugh:showAsAction="ifRoom" /> + + <item android:id="@+id/menu_go_location" + android:title="@string/menu_go_location" + ugh:showAsAction="never" /> + + <item android:id="@+id/menu_sync_location" + android:title="@string/menu_sync_location" + ugh:showAsAction="never" /> + + <item android:id="@+id/menu_toggle_orientation_lock" + android:title="@string/menu_toggle_orientation_lock" + ugh:showAsAction="never" /> + +</menu> diff --git a/app/src/main/res/menu/comic_archive_context.xml b/app/src/main/res/menu/comic_archive_context.xml new file mode 100644 index 0000000..9a39898 --- /dev/null +++ b/app/src/main/res/menu/comic_archive_context.xml @@ -0,0 +1,8 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/menu_mark_as_read" + android:title="Mark as read" /> + + <item android:id="@+id/menu_reset_progress" + android:title="Reset progress" /> + +</menu> diff --git a/app/src/main/res/values-large/dimens.xml b/app/src/main/res/values-large/dimens.xml new file mode 100644 index 0000000..5c4a502 --- /dev/null +++ b/app/src/main/res/values-large/dimens.xml @@ -0,0 +1,7 @@ +<resources> + + <dimen name="padding_small">8dp</dimen> + <dimen name="padding_medium">16dp</dimen> + <dimen name="padding_large">16dp</dimen> + +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values-v11/style.xml b/app/src/main/res/values-v11/style.xml new file mode 100644 index 0000000..54a7530 --- /dev/null +++ b/app/src/main/res/values-v11/style.xml @@ -0,0 +1,6 @@ +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="DarkDialogTheme" parent="android:Theme.Holo.Dialog"> + </style> + +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values-v21/style.xml b/app/src/main/res/values-v21/style.xml new file mode 100644 index 0000000..c3ac29a --- /dev/null +++ b/app/src/main/res/values-v21/style.xml @@ -0,0 +1,6 @@ +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="DarkDialogTheme" parent="android:Theme.Material.Dialog"> + </style> + +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..0d2c4cc --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..ec96646 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,7 @@ +<resources> + + <dimen name="padding_small">8dp</dimen> + <dimen name="padding_medium">8dp</dimen> + <dimen name="padding_large">16dp</dimen> + +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100755 index 0000000..baf00e5 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,71 @@ +<resources> + + <string name="app_name">Pow! Comics Reader</string> + <string name="hello_world">Hello world!</string> + <string name="menu_settings">Settings</string> + <string name="menu_go_location">Go to</string> + <string name="title_activity_main">Comics Reader</string> + <string name="title_activity_preferences">Settings</string> + <string name="prefs_comics_directory">Comics directory</string> + <string name="prefs_general">General</string> + <!-- <string name="prefs_dim_status_bar">Dim status bar</string> --> + <string name="file_progress_info">%1$d of %2$d (%3$d%%)</string> + <string name="menu_rescan">Reload</string> + <string name="tab_all_comics">All comics</string> + <string name="tab_unread">Unread</string> + <string name="tab_read">Finished</string> + <string name="dialog_need_prefs_message">Please configure base directory for comics archives.</string> + <string name="dialog_need_prefs_preferences">Settings</string> + <string name="cancel">Cancel</string> + <string name="comics_directory_default">/mnt/sdcard/Comics</string> + <string name="prefs_reading">Reading</string> + <string name="prefs_dark_theme">Dark theme</string> + <string name="dialog_open_location">Open location</string> + <string name="dialog_location_beginning">Beginning</string> + <string name="dialog_location_furthest">Furthest read location</string> + <string name="dialog_location_location">Location…</string> + <string name="dialog_location_end">End</string> + <string name="tab_unfinished">Unfinished</string> + <string name="menu_share">Share</string> + <string name="prefs_use_full_screen">Fullscreen mode</string> + <string name="error_could_not_prepare_file_for_sharing">Could not prepare file for sharing</string> + <string name="error_could_not_open_comic_archive">Could not open comic archive.</string> + <string name="error_out_of_memory">Out of memory</string> + <string name="error_loading_image">Error loading image</string> + <string name="error_could_not_read_folder_contents_">Could not read folder contents.</string> + <string name="picker_choose">Choose %1$s</string> + <string name="sync_server_has_further_page">You are currently on page %1$d. Furthest read page stored on the server is %2$d. Open it instead?</string> + <string name="dialog_open_page">Open page</string> + <string name="menu_sync_location">Sync to last page read</string> + <string name="share_comic">Share comic</string> + <string name="choose">Choose</string> + <string name="error_no_comic_archives_found_">No comic archives found.</string> + <string name="prefs_use_position_sync">Sync last read pages</string> + <string name="prefs_use_position_sync_summary">Requires at least one Google account on the device. No personally identifiable information is sent.</string> + <string name="error_sync_no_account">No Google account found, sync disabled.</string> + <string name="sync_uploading">Uploading sync data…</string> + <string name="error_sync_no_data">No information stored or you are on the furthest read page.</string> + <string name="dialog_clear_data">Clear data</string> + <string name="dialog_clear_data_title">Clear all remotely stored sync data?</string> + <string name="prefs_sync">Sync</string> + <string name="prefs_clear_sync_data">Clear sync data</string> + <string name="prefs_clear_sync_data_summary">Removes all remotely stored sync data.</string> + <string name="open_parent">Open parent</string> + <string name="file_unread">Unread (%1$d pages)</string> + <string name="file_finished">Finished</string> + <string name="menu_toggle_orientation_lock">(Un)lock orientation</string> + <string name="prefs_comics_directory_summary">Base directory for comic archives.</string> + <string name="prefs_fit_to_width">Fit to width</string> + <string name="sync_running_in_test_mode">Sync running in test mode.</string> + <string name="reset_remove_synced_progress">Would you like to remove synced progress too?</string> + <string name="error_cant_open_file">Can\'t open file: %1$s</string> + <string name="prefs_prevent_screen_sleep">Keep screen awake</string> + <string name="error_other_error">Error: unknown error (see log)</string> + <string name="list_type_directory">Directory</string> + <string name="list_type_unknown">Unknown</string> + <string name="prefs_version">%1$s (%2$d)</string> + <string name="prefs_version_title">Version</string> + <string name="prefs_build_timestamp">%1$s</string> + <string name="prefs_build_timestamp_title">Build timestamp</string> + <string name="crash_dialog_text">Unfortunately, Pow! Comics Reader has stopped. Submit crash report to tt-rss.org?</string> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml new file mode 100755 index 0000000..6852e4a --- /dev/null +++ b/app/src/main/res/values/style.xml @@ -0,0 +1,20 @@ +<resources> + + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <item name="colorPrimary">#FF0000</item> + <item name="colorPrimaryDark">#990000</item> + <item name="colorAccent">#FF7500</item> + </style> + + <style name="ViewLightTheme" parent="AppTheme"> + + </style> + + <style name="ViewDarkTheme" parent="Theme.AppCompat"> + <item name="colorPrimary">#600000</item> + <item name="colorPrimaryDark">#300000</item> + <item name="colorAccent">#FF6600</item> + </style> + + <style name="DarkDialogTheme" parent="android:Theme"></style> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml new file mode 100644 index 0000000..7ed6cb2 --- /dev/null +++ b/app/src/main/res/xml/preferences.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > + + <PreferenceCategory android:title="@string/prefs_general" > + <Preference + android:key="comics_directory" + android:hint="@string/comics_directory_default" + android:summary="@string/prefs_comics_directory_summary" + android:title="@string/prefs_comics_directory" > + </Preference> + </PreferenceCategory> + <PreferenceCategory android:title="@string/prefs_sync" > + <CheckBoxPreference + android:defaultValue="false" + android:key="use_position_sync" + android:title="@string/prefs_use_position_sync" + android:summary="@string/prefs_use_position_sync_summary" + /> + + <Preference + android:dependency="use_position_sync" + android:key="clear_sync_data" + android:title="@string/prefs_clear_sync_data" + android:summary="@string/prefs_clear_sync_data_summary" > + </Preference> + + </PreferenceCategory> + + <PreferenceCategory android:title="@string/prefs_reading" android:key="prefs_reading"> + <CheckBoxPreference + android:defaultValue="false" + android:key="use_dark_theme" + android:title="@string/prefs_dark_theme" /> + + <!-- <CheckBoxPreference + android:defaultValue="false" + android:key="dim_status_bar" + android:title="@string/prefs_dim_status_bar" /> --> + + <CheckBoxPreference + android:defaultValue="false" + android:key="use_full_screen" + android:title="@string/prefs_use_full_screen" /> + + <CheckBoxPreference + android:defaultValue="false" + android:key="fit_to_width" + android:title="@string/prefs_fit_to_width" /> + + <CheckBoxPreference + android:defaultValue="false" + android:key="prevent_screen_sleep" + android:title="@string/prefs_prevent_screen_sleep" /> + + </PreferenceCategory> + + <Preference + android:key="version" + android:enabled="false" + android:title="@string/prefs_version_title" /> + + <Preference + android:key="build_timestamp" + android:enabled="false" + android:title="@string/prefs_build_timestamp_title" /> +</PreferenceScreen>
\ No newline at end of file |