package com.vaguehope.onosendai.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;
import com.vaguehope.onosendai.C;
import com.vaguehope.onosendai.model.TweetListCursorAdapter;
public final class ScrollIndicator {
private static final double MAX_HEIGHT_RELATIVE = 0.95d;
private static final double POSITION_MAX_HEIGHT = C.DATA_TW_MAX_COL_ENTRIES;
private static final double ACCEL_BASE = 12d; // degree of non-linear effect.
private static final double LOG_ACCEL_BASE = Math.log(ACCEL_BASE);
/**
* Factored out for efficiency. The JIT might do this, but might as well be sure.
*/
private static final double SCALE_CONSTANT_THING = 1d / (Math.exp(MAX_HEIGHT_RELATIVE * LOG_ACCEL_BASE) - 1);
private static final int ACCEL_STOP_1 = 5;
private static final int ACCEL_STOP_2 = 15;
private static final int ACCEL_STOP_3 = 100;
private static final int BAR_WIDTH_DIP = 7;
private static final int COLOUR_BAR = Color.GRAY;
private static final int COLOUR_UNREAD = Color.parseColor("#268bd2"); // Solarized Blue.
private final BarMover barMover;
private ScrollIndicator (final BarMover barMover) {
this.barMover = barMover;
}
public void setUnreadTime (final long unreadTime) {
this.barMover.setUnreadTime(unreadTime);
}
public void setUnreadTimeIfNewer (final long unreadTime) {
if (unreadTime > this.barMover.getUnreadTime()) this.barMover.setUnreadTime(unreadTime);
}
public long getUnreadTime () {
return this.barMover.getUnreadTime();
}
public int getUnreadPosition () {
return this.barMover.getUnreadPosition();
}
public static ScrollIndicator attach (final Context context, final ViewGroup rootView, final ListView list, final OnScrollListener onScrollListener) {
final AbsoluteShape bar = new AbsoluteShape(context, list);
bar.layoutForce(0, 0, 0, 0);
bar.setBackgroundColor(COLOUR_BAR);
bar.bringToFront();
rootView.addView(bar);
final BarMover barMovingScrollListener = new BarMover(bar, list, (int) dipToPixels(context, BAR_WIDTH_DIP), onScrollListener);
list.setOnScrollListener(barMovingScrollListener);
return new ScrollIndicator(barMovingScrollListener);
}
private static float dipToPixels (final Context context, final int a) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, a, context.getResources().getDisplayMetrics());
}
/**
* @deprecated Replaced by barHeightRelative() which is relative to screen size.
*/
@Deprecated
protected static int barHeightPx (final int position, final float pxPerUnit) {
return (int) ((position
+ Math.min(position, ACCEL_STOP_1)
+ Math.min(position, ACCEL_STOP_2)
+ Math.min(position, ACCEL_STOP_3)) * pxPerUnit);
}
protected static double barHeightRelative (final int position) {
if (position == 0) return 0d;
return Math.log(((position / POSITION_MAX_HEIGHT) / SCALE_CONSTANT_THING) + 1d) / LOG_ACCEL_BASE;
}
protected static int barHeightPx (final int position, final View view) {
return (int) (barHeightRelative(position) * view.getHeight());
}
private static class AbsoluteShape extends View {
private final ListView list;
private final Rect unreadRect;
private final Paint unreadPaint;
private int unreadPosition;
public AbsoluteShape (final Context context, final ListView list) {
super(context);
this.list = list;
this.unreadRect = new Rect(0, 0, 1, 1);
this.unreadPaint = new Paint(0);
this.unreadPaint.setColor(COLOUR_UNREAD);
}
public void setUnreadPosition (final int unreadPosition) {
this.unreadPosition = unreadPosition;
invalidate();
}
public int getUnreadPosition () {
return this.unreadPosition;
}
@Override
public void layout (final int l, final int t, final int r, final int b) {
/* block calls so no layout manager can not mess with us. */
}
public void layoutForce (final int l, final int t, final int r, final int b) {
super.layout(l, t, r, b);
}
@Override
protected void onSizeChanged (final int w, final int h, final int oldw, final int oldh) {
this.unreadRect.right = w;
}
@Override
protected void onDraw (final Canvas canvas) {
super.onDraw(canvas);
this.unreadRect.bottom = barHeightPx(this.unreadPosition, this.list);
canvas.drawRect(this.unreadRect, this.unreadPaint);
}
}
private static class BarMover implements OnScrollListener {
private final AbsoluteShape bar;
private final ListView list;
private final TweetListCursorAdapter listAdaptor;
private final int barWidth;
private final OnScrollListener delagate;
private int lastPosition = -1;
private long unreadTime = -1;
public BarMover (final AbsoluteShape bar, final ListView list, final int barWidth, final OnScrollListener delagate) {
this.bar = bar;
this.list = list;
this.listAdaptor = (TweetListCursorAdapter) list.getAdapter();
this.barWidth = barWidth;
this.delagate = delagate;
}
public void setUnreadTime (final long unreadTime) {
this.unreadTime = unreadTime;
for (int i = 0; i < this.listAdaptor.getCount(); i++) {
if (this.listAdaptor.getItemTime(i) <= this.unreadTime) {
this.bar.setUnreadPosition(i);
return;
}
}
}
public long getUnreadTime () {
return this.unreadTime;
}
public int getUnreadPosition () {
return this.bar.getUnreadPosition();
}
private void updateBar () {
final int position = this.list.getFirstVisiblePosition();
if (position != this.lastPosition) {
this.bar.layoutForce(this.list.getRight() - this.barWidth,
this.list.getTop(),
this.list.getRight(),
this.list.getTop() + barHeightPx(position, this.list));
this.lastPosition = position;
final long time = this.listAdaptor.getItemTime(position);
if (time > this.unreadTime) {
this.unreadTime = time;
this.bar.setUnreadPosition(position);
}
}
}
@Override
public void onScrollStateChanged (final AbsListView view, final int scrollStateFlag) {
updateBar();
this.delagate.onScrollStateChanged(view, scrollStateFlag);
}
@Override
public void onScroll (final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
updateBar();
this.delagate.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
}