public final class Constants {
    public static final String KEY_POST_PARCEL = "post";
    public static final String KEY_IS_BOOKMARKED = "is_bookmarked";
    public static final String YCOMBINATOR_ITEM_URL = "https://news.ycombinator.com/item?id=";
    public static final String TYPE_SEARCH = "search";
    public static final String TYPE_BOOKMARK = "type_bookmark";
    public static final String TYPE_STORY = "type_story";
    public static final String KEY_API_URL = "https://hacker-news.firebaseio.com/v0";
    public static final String KEY_ITEM_URL = "https://hacker-news.firebaseio.com/v0/item/";
    public static final String STORY_TYPE_TOP_PATH = "topstories";
    public static final String STORY_TYPE_NEW_PATH = "newstories";
    public static final String STORY_TYPE_ASK_HN_PATH = "askstories";
    public static final String STORY_TYPE_SHOW_HN_PATH = "showstories";

    public static final String SEARCH_BASE_URL = "https://hn.algolia.com/api/v1/";

    public final static int NUM_LOADING_ITEMS = 25;
    public final static int NUM_LOADING_ITEMS_SPLIT = 7;

    public final static int LOADING_IDLE = 0;
    public final static int LOADING_IN_PROGRESS = 1;
    public final static int LOADING_FINISH = 2;
    public final static int LOADING_ERROR = 3;
    public final static int LOADING_PROMPT_NO_CONTENT = 4;

    public static final boolean LOGIN_STATE_IN = true;
    public static final boolean LOGIN_STATE_OUT = false;

    public static final int OPERATE_SUCCESS = 0;
    public static final int OPERATE_ERROR_NO_COOKIE = 1;
    public static final int OPERATE_ERROR_COOKIE_EXPIRED = 2;
    public static final int OPERATE_ERROR_HAVE_VOTED = 3;
    public static final int OPERATE_ERROR_NOT_ENOUGH_KARMA = 4;
    public static final int OPERATE_ERROR_UNKNOWN = 64;

    public static final int VOTE_UP = 1;
    public static final int VOTE_DOWN = 2;

    public static final String FRAGMENT_TAG_COMMENT = "comment_fragment";
} public class Utils implements com.leavjenn.hews.misc.UtilsContract {

    private Context mContext;

    public Utils(Context context) {
        mContext = context;
    }

    public static CharSequence formatTime(long timeStamp) {
        timeStamp = timeStamp * 1000;
        CharSequence timeAgo = DateUtils.getRelativeTimeSpanString(timeStamp,
            System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS);
        return timeAgo;
    }

    public static float convertPixelsToDp(float px, Context context) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        return px / (metrics.densityDpi / 160f);
    }

    public static int convertDpToPixels(int dp, Context context) {
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
        return px;
    }

    public static float convertSpToPixels(float sp, Context context) {
        float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
        return sp * scaledDensity;
    }


    public static int getScreenHeight(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        return size.y;
    }

    public static boolean isOnline(Context context) {
        ConnectivityManager connectivityManager = (ConnectivityManager) context
            .getSystemService(Context.CONNECTIVITY_SERVICE);
        return connectivityManager.getActiveNetworkInfo() != null;
    }

    public static void showLongToast(Context context, CharSequence text) {
        Toast.makeText(context, text, Toast.LENGTH_LONG).show();
    }

    public static void showLongToast(Context context, @StringRes int ResId) {
        Toast.makeText(context, ResId, Toast.LENGTH_LONG).show();
    }

    public static void setSnackBarTextColor(Snackbar snackbar, Context context, @ColorRes int color) {
        TextView tvSnackbarText = (TextView) snackbar.getView()
            .findViewById(android.support.design.R.id.snackbar_text);
        tvSnackbarText.setTextColor(context.getResources().getColor(color));
    }

    // uri scheme can only lower case char
    public static Uri validateAndParseUri(String uriString, long postId) {
        if (URLUtil.isValidUrl(uriString)) {
//        if (Patterns.WEB_URL.matcher(uriString).matches()) {
            if (uriString.startsWith("Http")) {
                Log.i("validateAndParseUri", "http" + uriString.substring(4));
                return Uri.parse("http" + uriString.substring(4));
            } else { // TODO other conditions
                return Uri.parse(uriString);
            }
        } else {
            return Uri.parse("https://news.ycombinator.com/item?id=" + postId);
        }
//        }
    }

    // setup url and pretty url
    public static void setupPostUrl(Post post) {
        String url = post.getUrl();
        if (url == null || url.isEmpty()) {
            url = Constants.YCOMBINATOR_ITEM_URL + post.getId();
            post.setUrl(url);
        }
        String[] splitUrl = url.split("/");
        if (splitUrl.length > 2) {
            url = splitUrl[2];
            post.setPrettyUrl(url);
        }
    }

    public static void setupIntentBuilder(CustomTabsIntent.Builder intentBuilder, Context context, SharedPreferences prefs) {
        if (SharedPrefsManager.getTheme(prefs).equals(SharedPrefsManager.THEME_DARK)) {
            intentBuilder.setToolbarColor(context.getResources().getColor(R.color.grey_900));
        } else if (SharedPrefsManager.getTheme(prefs).equals(SharedPrefsManager.THEME_AMOLED_BLACK)) {
            intentBuilder.setToolbarColor(context.getResources().getColor(android.R.color.black));
        } else {
            // use darker orange color here so chrome toolbar will fit dark theme
            intentBuilder.setToolbarColor(context.getResources().getColor(R.color.orange_800));
        }
        intentBuilder.enableUrlBarHiding();
        intentBuilder.setShowTitle(true);
        intentBuilder.setCloseButtonIcon(
            BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_arrow_back));
        // share link option
        String menuItemTitle = context.getString(R.string.share_link_to);
        Intent actionIntent = new Intent(context.getApplicationContext(), ShareBroadcastReceiver.class);
        PendingIntent menuItemPendingIntent =
            PendingIntent.getBroadcast(context.getApplicationContext(), 0, actionIntent, 0);
        intentBuilder.addMenuItem(menuItemTitle, menuItemPendingIntent);
        // TODO Use this way, it will show share to option when long click
//            String shareLabel = getString(R.string.share_link_to);
//            Intent shareIntent = new Intent();
//            shareIntent.setAction(Intent.ACTION_SEND);
//            String postUrl = post.getUrl();
//            shareIntent.putExtra(Intent.EXTRA_TEXT, postUrl);
//            shareIntent.setType("text/plain");
//            shareIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//            Intent chooserIntent = Intent.createChooser(shareIntent, getString(R.string.share_link_to));
//            startActivity(chooserIntent);
//            PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0,
//                    shareIntent, 0);
//            intentBuilder.addMenuItem(shareLabel, pendingIntent);
    }

    @Override
    public boolean isOnline() {
        ConnectivityManager connectivityManager = (ConnectivityManager) mContext
            .getSystemService(Context.CONNECTIVITY_SERVICE);
        return connectivityManager.getActiveNetworkInfo() != null;
    }

    @Override
    public String convertHtmlToString(String html) {
        return Html.fromHtml(html).toString();
    }
} public interface OnRecyclerViewCreatedListener {
    void onRecyclerViewCreated(RecyclerView recyclerView);
} public class ChromeCustomTabsHelper {
    private static final String TAG = "ChromeCustomTabsHelper";
    static final String STABLE_PACKAGE = "com.android.chrome";
    static final String BETA_PACKAGE = "com.chrome.beta";
    static final String DEV_PACKAGE = "com.chrome.dev";
    static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";
    private static String sPackageNameToUse;
    private CustomTabsClient mCustomTabsClient;
    private CustomTabsSession mCustomTabsSession;
    private CustomTabsServiceConnection mConnection;


    /**
     * Goes through all apps that handle VIEW intents and have a warmup service. Picks
     * the one chosen by the user if there is one, otherwise makes a best effort to return a
     * valid package name.
     * <p/>
     * This is <strong>not</strong> threadsafe.
     *
     * @param context {@link Context} to use for accessing {@link PackageManager}.
     * @return The package name recommended to use for connecting to custom tabs related components.
     */
    public static String getPackageNameToUse(Context context) {
        if (sPackageNameToUse != null) return sPackageNameToUse;

        PackageManager pm = context.getPackageManager();
        // Get default VIEW intent handler.
        Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
        ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);
        String defaultViewHandlerPackageName = null;
        if (defaultViewHandlerInfo != null) {
            defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName;
        }

        // Get all apps that can handle VIEW intents.
        List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
        List<String> packagesSupportingCustomTabs = new ArrayList<>();
        for (ResolveInfo info : resolvedActivityList) {
            Intent serviceIntent = new Intent();
            serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION);
            serviceIntent.setPackage(info.activityInfo.packageName);
            if (pm.resolveService(serviceIntent, 0) != null) {
                packagesSupportingCustomTabs.add(info.activityInfo.packageName);
            }
        }

        // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents
        // and service calls.
        if (packagesSupportingCustomTabs.isEmpty()) {
            sPackageNameToUse = null;
        } else if (packagesSupportingCustomTabs.size() == 1) {
            sPackageNameToUse = packagesSupportingCustomTabs.get(0);
        } else if (!TextUtils.isEmpty(defaultViewHandlerPackageName)
            && !hasSpecializedHandlerIntents(context, activityIntent)
            && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) {
            sPackageNameToUse = defaultViewHandlerPackageName;
        } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {
            sPackageNameToUse = STABLE_PACKAGE;
        } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {
            sPackageNameToUse = BETA_PACKAGE;
        } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {
            sPackageNameToUse = DEV_PACKAGE;
        } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
            sPackageNameToUse = LOCAL_PACKAGE;
        }
        return sPackageNameToUse;
    }

    /**
     * Used to check whether there is a specialized handler for a given intent.
     *
     * @param intent The intent to check with.
     * @return Whether there is a specialized handler for the given intent.
     */
    private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) {
        try {
            PackageManager pm = context.getPackageManager();
            List<ResolveInfo> handlers =
                pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
            if (handlers == null || handlers.size() == 0) {
                return false;
            }
            for (ResolveInfo resolveInfo : handlers) {
                IntentFilter filter = resolveInfo.filter;
                if (filter == null) continue;
                if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue;
                if (resolveInfo.activityInfo == null) continue;
                return true;
            }
        } catch (RuntimeException e) {
            Log.e(TAG, "Runtime exception while getting specialized handlers");
        }
        return false;
    }


    /**
     * Binds the Activity to the Custom Tabs Service
     *
     * @param activity the activity to be binded to the service
     */
    public void bindCustomTabsService(Activity activity) {
        if (mCustomTabsClient != null) return;

        String packageName = getPackageNameToUse(activity);
        if (packageName == null) return;
        mConnection = new CustomTabsServiceConnection() {
            @Override
            public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
                mCustomTabsClient = client;
                mCustomTabsClient.warmup(0L);
                // if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected();
                //Initialize a session as soon as possible.
                getSession();
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                mCustomTabsClient = null;
                // if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected();
            }
        };
        CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection);
    }

    /**
     * Creates or retrieves an exiting CustomTabsSession
     *
     * @return a CustomTabsSession
     */
    public CustomTabsSession getSession() {
        if (mCustomTabsClient == null) {
            mCustomTabsSession = null;
        } else if (mCustomTabsSession == null) {
            mCustomTabsSession = mCustomTabsClient.newSession(null);
        }
        return mCustomTabsSession;
    }

    /**
     * Unbinds the Activity from the Custom Tabs Service
     *
     * @param activity the activity that is connected to the service
     */
    public void unbindCustomTabsService(Activity activity) {
        if (mConnection == null) return;
        activity.unbindService(mConnection);
        mCustomTabsClient = null;
        mCustomTabsSession = null;
    }

    /**
     * Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView
     *
     * @param activity         The host activity
     * @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available
     * @param uri              the Uri to be opened
     * @param fallback         a CustomTabFallback to be used if Custom Tabs is not available
     */
    public static void openCustomTab(Activity activity,
                                     CustomTabsIntent customTabsIntent,
                                     Uri uri,
                                     CustomTabFallback fallback) {
        String packageName = getPackageNameToUse(activity);

        //If we cant find a package name, it means theres no browser that supports
        //Chrome Custom Tabs installed. So, we fallback to the webview
        if (packageName == null) {
            if (fallback != null) {
                fallback.openUri(activity, uri);
            }
        } else {
            customTabsIntent.intent.setPackage(packageName);
            customTabsIntent.launchUrl(activity, uri);
        }
    }

    public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
        if (mCustomTabsClient == null) return false;

        CustomTabsSession session = getSession();
        if (session == null) return false;

        return session.mayLaunchUrl(uri, extras, otherLikelyBundles);
    }


    /**
     * To be used as a fallback to open the Uri when Custom Tabs is not available
     */
    public interface CustomTabFallback {
        /**
         * @param activity The Activity that wants to open the Uri
         * @param uri      The uri to be opened by the fallback
         */
        void openUri(Activity activity, Uri uri);
    }
} public class SharedPrefsManager implements SharedPrefsContract {
    public static final String KEY_POST_FONT = "key_post_font";
    public static final String KEY_POST_FONT_SIZE = "key_post_font_size";
    public static final String KEY_POST_LINE_HEIGHT = "key_post_line_height";
    public static final String KEY_COMMENT_FONT = "key_comment_font";
    public static final String KEY_COMMENT_FONT_SIZE = "key_comment_font_size";
    public static final String KEY_COMMENT_LINE_HEIGHT = "key_comment_line_height";
    public static final String KEY_THEME = "themekey";
    public static final String KEY_USERNAME = "user_name";
    public static final String KEY_LOGIN_COOKIE = "login_cookie";
    public static final String KEY_REPLY_TEXT = "replying_text";
    public static final String KEY_POST_IS_READ = "key_post_is_read";
    public static final String KEY_POST_IS_BOOKMARKED = "key_post_is_bookmarked";
    public static final String KEY_COMMENTS_ARE_BOOKMARKED = "key_comments_are_bookmarked";
    public static final String THEME_LIGHT = "0";
    public static final String THEME_SEPIA = "1";
    public static final String THEME_DARK = "2";
    public static final String THEME_AMOLED_BLACK = "3";
    public static final String SCROLL_MODE = "fabkey";
    public static final String SCROLL_MODE_DISABLE = "0";
    public static final String SCROLL_MODE_BUTTON = "1";
    public static final String SCROLL_MODE_FAB_DRAG = "2";
    public static final String SCROLL_MODE_FAB_HOLD = "3";
    public static final String KEY_SHOW_POST_SUMMARY = "key_show_post_summary";

    static String[] fontsForComment = {"PT Sans", "Roboto", "Lato",
        "Open Sans", "Muli", "Slabo 27px", "Crimson Text", "Roboto Slab",
        "Vollkorn", "Merriweather"};
    static String[] fontsForPost = {"Open Sans", "Dosis SemiBold", "Roboto Slab", "Merriweather",
        "RobotoMono"};

    private Context mContext;
    private SharedPreferences mPreferences;

    public SharedPrefsManager(Context context, SharedPreferences preferences) {
        mContext = context;
        mPreferences = preferences;
    }

    public static String[] getPostFontsList() {
        return fontsForPost;
    }

    public static String[] getCommentFontList() {
        return fontsForComment;
    }

    public static String getPostFont(SharedPreferences sp) {
        return sp.getString(KEY_POST_FONT, "Roboto Slab");
    }

    public static void setPostFont(SharedPreferences sp, String fontName) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(SharedPrefsManager.KEY_POST_FONT, fontName);
        editor.apply();
    }

    public static float getPostFontSize(SharedPreferences sp) {
        return sp.getFloat(KEY_POST_FONT_SIZE, 16);
    }

    public static void setPostFontSize(SharedPreferences sp, float add) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putFloat(SharedPrefsManager.KEY_POST_FONT_SIZE, getPostFontSize(sp) + add);
        editor.apply();
    }

    public static float getPostLineHeight(SharedPreferences sp) {
        return sp.getFloat(KEY_POST_LINE_HEIGHT, 1.2f);
    }

    public static void setPostLineHeight(SharedPreferences sp, float add) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putFloat(SharedPrefsManager.KEY_POST_LINE_HEIGHT, getPostLineHeight(sp) + add);
        editor.apply();
    }

    public static String getCommentFont(SharedPreferences sp) {
        return sp.getString(KEY_COMMENT_FONT, "Open Sans");
    }

    public static void setCommentFont(SharedPreferences sp, String fontName) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(SharedPrefsManager.KEY_COMMENT_FONT, fontName);
        editor.apply();
    }

    public static float getCommentFontSize(SharedPreferences sp) {
        return sp.getFloat(KEY_COMMENT_FONT_SIZE, 15);
    }

    public static void setCommentFontSize(SharedPreferences sp, float add) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putFloat(SharedPrefsManager.KEY_COMMENT_FONT_SIZE, getCommentFontSize(sp) + add);
        editor.apply();
    }

    public static float getCommentLineHeight(SharedPreferences sp) {
        return sp.getFloat(KEY_COMMENT_LINE_HEIGHT, 1.0f);
    }

    public static void setCommentLineHeight(SharedPreferences sp, float add) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putFloat(SharedPrefsManager.KEY_COMMENT_LINE_HEIGHT, getCommentLineHeight(sp) + add);
        editor.apply();
    }

    public static void setLoginCookie(SharedPreferences sp, String cookie) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(SharedPrefsManager.KEY_LOGIN_COOKIE, cookie);
        editor.apply();
    }

    public static String getLoginCookie(SharedPreferences sp) {
        return sp.getString(KEY_LOGIN_COOKIE, "");
    }

    public static void setUsername(SharedPreferences sp, String username) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(SharedPrefsManager.KEY_USERNAME, username);
        editor.apply();
    }

    public static String getUsername(SharedPreferences sp, Context context) {
        return sp.getString(KEY_USERNAME, context.getResources().getString(R.string.nav_logout));
    }

    public static void setReplyText(SharedPreferences sp, String replyText) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(SharedPrefsManager.KEY_REPLY_TEXT, replyText);
        editor.apply();
    }

    public static String getReplyText(SharedPreferences sp) {
        return sp.getString(KEY_REPLY_TEXT, "");
    }

    public static void setPostRead(SharedPreferences sp, long postId) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putBoolean(KEY_POST_IS_READ + postId, true);
        editor.apply();
    }

    public static boolean isPostRead(SharedPreferences sp, long postId) {
        return sp.getBoolean(KEY_POST_IS_READ + postId, false);
    }

    public static void setPostBookmarked(SharedPreferences sp, long postId) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putBoolean(KEY_POST_IS_BOOKMARKED + postId, true);
        editor.apply();
    }

    public static void setPostUnbookmarked(SharedPreferences sp, long postId) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putBoolean(KEY_POST_IS_BOOKMARKED + postId, false);
        editor.apply();
    }

    public static boolean isPostBookmarked(SharedPreferences sp, long postId) {
        return sp.getBoolean(KEY_POST_IS_BOOKMARKED + postId, false);
    }

    public static void setCommentsBookmarked(SharedPreferences sp, long postId) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putBoolean(KEY_COMMENTS_ARE_BOOKMARKED + postId, true);
        editor.apply();
    }

    public static void setCommentsUnbookmarked(SharedPreferences sp, long postId) {
        SharedPreferences.Editor editor = sp.edit();
        editor.putBoolean(KEY_COMMENTS_ARE_BOOKMARKED + postId, false);
        editor.apply();
    }

    public static boolean areCommentsBookmarked(SharedPreferences sp, long postId) {
        return sp.getBoolean(KEY_COMMENTS_ARE_BOOKMARKED + postId, false);
    }

    //Settings
    public static String getTheme(SharedPreferences sp) {
        return sp.getString(KEY_THEME, THEME_LIGHT);
    }

    public static String getScrollMode(SharedPreferences sp) {
        return sp.getString(SCROLL_MODE, SCROLL_MODE_FAB_HOLD);
    }

    public static Boolean getShowPostSummary(SharedPreferences sp, Context context) {
        return sp.getBoolean(context.getResources().getString(R.string.pref_key_show_post_summary),
            false);
    }

    public static Boolean getIsOpenLinkInApp(SharedPreferences sp, Context context) {
        return sp.getBoolean(context.getResources().getString(R.string.pref_key_open_link_option), true);
    }

    @Override
    public boolean isPostRead(long postId) {
        return mPreferences.getBoolean(KEY_POST_IS_READ + postId, false);
    }

    @Override
    public boolean isShowPostSummary() {
        return mPreferences.getBoolean(mContext.getResources()
            .getString(R.string.pref_key_show_post_summary), false);
    }

    @Override
    public boolean isPostBookmarked(long postId) {
        return mPreferences.getBoolean(KEY_POST_IS_BOOKMARKED + postId, false);
    }

    @Override
    public boolean areCommentsBookmarked(long postId) {
        return mPreferences.getBoolean(KEY_COMMENTS_ARE_BOOKMARKED + postId, false);
    }

    @Override
    public void setPostBookmarked(long postId) {
        SharedPreferences.Editor editor = mPreferences.edit();
        editor.putBoolean(KEY_POST_IS_BOOKMARKED + postId, true);
        editor.apply();
    }

    @Override
    public void setCommentsBookmarked(long postId) {
        SharedPreferences.Editor editor = mPreferences.edit();
        editor.putBoolean(KEY_COMMENTS_ARE_BOOKMARKED + postId, true);
        editor.apply();
    }

    @Override
    public void setPostUnbookmarked(long postId) {
        SharedPreferences.Editor editor = mPreferences.edit();
        editor.putBoolean(KEY_POST_IS_BOOKMARKED + postId, false);
        editor.apply();
    }

    @Override
    public void setCommentsUnbookmarked(long postId) {
        SharedPreferences.Editor editor = mPreferences.edit();
        editor.putBoolean(KEY_COMMENTS_ARE_BOOKMARKED + postId, false);
        editor.apply();
    }
} @Parcel
@StorIOSQLiteType(table = "comment")
public class Comment extends HNItem {

    @StorIOSQLiteColumn(name = CommentTable.COLUMN_COMMENT_ID, key = true)
    @SerializedName("id")
    long commentId;

    @StorIOSQLiteColumn(name = CommentTable.COLUMN_INDEX)
    int _index;

    @StorIOSQLiteColumn(name = CommentTable.COLUMN_BY)
    String by;

    @StorIOSQLiteColumn(name = CommentTable.COLUMN_DELETED)
    boolean deleted;

    @StorIOSQLiteColumn(name = CommentTable.COLUMN_PARENT)
    long parent;

    ArrayList<Long> kids;

    @StorIOSQLiteColumn(name = CommentTable.COLUMN_TIME)
    long time;

    @StorIOSQLiteColumn(name = CommentTable.COLUMN_TEXT)
    String text;

    @StorIOSQLiteColumn(name = CommentTable.COLUMN_LEVEL)
    int level;

    //FIXME necessary?
    public String error;

    public Comment() {
    }

    public Comment(long commentId, String by, long parent, ArrayList<Long> kids, long time, String text) {
        this.commentId = commentId;
        this.by = by;
        this.parent = parent;
        this.kids = kids;
        this.time = time;
        this.text = text;
    }

    public int getIndex() {
        return _index;
    }

    public void setIndex(int _index) {
        this._index = _index;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getBy() {
        return by;
    }

    public void setBy(String by) {
        this.by = by;
    }

    public long getCommentId() {
        return commentId;
    }

    public void setCommentId(long commentId) {
        this.commentId = commentId;
    }

    public void setDeleted(boolean deleted) {
        this.deleted = deleted;
    }

    public boolean getDeleted() {
        return deleted;
    }

    public long getParent() {
        return parent;
    }

    public void setParent(long parent) {
        this.parent = parent;
    }

    public ArrayList<Long> getKids() {
        return kids;
    }

    public void setKids(ArrayList<Long> kids) {
        this.kids = kids;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }
} @Parcel
@StorIOSQLiteType(table = "post")
public class Post extends HNItem {

    @StorIOSQLiteColumn(name = "id", key = true)
    long id;

    int index;

    @StorIOSQLiteColumn(name = "by")
    String by;

    @StorIOSQLiteColumn(name = "descendants")
    long descendants;

    // StorIO not support List annotation
    List<Long> kids;

    @StorIOSQLiteColumn(name = "score")
    long score;

    @StorIOSQLiteColumn(name = "text")
    String text;

    @StorIOSQLiteColumn(name = "time")
    long time;

    @StorIOSQLiteColumn(name = "title")
    String title;

    @StorIOSQLiteColumn(name = "type")
    String type;

    @StorIOSQLiteColumn(name = "url")
    String url;

    @StorIOSQLiteColumn(name = "prettyUrl")
    String prettyUrl;

    @StorIOSQLiteColumn(name = "summary")
    String summary;

    @StorIOSQLiteColumn(name = "isBookmarked")
    boolean isBookmarked;

    boolean isRead;

    public Post() {
    }

    public Post(Long id) {
        this.id = id;
    }

    public Post(Long id, int index) {
        this.id = id;
        this.index = index;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public String getBy() {
        return by;
    }

    public void setBy(String by) {
        this.by = by;
    }

    public long getDescendants() {
        return descendants;
    }

    public void setDescendants(long descendants) {
        this.descendants = descendants;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public List<Long> getKids() {
        return kids;
    }

    public void setKids(List<Long> kids) {
        this.kids = kids;
    }

    public long getScore() {
        return score;
    }

    public void setScore(long score) {
        this.score = score;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getPrettyUrl() {
        return prettyUrl;
    }

    public void setPrettyUrl(String prettyUrl) {
        this.prettyUrl = prettyUrl;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public boolean isBookmarked() {
        return isBookmarked;
    }

    public void setIsBookmarked(boolean isBookmarked) {
        this.isBookmarked = isBookmarked;
    }

    public boolean isRead() {
        return isRead;
    }

    public void setRead(boolean read) {
        isRead = read;
    }
} public class DataManager {
    public static final String HACKER_NEWS_BASE_URL = "https://news.ycombinator.com/";
    public static final String HACKER_NEWS_ITEM_URL = "https://news.ycombinator.com/item?id=";
    private static final int MINIMUM_STRING = 20;
    HackerNewsService mHackerNewsService, mSearchService;

    public DataManager() {
        mHackerNewsService = new RetrofitHelper().getHackerNewsService();
        mSearchService = new RetrofitHelper().getSearchService();
    }

    public Observable<List<Long>> getPostList(String type) {
        return mHackerNewsService.getStories(type);
    }

    public Observable<Post> getPost(Long postId) {
        return mHackerNewsService.getStory(String.valueOf(postId));
    }

    public Observable<Post> getPostsn(List<Long> postIds, boolean isByOrder) {
        if (isByOrder) {
            return Observable.from(postIds)
                .concatMap(new Func1<Long, Observable<Post>>() {
                    @Override
                    public Observable<Post> call(Long aLong) {
                        return mHackerNewsService.getStory(String.valueOf(aLong));
                    }
                })
                .onErrorReturn(new Func1<Throwable, Post>() {
                    @Override
                    public Post call(Throwable throwable) {
                        Log.e("getPosts", throwable.toString());
                        return null;
                    }
                })
                .filter(new Func1<Post, Boolean>() {
                    @Override
                    public Boolean call(Post post) {
                        return post != null && post.getTitle() != null;
                    }
                });
        } else {
            return Observable.from(postIds)
                .flatMap(new Func1<Long, Observable<Post>>() {
                    @Override
                    public Observable<Post> call(Long aLong) {
                        return mHackerNewsService.getStory(String.valueOf(aLong));
                    }
                })
                .onErrorReturn(new Func1<Throwable, Post>() {
                    @Override
                    public Post call(Throwable throwable) {
                        Log.e("getPosts", throwable.toString());
                        return null;
                    }
                })
                .filter(new Func1<Post, Boolean>() {
                    @Override
                    public Boolean call(Post post) {
                        return post != null && post.getTitle() != null;
                    }
                });
        }
    }

    public Observable<Post> getPosts(List<Long> postIds) {
        if (postIds.size() > Constants.NUM_LOADING_ITEMS_SPLIT * 2) {
            return Observable.concat(
                getSubListPosts(postIds.subList(0, Constants.NUM_LOADING_ITEMS_SPLIT)),
                getSubListPosts(postIds.subList(Constants.NUM_LOADING_ITEMS_SPLIT,
                    Constants.NUM_LOADING_ITEMS_SPLIT * 2)),
                getSubListPosts(postIds.subList(Constants.NUM_LOADING_ITEMS_SPLIT * 2,
                    postIds.size())))
                .flatMap(new Func1<List<Post>, Observable<Post>>() {
                    @Override
                    public Observable<Post> call(List<Post> posts) {
                        return Observable.from(posts);
                    }
                });
        } else {
            return getSubListPosts(postIds)
                .flatMap(new Func1<List<Post>, Observable<Post>>() {
                    @Override
                    public Observable<Post> call(List<Post> posts) {
                        return Observable.from(posts);
                    }
                });
        }
    }

    public Observable<List<Post>> getSubListPosts(final List<Long> postIds) {
        return Observable.from(postIds)
            .flatMap(new Func1<Long, Observable<Post>>() {
                @Override
                public Observable<Post> call(Long aLong) {
                    return mHackerNewsService.getStory(String.valueOf(aLong));
                }
            })
            .onErrorReturn(new Func1<Throwable, Post>() {
                @Override
                public Post call(Throwable throwable) {
                    Log.e("getPosts", throwable.toString());
                    return null;
                }
            })
            .filter(new Func1<Post, Boolean>() {
                @Override
                public Boolean call(Post post) {
                    return post != null && post.getTitle() != null;
                }
            })
            .toList()
            .map(new Func1<List<Post>, List<Post>>() {
                @Override
                public List<Post> call(List<Post> posts) {
                    return sortPosts(postIds, posts);
                }
            });
    }

    private List<Post> sortPosts(List<Long> postIds, List<Post> postList) {
        HashMap<Long, Post> postsMap = new HashMap<>();
        List<Post> orderedPostList = new ArrayList<>();
        for (Post post : postList) {
            postsMap.put(post.getId(), post);
        }
        for (Long id : postIds) {
            orderedPostList.add(postsMap.get(id));
        }
        return orderedPostList;
    }

    public Observable<Comment> getSummary(List<Long> commentIds) {
        return Observable.from(commentIds)
            // if use flatMap(), there would be too many concurrent calls
            .concatMap(new Func1<Long, Observable<Comment>>() {
                @Override
                public Observable<Comment> call(Long aLong) {
                    return mHackerNewsService.getComment(aLong);
                }
            }).filter(new Func1<Comment, Boolean>() {
                @Override
                public Boolean call(Comment comment) {
                    if (comment != null
                        && comment.getBy() != null && !comment.getBy().trim().isEmpty()
                        && comment.getText() != null && !comment.getText().trim().isEmpty()
                        && comment.getText().length() > MINIMUM_STRING * 4) {
                        //TODO length num improve needed.
                        String s = Html.fromHtml(comment.getText()).toString().substring(0,
                            MINIMUM_STRING)
                            .toLowerCase();
                        return s.contains("summary")
                            || s.contains("tldr")
                            || s.contains("tl;dr")
                            || s.contains("tl; dr");
                    } else {
                        return false;
                    }
                }
            }).firstOrDefault(null);
    }

    public Observable<List<Comment>> getComments(Post post, int level) {
        List<Long> commentIds = post.getKids();
        long descendants = post.getDescendants();
        if (commentIds.size() > 3 && descendants > 15) {
            Log.i("---getComments", "kids > 3");
            return Observable.concat(getCommentsByBranches(commentIds.subList(0, 3), level),
                getCommentsAllAtOnce(commentIds.subList(3, commentIds.size()), level));
        } else if (descendants / commentIds.size() > 15) {
            Log.i("---getComments", "few kids, big branch");
            return getCommentsByBranches(commentIds, level);
        } else {
            Log.i("---getComments", "other");
            return getCommentsAllAtOnce(commentIds, level);
        }
    }

    public Observable<List<Comment>> getCommentsByBranches(List<Long> commentIds, final int level) {
        Log.i("---", "getCommentsByBranches");
        return Observable.from(commentIds)
            .flatMap(new Func1<Long, Observable<List<Comment>>>() {
                @Override
                public Observable<List<Comment>> call(Long commentId) {
                    return getOneBranchComments(commentId, level);
                }
            });
    }

    public Observable<List<Comment>> getOneBranchComments(final long commentId, final int level) {
        return mHackerNewsService.getComment(commentId)
            .onErrorReturn(new Func1<Throwable, Comment>() {
                @Override
                public Comment call(Throwable throwable) {
                    Log.e("getOneBranchComments", throwable.toString());
                    return null;
                }
            })
            .filter(new Func1<Comment, Boolean>() {
                @Override
                public Boolean call(Comment comment) {
                    return (comment != null) && !comment.getDeleted() && comment.getText() != null;
                }
            })
            .flatMap(new Func1<Comment, Observable<Comment>>() {
                @Override
                public Observable<Comment> call(Comment comment) {
                    Log.i("---getOneBranchComments", String.valueOf(comment.getCommentId()));
                    return getInnerComments(comment, level);
                }
            })
            .toList()
            .map(new Func1<List<Comment>, List<Comment>>() {
                @Override
                public List<Comment> call(List<Comment> allComments) {
                    List<Long> firstLevelCommentAsList = new ArrayList<>();
                    firstLevelCommentAsList.add(commentId);
                    return sortComments(firstLevelCommentAsList, allComments);
                }
            });
    }

    public Observable<List<Comment>> getCommentsAllAtOnce(final List<Long> firstLevelCommentIds, final int level) {
        Log.i("---", "getCommentsAllAtOnce");
        return Observable.from(firstLevelCommentIds)
            .flatMap(new Func1<Long, Observable<Comment>>() {
                @Override
                public Observable<Comment> call(Long commentId) {
                    return mHackerNewsService.getComment(commentId)
                        .onErrorReturn(new Func1<Throwable, Comment>() {
                            @Override
                            public Comment call(Throwable throwable) {
                                Log.e("getCommentsAllAtOnce", throwable.toString());
                                return null;
                            }
                        });
                }
            })
            .filter(new Func1<Comment, Boolean>() {
                @Override
                public Boolean call(Comment comment) {
                    return (comment != null) && !comment.getDeleted() && comment.getText() != null;
                }
            })
            .flatMap(new Func1<Comment, Observable<Comment>>() {
                @Override
                public Observable<Comment> call(Comment comment) {
                    return getInnerComments(comment, level);
                }
            })
            .toList()
            .map(new Func1<List<Comment>, List<Comment>>() {
                @Override
                public List<Comment> call(List<Comment> allComments) {
                    return sortComments(firstLevelCommentIds, allComments);
                }
            });
    }

    private Observable<Comment> getInnerComments(Comment comment, final int level) {
        if (comment == null || comment.getDeleted() || comment.getText() == null) {
            return null;
        }
        comment.setLevel(level);
        if (comment.getKids() != null && !comment.getKids().isEmpty()) {
            return Observable.just(comment)
                .mergeWith(Observable.from(comment.getKids())
                    .flatMap(new Func1<Long, Observable<Comment>>() {
                        @Override
                        public Observable<Comment> call(Long commentId) {
                            return mHackerNewsService.getComment(commentId)
                                .onErrorReturn(new Func1<Throwable, Comment>() {
                                    @Override
                                    public Comment call(Throwable throwable) {
                                        Log.e("getInnerComments", throwable.toString());
                                        return null;
                                    }
                                });
                        }
                    })
                    .filter(new Func1<Comment, Boolean>() {
                        @Override
                        public Boolean call(Comment comment) {
                            return (comment != null) && !comment.getDeleted()
                                && comment.getText() != null;
                        }
                    })
                    .flatMap(new Func1<Comment, Observable<Comment>>() {
                        @Override
                        public Observable<Comment> call(Comment comment) {
                            return getInnerComments(comment, level + 1);
                        }
                    })
                );
        }
        return Observable.just(comment);
    }

    private List<Comment> sortComments(List<Long> firstLevelCommentIds, List<Comment> allComments) {
        HashMap<Long, Comment> allCommentsMap = new HashMap<>();
        for (Comment childComment : allComments) {
            allCommentsMap.put(childComment.getCommentId(), childComment);
        }
        List<Comment> validFirstLevelCommentList = new ArrayList<>();
        for (Long id : firstLevelCommentIds) {
            Comment firstLevelComment = allCommentsMap.get(id);
            if (firstLevelComment != null && !firstLevelComment.getDeleted()
                && firstLevelComment.getText() != null) {
                validFirstLevelCommentList.add(firstLevelComment);
            }
        }
        return sortAllComments(validFirstLevelCommentList, allCommentsMap);
    }

    private List<Comment> sortAllComments(List<Comment> commentList, HashMap<Long, Comment> allCommentsMap) {
        List<Comment> sortedList = new ArrayList<>();
        for (Comment comment : commentList) {
            sortedList.add(comment);
            if (comment.getKids() != null && comment.getKids().size() > 0) {
                List<Comment> validChildCommentList = new ArrayList<>();
                for (long id : comment.getKids()) {
                    Comment childComment = allCommentsMap.get(id);
                    if (childComment != null && !childComment.getDeleted() && childComment.getText() != null) {
                        validChildCommentList.add(childComment);
                    }
                }
                sortedList.addAll(sortAllComments(validChildCommentList, allCommentsMap));
            }
        }
        return sortedList;
    }

    public Observable<HNItem.SearchResult> getPopularPosts(String startTime, int page) {
        return mSearchService.searchPopularity(startTime, page, Constants.NUM_LOADING_ITEMS);
    }

    public Observable<HNItem.SearchResult> getSearchResult(String keyword, String timeRange,
                                                           int page, boolean isSortByDate) {
        if (isSortByDate) {
            return mSearchService.searchByDate(keyword, timeRange, page, Constants.NUM_LOADING_ITEMS);
        } else {
            return mSearchService.search(keyword, timeRange, page, Constants.NUM_LOADING_ITEMS);
        }
    }

    public Observable<String> login(final String username, final String password) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                try {
                    Connection login = Jsoup.connect(HACKER_NEWS_BASE_URL + "login");
                    login.header("Accept-Encoding", "gzip")
                        .data("go_to", "news")
                        .data("acct", username)
                        .data("pw", password)
                        .header("Origin", "https://news.ycombinator.com")
                        .followRedirects(true)
                        .referrer(HACKER_NEWS_BASE_URL + "login?go_to=news")
                        .method(Connection.Method.POST);
                    Connection.Response response = login.execute();
                    String cookie = response.cookie("user");
                    if (cookie == null) {
                        subscriber.onNext("");
                    } else {
                        subscriber.onNext(cookie);
                    }
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            }
        });
    }

    public Observable<Integer> vote(final long itemId, final int voteState, final SharedPreferences sp) {
        return Observable.create(new Observable.OnSubscribe<Integer>() {
            @Override
            public void call(Subscriber<? super Integer> subscriber) {
                String cookieLogin = SharedPrefsManager.getLoginCookie(sp);
                if (cookieLogin.isEmpty()) {
                    subscriber.onNext(Constants.OPERATE_ERROR_NO_COOKIE);
                    subscriber.onCompleted();
                }
                try {
                    Connection vote = Jsoup.connect(HACKER_NEWS_ITEM_URL + itemId)
                        .header("Accept-Encoding", "gzip")
                        .cookie("user", cookieLogin);
                    Document commentsDocument = vote.get();
                    /*
                    votable element:
                    <a id="up_10276091"
                     href="vote?for=10276091&dir=up&auth=3ecc4be748d7cc412223ea906b559ce72ccdc262&goto=news"
                     onclick="return vote(this)">

                     url:
                    https://news.ycombinator.com/vote?for=10276091&dir=up
                     &auth=3ecc4be748d7cc412223ea906b559ce72ccdc262&goto=news

                    logout element:
                    <a id="up_10276091" href="vote?for=10276091&dir=up&goto=item%3Fid%3D10276091">

                    url:
                    https://news.ycombinator.com/vote?for=10276091&dir=up&goto=item%3Fid%3D10276091
                    */
                    Elements links;
                    if (voteState == Constants.VOTE_DOWN) {
                        links = commentsDocument.select("a[id=down_" + itemId + "]");
                    } else {
                        links = commentsDocument.select("a[id=up_" + itemId + "]");
                    }
                    if (links.size() == 0) {
                        if (voteState == Constants.VOTE_DOWN) {
                            subscriber.onNext(Constants.OPERATE_ERROR_NOT_ENOUGH_KARMA);
                        } else {
                            subscriber.onNext(Constants.OPERATE_ERROR_HAVE_VOTED);
                        }
                        subscriber.onCompleted();
                    } else {
                        Element voteElement = links.get(0).select("a[href^=vote]").first();
                        if (!voteElement.attr("href").contains("auth=")) {
                            subscriber.onNext(Constants.OPERATE_ERROR_COOKIE_EXPIRED);
                            subscriber.onCompleted();
                        }
                        if (voteElement.attr("href").contains("auth=")) {
                            String url = (voteElement.attr("href"));
                            Request voteRequest = new Request.Builder()
                                .addHeader("cookie", "user=" + cookieLogin)
                                .url(HACKER_NEWS_BASE_URL + url)
                                .build();
                            Response response = new OkHttpClient().newCall(voteRequest).execute();
                            if (response.code() == 200) {
                                if (response.body() == null) {
                                    subscriber.onNext(Constants.OPERATE_ERROR_UNKNOWN);
                                } else {
                                    subscriber.onNext(Constants.OPERATE_SUCCESS);
                                }
                            } else {
                                subscriber.onNext(Constants.OPERATE_ERROR_UNKNOWN);
                            }
                        }
                    }
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            }
        });
    }

    public Observable<Integer> reply(final long itemId, final String replyText, final String cookieLogin) {
        return Observable.create(new Observable.OnSubscribe<Integer>() {
            @Override
            public void call(Subscriber<? super Integer> subscriber) {
                if (cookieLogin.isEmpty()) {
                    subscriber.onNext(Constants.OPERATE_ERROR_NO_COOKIE);
                    subscriber.onCompleted();
                }
                try {
                    Connection reply = Jsoup.connect(HACKER_NEWS_ITEM_URL + itemId)
                        .header("Accept-Encoding", "gzip")
                        .cookie("user", cookieLogin);
                    Document replyDocument = reply.get();
                    Element element = replyDocument.select("input[name=hmac]").first();
                    if (element != null) {
                        String replyHmac = element.attr("value");
                        RequestBody requestBody = (new FormEncodingBuilder())
                            .add("parent", String.valueOf(itemId))
                            .add("goto", (new StringBuilder()).append("item?id=").append(itemId).toString())
                            .add("hmac", replyHmac)
                            .add("text", replyText)
                            .build();
                        Request request = new Request.Builder()
                            .addHeader("cookie", "user=" + cookieLogin)
                            .url(HACKER_NEWS_BASE_URL + "comment")
                            .post(requestBody)
                            .build();

                        Response response = new OkHttpClient().newCall(request).execute();
                        if (response.code() == 200) {
                            subscriber.onNext(Constants.OPERATE_SUCCESS);
                        } else {
                            subscriber.onNext(Constants.OPERATE_ERROR_UNKNOWN);
                        }
                    } else {
                        subscriber.onNext(Constants.OPERATE_ERROR_COOKIE_EXPIRED);
                    }
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            }
        });
    }
} public class CommentOptionDialogFragment extends DialogFragment {
    OnSelectCommentListener mListener;
    Comment mComment;

    public CommentOptionDialogFragment() {
        // Required empty public constructor
    }

    public static CommentOptionDialogFragment newInstance(OnSelectCommentListener listener, Comment comment) {
        CommentOptionDialogFragment fragment = new CommentOptionDialogFragment();
        fragment.mListener = listener;
        fragment.mComment = comment;
        return fragment;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setItems(R.array.dialog_comment, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                    case 0:
                        mListener.onUpVote(mComment);
                        break;
                    case 1:
                        mListener.onDownVote(mComment);
                        break;
                    case 2:
                        mListener.onReply(mComment);
                        break;
                    case 3:
                        mListener.onAuthorProfile(mComment);
                        break;
                    case 4:
                        mListener.onShare(mComment);
                        break;
                    case 5:
                        mListener.onShareCommentTextTo(mComment);
                        break;
                }
            }
        });
        return builder.create();
    }

    public void setOnSelectCommentListener(OnSelectCommentListener listener) {
        mListener = listener;
    }

    public void setSelectedComment(Comment comment) {
        mComment = comment;
    }

    public interface OnSelectCommentListener {
        void onUpVote(Comment comment);

        void onDownVote(Comment comment);

        void onReply(Comment comment);

        void onAuthorProfile(Comment comment);

        void onShare(Comment comment);

        void onShareCommentTextTo(Comment comment);
    }
} public class FloatingScrollDownButton extends android.support.design.widget.FloatingActionButton {

    public static final int FAB_DRAG_MODE = 1;
    public static final int FAB_HOLD_MODE = 2;
//    public static final int FAB_CLICK_MODE = 3;

    private int mScrollDownMode;
    private float mOriginX, mOriginY;
    private RecyclerView recyclerView;
    private Handler handler = new Handler();


    public FloatingScrollDownButton(Context context) {
        this(context, null);
    }

    public FloatingScrollDownButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FloatingScrollDownButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private Runnable scrollDownThread = new Runnable() {
        @Override
        public void run() {
            if (recyclerView != null) {
                recyclerView.scrollBy(0, 6);
            }
            handler.postDelayed(this, 10);
        }
    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //int iOffsetX, iOffsetY, iNewX, iNewY, iRelX = 0, iRelY = 0;
        int[] aLocation = new int[2];
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                // preserve offset of this touch within the View
//                iRelX = (int) event.getX();
//                iRelY = (int) event.getY();
                mOriginX = event.getRawX();
                mOriginY = event.getRawY();
                if (mScrollDownMode == FAB_HOLD_MODE) {
                    handler.post(scrollDownThread);
                }
                return true;
            case MotionEvent.ACTION_MOVE:
                setFABPosition(aLocation, event);
                if (mScrollDownMode == FAB_DRAG_MODE) {
                    scrollDownWhenDragging(event);
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mScrollDownMode == FAB_HOLD_MODE) {
                    handler.removeCallbacks(scrollDownThread);
                }

//                else if (mScrollDownMode == SharedPrefsManager.FAB_CLICK_SCROLL_DOWN) {
//                    LinearLayoutManager linearLayoutManager =
//                            ((LinearLayoutManager) recyclerView.getLayoutManager());
//                    int currentPosition = linearLayoutManager.findFirstVisibleItemPosition();
//                    if (currentPosition != linearLayoutManager.findLastVisibleItemPosition()) {
//                        linearLayoutManager.scrollToPositionWithOffset(currentPosition + 1, 0);
//                    } else {
//                        recyclerView.scrollBy(0,mScreenHeight/2);
//                        linearLayoutManager.scrollToPositionWithOffset(currentPosition, -mScreenHeight / 2);
//                    }
//                }
                break;
        }
        return super.onTouchEvent(event);
    }

    public void setRecyclerView(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
        //recyclerView.setOnScrollListener(scrollListener);
    }

    public void setScrollDownMode(int mode) {
        mScrollDownMode = mode;
    }

    public void setScrollDownMode(String mode) {
        if (mode.equals(SharedPrefsManager.SCROLL_MODE_FAB_HOLD)) {
            mScrollDownMode = FAB_HOLD_MODE;
        } else if (mode.equals(SharedPrefsManager.SCROLL_MODE_FAB_DRAG)) {
            mScrollDownMode = FAB_DRAG_MODE;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setIcon(mScrollDownMode);
        }
    }

    private void setFABPosition(int[] location, MotionEvent event) {
        // subtract out this View's relative location within its parent View
        getLocationOnScreen(location);
        // ...yielding the offsets that convert getRawX/Y's coords to setX/Y's coords
        int offsetX = (location[0] - (int) getX());
        int offsetY = (location[1] - (int) getY());
        // get absolute physical coords of this touch
        int newX = (int) event.getRawX();
        int newY = (int) event.getRawY();
        // remove parent View's screen offset (calc'd above)
        newX -= offsetX;
        newY -= offsetY;
        // remove stored touch offset
//                iNewX -= iRelX;
//                iNewY -= iRelY;
        newX -= getWidth() / 2;
        newY -= getHeight() / 2;

        setX(newX);
        setY(newY);
//                setX(event.getRawX() - 56);
//                setY(event.getRawY() - 214);
    }

    private void scrollDownWhenDragging(MotionEvent event) {
        float disX = event.getRawX() - mOriginX;
        float disY = event.getRawY() - mOriginY;
        float dis = (float) Math.sqrt(disX * disX + disY * disY);
        if (recyclerView != null) {
            recyclerView.scrollBy(0, (int) (dis * 2));
        }
        mOriginX = event.getRawX();
        mOriginY = event.getRawY();
    }

//    public void setScreenHeight(int screenHeight) {
//        mScreenHeight = screenHeight;
//    }

    private void setIcon(int mode) {
        switch (mode) {
            case FAB_DRAG_MODE:
                setImageResource(R.drawable.fab_arrow_down_drag);
                // override fab icon size
                FloatingScrollDownButton.this.setPadding(16, 16, 16, 16);
                break;
            case FAB_HOLD_MODE:
                setImageResource(R.drawable.fab_arrow_down_hold);
                // override fab icon size
                FloatingScrollDownButton.this.setPadding(16, 16, 16, 16);
                break;
//            case FAB_CLICK_MODE:
//                setImageResource(R.drawable.fab_arrow_down_click);
//                break;
        }
    }
} public class LoginDialogFragment extends DialogFragment {
    OnLoginListener mListener;
    TextInputLayout tiLayoutName;
    TextInputLayout tiLayoutPassword;
    ProgressBar progress;
    TextView tvPrompt;

    public LoginDialogFragment() {
        // Required empty public constructor
    }

    public static LoginDialogFragment newInstance(OnLoginListener mOnLoginListener) {
        LoginDialogFragment fragment = new LoginDialogFragment();
        fragment.mListener = mOnLoginListener;
        return fragment;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        LayoutInflater inflater = getActivity().getLayoutInflater();
        View v = inflater.inflate(R.layout.dialog_login, null);

        tiLayoutName = (TextInputLayout) v.findViewById(R.id.tilayout_user_name);
        tiLayoutPassword = (TextInputLayout) v.findViewById(R.id.tilayout_password);
        progress = (ProgressBar) v.findViewById(R.id.progressbar_login);
        tvPrompt = (TextView) v.findViewById(R.id.tv_prompt);
        builder.setView(v)
            .setPositiveButton(R.string.login_dialog_login, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
//                                String username = tiLayoutName.getEditText().getText().toString();
//                                String password = tiLayoutPassword.getEditText().getText().toString();
//                                if (Utils.isOnline(LoginDialogFragment.this.getActivity())) {
//                                    if (!username.isEmpty() && password.length() >= 8) {
//
//                                        ProgressDialog progressDialog = new ProgressDialog(getActivity());
//                                        progressDialog.setMessage("Logging in...");
//                                        progressDialog.show();
//                                        mListener.onLogin(username, password);
//                                        tiLayoutName.setVisibility(View.GONE);
//                                        tiLayoutPassword.setVisibility(View.GONE);
//                                        progress.setVisibility(View.VISIBLE);
//                                    }
//                                } else {
//                                    Toast.makeText(LoginDialogFragment.this.getActivity(),
//                                            "No connection:)", Toast.LENGTH_LONG).show();
//                                }
                    }
                }
            )
            .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }
            )
            .setTitle(R.string.login_dialog_login);

        return builder.create();
    }

    @Override
    public void onStart() {
        super.onStart();
        final AlertDialog d = (AlertDialog) getDialog();
        if (d != null) {
            Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
            positiveButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String username;
                    username = tiLayoutName.getEditText().getText().toString();
                    String password;
                    password = tiLayoutPassword.getEditText().getText().toString();
                    if (Utils.isOnline(LoginDialogFragment.this.getActivity())) {
                        if (!username.isEmpty() && password.length() >= 6) {
                            mListener.onLogin(username, password);
                            tiLayoutName.setVisibility(View.GONE);
                            tiLayoutPassword.setVisibility(View.GONE);
                            progress.setVisibility(View.VISIBLE);
                            tvPrompt.setTextColor(getActivity().getResources().getColor(android.R.color.black));
                            tvPrompt.setText(R.string.login_prompt_logging_in);
                        } else if (username.isEmpty()) {
                            tvPrompt.setTextColor(getActivity().getResources().getColor(android.R.color.holo_red_dark));
                            tvPrompt.setText(R.string.login_prompt_error_empty_username);
                        } else if (password.length() < 6) {
                            // according to a user report, the password may less than 8 characters
                            tvPrompt.setTextColor(getActivity().getResources().getColor(android.R.color.holo_red_dark));
                            tvPrompt.setText(R.string.login_prompt_error_short_password);
                        }
                    } else {
                        tvPrompt.setTextColor(getActivity().getResources().getColor(android.R.color.holo_red_dark));
                        tvPrompt.setText(R.string.login_prompt_error_offline);
                    }
                }
            });
        }
    }

    public void resetLogin() {
        tiLayoutName.setVisibility(View.VISIBLE);
        tiLayoutPassword.setVisibility(View.VISIBLE);
        progress.setVisibility(View.GONE);
        tvPrompt.setTextColor(getActivity().getResources().getColor(android.R.color.holo_red_dark));
        tvPrompt.setText(R.string.login_prompt_error_wrong_info);
    }

    public void setOnLoginListener(OnLoginListener onLoginListener) {
        mListener = onLoginListener;
    }

    public interface OnLoginListener {
        void onLogin(String username, String password);
    }
} public class PopupFloatingWindow implements View.OnClickListener {
    private boolean isShowing;
    private Context mContext;
    private PopupWindow mWindow;
    private View mRootView;
    private String nameOfShowingActivity;
    private Drawable mBackground = null;
    // private WindowManager mWindowManager;
    private Spinner mSpinnerFont;
    private ImageButton mBtnFontLarge, mBtnFontSmall, mBtnLineNarrow, mBtnLineWide;
    Button mBtnDone;
    private SharedPreferences prefs;
    View mAttachView;


    public PopupFloatingWindow(Context context, View attachView) {
        isShowing = false;
        mContext = context;
        mAttachView = attachView;
        //mWindow = new PopupWindow(context);
        // mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        nameOfShowingActivity = mContext.getClass().getSimpleName();
        prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        //setContentView(R.layout.popup_window);
        setView(R.layout.popup_window);
        setUpButtons();
        mSpinnerFont = (Spinner) mRootView.findViewById(R.id.spinner_font);
        setUpFontSpinner(mSpinnerFont);
    }

    private void setUpButtons() {
        mBtnFontLarge = (ImageButton) mRootView.findViewById(R.id.imgbtn_font_large);
        mBtnFontLarge.setOnClickListener(this);
        mBtnFontSmall = (ImageButton) mRootView.findViewById(R.id.imgbtn_font_small);
        mBtnFontSmall.setOnClickListener(this);
        mBtnLineNarrow = (ImageButton) mRootView.findViewById(R.id.imgbtn_line_height_narrow);
        mBtnLineNarrow.setOnClickListener(this);
        mBtnLineWide = (ImageButton) mRootView.findViewById(R.id.imgbtn_line_height_wide);
        mBtnLineWide.setOnClickListener(this);
        mBtnDone = (Button) mRootView.findViewById(R.id.btn_done);
        mBtnDone.setOnClickListener(this);
    }

    private void setUpFontSpinner(Spinner spinner) {
        final Map<String, Typeface> fontMap = new ArrayMap<>();
        final List<String> fontList = new ArrayList<>();
        List<String> fontsName;
        if (nameOfShowingActivity.equals("MainActivity")) {
            fontsName = new ArrayList<>(Arrays.asList(SharedPrefsManager.getPostFontsList()));
        } else {
            fontsName = new ArrayList<>(Arrays.asList(SharedPrefsManager.getCommentFontList()));
        }

        for (String fontName : fontsName) {
            fontMap.put(fontName,
                Typeface.createFromAsset(mContext.getAssets(), fontName + ".ttf"));
            fontList.add(fontName);
        }

        spinner.setAdapter(new BaseAdapter() {
            @Override
            public int getCount() {
                return fontList.size();
            }

            @Override
            public Object getItem(int position) {
                return fontList.get(position);
            }

            @Override
            public long getItemId(int position) {
                return fontList.get(position).hashCode();
            }

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                if (convertView == null) {
                    convertView = LayoutInflater.from(mContext)
                        .inflate(android.R.layout.simple_list_item_1, parent, false);
                }
                ((TextView) convertView).setTypeface(fontMap.get(fontList.get(position)));
                ((TextView) convertView).setText(fontList.get(position));
                ((TextView) convertView).setPaintFlags(((TextView) convertView).getPaintFlags()
                    | Paint.SUBPIXEL_TEXT_FLAG);
                return convertView;
            }
        });

        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                if (nameOfShowingActivity.equals("MainActivity")) {
                    SharedPrefsManager.setPostFont(prefs, fontList.get(position));
                } else {
                    SharedPrefsManager.setCommentFont(prefs, fontList.get(position));
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });

        if (nameOfShowingActivity.equals("MainActivity")) {
            spinner.setSelection(fontList.indexOf(SharedPrefsManager.getPostFont(prefs)));
        } else {
            spinner.setSelection(fontList.indexOf(SharedPrefsManager.getCommentFont(prefs)));
        }
    }

    /**
     * On pre show
     */
    protected void preShow() {
        if (mRootView == null)
            throw new IllegalStateException("setContentView was not called with a view to display.");

        if (mBackground == null)
            mWindow.setBackgroundDrawable(mContext.getResources().getDrawable(R.color.grey_400));
        else
            mWindow.setBackgroundDrawable(mBackground);

//        mWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
//        mWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
        // mWindow.setContentView(mRootView);
        mWindow.setTouchable(true);
        //mWindow.setFocusable(true);
        //mWindow.setOutsideTouchable(true);

    }

    public void show() {
        preShow();
        //Error
        // android.view.WindowManager$BadTokenException:
        // Unable to add window -- token android.view.ViewRootImpl$W@2274902e is not valid;
        // is your activity running?
        // Added android:spinnerMode="dialog" in xml
        // mWindow.showAtLocation(((Activity) mContext).getWindow().
        //getDecorView().findViewById(android.R.id.content), Gravity.NO_GRAVITY, 20, 200);
        mWindow.showAsDropDown(mAttachView, 0, 0);
        isShowing = true;
    }

//    /**
//     * On dismiss
//     */
//    protected void onDismiss() {
//    }

    /**
     * Set background drawable.
     *
     * @param background Background drawable
     */
    public void setBackgroundDrawable(Drawable background) {
        mBackground = background;
    }

    void setView(int layoutID) {
        LayoutInflater layoutInflater =
            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mRootView = layoutInflater.inflate(layoutID, null);
        //mWindow = new PopupWindow(mContext);
        //mWindow.setContentView(mRootView);

        mWindow = new PopupWindow(mRootView, ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    /**
     * Set content view.
     *
     * @param layoutResID Resource id
     */
    public void setContentView(int layoutResID) {
        LayoutInflater inflator = (LayoutInflater) mContext
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mRootView = inflator.inflate(layoutResID, null);
        mWindow.setContentView(mRootView);
    }

    /**
     * Dismiss the popup window.
     */
    public void dismiss() {
        mWindow.dismiss();
        isShowing = false;
    }

    public boolean isWindowShowing() {
        return isShowing;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.imgbtn_font_small:
                if (nameOfShowingActivity.equals("MainActivity")) {
                    SharedPrefsManager.setPostFontSize(prefs, -0.5f);
                } else {
                    SharedPrefsManager.setCommentFontSize(prefs, -0.5f);
                }
                break;
            case R.id.imgbtn_font_large:
                if (nameOfShowingActivity.equals("MainActivity")) {
                    SharedPrefsManager.setPostFontSize(prefs, 0.5f);
                } else {
                    SharedPrefsManager.setCommentFontSize(prefs, 0.5f);
                }
                break;
            case R.id.imgbtn_line_height_narrow:
                if (nameOfShowingActivity.equals("MainActivity")) {
                    SharedPrefsManager.setPostLineHeight(prefs, -0.1f);
                } else {
                    SharedPrefsManager.setCommentLineHeight(prefs, -0.1f);
                }
                break;
            case R.id.imgbtn_line_height_wide:
                if (nameOfShowingActivity.equals("MainActivity")) {
                    SharedPrefsManager.setPostLineHeight(prefs, 0.1f);
                } else {
                    SharedPrefsManager.setCommentLineHeight(prefs, 0.1f);
                }
                break;
            case R.id.btn_done:
                this.dismiss();
                break;
        }
    }
}
import android.app.AlertDialog;
import android.app.FragmentManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.customtabs.CustomTabsIntent;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.firebase.client.Firebase;
import com.leavjenn.hews.Constants;
import com.leavjenn.hews.R;
import com.leavjenn.hews.misc.Utils;
import com.leavjenn.hews.listener.OnRecyclerViewCreatedListener;
import com.leavjenn.hews.misc.ChromeCustomTabsHelper;
import com.leavjenn.hews.misc.SharedPrefsManager;
import com.leavjenn.hews.model.Comment;
import com.leavjenn.hews.model.Post;
import com.leavjenn.hews.data.remote.DataManager;
import com.leavjenn.hews.ui.widget.CommentOptionDialogFragment;
import com.leavjenn.hews.ui.widget.FloatingScrollDownButton;
import com.leavjenn.hews.ui.widget.LoginDialogFragment;
import com.leavjenn.hews.ui.widget.PopupFloatingWindow;
import org.parceler.Parcels;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
package com.leavjenn.hews.ui.comment;





public class CommentsActivity extends AppCompatActivity implements OnRecyclerViewCreatedListener,
    AppBarLayout.OnOffsetChangedListener {
    private static final String FRAGMENT_TAG_SELECT_COMMENT_DIALOG = "select_comment_dialog_fragment";
    private static final String FRAGMENT_TAG_LOGIN_DIALOG = "login_dialog_fragment";
    private static final String STATE_SELECTED_COMMENT = "selected_comment";
    private static final String STATE_IS_IN_REPLAY_MODE = "is_in_reply_mode";
    private static final String STATE_REPLY_ITEM_ID = "reply_item_id";
    private static final String STATE_POST_ID = "post_id";
    private static final String STATE_URL = "url";

    private PopupFloatingWindow mWindow;
    private FloatingScrollDownButton mFab;
    private CoordinatorLayout coordinatorLayout;
    private AppBarLayout appbar;
    private LinearLayout layoutReply;
    private EditText etReply;
    private FloatingActionButton btnReplySend;
    private CommentOptionDialogFragment dialogSelectComment;
    private LoginDialogFragment dialogLogin;

    private int mAppBarOffset;
    private long mPostId;
    private long mReplyItemId;
    private boolean mIsInReplyMode;
    private boolean mIsKeyScrollEnabled;
    private String mUrl;
    private Comment mSelectedComment;
    private Menu mMenu;
    private SharedPreferences prefs;
    private ChromeCustomTabsHelper mChromeCustomTabsHelper;
    private DataManager mDataManager;
    private CompositeSubscription mCompositeSubscription;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Set theme
        prefs = PreferenceManager.getDefaultSharedPreferences(this);
        String theme = SharedPrefsManager.getTheme(prefs);
        switch (theme) {
            case SharedPrefsManager.THEME_SEPIA:
                setTheme(R.style.AppTheme_Sepia);
                break;
            case SharedPrefsManager.THEME_DARK:
                setTheme(R.style.AppTheme_Dark);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                    getWindow().setStatusBarColor(getResources().getColor(R.color.grey_900));
                }
                break;
            case SharedPrefsManager.THEME_AMOLED_BLACK:
                setTheme(R.style.AppTheme_AMOLEDBlack);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                    getWindow().setStatusBarColor(getResources().getColor(android.R.color.black));
                }
                break;
        }
        setContentView(R.layout.activity_comments);
        Firebase.setAndroidContext(this);
        appbar = (AppBarLayout) findViewById(R.id.appbar);
        appbar.addOnOffsetChangedListener(this);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setTitle("Comments");
            actionBar.setHomeButtonEnabled(true);
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        mWindow = new PopupFloatingWindow(this, toolbar);
        mFab = (FloatingScrollDownButton) findViewById(R.id.fab);
        coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout);
        layoutReply = (LinearLayout) findViewById(R.id.layout_reply);
        btnReplySend = (FloatingActionButton) findViewById(R.id.btn_reply_send);
        etReply = (EditText) findViewById(R.id.et_reply);

        Intent intent = getIntent();
        CommentsFragment commentsFragment = null;
        Parcelable postParcel = intent.getParcelableExtra(Constants.KEY_POST_PARCEL);