list = packageManager.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY);
+ return list.size() > 0;
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/CircleImageView.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/CircleImageView.java
new file mode 100644
index 0000000..a2d80f8
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/CircleImageView.java
@@ -0,0 +1,270 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ *
+ * 描述:圆形ImageView
+ * @author likebamboo
+ * @version [版本号, 2015年05月14日]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+public class CircleImageView extends ImageView {
+ private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
+
+ private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+
+ private static final int COLORDRAWABLE_DIMENSION = 2;
+
+ private static final int DEFAULT_BORDER_WIDTH = 2;
+
+ private static final int DEFAULT_BORDER_COLOR = Color.WHITE;
+
+ private final RectF mDrawableRect = new RectF();
+
+ private final RectF mBorderRect = new RectF();
+
+ private final Matrix mShaderMatrix = new Matrix();
+
+ private final Paint mBitmapPaint = new Paint();
+
+ private final Paint mBorderPaint = new Paint();
+
+ private int mBorderColor = DEFAULT_BORDER_COLOR;
+
+ private int mBorderWidth = DEFAULT_BORDER_WIDTH;
+
+ private Bitmap mBitmap;
+
+ private BitmapShader mBitmapShader;
+
+ private int mBitmapWidth;
+
+ private int mBitmapHeight;
+
+ private float mDrawableRadius;
+
+ private float mBorderRadius;
+
+ private boolean mReady;
+
+ private boolean mSetupPending;
+
+ public CircleImageView(Context context) {
+ super(context);
+
+ init();
+ }
+
+ public CircleImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mBorderWidth = DEFAULT_BORDER_WIDTH;
+ mBorderColor = DEFAULT_BORDER_COLOR;
+
+ init();
+ }
+
+ private void init() {
+ super.setScaleType(SCALE_TYPE);
+ mReady = true;
+
+ if (mSetupPending) {
+ setup();
+ mSetupPending = false;
+ }
+ }
+
+ @Override
+ public ScaleType getScaleType() {
+ return SCALE_TYPE;
+ }
+
+ @Override
+ public void setScaleType(ScaleType scaleType) {
+ if (scaleType != SCALE_TYPE) {
+ throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
+ }
+ }
+
+ @Override
+ public void setAdjustViewBounds(boolean adjustViewBounds) {
+ if (adjustViewBounds) {
+ throw new IllegalArgumentException("adjustViewBounds not supported.");
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (getDrawable() == null) {
+ return;
+ }
+
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
+ if (mBorderWidth != 0) {
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, mBorderRadius, mBorderPaint);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ setup();
+ }
+
+ public int getBorderColor() {
+ return mBorderColor;
+ }
+
+ public void setBorderColor(int borderColor) {
+ if (borderColor == mBorderColor) {
+ return;
+ }
+
+ mBorderColor = borderColor;
+ mBorderPaint.setColor(mBorderColor);
+ invalidate();
+ }
+
+ public int getBorderWidth() {
+ return mBorderWidth;
+ }
+
+ public void setBorderWidth(int borderWidth) {
+ if (borderWidth == mBorderWidth) {
+ return;
+ }
+
+ mBorderWidth = borderWidth;
+ setup();
+ }
+
+ @Override
+ public void setImageBitmap(Bitmap bm) {
+ super.setImageBitmap(bm);
+ mBitmap = bm;
+ setup();
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ mBitmap = getBitmapFromDrawable(drawable);
+ setup();
+ }
+
+ @Override
+ public void setImageResource(int resId) {
+ super.setImageResource(resId);
+ mBitmap = getBitmapFromDrawable(getDrawable());
+ setup();
+ }
+
+ @Override
+ public void setImageURI(Uri uri) {
+ super.setImageURI(uri);
+ mBitmap = getBitmapFromDrawable(getDrawable());
+ setup();
+ }
+
+ private Bitmap getBitmapFromDrawable(Drawable drawable) {
+ if (drawable == null) {
+ return null;
+ }
+
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ try {
+ Bitmap bitmap;
+
+ if (drawable instanceof ColorDrawable) {
+ bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
+ } else {
+ bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ } catch (OutOfMemoryError e) {
+ return null;
+ }
+ }
+
+ private void setup() {
+ if (!mReady) {
+ mSetupPending = true;
+ return;
+ }
+
+ if (mBitmap == null) {
+ return;
+ }
+
+ mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+
+ mBitmapPaint.setAntiAlias(true);
+ mBitmapPaint.setShader(mBitmapShader);
+
+ mBorderPaint.setStyle(Paint.Style.STROKE);
+ mBorderPaint.setAntiAlias(true);
+ mBorderPaint.setColor(mBorderColor);
+ mBorderPaint.setStrokeWidth(mBorderWidth);
+
+ mBitmapHeight = mBitmap.getHeight();
+ mBitmapWidth = mBitmap.getWidth();
+
+ mBorderRect.set(0, 0, getWidth(), getHeight());
+ mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2, (mBorderRect.width() - mBorderWidth) / 2);
+
+ mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height()
+ - mBorderWidth);
+ mDrawableRadius = Math.min(mDrawableRect.height() / 2, mDrawableRect.width() / 2);
+
+ updateShaderMatrix();
+ invalidate();
+ }
+
+ private void updateShaderMatrix() {
+ float scale;
+ float dx = 0;
+ float dy = 0;
+
+ mShaderMatrix.set(null);
+
+ if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
+ scale = mDrawableRect.height() / (float) mBitmapHeight;
+ dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
+ } else {
+ scale = mDrawableRect.width() / (float) mBitmapWidth;
+ dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
+ }
+
+ mShaderMatrix.setScale(scale, scale);
+ mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, (int) (dy + 0.5f) + mBorderWidth);
+
+ mBitmapShader.setLocalMatrix(mShaderMatrix);
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/CommonWebView.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/CommonWebView.java
new file mode 100644
index 0000000..b7c5a43
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/CommonWebView.java
@@ -0,0 +1,249 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.http.SslError;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebSettings.PluginState;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.FrameLayout;
+
+import com.likebamboo.osa.android.R;
+
+/**
+ * 通用webview,包装webview,向外暴露webview部分接口,统一封装了native功能,提供给页面使用
+ *
+ * @version [版本号, 2015-5-24]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+public class CommonWebView extends FrameLayout {
+ /**
+ * 上下文对象
+ */
+ private Context mContext = null;
+
+ /**
+ * webView
+ */
+ private ObservedWebView mWebview = null;
+
+ /**
+ * loading
+ */
+ private LoadingLayout mLoadingLayout = null;
+
+ /**
+ * 工具栏
+ */
+ private WebViewToolBar mToolBar = null;
+
+ /**
+ * 状态回调
+ */
+ private IWebViewStatusListener mListener = null;
+
+ /**
+ * webView client
+ */
+ private WebViewClient mWebViewClient = new WebViewClient() {
+
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ mLoadingLayout.showLoading(true);
+ if (mListener != null) {
+ mListener.onPageStarted(url);
+ }
+ }
+
+ public void onPageFinished(WebView view, String url) {
+ mLoadingLayout.showLoading(false);
+ if (mListener != null) {
+ mListener.onPageFinished(url);
+ }
+ // 标题
+ if (!TextUtils.isEmpty(view.getTitle())) {
+ if (mListener != null) {
+ mListener.onReceiveTitle(view.getTitle());
+ }
+ }
+
+ if (mToolBar != null) {
+ // 更新工具栏状态
+ mToolBar.updateStatus();
+ }
+ }
+
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ // Android webview访问HTTPS web page忽略验证 .
+ handler.proceed(); // 接受所有网站的证书
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (url == null) {
+ return true;
+ }
+ if (mListener != null && mListener.shouldOverrideUrl(url)) {
+ return true;
+ }
+ return false;
+ }
+ };
+
+ /**
+ * WebChromeClient
+ */
+ private WebChromeClient mWebChromeClient = new WebChromeClient() {
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ super.onProgressChanged(view, newProgress);
+ }
+
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ super.onReceivedTitle(view, title);
+ if (!TextUtils.isEmpty(title)) {
+ if (mListener != null) {
+ mListener.onReceiveTitle(view.getTitle());
+ }
+ }
+ }
+ };
+
+ public CommonWebView(Context context) {
+ this(context, null);
+ }
+
+ public CommonWebView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CommonWebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initView(context);
+ }
+
+ /**
+ * 初始化view
+ */
+ private void initView(Context context) {
+ mContext = context;
+ LayoutInflater.from(context).inflate(R.layout.common_webview, this, true);
+ mWebview = (ObservedWebView) findViewById(R.id.common_webview);
+ mLoadingLayout = (LoadingLayout) findViewById(R.id.webview_loading_layout);
+ mToolBar = (WebViewToolBar) findViewById(R.id.webview_tool_bar);
+
+ initWebViewSettings();
+ mToolBar.attachToWebView(mWebview);
+ }
+
+ /**
+ * 初始化webview配置
+ */
+ private void initWebViewSettings() {
+ WebSettings webSettings = mWebview.getSettings();
+ // 打开h5 localstorage
+ webSettings.setDomStorageEnabled(true);
+ // 不设置setDatabasePath,html5数据只会保存在内存
+ webSettings.setDatabaseEnabled(true);
+ webSettings.setJavaScriptEnabled(true);
+ webSettings.setPluginState(PluginState.ON);
+ // 禁止window open
+ webSettings.setJavaScriptCanOpenWindowsAutomatically(false);
+ // 支持viewport meta tag
+ webSettings.setUseWideViewPort(true);
+ webSettings.setLoadWithOverviewMode(true);
+ webSettings.setSupportZoom(false);
+ mWebview.setVerticalScrollBarEnabled(false);
+ mWebview.setHorizontalScrollBarEnabled(false);
+ // 滚动条风格,为0就是不给滚动条留空间,滚动条覆盖在网页上
+ mWebview.setScrollBarStyle(WebView.SCROLLBARS_INSIDE_OVERLAY);
+ mWebview.setWebChromeClient(mWebChromeClient);
+ mWebview.setWebViewClient(mWebViewClient);
+ }
+
+ // ==============api method ===========
+
+ /**
+ * 加载指定url
+ */
+ public void loadUrl(String url) {
+ mWebview.loadUrl(url);
+ }
+
+ /**
+ * onpause
+ */
+ public void onPause() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mWebview.onPause();
+ }
+ }
+
+ /**
+ * onResume
+ */
+ public void onResume() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ mWebview.onResume();
+ }
+ }
+
+ /**
+ * 设置toolbar是否显示
+ *
+ * @param visibility
+ */
+ public void setToolBarVisibility(int visibility) {
+ mToolBar.setVisibility(visibility);
+ }
+
+ /**
+ * 设置状态监听回调
+ *
+ * @param mListener
+ */
+ public void setStatusListener(IWebViewStatusListener mListener) {
+ this.mListener = mListener;
+ }
+
+ /**
+ * 返回
+ *
+ * @return
+ */
+ public boolean goBack() {
+ if (mWebview.canGoBack()) {
+ mWebview.goBack();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * getwebView
+ *
+ * @return
+ */
+ public ObservedWebView getWebView() {
+ return mWebview;
+ }
+
+ public interface IWebViewStatusListener {
+ void onPageStarted(String url);
+
+ void onPageFinished(String url);
+
+ void onReceiveTitle(String title);
+
+ boolean shouldOverrideUrl(String url);
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/LoadingLayout.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/LoadingLayout.java
new file mode 100644
index 0000000..70e6e5b
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/LoadingLayout.java
@@ -0,0 +1,159 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.likebamboo.osa.android.R;
+
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+
+/**
+ * @author likebamboo
+ * @date 2015/5/13.
+ * @desc 描述: Loading(整块区域或者加载更多时loading)
+ */
+public class LoadingLayout extends LinearLayout {
+ /**
+ * 正在加载view
+ */
+ @InjectView(R.id.loading_layout)
+ View mLoadingView = null;
+
+ /**
+ * Loading 提示TextView
+ */
+ @InjectView(R.id.loading_tv)
+ TextView mLoadingTv = null;
+
+ /**
+ * 重试布局
+ */
+ @InjectView(R.id.loading_fail_layout)
+ View mRetryLayout = null;
+
+ /**
+ * 错误提示TextView
+ */
+ @InjectView(R.id.loading_fail_tv)
+ TextView mErrorTv = null;
+
+ /**
+ * 重试接口
+ */
+ private IRetryListener mRetryListener = null;
+
+ /**
+ * 是否可以重试
+ */
+ private boolean canRetry = true;
+
+ public interface IRetryListener {
+ void onRetry();
+ }
+
+ public LoadingLayout(Context context) {
+ this(context, null);
+ }
+
+ public LoadingLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @SuppressLint("NewApi")
+ public LoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ if (isInEditMode()) {
+ return;
+ }
+ ButterKnife.inject(this);
+ mRetryLayout.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!canRetry) {
+ return;
+ }
+ if (mRetryListener != null) {
+ mRetryListener.onRetry();
+ }
+ }
+ });
+ }
+
+ /**
+ * 显示/隐藏正在加载中。。。
+ *
+ * @param show
+ * @see [类、类#方法、类#成员]
+ */
+ public void showLoading(boolean show) {
+ showLoading(show, null);
+ }
+
+ /**
+ * 显示/隐藏正在加载中。。。
+ *
+ * @param show
+ * @param text
+ * @see [类、类#方法、类#成员]
+ */
+ public void showLoading(boolean show, String text) {
+ if (show) {
+ setVisibility(View.VISIBLE);
+ mLoadingView.setVisibility(View.VISIBLE);
+ mRetryLayout.setVisibility(View.GONE);
+ if (!TextUtils.isEmpty(text)) {
+ mLoadingTv.setText(text);
+ }
+ } else {
+ setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * 显示加载失败信息
+ *
+ * @param msg
+ * @see [类、类#方法、类#成员]
+ */
+ public void showError(String msg) {
+ setVisibility(View.VISIBLE);
+ mLoadingView.setVisibility(View.GONE);
+ mRetryLayout.setVisibility(View.VISIBLE);
+ if (!TextUtils.isEmpty(msg)) {
+ mErrorTv.setText(msg);
+ }
+ canRetry = true;
+ }
+
+ /**
+ * 显示"空"信息
+ *
+ * @param msg
+ */
+ public void showEmpty(String msg) {
+ showError(msg);
+ // 不允许重试
+ canRetry = false;
+ }
+
+ /**
+ * 设置重试监听器。
+ *
+ * @param listener
+ */
+ public void setRetryListener(IRetryListener listener) {
+ this.mRetryListener = listener;
+ }
+
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/ObservedWebView.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/ObservedWebView.java
new file mode 100644
index 0000000..6bfc16e
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/ObservedWebView.java
@@ -0,0 +1,49 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.webkit.WebView;
+
+/**
+ * 可监听滚动的WebView
+ *
+ * Created by wentaoli on 2015/5/22.
+ */
+public class ObservedWebView extends WebView {
+ private OnScrollChangedCallback mOnScrollChangedCallback;
+
+ public ObservedWebView(final Context context) {
+ super(context);
+ }
+
+ public ObservedWebView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ObservedWebView(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ if (mOnScrollChangedCallback != null) {
+ mOnScrollChangedCallback.onScroll(l, t);
+ }
+ }
+
+ public OnScrollChangedCallback getOnScrollChangedCallback() {
+ return mOnScrollChangedCallback;
+ }
+
+ public void setOnScrollChangedCallback(final OnScrollChangedCallback onScrollChangedCallback) {
+ mOnScrollChangedCallback = onScrollChangedCallback;
+ }
+
+ /**
+ * Impliment in the activity/fragment/view that you want to listen to the webview
+ */
+ public interface OnScrollChangedCallback {
+ void onScroll(int l, int t);
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/TagGroup.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/TagGroup.java
new file mode 100644
index 0000000..80de813
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/TagGroup.java
@@ -0,0 +1,508 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.likebamboo.osa.android.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 参考https://github.com/2dxgujun/AndroidTagGroup
+ *
+ * @author likebamboo
+ * @version 1.0
+ * @since 2015-05-18
+ */
+public class TagGroup extends ViewGroup {
+
+ /**
+ * tag 字体大小
+ */
+ private float mTagTextSize;
+
+ /**
+ * 字体背景
+ */
+ private int mTagBackgroundColor;
+
+ /**
+ * 字体背景
+ */
+ private int mTagBackgroundResId;
+
+ /**
+ * 字体颜色
+ */
+ private int mTagTextColor;
+
+ /**
+ * The horizontal tag spacing,
+ */
+ private int mHorizontalSpacing;
+
+ /**
+ * The vertical tag spacing
+ */
+ private int mVerticalSpacing;
+
+ /**
+ * 标签内边距
+ */
+ private int mHorizontalPadding;
+
+ /**
+ * 标签内边距
+ */
+ private int mVerticalPadding;
+
+ /**
+ * Listener used to dispatch tag click event.
+ */
+ private IOnTagClickListener mOnTagClickListener;
+
+ public TagGroup(Context context) {
+ this(context, null);
+ }
+
+ public TagGroup(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TagGroup(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ // Load styled attributes.
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TagGroup, defStyleAttr, R.style.TagGroup);
+ try {
+ mTagTextColor = a.getColor(R.styleable.TagGroup_tagTextColor, Color.WHITE);
+ try {
+ mTagBackgroundColor = a.getColor(R.styleable.TagGroup_tagBackground, 0);
+ } catch (Exception e) {
+ mTagBackgroundResId = a.getResourceId(R.styleable.TagGroup_tagBackground, 0);
+ }
+ mTagTextSize = a.getDimensionPixelOffset(R.styleable.TagGroup_tagTextSize, (int) dp2px(14));
+ mHorizontalSpacing = (int) a.getDimension(R.styleable.TagGroup_horizontalSpacing, dp2px(8));
+ mVerticalSpacing = (int) a.getDimension(R.styleable.TagGroup_verticalSpacing, dp2px(4));
+
+ mHorizontalPadding = (int) a.getDimension(R.styleable.TagGroup_horizontalPadding, dp2px(6));
+ mVerticalPadding = (int) a.getDimension(R.styleable.TagGroup_verticalPadding, dp2px(1));
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ a.recycle();
+ }
+
+ }
+
+ public void setTextSize(float textSize) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ TagView tagView = getTagViewAt(i);
+ tagView.setTextSize(textSize);
+ }
+ requestLayout();
+ }
+
+ /**
+ * @param resId
+ */
+ public void setTagColor(int resId) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ TagView tagView = getTagViewAt(i);
+ tagView.setTextColor(resId);
+ }
+ }
+
+ /**
+ * @param resId
+ */
+ public void setTagBackgroundResource(int resId) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ TagView tagView = getTagViewAt(i);
+ tagView.setBackgroundResource(resId);
+ }
+ }
+
+ /**
+ * @param color
+ */
+ public void setTagBackgroundColor(int color) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ TagView tagView = getTagViewAt(i);
+ tagView.setBackgroundColor(color);
+ }
+ }
+
+ public int getHorizontalSpacing() {
+ return mHorizontalSpacing;
+ }
+
+ public void setHorizontalSpacing(int horizontalSpacing) {
+ mHorizontalSpacing = horizontalSpacing;
+ requestLayout();
+ }
+
+ public int getVerticalSpacing() {
+ return mVerticalSpacing;
+ }
+
+ public void setVerticalSpacing(int verticalSpacing) {
+ mVerticalSpacing = verticalSpacing;
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+
+ int width = 0;
+ int height = 0;
+
+ int row = 0; // The row counter.
+ int rowWidth = 0; // Calc the current row width.
+ int rowMaxHeight = 0; // Calc the max tag height, in current row.
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ if (child.getVisibility() != GONE) {
+ rowWidth += childWidth;
+ if (rowWidth > widthSize) { // Next line.
+ rowWidth = childWidth; // The next row width.
+ height += rowMaxHeight + mVerticalSpacing;
+ rowMaxHeight = childHeight; // The next row max height.
+ row++;
+ } else { // This line.
+ rowMaxHeight = Math.max(rowMaxHeight, childHeight);
+ }
+ rowWidth += mHorizontalSpacing;
+ }
+ }
+ // Account for the last row height.
+ height += rowMaxHeight;
+
+ // Account for the padding too.
+ height += getPaddingTop() + getPaddingBottom();
+
+ // If the tags grouped in one row, set the width to wrap the tags.
+ if (row == 0) {
+ width = rowWidth;
+ width += getPaddingLeft() + getPaddingRight();
+ } else {// If the tags grouped exceed one line, set the width to match the parent.
+ width = widthSize;
+ }
+
+ setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
+ heightMode == MeasureSpec.EXACTLY ? heightSize : height);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int parentLeft = getPaddingLeft();
+ final int parentRight = r - l - getPaddingRight();
+ final int parentTop = getPaddingTop();
+ final int parentBottom = b - t - getPaddingBottom();
+
+ int childLeft = parentLeft;
+ int childTop = parentTop;
+
+ int rowMaxHeight = 0;
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ final int width = child.getMeasuredWidth();
+ final int height = child.getMeasuredHeight();
+
+ if (child.getVisibility() != GONE) {
+ if (childLeft + width > parentRight) { // Next line
+ childLeft = parentLeft;
+ childTop += rowMaxHeight + mVerticalSpacing;
+ rowMaxHeight = height;
+ } else {
+ rowMaxHeight = Math.max(rowMaxHeight, height);
+ }
+ child.layout(childLeft, childTop, childLeft + width, childTop + height);
+
+ childLeft += width + mHorizontalSpacing;
+ }
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+ ss.tags = getTags().toArray(new String[]{});
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ setTags(ss.tags);
+ }
+
+
+ /**
+ * Returns the tags array in group.
+ *
+ * @return the tag array
+ */
+ public List getTags() {
+ final int count = getChildCount();
+ final List tagList = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ final TagView tagView = getTagViewAt(i);
+ tagList.add(tagView.getText().toString());
+ }
+
+ return tagList;
+ }
+
+ /**
+ * Returns the tag view at the specified position in the group.
+ *
+ * @param index the position at which to get the tag view from
+ * @return the tag view at the specified position or null if the position
+ * does not exists within this group
+ */
+ protected TagView getTagViewAt(int index) {
+ return (TagView) getChildAt(index);
+ }
+
+ public void setOnTagClickListener(IOnTagClickListener l) {
+ mOnTagClickListener = l;
+ }
+
+ /**
+ * @see #setTags(String...)
+ */
+ public void setTags(List tagList) {
+ setTags(tagList.toArray(new String[]{}));
+ }
+
+ /**
+ * Set the tag to this group. It will remove all tags first.
+ *
+ * @param tags the tag list to set
+ */
+ public void setTags(String... tags) {
+ removeAllViews();
+ for (final String tag : tags) {
+ if (TextUtils.isEmpty(tag)) {
+ continue;
+ }
+ addTag(tag);
+ }
+ }
+
+ /**
+ * 删除标签
+ *
+ * @param tag
+ */
+ public void deleteTag(CharSequence tag) {
+ if (TextUtils.isEmpty(tag)) {
+ return;
+ }
+ int index = 0;
+ for (; index < getChildCount(); index++) {
+ if (getTagViewAt(index).getText().equals(tag)) {
+ break;
+ }
+ }
+ if (index < getChildCount()) {
+ removeViewAt(index);
+ }
+ }
+
+ /**
+ * 判断是否已经有标签
+ *
+ * @param tag
+ */
+ public boolean hasTag(CharSequence tag) {
+ if (TextUtils.isEmpty(tag)) {
+ return true;
+ }
+ return getTags().contains(tag + "");
+ }
+
+ /**
+ * 添加标签
+ *
+ * @param tag
+ */
+ public void addTag(CharSequence tag) {
+ addTag(getChildCount(), tag);
+ }
+
+ /**
+ * 添加标签
+ *
+ * @param index
+ * @param tag the tag to append
+ */
+ public void addTag(int index, CharSequence tag) {
+ // 如果已经有该标签,将标签提到最前面
+ if (hasTag(tag)) {
+ deleteTag(tag);
+ index = 0;
+ }
+ final TagView tagView = new TagView(getContext());
+ tagView.setText(tag);
+ tagView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTagTextSize);
+ tagView.setTextColor(mTagTextColor);
+ if (mTagBackgroundColor != 0) {
+ tagView.setBackgroundColor(mTagBackgroundColor);
+ } else if (mTagBackgroundResId != 0) {
+ tagView.setBackgroundResource(mTagBackgroundResId);
+ }
+
+ tagView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (view instanceof TagView) {
+ TagView v = (TagView) view;
+ if (mOnTagClickListener != null) {
+ mOnTagClickListener.onTagClick(v.getText().toString().trim());
+ }
+ }
+ }
+ });
+ tagView.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ // 长按震动反馈
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ if (view instanceof TagView) {
+ TagView v = (TagView) view;
+ if (mOnTagClickListener != null) {
+ mOnTagClickListener.onTagLongClick(v.getText().toString().trim());
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+
+ // 设置标签内边距
+ tagView.setPadding(mHorizontalPadding, mVerticalPadding, mHorizontalPadding, mVerticalPadding);
+ addView(tagView, index, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ }
+
+ public float dp2px(float dp) {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
+ getResources().getDisplayMetrics());
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ public interface IOnTagClickListener {
+ /**
+ * @param tag tagName
+ */
+ void onTagClick(String tag);
+
+ /**
+ * tagLongClick
+ *
+ * @param tag tagName
+ */
+ void onTagLongClick(String tag);
+ }
+
+ /**
+ * For {@link TagGroup} save and restore state.
+ */
+ static class SavedState extends BaseSavedState {
+ int tagCount;
+ String[] tags;
+ int checkedPosition;
+ String input;
+
+ public SavedState(Parcel source) {
+ super(source);
+ tagCount = source.readInt();
+ tags = new String[tagCount];
+ source.readStringArray(tags);
+ checkedPosition = source.readInt();
+ input = source.readString();
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ tagCount = tags.length;
+ dest.writeInt(tagCount);
+ dest.writeStringArray(tags);
+ dest.writeInt(checkedPosition);
+ dest.writeString(input);
+ }
+
+ public static final Creator CREATOR =
+ new Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ /**
+ * The tag view
+ */
+ class TagView extends TextView {
+ public TagView(Context context) {
+ super(context);
+ }
+
+ public TagView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TagView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/WebViewToolBar.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/WebViewToolBar.java
new file mode 100644
index 0000000..a4071e3
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/WebViewToolBar.java
@@ -0,0 +1,142 @@
+package com.likebamboo.osa.android.ui.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.webkit.WebView;
+import android.widget.RelativeLayout;
+
+import com.likebamboo.osa.android.R;
+
+/**
+ * WebView 操作栏封装,提供 back ,refresh ,forward 功能
+ *
+ * @author likebamboo
+ * @version [版本号, 2015年5月20日]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+public class WebViewToolBar extends RelativeLayout implements View.OnClickListener
+{
+
+ /**
+ * 后退
+ */
+ private View mWebviewGoBack;
+
+ /**
+ * 刷新
+ */
+ private View mWebviewRefresh;
+
+ /**
+ * 前进
+ */
+ private View mWebviewGoForward;
+
+ /**
+ * 目标WebView
+ */
+ private WebView mTargetView = null;
+
+ public WebViewToolBar(Context context)
+ {
+ this(context, null);
+ }
+
+ public WebViewToolBar(Context context, AttributeSet attrs)
+ {
+ this(context, attrs, 0);
+ }
+
+ public WebViewToolBar(Context context, AttributeSet attrs, int defStyleAttr)
+ {
+ super(context, attrs, defStyleAttr);
+ LayoutInflater.from(context).inflate(R.layout.webview_toolbar, this, true);
+ if (!isInEditMode())
+ {
+ // 初始化
+ initView();
+ // 添加监听器
+ addListener();
+ }
+ }
+
+ /**
+ * 初始化界面元素
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ private void initView()
+ {
+ mWebviewGoBack = findViewById(R.id.webviewGoBack);
+ mWebviewGoBack.setEnabled(false);
+ mWebviewRefresh = findViewById(R.id.webviewRefresh);
+ mWebviewRefresh.setEnabled(false);
+ mWebviewGoForward = findViewById(R.id.webviewGoForward);
+ mWebviewGoForward.setEnabled(false);
+ }
+
+ /**
+ * 添加监听器
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ private void addListener()
+ {
+ mWebviewGoBack.setOnClickListener(this);
+ mWebviewRefresh.setOnClickListener(this);
+ mWebviewGoForward.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v)
+ {
+ if (v == null || mTargetView == null)
+ {
+ return;
+ }
+ switch (v.getId())
+ {
+ case R.id.webviewGoBack:// 后退
+ mTargetView.goBack();
+ break;
+ case R.id.webviewRefresh:// 刷新
+ mTargetView.reload();
+ break;
+ case R.id.webviewGoForward:// 向前
+ mTargetView.goForward();
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * 将本控件绑定到具体的webView 上
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ public void attachToWebView(WebView target)
+ {
+ mTargetView = target;
+ }
+
+ /**
+ * 更新状态
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ public void updateStatus()
+ {
+ if (mTargetView == null)
+ {
+ return;
+ }
+ mWebviewGoBack.setEnabled(mTargetView.canGoBack());
+ mWebviewRefresh.setEnabled(true);
+ mWebviewGoForward.setEnabled(mTargetView.canGoForward());
+ }
+
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Blur.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Blur.java
new file mode 100644
index 0000000..180d371
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Blur.java
@@ -0,0 +1,276 @@
+package com.likebamboo.osa.android.ui.view.blur;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Build.VERSION;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicBlur;
+
+// Slightly modified source from Nicolas Pomepuy
+// https://github.com/PomepuyN/BlurEffectForAndroidDesign
+
+public class Blur {
+
+ public static Bitmap apply(Context context, Bitmap sentBitmap) {
+ return apply(context, sentBitmap, 10);
+ }
+
+ public static Bitmap apply(Context context, Bitmap sentBitmap, int radius) {
+ return apply(context, sentBitmap, 1, radius);
+ }
+
+ @SuppressLint("NewApi")
+ public static Bitmap apply(Context context, Bitmap sentBitmap, float scale, int radius) {
+ if (sentBitmap == null) {
+ return null;
+ }
+ if (scale < 0.5) {
+ scale = 0.5F;
+ }
+ if (scale > 1) {
+ scale = 1;
+ }
+
+ Bitmap bitmap = Bitmap.createScaledBitmap(sentBitmap, (int) (sentBitmap.getWidth() * scale), (int) (sentBitmap.getHeight() * scale), false);
+
+ if (VERSION.SDK_INT > 16) {
+ final RenderScript rs = RenderScript.create(context);
+ final Allocation input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+ final Allocation output = Allocation.createTyped(rs, input.getType());
+ final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
+ script.setRadius(radius);
+ script.setInput(input);
+ script.forEach(output);
+ output.copyTo(bitmap);
+
+ return bitmap;
+ }
+
+ // Stack Blur v1.0 from
+ // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
+ //
+ // Java Author: Mario Klingemann
+ // http://incubator.quasimondo.com
+ // created Feburary 29, 2004
+ // Android port : Yahel Bouaziz
+ // http://www.kayenko.com
+ // ported april 5th, 2012
+
+ // This is a compromise between Gaussian Blur and Box blur
+ // It creates much better looking blurs than Box Blur, but is
+ // 7x faster than my Gaussian Blur implementation.
+ //
+ // I called it Stack Blur because this describes best how this
+ // filter works internally: it creates a kind of moving stack
+ // of colors whilst scanning through the image. Thereby it
+ // just has to add one new block of color to the right side
+ // of the stack and remove the leftmost color. The remaining
+ // colors on the topmost layer of the stack are either added on
+ // or reduced by one, depending on if they are on the right or
+ // on the left side of the stack.
+ //
+ // If you are using this algorithm in your code please add
+ // the following line:
+ //
+ // Stack Blur Algorithm by Mario Klingemann
+
+ if (radius < 1) {
+ return (null);
+ }
+
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+
+ int[] pix = new int[w * h];
+ bitmap.getPixels(pix, 0, w, 0, 0, w, h);
+
+ int wm = w - 1;
+ int hm = h - 1;
+ int wh = w * h;
+ int div = radius + radius + 1;
+
+ int r[] = new int[wh];
+ int g[] = new int[wh];
+ int b[] = new int[wh];
+ int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
+ int vmin[] = new int[Math.max(w, h)];
+
+ int divsum = (div + 1) >> 1;
+ divsum *= divsum;
+ int dv[] = new int[256 * divsum];
+ for (i = 0; i < 256 * divsum; i++) {
+ dv[i] = (i / divsum);
+ }
+
+ yw = yi = 0;
+
+ int[][] stack = new int[div][3];
+ int stackpointer;
+ int stackstart;
+ int[] sir;
+ int rbs;
+ int r1 = radius + 1;
+ int routsum, goutsum, boutsum;
+ int rinsum, ginsum, binsum;
+
+ for (y = 0; y < h; y++) {
+ rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
+ for (i = -radius; i <= radius; i++) {
+ p = pix[yi + Math.min(wm, Math.max(i, 0))];
+ sir = stack[i + radius];
+ sir[0] = (p & 0xff0000) >> 16;
+ sir[1] = (p & 0x00ff00) >> 8;
+ sir[2] = (p & 0x0000ff);
+ rbs = r1 - Math.abs(i);
+ rsum += sir[0] * rbs;
+ gsum += sir[1] * rbs;
+ bsum += sir[2] * rbs;
+ if (i > 0) {
+ rinsum += sir[0];
+ ginsum += sir[1];
+ binsum += sir[2];
+ } else {
+ routsum += sir[0];
+ goutsum += sir[1];
+ boutsum += sir[2];
+ }
+ }
+ stackpointer = radius;
+
+ for (x = 0; x < w; x++) {
+
+ r[yi] = dv[rsum];
+ g[yi] = dv[gsum];
+ b[yi] = dv[bsum];
+
+ rsum -= routsum;
+ gsum -= goutsum;
+ bsum -= boutsum;
+
+ stackstart = stackpointer - radius + div;
+ sir = stack[stackstart % div];
+
+ routsum -= sir[0];
+ goutsum -= sir[1];
+ boutsum -= sir[2];
+
+ if (y == 0) {
+ vmin[x] = Math.min(x + radius + 1, wm);
+ }
+ p = pix[yw + vmin[x]];
+
+ sir[0] = (p & 0xff0000) >> 16;
+ sir[1] = (p & 0x00ff00) >> 8;
+ sir[2] = (p & 0x0000ff);
+
+ rinsum += sir[0];
+ ginsum += sir[1];
+ binsum += sir[2];
+
+ rsum += rinsum;
+ gsum += ginsum;
+ bsum += binsum;
+
+ stackpointer = (stackpointer + 1) % div;
+ sir = stack[(stackpointer) % div];
+
+ routsum += sir[0];
+ goutsum += sir[1];
+ boutsum += sir[2];
+
+ rinsum -= sir[0];
+ ginsum -= sir[1];
+ binsum -= sir[2];
+
+ yi++;
+ }
+ yw += w;
+ }
+ for (x = 0; x < w; x++) {
+ rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
+ yp = -radius * w;
+ for (i = -radius; i <= radius; i++) {
+ yi = Math.max(0, yp) + x;
+
+ sir = stack[i + radius];
+
+ sir[0] = r[yi];
+ sir[1] = g[yi];
+ sir[2] = b[yi];
+
+ rbs = r1 - Math.abs(i);
+
+ rsum += r[yi] * rbs;
+ gsum += g[yi] * rbs;
+ bsum += b[yi] * rbs;
+
+ if (i > 0) {
+ rinsum += sir[0];
+ ginsum += sir[1];
+ binsum += sir[2];
+ } else {
+ routsum += sir[0];
+ goutsum += sir[1];
+ boutsum += sir[2];
+ }
+
+ if (i < hm) {
+ yp += w;
+ }
+ }
+ yi = x;
+ stackpointer = radius;
+ for (y = 0; y < h; y++) {
+ // Preserve alpha channel: ( 0xff000000 & pix[yi] )
+ pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
+
+ rsum -= routsum;
+ gsum -= goutsum;
+ bsum -= boutsum;
+
+ stackstart = stackpointer - radius + div;
+ sir = stack[stackstart % div];
+
+ routsum -= sir[0];
+ goutsum -= sir[1];
+ boutsum -= sir[2];
+
+ if (x == 0) {
+ vmin[y] = Math.min(y + r1, hm) * w;
+ }
+ p = x + vmin[y];
+
+ sir[0] = r[p];
+ sir[1] = g[p];
+ sir[2] = b[p];
+
+ rinsum += sir[0];
+ ginsum += sir[1];
+ binsum += sir[2];
+
+ rsum += rinsum;
+ gsum += ginsum;
+ bsum += binsum;
+
+ stackpointer = (stackpointer + 1) % div;
+ sir = stack[stackpointer];
+
+ routsum += sir[0];
+ goutsum += sir[1];
+ boutsum += sir[2];
+
+ rinsum -= sir[0];
+ ginsum -= sir[1];
+ binsum -= sir[2];
+
+ yi += w;
+ }
+ }
+
+ bitmap.setPixels(pix, 0, w, 0, 0, w, h);
+ return (bitmap);
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurBehind.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurBehind.java
new file mode 100644
index 0000000..f67c807
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurBehind.java
@@ -0,0 +1,121 @@
+package com.likebamboo.osa.android.ui.view.blur;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.AsyncTask;
+import android.support.v4.util.LruCache;
+import android.view.View;
+
+/**
+ * READ ME
+ *
+ * Created by wentaoli on 2015/5/18.
+ */
+public class BlurBehind {
+
+ private static final String KEY_CACHE_BLURRED_BACKGROUND_IMAGE = "KEY_CACHE_BLURRED_BACKGROUND_IMAGE";
+ private static final int CONSTANT_BLUR_RADIUS = 10;
+ private static final int CONSTANT_DEFAULT_ALPHA = 100;
+
+ private static final LruCache mImageCache = new LruCache(1);
+ private static CacheBlurBehindAndExecuteTask cacheBlurBehindAndExecuteTask;
+
+ private int mAlpha = CONSTANT_DEFAULT_ALPHA;
+ private int mFilterColor = -1;
+
+ private enum State {
+ READY,
+ EXECUTING
+ }
+
+ private State mState = State.READY;
+
+ private static BlurBehind mInstance;
+
+ public static BlurBehind getInstance() {
+ if (mInstance == null) {
+ mInstance = new BlurBehind();
+ }
+ return mInstance;
+ }
+
+ public void execute(Activity activity, OnBlurCompleteListener onBlurCompleteListener) {
+ if (mState.equals(State.READY)) {
+ mState = State.EXECUTING;
+ cacheBlurBehindAndExecuteTask = new CacheBlurBehindAndExecuteTask(activity, onBlurCompleteListener);
+ cacheBlurBehindAndExecuteTask.execute();
+ }
+ }
+
+ public BlurBehind withAlpha(int alpha) {
+ this.mAlpha = alpha;
+ return this;
+ }
+
+ public BlurBehind withFilterColor(int filterColor) {
+ this.mFilterColor = filterColor;
+ return this;
+ }
+
+ public void setBackground(Activity activity) {
+ if (mImageCache.size() != 0) {
+ BitmapDrawable bd = new BitmapDrawable(activity.getResources(), mImageCache.get(KEY_CACHE_BLURRED_BACKGROUND_IMAGE));
+ bd.setAlpha(mAlpha);
+ if (mFilterColor != -1) {
+ bd.setColorFilter(mFilterColor, PorterDuff.Mode.DST_ATOP);
+ }
+ activity.getWindow().setBackgroundDrawable(bd);
+ mImageCache.remove(KEY_CACHE_BLURRED_BACKGROUND_IMAGE);
+ cacheBlurBehindAndExecuteTask = null;
+ }
+ }
+
+ private class CacheBlurBehindAndExecuteTask extends AsyncTask {
+ private Activity activity;
+ private OnBlurCompleteListener onBlurCompleteListener;
+
+ private View decorView;
+ private Bitmap image;
+
+ public CacheBlurBehindAndExecuteTask(Activity activity, OnBlurCompleteListener onBlurCompleteListener) {
+ this.activity = activity;
+ this.onBlurCompleteListener = onBlurCompleteListener;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+
+ decorView = activity.getWindow().getDecorView();
+ decorView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_LOW);
+ decorView.setDrawingCacheEnabled(true);
+ decorView.buildDrawingCache();
+
+ image = decorView.getDrawingCache();
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ Bitmap blurredBitmap = Blur.apply(activity, image, 0.5F, CONSTANT_BLUR_RADIUS);
+ mImageCache.put(KEY_CACHE_BLURRED_BACKGROUND_IMAGE, blurredBitmap);
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+
+ decorView.destroyDrawingCache();
+ decorView.setDrawingCacheEnabled(false);
+
+ activity = null;
+
+ onBlurCompleteListener.onBlurComplete();
+
+ mState = State.READY;
+ }
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurDialogFragmentHelper.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurDialogFragmentHelper.java
new file mode 100644
index 0000000..d89e80f
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/BlurDialogFragmentHelper.java
@@ -0,0 +1,140 @@
+package com.likebamboo.osa.android.ui.view.blur;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.StyleRes;
+import android.support.v4.app.DialogFragment;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.likebamboo.osa.android.R;
+
+/**
+ * BlurDialogFragmentHelper.java
+ *
+ * @author Manabu-GT on 6/12/14.
+ */
+public class BlurDialogFragmentHelper {
+
+ private final DialogFragment mFragment;
+
+ private ViewGroup mRoot;
+
+ private ViewGroup mBlurContainer;
+
+ private View mBlurBgView;
+
+ private ImageView mBlurImgView;
+
+ private int mAnimDuration;
+
+ private int mWindowAnimStyle;
+
+ private int mBgColorResId;
+
+ public BlurDialogFragmentHelper(@NonNull DialogFragment fragment) {
+ mFragment = fragment;
+ mAnimDuration = fragment.getActivity().getResources().getInteger(android.R.integer.config_mediumAnimTime);
+ mWindowAnimStyle = R.style.DialogSlideAnimation;
+ mBgColorResId = R.color.bg_glass;
+ }
+
+ /**
+ * Duration of the alpha animation.
+ * @param animDuration The length of ensuing property animations, in milliseconds.
+ * The value cannot be negative.
+ */
+ public void setAnimDuration(int animDuration) {
+ mAnimDuration = animDuration;
+ }
+
+ public void setWindowAnimStyle(@StyleRes int windowAnimStyle) {
+ mWindowAnimStyle = windowAnimStyle;
+ }
+
+ public void setBgColorResId(@ColorRes int bgColorResId) {
+ mBgColorResId = bgColorResId;
+ }
+
+ public void onCreate() {
+ mFragment.setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Translucent_NoTitleBar);
+ }
+
+ public void onActivityCreated() {
+ Window window = mFragment.getDialog().getWindow();
+ window.setWindowAnimations(mWindowAnimStyle);
+
+ mRoot = (ViewGroup) mFragment.getActivity().getWindow().getDecorView();
+ Rect visibleFrame = new Rect();
+ mRoot.getWindowVisibleDisplayFrame(visibleFrame);
+
+ mBlurContainer = new FrameLayout(mFragment.getActivity());
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ params.topMargin = visibleFrame.top;
+ params.bottomMargin = mRoot.getHeight() - visibleFrame.bottom;
+ mBlurContainer.setLayoutParams(params);
+
+ mBlurBgView = new View(mFragment.getActivity());
+ mBlurBgView.setBackgroundColor(mFragment.getResources().getColor(mBgColorResId));
+ Util.setAlpha(mBlurBgView, 0f);
+
+ mBlurImgView = new ImageView(mFragment.getActivity());
+ Util.setAlpha(mBlurImgView, 0f);
+
+ mBlurContainer.addView(mBlurImgView);
+ mBlurContainer.addView(mBlurBgView);
+
+ mRoot.addView(mBlurContainer);
+
+ Bitmap bitmap = Util.drawViewToBitmap(mRoot, mRoot.getWidth(),
+ visibleFrame.bottom, 0, visibleFrame.top, 3);
+ Bitmap blurred = Blur.apply(mFragment.getActivity(), bitmap);
+ if (blurred == null) {
+ return;
+ }
+ mBlurImgView.setScaleType(ImageView.ScaleType.FIT_XY);
+ mBlurImgView.setImageBitmap(blurred);
+ bitmap.recycle();
+
+ View view = mFragment.getView();
+ if (view != null) {
+ view.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ mFragment.dismiss();
+ return true;
+ }
+ });
+ }
+ }
+
+ public void onStart() {
+ startEnterAnimation();
+ }
+
+ public void onDismiss() {
+ startExitAnimation();
+ }
+
+ private void startEnterAnimation() {
+ Util.animateAlpha(mBlurBgView, 0f, 1f, mAnimDuration, null);
+ Util.animateAlpha(mBlurImgView, 0f, 1f, mAnimDuration, null);
+ }
+
+ private void startExitAnimation() {
+ Util.animateAlpha(mBlurBgView, 1f, 0f, mAnimDuration, null);
+ Util.animateAlpha(mBlurImgView, 1f, 0f, mAnimDuration, new Runnable() {
+ @Override
+ public void run() {
+ mRoot.removeView(mBlurContainer);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/EtsyActionBarDrawerToggle.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/EtsyActionBarDrawerToggle.java
new file mode 100644
index 0000000..f673585
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/EtsyActionBarDrawerToggle.java
@@ -0,0 +1,107 @@
+package com.likebamboo.osa.android.ui.view.blur;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.likebamboo.osa.android.R;
+
+/**
+ * EtsyActionBarDrawerToggle.java
+ *
+ * @author Manabu-GT on 6/12/14.
+ */
+public class EtsyActionBarDrawerToggle extends ActionBarDrawerToggle {
+
+ private static final int DEFAULT_RADIUS = 10;
+ private static final int DEFAULT_DOWN_SAMPLING = 3;
+
+ private Activity mActivity;
+
+ private View mContainer;
+ private ImageView mBlurImage;
+
+ private int mBlurRadius;
+ private int mDownSampling;
+
+ public EtsyActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
+ int openDrawerContentDescRes, int closeDrawerContentDescRes) {
+ super(activity, drawerLayout, openDrawerContentDescRes, closeDrawerContentDescRes);
+ init(activity);
+ }
+
+ public EtsyActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, Toolbar toolbar,
+ int openDrawerContentDescRes, int closeDrawerContentDescRes) {
+ super(activity, drawerLayout, toolbar, openDrawerContentDescRes, closeDrawerContentDescRes);
+ init(activity);
+ }
+
+ public void setBlurImage() {
+ mBlurImage.setImageBitmap(null);
+ mBlurImage.setVisibility(View.VISIBLE);
+ // do the downscaling for faster processing
+ Bitmap downScaled = Util.drawViewToBitmap(mContainer,
+ mContainer.getWidth(), mContainer.getHeight(), mDownSampling);
+ if (downScaled == null) {
+ return;
+ }
+ // apply the blur using the renderscript
+ Bitmap blurred = Blur.apply(mActivity, downScaled, mBlurRadius);
+ if (blurred == null) {
+ return;
+ }
+ mBlurImage.setImageBitmap(blurred);
+ downScaled.recycle();
+ }
+
+ public void clearBlurImage() {
+ mBlurImage.setVisibility(View.GONE);
+ mBlurImage.setImageBitmap(null);
+ }
+
+ public void setBlurRadius(int blurRadius) {
+ if (0 < blurRadius && blurRadius <= 25) {
+ mBlurRadius = blurRadius;
+ }
+ }
+
+ public void setDownSampling(int downSampling) {
+ mDownSampling = downSampling;
+ }
+
+ private void init(Activity activity) {
+ mActivity = activity;
+ mBlurRadius = DEFAULT_RADIUS;
+ mDownSampling = DEFAULT_DOWN_SAMPLING;
+
+ mContainer = activity.findViewById(R.id.container);
+ mBlurImage = (ImageView) activity.findViewById(R.id.blur_view);
+ }
+
+ private void setBlurAlpha(float slideOffset) {
+ if (mBlurImage.getVisibility() != View.VISIBLE) {
+ setBlurImage();
+ }
+ Util.setAlpha(mBlurImage, slideOffset);
+ }
+
+ @Override
+ public void onDrawerSlide(final View drawerView, final float slideOffset) {
+ super.onDrawerSlide(drawerView, slideOffset);
+ if (slideOffset > 0f) {
+ setBlurAlpha(slideOffset);
+ } else {
+ clearBlurImage();
+ }
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ super.onDrawerClosed(drawerView);
+ clearBlurImage();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/OnBlurCompleteListener.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/OnBlurCompleteListener.java
new file mode 100644
index 0000000..09bf86c
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/OnBlurCompleteListener.java
@@ -0,0 +1,8 @@
+package com.likebamboo.osa.android.ui.view.blur;
+
+/**
+ * Created by wentaoli on 2015/5/18.
+ */
+public interface OnBlurCompleteListener {
+ void onBlurComplete();
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Util.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Util.java
new file mode 100644
index 0000000..6ccc9c8
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/blur/Util.java
@@ -0,0 +1,94 @@
+package com.likebamboo.osa.android.ui.view.blur;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Handler;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+
+/**
+ * READ ME
+ * Util.java
+ *
+ * @author Manabu-GT on 6/12/14.
+ */
+public class Util {
+
+ public static Bitmap drawViewToBitmap(View view, int width, int height, int downSampling) {
+ return drawViewToBitmap(view, width, height, 0f, 0f, downSampling);
+ }
+
+ public static Bitmap drawViewToBitmap(View view, int width, int height, float translateX,
+ float translateY, int downSampling) {
+ if (width * height == 0) {
+ return null;
+ }
+ float scale = 1f / downSampling;
+ int bmpWidth = (int) (width * scale - translateX / downSampling);
+ int bmpHeight = (int) (height * scale - translateY / downSampling);
+ Bitmap dest = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(dest);
+ c.translate(-translateX / downSampling, -translateY / downSampling);
+ if (downSampling > 1) {
+ c.scale(scale, scale);
+ }
+ view.draw(c);
+ return dest;
+ }
+
+ public static boolean isPostHoneycomb() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+ public static void setAlpha(View view, float alpha) {
+ if (isPostHoneycomb()) {
+ view.setAlpha(alpha);
+ } else {
+ AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha);
+ // make it instant
+ alphaAnimation.setDuration(0);
+ alphaAnimation.setFillAfter(true);
+ view.startAnimation(alphaAnimation);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+ public static void animateAlpha(final View view, float fromAlpha, float toAlpha, int duration, final Runnable endAction) {
+ if (isPostHoneycomb()) {
+ ViewPropertyAnimator animator = view.animate().alpha(toAlpha).setDuration(duration);
+ if (endAction != null) {
+ animator.setListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ });
+ }
+ } else {
+ AlphaAnimation alphaAnimation = new AlphaAnimation(fromAlpha, toAlpha);
+ alphaAnimation.setDuration(duration);
+ alphaAnimation.setFillAfter(true);
+ if (endAction != null) {
+ alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // fixes the crash bug while removing views
+ Handler handler = new Handler();
+ handler.post(endAction);
+ }
+ @Override
+ public void onAnimationStart(Animation animation) { }
+ @Override
+ public void onAnimationRepeat(Animation animation) { }
+ });
+ }
+ view.startAnimation(alphaAnimation);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/ButtonAwesome.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/ButtonAwesome.java
new file mode 100644
index 0000000..59014e0
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/ButtonAwesome.java
@@ -0,0 +1,45 @@
+package com.likebamboo.osa.android.ui.view.fa;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.support.v4.util.LruCache;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * ButtonAwesome
+ *
+ * @see FontAwesomeAndroid
+ */
+public class ButtonAwesome extends Button {
+ private final static String NAME = "FONTAWESOME";
+ private static LruCache sTypefaceCache = new LruCache(12);
+
+ public ButtonAwesome(Context context) {
+ super(context);
+ init();
+
+ }
+
+ public ButtonAwesome(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ButtonAwesome(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public synchronized void init() {
+ Typeface typeface = sTypefaceCache.get(NAME);
+ if (typeface == null) {
+ typeface = Typeface.createFromAsset(getContext().getAssets(), "fontawesome-webfont.ttf");
+ sTypefaceCache.put(NAME, typeface);
+ }
+
+ setTypeface(typeface);
+ }
+}
+
+
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/DrawableAwesome.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/DrawableAwesome.java
new file mode 100644
index 0000000..d7c66fc
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/DrawableAwesome.java
@@ -0,0 +1,148 @@
+package com.likebamboo.osa.android.ui.view.fa;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+
+/**
+ * DrawableAwesome
+ *
+ * @see FontAwesomeAndroid
+ */
+public class DrawableAwesome extends Drawable {
+
+ private static final float PADDING_RATIO = 0.88f;
+
+ private final Context context;
+ private final int icon;
+ private final Paint paint;
+ private final int width;
+ private final int height;
+ private final float size;
+ private final int color;
+ private final boolean antiAliased;
+ private final boolean fakeBold;
+ private final float shadowRadius;
+ private final float shadowDx;
+ private final float shadowDy;
+ private final int shadowColor;
+
+ public DrawableAwesome(int icon, int sizeDpi, int color,
+ boolean antiAliased, boolean fakeBold, float shadowRadius,
+ float shadowDx, float shadowDy, int shadowColor, Context context) {
+ super();
+ this.context = context;
+ this.icon = icon;
+ this.size = dpToPx(sizeDpi) * PADDING_RATIO;
+ this.height = dpToPx(sizeDpi);
+ this.width = dpToPx(sizeDpi);
+ this.color = color;
+ this.antiAliased = antiAliased;
+ this.fakeBold = fakeBold;
+ this.shadowRadius = shadowRadius;
+ this.shadowDx = shadowDx;
+ this.shadowDy = shadowDy;
+ this.shadowColor = shadowColor;
+ this.paint = new Paint();
+
+ paint.setStyle(Paint.Style.FILL);
+ paint.setTextAlign(Paint.Align.CENTER);
+ this.paint.setColor(this.color);
+ this.paint.setTextSize(this.size);
+ Typeface font = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
+ this.paint.setTypeface(font);
+ this.paint.setAntiAlias(this.antiAliased);
+ this.paint.setFakeBoldText(this.fakeBold);
+ this.paint.setShadowLayer(this.shadowRadius, this.shadowDx, this.shadowDy, this.shadowColor);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return height;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return width;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ float xDiff = (width / 2.0f);
+ String stringIcon = this.context.getResources().getString(icon);
+ canvas.drawText(stringIcon, xDiff, size, paint);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ paint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ paint.setColorFilter(cf);
+ }
+
+ private int dpToPx(int dp) {
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
+ return px;
+ }
+
+ public static class DrawableAwesomeBuilder {
+ private Context context;
+ private int icon;
+ private int sizeDpi = 32;
+ private int color = Color.GRAY;
+ private boolean antiAliased = true;
+ private boolean fakeBold = true;
+ private float shadowRadius = 0;
+ private float shadowDx = 0;
+ private float shadowDy = 0;
+ private int shadowColor = Color.WHITE;
+
+ public DrawableAwesomeBuilder(Context context, int icon) {
+ this.context = context;
+ this.icon = icon;
+ }
+
+ public void setSize(int size) {
+ this.sizeDpi = size;
+ }
+
+ public void setColor(int color) {
+ this.color = color;
+ }
+
+ public void setAntiAliased(boolean antiAliased) {
+ this.antiAliased = antiAliased;
+ }
+
+ public void setFakeBold(boolean fakeBold) {
+ this.fakeBold = fakeBold;
+ }
+
+ public void setShadow(float radius, float dx, float dy, int color) {
+ this.shadowRadius = radius;
+ this.shadowDx = dx;
+ this.shadowDy = dy;
+ this.shadowColor = color;
+ }
+
+ public DrawableAwesome build() {
+ return new DrawableAwesome(icon, sizeDpi, color, antiAliased, fakeBold,
+ shadowRadius, shadowDx, shadowDy, shadowColor, context);
+ }
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/TextAwesome.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/TextAwesome.java
new file mode 100644
index 0000000..08b0d57
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fa/TextAwesome.java
@@ -0,0 +1,51 @@
+package com.likebamboo.osa.android.ui.view.fa;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.support.v4.util.LruCache;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * DrawableAwesome
+ *
+ * @see FontAwesomeAndroid
+ */
+public class TextAwesome extends TextView {
+
+ private final static String NAME = "FONTAWESOME";
+ private static LruCache sTypefaceCache = new LruCache(12);
+
+ public TextAwesome(Context context) {
+ super(context);
+ init();
+ }
+
+ public TextAwesome(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public synchronized void init() {
+ Typeface typeface = sTypefaceCache.get(NAME);
+ if (typeface == null) {
+ typeface = Typeface.createFromAsset(getContext().getAssets(), "fontawesome-webfont.ttf");
+ sTypefaceCache.put(NAME, typeface);
+ }
+ setTypeface(typeface);
+ }
+
+ /**
+ * 设置文字
+ *
+ * @param icon
+ * @param text
+ */
+ public void setText(int icon, String text) {
+ String iconStr = getResources().getString(icon);
+ setText(iconStr + " " + text);
+ }
+
+}
+
+
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/DirectionScrollListener.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/DirectionScrollListener.java
new file mode 100644
index 0000000..de978f9
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/DirectionScrollListener.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2014 SBG Apps
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.likebamboo.osa.android.ui.view.fab;
+
+import android.view.View;
+import android.widget.AbsListView;
+
+/**
+ * Created by Stéphane on 09/07/2014.
+ */
+class DirectionScrollListener implements AbsListView.OnScrollListener {
+
+ private static final int DIRECTION_CHANGE_THRESHOLD = 1;
+ private final FloatingActionButton mFloatingActionButton;
+ private int mPrevPosition;
+ private int mPrevTop;
+ private boolean mUpdated;
+
+ DirectionScrollListener(FloatingActionButton floatingActionButton) {
+ mFloatingActionButton = floatingActionButton;
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ final View topChild = view.getChildAt(0);
+ int firstViewTop = 0;
+ if (topChild != null) {
+ firstViewTop = topChild.getTop();
+ }
+ boolean goingDown;
+ boolean changed = true;
+ if (mPrevPosition == firstVisibleItem) {
+ final int topDelta = mPrevTop - firstViewTop;
+ goingDown = firstViewTop < mPrevTop;
+ changed = Math.abs(topDelta) > DIRECTION_CHANGE_THRESHOLD;
+ } else {
+ goingDown = firstVisibleItem > mPrevPosition;
+ }
+ if (changed && mUpdated) {
+ mFloatingActionButton.hide(goingDown);
+ }
+ mPrevPosition = firstVisibleItem;
+ mPrevTop = firstViewTop;
+ mUpdated = true;
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FabToolbar.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FabToolbar.java
new file mode 100644
index 0000000..e4c2997
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FabToolbar.java
@@ -0,0 +1,269 @@
+package com.likebamboo.osa.android.ui.view.fab;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.LinearLayout;
+
+import com.likebamboo.osa.android.R;
+import com.likebamboo.osa.android.ui.view.ObservedWebView;
+
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+import io.codetail.animation.SupportAnimator;
+import io.codetail.animation.ViewAnimationUtils;
+import io.codetail.widget.RevealFrameLayout;
+
+/**
+ * fab 对应的工具栏
+ */
+public class FabToolbar extends RevealFrameLayout {
+ /**
+ * 内容区
+ */
+ @InjectView(R.id.container)
+ LinearLayout mContainer = null;
+
+ @InjectView(R.id.fabbutton)
+ FloatingActionButton mFAB = null;
+
+ /**
+ * 屏幕宽度
+ */
+ private float mScreenWidth = 0;
+ /**
+ * 动画执行时间
+ */
+ private int mDuration = 500;
+
+ /**
+ * 是否处于动画执行状态
+ */
+ private boolean isAniming = false;
+
+ public FabToolbar(Context context) {
+ this(context, null);
+ }
+
+ public FabToolbar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FabToolbar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ if (attrs != null) {
+ loadAttributes(attrs);
+ }
+ }
+
+
+ /**
+ * 初始化
+ */
+ private void init() {
+ mScreenWidth = getResources().getDisplayMetrics().widthPixels;
+ View v = inflate(getContext(), R.layout.fab_tool_bar, this);
+ ButterKnife.inject(this, v);
+ mContainer.setVisibility(View.GONE);
+
+ mFAB.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // 正执行动画
+ if (isAniming) {
+ return;
+ }
+
+ // 如果是在展示状态
+ if (mContainer.getVisibility() == View.VISIBLE) {
+ hideMenu();
+ return;
+ }
+ showMenu();
+ }
+ });
+ }
+
+ /**
+ * 加载布局属性
+ *
+ * @param attrs
+ */
+ private void loadAttributes(AttributeSet attrs) {
+ TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.FabToolbar, 0, 0);
+
+ try {
+ mContainer.setBackgroundColor(a.getColor(R.styleable.FabToolbar_tb_color, getResources().getColor(R.color.bg_drawer)));
+ mDuration = a.getInteger(R.styleable.FabToolbar_tb_anim_duration, 500);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ /**
+ * @param startRadius
+ * @param endRadius
+ * @param listener
+ */
+ private void animateCircle(float startRadius, float endRadius, SupportAnimator.AnimatorListener listener) {
+ int cX = (mFAB.getLeft() + mFAB.getRight()) / 2;
+ int cY = (mFAB.getTop() + mFAB.getBottom()) / 2;
+ SupportAnimator animator = ViewAnimationUtils.createCircularReveal(mContainer, cX, cY, startRadius, endRadius);
+ animator.setInterpolator(new AccelerateDecelerateInterpolator());
+ animator.setDuration(mDuration);
+ if (listener != null) {
+ animator.addListener(listener);
+ }
+ animator.start();
+ }
+
+ @Override
+ public void addView(@NonNull View child) {
+ if (canAddViewToContainer(child)) {
+ mContainer.addView(child);
+ } else {
+ super.addView(child);
+ }
+ }
+
+ @Override
+ public void addView(@NonNull View child, int width, int height) {
+ if (canAddViewToContainer(child)) {
+ mContainer.addView(child, width, height);
+ } else {
+ super.addView(child, width, height);
+ }
+ }
+
+ @Override
+ public void addView(@NonNull View child, ViewGroup.LayoutParams params) {
+ if (canAddViewToContainer(child)) {
+ mContainer.addView(child, params);
+ } else {
+ super.addView(child, params);
+ }
+ }
+
+ @Override
+ public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) {
+ if (canAddViewToContainer(child)) {
+ mContainer.addView(child, index, params);
+ } else {
+ super.addView(child, index, params);
+ }
+ }
+
+ /**
+ * 判断View是否可以添加
+ *
+ * @param child
+ * @return
+ */
+ private boolean canAddViewToContainer(View child) {
+ return mContainer != null && !(child instanceof FloatingActionButton);
+ }
+
+ private class ToolbarCollapseListener implements SupportAnimator.AnimatorListener {
+
+ private boolean show = false;
+
+ public ToolbarCollapseListener(boolean show) {
+ this.show = show;
+ }
+
+ @Override
+ public void onAnimationEnd() {
+ if (show) {
+ mContainer.setVisibility(VISIBLE);
+ } else {
+ mContainer.setVisibility(GONE);
+ }
+ isAniming = false;
+ }
+
+ @Override
+ public void onAnimationStart() {
+ isAniming = true;
+ }
+
+ @Override
+ public void onAnimationCancel() {
+ }
+
+ @Override
+ public void onAnimationRepeat() {
+ }
+ }
+
+
+ //==========api method==================
+
+ /**
+ * 显示toolbar
+ */
+ public void showMenu() {
+ mContainer.setVisibility(VISIBLE);
+ animateCircle(0, mScreenWidth, new ToolbarCollapseListener(true));
+ }
+
+ /**
+ * 隐藏toolbar
+ */
+ public void hideMenu() {
+ animateCircle(mScreenWidth, 0, new ToolbarCollapseListener(false));
+ }
+
+ /**
+ * 设置动画执行事件
+ *
+ * @param duration
+ */
+ public void setAnimationDuration(int duration) {
+ mDuration = duration;
+ }
+
+ /**
+ * 将fabtoolbar attach到webView上
+ *
+ * @param webView
+ */
+ public void attachTo(ObservedWebView webView) {
+ webView.setOnScrollChangedCallback(new DirectionWebViweScrollListener());
+ }
+
+ /**
+ * 滚动监听
+ */
+ public class DirectionWebViweScrollListener implements ObservedWebView.OnScrollChangedCallback {
+
+ private static final int DIRECTION_CHANGE_THRESHOLD = 8;
+ private int mPrevTop;
+ private boolean mUpdated;
+
+ @Override
+ public void onScroll(int l, int t) {
+ if (isAniming) {
+ return;
+ }
+ if (mContainer.getVisibility() == View.VISIBLE) {
+ hideMenu();
+ return;
+ }
+ if (mFAB == null) {
+ return;
+ }
+ boolean goingDown = t > mPrevTop;
+ boolean changed = Math.abs(t - mPrevTop) > DIRECTION_CHANGE_THRESHOLD;
+ if (changed && mUpdated) {
+ mFAB.hide(goingDown);
+ }
+ mPrevTop = t;
+ mUpdated = true;
+ }
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FloatingActionButton.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FloatingActionButton.java
new file mode 100644
index 0000000..a841d86
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fab/FloatingActionButton.java
@@ -0,0 +1,183 @@
+package com.likebamboo.osa.android.ui.view.fab;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.AbsListView;
+
+import com.likebamboo.osa.android.R;
+import com.nineoldandroids.animation.ObjectAnimator;
+import com.nineoldandroids.view.ViewHelper;
+
+/**
+ * READ ME
+ * fab
+ */
+public class FloatingActionButton extends View {
+
+ private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+ private final Paint mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private Bitmap mBitmap;
+ private int mColor;
+ private boolean mHidden = false;
+ private Rect rect;
+ private int mLeftDisplayed = -1;
+ private int mRightDisplayed = -1;
+ private int mTopDisplayed = -1;
+ private int mBottomDisplayed = -1;
+ /**
+ * The FAB button's Y position when it is displayed.
+ */
+ private float mYDisplayed = -1;
+ /**
+ * The FAB button's Y position when it is hidden.
+ */
+ private float mYHidden = -1;
+
+ public FloatingActionButton(Context context) {
+ this(context, null);
+ }
+
+ public FloatingActionButton(Context context, AttributeSet attributeSet) {
+ this(context, attributeSet, 0);
+ }
+
+
+ public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FloatingActionButton);
+ mColor = a.getColor(R.styleable.FloatingActionButton_fab_color, Color.WHITE);
+ mButtonPaint.setStyle(Paint.Style.FILL);
+ mButtonPaint.setColor(mColor);
+ float radius, dx, dy;
+ radius = a.getFloat(R.styleable.FloatingActionButton_fab_shadowRadius, 10.0f);
+ dx = a.getFloat(R.styleable.FloatingActionButton_fab_shadowDx, 0.0f);
+ dy = a.getFloat(R.styleable.FloatingActionButton_fab_shadowDy, 3.5f);
+ int color = a.getInteger(R.styleable.FloatingActionButton_fab_shadowColor, Color.argb(100, 0, 0, 0));
+ mButtonPaint.setShadowLayer(radius, dx, dy, color);
+
+ Drawable drawable = a.getDrawable(R.styleable.FloatingActionButton_fab_drawable);
+ if (null != drawable) {
+ mBitmap = ((BitmapDrawable) drawable).getBitmap();
+ }
+ setWillNotDraw(false);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ WindowManager mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = mWindowManager.getDefaultDisplay();
+ Point size = new Point();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
+ display.getSize(size);
+ mYHidden = size.y;
+ } else {
+ mYHidden = display.getHeight();
+ }
+ }
+
+ public static int darkenColor(int color) {
+ float[] hsv = new float[3];
+ Color.colorToHSV(color, hsv);
+ hsv[2] *= 0.8f;
+ return Color.HSVToColor(hsv);
+ }
+
+ public void setColor(int color) {
+ mColor = color;
+ mButtonPaint.setColor(mColor);
+ invalidate();
+ }
+
+ public void setDrawable(Drawable drawable) {
+ mBitmap = ((BitmapDrawable) drawable).getBitmap();
+ invalidate();
+ }
+
+ public void setShadow(float radius, float dx, float dy, int color) {
+ mButtonPaint.setShadowLayer(radius, dx, dy, color);
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, (float) (getWidth() / 2.6), mButtonPaint);
+ if (null != mBitmap) {
+ canvas.drawBitmap(mBitmap, (getWidth() - mBitmap.getWidth()) / 2,
+ (getHeight() - mBitmap.getHeight()) / 2, mDrawablePaint);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ // Perform the default behavior
+ super.onLayout(changed, left, top, right, bottom);
+ if (mLeftDisplayed == -1) {
+ mLeftDisplayed = left;
+ mRightDisplayed = right;
+ mTopDisplayed = top;
+ mBottomDisplayed = bottom;
+ }
+
+ // Store the FAB button's displayed Y position if we are not already aware of it
+ if (mYDisplayed == -1) {
+ mYDisplayed = ViewHelper.getY(this);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int color;
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ color = mColor;
+ } else {
+ color = darkenColor(mColor);
+ rect = new Rect(mLeftDisplayed, mTopDisplayed, mRightDisplayed, mBottomDisplayed);
+ }
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ if (!rect.contains(mLeftDisplayed + (int) event.getX(), mTopDisplayed + (int) event.getY())) {
+ color = mColor;
+ }
+ }
+ mButtonPaint.setColor(color);
+ invalidate();
+ return super.onTouchEvent(event);
+ }
+
+ public void hide(boolean hide) {
+ // If the hidden state is being updated
+ if (mHidden != hide) {
+ // Store the new hidden state
+ mHidden = hide;
+
+ // Animate the FAB to it's new Y position
+ ObjectAnimator animator = ObjectAnimator.ofFloat(this, "y", mHidden ? mYHidden : mYDisplayed).setDuration(500);
+ animator.setInterpolator(mInterpolator);
+ animator.start();
+ }
+ }
+
+ public void listenTo(AbsListView listView) {
+ if (null != listView) {
+ listView.setOnScrollListener(new DirectionScrollListener(this));
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/BubbleTextGetter.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/BubbleTextGetter.java
new file mode 100644
index 0000000..cb0b06e
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/BubbleTextGetter.java
@@ -0,0 +1,8 @@
+package com.likebamboo.osa.android.ui.view.fastscroll;
+
+/**
+ *
+ */
+public interface BubbleTextGetter {
+ String getTextToShowInBubble(int pos);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/FastScroller.java b/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/FastScroller.java
new file mode 100644
index 0000000..465817c
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/ui/view/fastscroll/FastScroller.java
@@ -0,0 +1,192 @@
+package com.likebamboo.osa.android.ui.view.fastscroll;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.likebamboo.osa.android.R;
+
+import static android.support.v7.widget.RecyclerView.OnScrollListener;
+
+/**
+ *
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+public class FastScroller extends LinearLayout {
+ private static final int BUBBLE_ANIMATION_DURATION = 500;
+ private static final int TRACK_SNAP_RANGE = 5;
+
+ private TextView bubble;
+ private View handle;
+ private RecyclerView recyclerView;
+ private final ScrollListener scrollListener = new ScrollListener();
+ private int height;
+
+ private boolean isMoving = false;
+
+ private ObjectAnimator currentAnimator = null;
+
+ public FastScroller(final Context context, final AttributeSet attrs, final int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialise(context);
+ }
+
+ public FastScroller(final Context context) {
+ super(context);
+ initialise(context);
+ }
+
+ public FastScroller(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ initialise(context);
+ }
+
+ private void initialise(Context context) {
+ setOrientation(HORIZONTAL);
+ setClipChildren(false);
+ LayoutInflater inflater = LayoutInflater.from(context);
+ inflater.inflate(R.layout.recycler_view_fast_scroller, this, true);
+ bubble = (TextView) findViewById(R.id.fastscroller_bubble);
+ handle = findViewById(R.id.fastscroller_handle);
+ bubble.setVisibility(INVISIBLE);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ height = h;
+ }
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ final int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ isMoving = false;
+ if (event.getX() < handle.getX())
+ return false;
+ if (currentAnimator != null)
+ currentAnimator.cancel();
+ if (bubble.getVisibility() == INVISIBLE)
+ showBubble();
+ handle.setSelected(true);
+ case MotionEvent.ACTION_MOVE:
+ isMoving = true;
+ final float y = event.getY();
+ setBubbleAndHandlePosition(y);
+ setRecyclerViewPosition(y);
+ return true;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ isMoving = false;
+ handle.setSelected(false);
+ hideBubble();
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ public void setRecyclerView(RecyclerView recyclerView) {
+ this.recyclerView = recyclerView;
+ recyclerView.addOnScrollListener(scrollListener);
+ }
+
+ private void setRecyclerViewPosition(float y) {
+ if (recyclerView != null) {
+ int itemCount = recyclerView.getAdapter().getItemCount();
+ float proportion;
+ if (handle.getY() == 0)
+ proportion = 0f;
+ else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE)
+ proportion = 1f;
+ else
+ proportion = y / (float) height;
+ int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));
+ Log.d("AppLog", "targetPos:" + targetPos);
+ ((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
+ // recyclerView.oPositionWithOffset(targetPos);
+ String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos);
+ bubble.setText(bubbleText);
+ }
+ }
+
+ private int getValueInRange(int min, int max, int value) {
+ int minimum = Math.max(min, value);
+ return Math.min(minimum, max);
+ }
+
+ private void setBubbleAndHandlePosition(float y) {
+ int bubbleHeight = bubble.getHeight();
+ int handleHeight = handle.getHeight();
+ handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2)));
+ bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
+ }
+
+ private void showBubble() {
+ AnimatorSet animatorSet = new AnimatorSet();
+ bubble.setVisibility(VISIBLE);
+ if (currentAnimator != null)
+ currentAnimator.cancel();
+ currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
+ currentAnimator.start();
+ }
+
+ private void hideBubble() {
+ if (currentAnimator != null)
+ currentAnimator.cancel();
+ currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
+ currentAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ bubble.setVisibility(INVISIBLE);
+ currentAnimator = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ bubble.setVisibility(INVISIBLE);
+ currentAnimator = null;
+ }
+ });
+ currentAnimator.start();
+ }
+
+ private class ScrollListener extends OnScrollListener {
+ @Override
+ public void onScrolled(RecyclerView rv, int dx, int dy) {
+ if (isMoving) {
+ return;
+ }
+ View firstVisibleView = recyclerView.getChildAt(0);
+ int firstVisiblePosition = recyclerView.getChildPosition(firstVisibleView);
+ int visibleRange = recyclerView.getChildCount();
+ int lastVisiblePosition = firstVisiblePosition + visibleRange;
+ int itemCount = recyclerView.getAdapter().getItemCount();
+ int position;
+ if (firstVisiblePosition == 0)
+ position = 0;
+ else if (lastVisiblePosition == itemCount)
+ position = itemCount;
+ else
+ position = (int) (((float) firstVisiblePosition / (((float) itemCount - (float) visibleRange))) * (float) itemCount);
+ float proportion = (float) position / (float) itemCount;
+ setBubbleAndHandlePosition(height * proportion);
+ }
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/utils/DateUtil.java b/app/src/main/java/com/likebamboo/osa/android/utils/DateUtil.java
new file mode 100644
index 0000000..265100a
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/utils/DateUtil.java
@@ -0,0 +1,76 @@
+package com.likebamboo.osa.android.utils;
+
+import android.text.TextUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ *
+ */
+public class DateUtil {
+
+ /**
+ * 格式化服务器端给的时间字符串
+ *
+ * @param timeStr
+ * @return
+ */
+ public static String parseDate(String timeStr) {
+ return parseDate(timeStr, "yyyy-MM-dd");
+ }
+
+ /**
+ * 将时间字符串转为本地显示的时间形式
+ *
+ * @param timeStr
+ * @param pattern
+ * @return
+ */
+ public static String parseDate(String timeStr, String pattern) {
+ if (TextUtils.isEmpty(timeStr) || TextUtils.isEmpty(pattern)) {
+ return "";
+ }
+
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+ // 将字符串转为日期
+ Date d = sdf.parse(timeStr);
+ return parseDate(d.getTime());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return timeStr;
+ }
+
+ /**
+ * 将时间戳转为本地显示的时间形式
+ *
+ * @param timeStamp
+ * @return
+ */
+ public static String parseDate(long timeStamp) {
+ long now = System.currentTimeMillis();
+ SimpleDateFormat sdf = null;
+ try {
+ // 将字符串转为日期
+ Date d = new Date(timeStamp);
+ Date dd = new Date();
+ // 如果是同一天
+ if ((now - timeStamp < ONE_DAY) && (d.getDate() == dd.getDate())) {
+ // sdf = new SimpleDateFormat("HH:mm");
+ return "今天";
+ } else if (dd.getYear() != d.getYear()) {// 如果是不同年份
+ sdf = new SimpleDateFormat("yyyy-MM-dd");
+ } else {
+ sdf = new SimpleDateFormat("MM-dd");
+ }
+ return sdf.format(d);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+ public static final long ONE_DAY = 24 * 60 * 60 * 1000L;
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/utils/DeviceUtil.java b/app/src/main/java/com/likebamboo/osa/android/utils/DeviceUtil.java
new file mode 100644
index 0000000..4dc5f0c
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/utils/DeviceUtil.java
@@ -0,0 +1,53 @@
+package com.likebamboo.osa.android.utils;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+/**
+ * Created by likebamboo on 2015/5/30.
+ */
+public class DeviceUtil {
+
+ /**
+ * 获取软件版本名称
+ *
+ * @param ctx
+ * @return
+ */
+ public static String getVersionName(Context ctx) {
+ if (ctx == null) {
+ return "";
+ }
+
+ try {
+ PackageManager pm = ctx.getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), 0);
+ return pi.versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+ /**
+ * 获取软件版本号
+ *
+ * @param ctx
+ * @return
+ */
+ public static int getVersionCode(Context ctx) {
+ if (ctx == null) {
+ return 0;
+ }
+
+ try {
+ PackageManager pm = ctx.getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), 0);
+ return pi.versionCode;
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return 0;
+ }
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/utils/NetworkUtil.java b/app/src/main/java/com/likebamboo/osa/android/utils/NetworkUtil.java
new file mode 100644
index 0000000..f858440
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/utils/NetworkUtil.java
@@ -0,0 +1,82 @@
+
+package com.likebamboo.osa.android.utils;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
+
+/**
+ * @author likebamboo
+ * @date 2015年5月17日
+ */
+public class NetworkUtil {
+
+ /**
+ * Returns whether the network is available
+ *
+ * @param context Context
+ * @return 网络是否可用
+ * @see [类、类#方法、类#成员]
+ */
+ public static boolean isNetworkAvailable(Context context) {
+ return getConnectedNetworkInfo(context) != null;
+ }
+
+ /**
+ * 获取网络类型
+ *
+ * @param context Context
+ * @return 网络类型
+ * @see [类、类#方法、类#成员]
+ */
+ public static int getNetworkType(Context context) {
+ NetworkInfo networkInfo = getConnectedNetworkInfo(context);
+ if (networkInfo != null) {
+ return networkInfo.getType();
+ }
+
+ return -1;
+ }
+
+ public static NetworkInfo getConnectedNetworkInfo(Context context) {
+ try {
+ ConnectivityManager connectivity = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connectivity == null) {
+ Log.e("network", "couldn't get connectivity manager");
+ } else {
+ NetworkInfo info = connectivity.getActiveNetworkInfo();
+ if (info != null) {
+ return info;
+ }
+ }
+ } catch (Exception e) {
+ Log.e("network", e.toString(), e);
+ }
+ return null;
+ }
+
+ /**
+ * 判断网络是不是手机网络,非wifi
+ *
+ * @param context Context
+ * @return boolean
+ * @see [类、类#方法、类#成员]
+ */
+ public static boolean isMobileNetwork(Context context) {
+ return (ConnectivityManager.TYPE_MOBILE == getNetworkType(context));
+ }
+
+ /**
+ * 判断网络是不是wifi
+ *
+ * @param context Context
+ * @return boolean
+ * @see [类、类#方法、类#成员]
+ */
+ public static boolean isWifiNetwork(Context context) {
+ return (ConnectivityManager.TYPE_WIFI == getNetworkType(context));
+ }
+
+}
diff --git a/app/src/main/java/com/likebamboo/osa/android/utils/UrlDetect.java b/app/src/main/java/com/likebamboo/osa/android/utils/UrlDetect.java
new file mode 100644
index 0000000..b17bf7a
--- /dev/null
+++ b/app/src/main/java/com/likebamboo/osa/android/utils/UrlDetect.java
@@ -0,0 +1,111 @@
+package com.likebamboo.osa.android.utils;
+
+import android.text.TextUtils;
+import android.webkit.URLUtil;
+
+import com.likebamboo.osa.android.request.RequestUrl;
+
+/**
+ * URL 检测
+ * Created by wentaoli on 2015/5/27.
+ */
+public class UrlDetect {
+
+ /**
+ * 是否为正确的url
+ *
+ * @param url
+ * @return
+ */
+ public static boolean isValidURL(String url) {
+ if (TextUtils.isEmpty(url)) {
+ return false;
+ }
+ return URLUtil.isValidUrl(url);
+ }
+
+ /**
+ * 是否为本站URL
+ *
+ * @param url
+ */
+ public static boolean isOurselvesURL(String url) {
+ if (!isValidURL(url)) {
+ return false;
+ }
+ if (!url.startsWith("http")) {
+ url = "http://" + url;
+ }
+ System.out.println(url);
+ if (url.startsWith(RequestUrl.BASE_URL)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 是否是博客链接
+ *
+ * @param url
+ * @return
+ */
+ public static String isBlogUrl(String url) {
+ if (!isOurselvesURL(url)) {
+ return "";
+ }
+ if (!url.startsWith("http")) {
+ url = "http://" + url;
+ }
+ // 查看该链接是否以博客url开头
+ if (!url.startsWith(RequestUrl.BLOG_URL)) {
+ return "";
+ }
+ // 去掉链接前面的内容
+ url = url.substring((RequestUrl.BLOG_URL + "/").length());
+ // 如果处理后的 url 不含"/",或者只有最后一个字符是"/",说明是blog链接
+ if (!url.contains("/") || url.indexOf("/") == url.length() - 1) {
+ if (url.endsWith("/")) {
+ url = url.substring(0, url.length() - 1);
+ }
+ return url;
+ }
+ return "";
+ }
+
+
+ /**
+ * 是否是标签blog列表链接
+ *
+ * @param url
+ * @return
+ */
+ public static String isTagBlogUrl(String url) {
+ if (!isOurselvesURL(url)) {
+ return "";
+ }
+ if (!url.startsWith("http")) {
+ url = "http://" + url;
+ }
+ // 查看该链接是否以博客url开头
+ if (!url.startsWith(RequestUrl.BLOG_URL)) {
+ return "";
+ }
+ // 去掉链接前面的内容
+ url = url.substring((RequestUrl.BLOG_URL + "/").length());
+ // 如果 url 不是以 "tag/" 开头
+ if (!url.toLowerCase().startsWith("tag/")) {
+ return "";
+ }
+ // 截取 "tag/" 后边的内容
+ url = url.substring(4);
+ // 如果处理后的 url 不含"/",或者只有最后一个字符是"/",说明是 tag blog链接
+ if (!url.contains("/") || url.indexOf("/") == url.length() - 1) {
+ if (url.endsWith("/")) {
+ url = url.substring(0, url.length() - 1);
+ }
+ return url;
+ }
+ return "";
+ }
+
+}
diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml
new file mode 100644
index 0000000..26d3d2f
--- /dev/null
+++ b/app/src/main/res/anim/fade_in.xml
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml
new file mode 100644
index 0000000..2811a30
--- /dev/null
+++ b/app/src/main/res/anim/fade_out.xml
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/slide_down_dialog.xml b/app/src/main/res/anim/slide_down_dialog.xml
new file mode 100644
index 0000000..7e78c0c
--- /dev/null
+++ b/app/src/main/res/anim/slide_down_dialog.xml
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/slide_up_dialog.xml b/app/src/main/res/anim/slide_up_dialog.xml
new file mode 100644
index 0000000..584b591
--- /dev/null
+++ b/app/src/main/res/anim/slide_up_dialog.xml
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/bg_card.9.png b/app/src/main/res/drawable-hdpi/bg_card.9.png
new file mode 100644
index 0000000..5986e50
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/bg_card.9.png differ
diff --git a/app/src/main/res/drawable-hdpi/bg_card_active.9.png b/app/src/main/res/drawable-hdpi/bg_card_active.9.png
new file mode 100644
index 0000000..3221b9a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/bg_card_active.9.png differ
diff --git a/app/src/main/res/drawable-hdpi/default_avatar.png b/app/src/main/res/drawable-hdpi/default_avatar.png
new file mode 100644
index 0000000..7501748
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/default_avatar.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_add_white.png b/app/src/main/res/drawable-hdpi/ic_add_white.png
new file mode 100644
index 0000000..481643e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add_white.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..e208081
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_up.png b/app/src/main/res/drawable-hdpi/ic_up.png
new file mode 100644
index 0000000..817cea9
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_up.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..5499aca
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..ec84e34
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_up.png b/app/src/main/res/drawable-xhdpi/ic_up.png
new file mode 100644
index 0000000..b1b6793
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_up.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..e9b39a1
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable/bg_card_selector.xml b/app/src/main/res/drawable/bg_card_selector.xml
new file mode 100644
index 0000000..a248210
--- /dev/null
+++ b/app/src/main/res/drawable/bg_card_selector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_list_item_selector.xml b/app/src/main/res/drawable/bg_list_item_selector.xml
new file mode 100644
index 0000000..c8a926e
--- /dev/null
+++ b/app/src/main/res/drawable/bg_list_item_selector.xml
@@ -0,0 +1,26 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_tag_selector.xml b/app/src/main/res/drawable/bg_tag_selector.xml
new file mode 100644
index 0000000..dc4f31e
--- /dev/null
+++ b/app/src/main/res/drawable/bg_tag_selector.xml
@@ -0,0 +1,27 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_translucent_gradient.xml b/app/src/main/res/drawable/bg_translucent_gradient.xml
new file mode 100644
index 0000000..bd57b9d
--- /dev/null
+++ b/app/src/main/res/drawable/bg_translucent_gradient.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/recycler_view_fast_scroller__bubble.xml b/app/src/main/res/drawable/recycler_view_fast_scroller__bubble.xml
new file mode 100644
index 0000000..0d6f2be
--- /dev/null
+++ b/app/src/main/res/drawable/recycler_view_fast_scroller__bubble.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/recycler_view_fast_scroller__handle.xml b/app/src/main/res/drawable/recycler_view_fast_scroller__handle.xml
new file mode 100644
index 0000000..d802119
--- /dev/null
+++ b/app/src/main/res/drawable/recycler_view_fast_scroller__handle.xml
@@ -0,0 +1,17 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
new file mode 100644
index 0000000..5c58b86
--- /dev/null
+++ b/app/src/main/res/layout/activity_about.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_author.xml b/app/src/main/res/layout/activity_author.xml
new file mode 100644
index 0000000..e2d8d74
--- /dev/null
+++ b/app/src/main/res/layout/activity_author.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_blog.xml b/app/src/main/res/layout/activity_blog.xml
new file mode 100644
index 0000000..d362aeb
--- /dev/null
+++ b/app/src/main/res/layout/activity_blog.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_blog_list.xml b/app/src/main/res/layout/activity_blog_list.xml
new file mode 100644
index 0000000..f6e7059
--- /dev/null
+++ b/app/src/main/res/layout/activity_blog_list.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_category.xml b/app/src/main/res/layout/activity_category.xml
new file mode 100644
index 0000000..04b6716
--- /dev/null
+++ b/app/src/main/res/layout/activity_category.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_navigation.xml b/app/src/main/res/layout/activity_navigation.xml
new file mode 100644
index 0000000..b061b22
--- /dev/null
+++ b/app/src/main/res/layout/activity_navigation.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml
new file mode 100644
index 0000000..c0d6291
--- /dev/null
+++ b/app/src/main/res/layout/activity_search.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_webview.xml b/app/src/main/res/layout/activity_webview.xml
new file mode 100644
index 0000000..523502d
--- /dev/null
+++ b/app/src/main/res/layout/activity_webview.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/common_webview.xml b/app/src/main/res/layout/common_webview.xml
new file mode 100644
index 0000000..969abb4
--- /dev/null
+++ b/app/src/main/res/layout/common_webview.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fab_tool_bar.xml b/app/src/main/res/layout/fab_tool_bar.xml
new file mode 100644
index 0000000..8e19eb2
--- /dev/null
+++ b/app/src/main/res/layout/fab_tool_bar.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/footer_loading_layout.xml b/app/src/main/res/layout/footer_loading_layout.xml
new file mode 100644
index 0000000..0ddddf3
--- /dev/null
+++ b/app/src/main/res/layout/footer_loading_layout.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_author_info.xml b/app/src/main/res/layout/fragment_author_info.xml
new file mode 100644
index 0000000..558f8f5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_author_info.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_blog_info.xml b/app/src/main/res/layout/fragment_blog_info.xml
new file mode 100644
index 0000000..713a4e7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_blog_info.xml
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_navigation_drawer.xml b/app/src/main/res/layout/fragment_navigation_drawer.xml
new file mode 100644
index 0000000..cce7ade
--- /dev/null
+++ b/app/src/main/res/layout/fragment_navigation_drawer.xml
@@ -0,0 +1,10 @@
+
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 0000000..1a7deec
--- /dev/null
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_author.xml b/app/src/main/res/layout/item_author.xml
new file mode 100644
index 0000000..fe7d3e7
--- /dev/null
+++ b/app/src/main/res/layout/item_author.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_blog.xml b/app/src/main/res/layout/item_blog.xml
new file mode 100644
index 0000000..726dd48
--- /dev/null
+++ b/app/src/main/res/layout/item_blog.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml
new file mode 100644
index 0000000..8a7d239
--- /dev/null
+++ b/app/src/main/res/layout/item_category.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_nav_menu.xml b/app/src/main/res/layout/item_nav_menu.xml
new file mode 100644
index 0000000..59989e9
--- /dev/null
+++ b/app/src/main/res/layout/item_nav_menu.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/recycler_view_fast_scroller.xml b/app/src/main/res/layout/recycler_view_fast_scroller.xml
new file mode 100644
index 0000000..1e3a7b5
--- /dev/null
+++ b/app/src/main/res/layout/recycler_view_fast_scroller.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/simple_text.xml b/app/src/main/res/layout/simple_text.xml
new file mode 100644
index 0000000..8753903
--- /dev/null
+++ b/app/src/main/res/layout/simple_text.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/app/src/main/res/layout/space_loading_layout.xml b/app/src/main/res/layout/space_loading_layout.xml
new file mode 100644
index 0000000..d876b8d
--- /dev/null
+++ b/app/src/main/res/layout/space_loading_layout.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/webview_toolbar.xml b/app/src/main/res/layout/webview_toolbar.xml
new file mode 100644
index 0000000..4fdc577
--- /dev/null
+++ b/app/src/main/res/layout/webview_toolbar.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/global.xml b/app/src/main/res/menu/global.xml
new file mode 100644
index 0000000..517c7de
--- /dev/null
+++ b/app/src/main/res/menu/global.xml
@@ -0,0 +1,3 @@
+
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
new file mode 100644
index 0000000..99bd67e
--- /dev/null
+++ b/app/src/main/res/menu/main.xml
@@ -0,0 +1,11 @@
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..33a526a
--- /dev/null
+++ b/app/src/main/res/values/arrays.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ - 发现
+ - 分类
+ - 作者
+ - 设置
+
+
+
+
+ - 意见反馈
+ - 关于作者
+ - 关于APP
+
+
\ 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..68cb7fc
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/attrs_fab.xml b/app/src/main/res/values/attrs_fab.xml
new file mode 100644
index 0000000..7faba5c
--- /dev/null
+++ b/app/src/main/res/values/attrs_fab.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f125d65
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,25 @@
+
+
+
+ #eeeeee
+
+ #34322d
+
+ #e25600
+
+ #88ffffff
+
+ #faffffff
+
+
+ #55000000
+
+
+ #1a000000
+
+ #ff202020
+
+ #ffe0e0e0
+ #ffe9e9e9
+
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..5f965f6
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,45 @@
+
+
+
+ 16dp
+ 16dp
+
+ 240dp
+
+ 16.0dip
+ 24.0dip
+ 8.0dip
+ 12.0dip
+ 4.0dip
+ 6.0dip
+ 2.0dip
+ 1.0dip
+ 32.0dip
+ 48.0dip
+
+ 16.0dip
+ 24.0dip
+ 8.0dip
+ 12.0dip
+ 4.0dip
+ 2.0dip
+ 32.0dip
+ 36.0dip
+ 48.0dip
+
+
+ 16.0dip
+ 18.0dip
+ 14.0dip
+ 12.0dip
+ 10.0dip
+ 20.0dip
+ 8.0dip
+ 22.0dip
+ 24.0dip
+ 28.0dip
+
+ 56dp
+ 16dp
+
diff --git a/app/src/main/res/values/font_awesome.xml b/app/src/main/res/values/font_awesome.xml
new file mode 100644
index 0000000..c96808e
--- /dev/null
+++ b/app/src/main/res/values/font_awesome.xml
@@ -0,0 +1,554 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
new file mode 100644
index 0000000..1a5abbf
--- /dev/null
+++ b/app/src/main/res/values/ids.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml
new file mode 100644
index 0000000..eb15c86
--- /dev/null
+++ b/app/src/main/res/values/integers.xml
@@ -0,0 +1,43 @@
+
+
+ 2
+ 3
+ 1000
+ 2000
+ 3
+ 2
+ 2000
+ 0
+ 2
+ 0
+ 300
+ 400
+ 350
+ 200
+ 3
+ 2
+ 60
+ 1800
+ 6587000
+ 2
+ 3
+ 2
+ 2
+ 2
+ 2
+ 1
+ 2
+ 300
+ 600
+ 5
+ 200
+ 5
+ 3
+ 4
+ 1
+ 3
+ 2
+ 20
+ 200
+ 3
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..141da29
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,39 @@
+
+
+ Android博客
+
+ Open navigation drawer
+ Close navigation drawer
+ 数据加载中...
+ 没有啦...
+ 没有找到想要的数据
+ 请重试
+
+ 请输入关键字
+
+ 历史记录
+ 网络连接错误,请检查网络状态
+
+ 反馈
+ 详情
+ issue
+ 收藏
+
+ 译者
+ 作者
+ 分类
+ 原文作者
+ 发表时间
+ 原文发表时间
+ 来源链接
+ 原文链接
+
+ 获取博客信息失败
+
+ 关于
+ 版本:%1$s
+ [Android博客]是一个简约、直观、漂亮并且开源的博客浏览应用,遵循Android设计风格。
+ UI借鉴国外应用Etsy 。]]>
+
+ 更多...
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..30dd22d
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/art/author.png b/art/author.png
new file mode 100644
index 0000000..ad2a91f
Binary files /dev/null and b/art/author.png differ
diff --git a/art/blog.png b/art/blog.png
new file mode 100644
index 0000000..c5a58c9
Binary files /dev/null and b/art/blog.png differ
diff --git a/art/home.png b/art/home.png
new file mode 100644
index 0000000..a4f0b1d
Binary files /dev/null and b/art/home.png differ
diff --git a/art/info.png b/art/info.png
new file mode 100644
index 0000000..de91729
Binary files /dev/null and b/art/info.png differ
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..ee1ddfe
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,20 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.2.2'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven {url "https://jitpack.io"}
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/osa-android.iml b/osa-android.iml
new file mode 100644
index 0000000..efab83a
--- /dev/null
+++ b/osa-android.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'